Hey there! My name is Jessica and I’m a Software Engineering student from the University of Waterloo. I’m currently on a coop term at 500px as a part of their mobile team. I contribute to the 500px Android application (try it out!).

The Problem

500px builds mobile applications on two platforms: Android and iOS. Both of these platforms have different style guides and general flows, but the strings they use overlap significantly. We wanted to simplify and unify these to reduce duplication of effort, make app localization easier, and reduce translation costs.

Consolidating string resources across the two platforms isn’t as straightforward as it may seem. Looking deeper, Android and iOS have very different string resource formats. Consider typical string definitions:

Android strings.xml:

<string name="hello_world">Hello world</string>

iOS Localizable.strings:

"Hello world" = "Hello world"

Beyond superficial differences, there are also more complicated differences, such as formatting strings with variables (used primarily for pluralization and gender rules), HTML styling, ways of handling pluralization, etc. As you can see, there are going to be some complications already.

We can’t be the only ones struggling

“This must be a common problem, right?” we thought, and so we scoured the internet for help…and we found Mobiata’s Twine.

Twine

Twine is a set of Ruby scripts that, through the command line, help manage strings and their translations. This is done through storing them all in a human-readable master text file, which is then used to import and export localization files. It supports a whopping 6 different localized string file formats, this was too good to be true! This was 4 too many for our purposes, but it had the two platforms we were interested in: Android and iOS.

However, digging deeper, we found out that Twine did not support plural string resource conversions (for various reasons). Which is totally understandable once you look at how differently they are done:

Android, included in strings.xml:

<plurals name="days_left">

<item quantity="one">1 day left</item>

<item quantity="other">%d days left</item>

</plurals>

iOS Localizable.stringsdict (brace yourselves):

<key>days_left</key>

<dict>

<key>NSStringLocalizedFormatKey</key>

<key>d_days_left</key>

<string>%#@d_days_left@</string>

<key>d_days_left</key>

<dict>

<key>NSStringFormatSpecTypeKey</key>

<string>NSStringPluralRuleType</string>

<key>NSStringFormatValueTypeKey</key>

<string>d</string>

<key>one</key>

<string>1 day left</string>

<key>other</key>

<string>%d days left</string>

</dict>

</dict>

Another issue arose: iOS string keys don’t follow the snake_case naming convention like Android does [1], which means if we did unify our strings into some sort of huge master file, the copy that overlapped would not be squashed together when using Twine.

The battle plan

Refactor all of our iOS strings to abide by Android’s convention of having a snake_case key and maintain this standard from here on out (with the help of Lin autocomplete, this wouldn’t be too painful for the future [2])

of our iOS strings to abide by Android’s convention of having a snake_case key and maintain this standard from here on out (with the help of Lin autocomplete, this wouldn’t be too painful for the future [2]) Patch Twine to consume plural string resources and spit them out in their respective formats

Run all of our localization files into Twine and generate a master string file, which can then be used to create future localization files. This could also be sent to translation services to integrate new languages into our applications!

Generate all our string files from this master file

And…we did it! Our fork of Twine with all the plural string goodness can be found on GitHub.

How to use Twine

$ gem install rubyzip

$ git clone git://github.com/500px/twine.git

$ cd twine

$ ./twine — help

Running Twine at the root of the project will setup your Ruby library path properly.

Now that you have Twine, there are 2 major commands:

$ twine generate-all-localization-files /path/to/twine.txt /path/to/project/locales/directory

This command will create your localized string files in the directory that you specify.

$ twine consume-all-localization-files /path/to/twine.txt /path/to/project/locales/directory

This command will fill your master file from the directory that you specify.

If you’re curious about trying it out there are more explicit instructions, details about the format of the master file, and commands that can be found in the README.

Some next steps

So, now that we have this slick master file, how do we add new strings on each of the platforms? Adding to that, how do we modify existing strings and differentiate these changes from string additions?

One possible solution:

Create a git hook that will run every time the developer commits a change to strings.xml or Localizable.strings, which will in turn, run that file through Twine and send an update to our master file

The master file could be stored in another git repo so we can keep track of changes

And that’s it!

This is our first iteration on trying to streamline this tedious process. With this thorn in every multi-platform application company’s side, we are welcome to (and asking for!) suggestions, feedback, and more insight on your workflow. Feel free to comment below on your processes!

[1] Localizable.strings is generated using the genstrings script on the codebase, which sifts through all the uses of NSLocalizedString and produces a human-readable text file and the default behaviour is having the key match the value of the string.

[2] Unfortunately, Lin doesn’t work in XCode 8 because Apple deprecated the XCode plugin APIs it uses.