When coding i18n support in an iOS app, you need to know technical things like how to use
Localizable.strings files and
NSLocalizedString(). And when designing the UI, you need to understand right-to-left interfaces and remember that any element with text can vary in width considerably. But one thing I didn’t anticipate was how important the translation files were for making the app easy to translate. Before we get started, if you missed part one on Internationalizing Trello, you can check it out here.
The translation files are a de facto user interface for translators. They have two elements that you can use to communicate with the translator—the string itself and a comment to explain how the string is used.
Translation comments are key
The official docs don’t stress this enough, but in large projects, if you aren’t clear in your comments, you are going to get buried in questions from translators. A three word “hint” is not enough.
Here’s a simple example from Trello:
/* Medium-sized string for a button. "App Store" is the brand name of the Apple App Store. You may leave it as "App Store". Do NOT use "Play Store". */ "rate_us" = "Rate Us on the App Store";
This particular comment was changed because of translation mistakes. We borrowed Trello support’s fix-it-twice mentality and update comments (and others like it) whenever we have a translation question or problem.
In any particular comment, we might include:
- A reminder about brand names, like Taco
- A width restriction (e.g. Use less than 15 characters, abbreviate if necessary)
- Instructions on how to find the string in the app
- A link to this guide for any string with numbers and possibly pluralized words
- Instructions for string interpolation or formatting
We also provide a glossary so that words like board, list, and card are translated consistently across the apps.
Our team has a coding-standard that 100% of our strings require a translation comment, and you’d think this would be easy, but Xcode has no way to provide a comment for a string that you specify in an XIB or Storyboard (see my Radar about i18n support in IB). We solved this with a post-process script that interleaves comments that we provide separately.
Here’s an example of a string in an XIB where the comment would be absolutely necessary to get an accurate translation:
/* Text on a button that clears out the due date (removes it) */ "FCTCardDueDatePhoneViewController_21.normalTitle" = "Clear";
Without that, the translator would just see the word “Clear” with no context of its meaning. My dictionary shows fifteen definitions, and the one we want is the third one in verbs.
Translation and string interpolation
To help with string interpolation, we decided to not use the default behavior of
stringWithFormat. To see why, consider this simple example:
@"You have a %@ %@", color, noun
This makes the assumption that colors come before nouns in sentences. A localizable version looks like this:
@"You have a %1$@ %2$@", color, noun
And this is what you’d have to give a translator:
/* A string to describe an item in your inventory. %1$@ will be replaced with a color and %2$@ will be replaced with the inventory item. It is ok to put %2$@ before %1$@. */ "inventory_item"="You have a %1$@ %2$@";
If the translator gets any part of the symbol soup wrong, and you pass this to
stringWithFormat, it will crash because it doesn’t match the argument list.
This is one reason why we decided to implement placeholders from Google’s chrome.i18n standard. Our strings look like this:
/* A string to describe an item in your inventory. $COLOR$ will be replaced with a color and $ITEM$ will be replaced with the inventory item. */ "inventory_item"="You have a $COLOR$ $ITEM$";
Translation and embedded formatting
A more complex example is how we’ve implemented embedded formatting (like bold or web links). A common way to do this would be to allow HTML, but I’ve been to (fellow Trello developer) Daniel LeCheminant’s very scary web security presentations, so I know not to attempt parsing externally supplied HTML in our app.
A typical string looks like this:
Another benefit of these simpler formats is that we were able to implement a linter to run on translated strings before importing them.
We still have translation issues and questions, but it’s now at the point where they are usually novel cases.
Check out Lou’s talk on the i18n project: