While being a mobile developer that only supports one platform it’s easy to think that you cover internationalization very well. It’s not such a difficult topic after all. I was under this impression as well as a lead android developer of a Swedish health & fitness service. At some point, my team got a green light on conquering another market. We had pretty much identical Android and iOS apps so it sounded fairly easy.
Life is hard
As usually, life proved to be more difficult than we anticipate. Mistakes have been made in the past, that had to be fixed before we move on. If you think it’s not your problem, just check the below statements:
- My Android and iOS apps use a shared base of texts
- Text resources ids are consistent between my platforms
- I don’t have any styling elements inside translatable resources (html tags, CDATA)
Would you put FALSE to any of them? Maybe just starting a new project? Even if you don’t plan to go global any time soon it’s worth to at least asses if the initial small cost isn’t worth a great saving in the future. Because that’s what it is if you end up with the 2 apps that were supposed to be identical, but instead these use a base of ~500 strings each with the different keys, and formatting.
How we put ourselves in troubles
As mentioned before, our Swedish service will have to become also a Norwegian service. Our apps are identical, besides some really minor platform-specific differences. Thing is, I never aligned with iOS developers on the texts we put into the apps. Sure, we cared for internationalization, but separately. This means that we’ve been putting our translatable resources under different ids we’ve been naming separately. Our features weren’t usually done simultaneously. Also, we don’t even speak the Swedish language (neither we do Norwegian). We’ve been implementing a feature in English, then we asked a Swedish translator to translate resources we added along with this new feature. Also, as one platform did the same as another, but later, it was another dev asking this, or maybe different translator for translations for the same translations of English texts. As you can imagine, the output was different sometimes. The translator used different wording or misunderstood the context.
We’ve been developing features in Swedish from the start as well. Here again – the second platform was usually the second iteration of a feature. Texts were often changing then, but the platform that was first was never updated. Now just add the fact we often been using variables differently and even did some in-resources styling on Android.
We had a dream …
We had these identical apps. Why don’t we have a shared database of translatable resources? Some that could be maintained in a web app with a collaboration of our translators. A tool that would let us easily import most recent translations right into the project, iOS or Android. Or even a Web app (yes, we have a web app as well). There are many tools on the market. We just needed to pick the one matching our needs.
The tools …
We took a closer look at 3 of them and we quickly understood we’re not ready for any of them. Also, we quickly discovered, most of the tools forces separated projects for each platform. That was against our first requirement – a shared database of strings for identical apps. We eventually picked one, that felt simple, and also matched perfectly to our needs. I’m not getting paid for advertisements, but later on, this one proved to be better than we anticipated. A perfect choice. It’s best to make your own research, but for the case, I’m describing it was a way to go. Ladies and gentlemen – meet Loco!
Preparations
No matter how good Loco was, we had a lot to do on both platforms, before we could actually use it. This is how we dealt with the major obstacles:
1. Variables
Many translatable resources contain places for injecting variables. On Android these are usually like %1$s, %2$d (indexed and even pointing at the type). iOS was simpler, just $@ (but luckily iOS supports indexing as well – %1$@). That is of course if you don’t use any fancy libraries for that, like Pharse for instance. Which we did on Android…
First, we agreed that we’re going to use indexed string-only variables. That is %1$@ for iOS and %1$s for android. This way we’re spare our non-technical translators unnecessary confusion. That was our first refactor. Android had to remove specific types and all Phrase cases and iOS added indexing.
2. Styles
We also agreed that anything that styles the text doesn’t belong to translatable resources. That meant all the CDATA and html tags we used on Android had to be gone. \n line breaks are fine tho. Even Loco understands them and just turn into the line brakes and then back to \n when exporting. This was another refactor, mostly Android this time. I had to move all the styling to the java/kotlin code. In the places I needed html I was just injecting it there, right before setting text to the widget.
3. Different keys
We’ve been aware that our text resources have completely different keys. On the way to have the same keys and the same text we started by analyzing our texts, as these should be at least partialy the same. I’ve wrote a small kotlin program for that. First just make sure you have all the Android translations in the one file and then compare this file to iOS one using mentione tool. It will generate 4 csv files that you’ll be easily importing in any excel-like app. I used Google Docs for that. The files are:
- Identical texts with its keys
- Nearly identical texts with its keys (case insensitive)
- Similar texts with its keys
- All the remaining texts with its keys
3. Differently concatenated resources
Sometimes the final translation might consist of several resources, which was also not agreed on. This also had to be aligned.
Next week I worked with iOS developer on the analyzer spreadsheet. We agreed on the keys, even created some naming concept for them, so these are similar in the future. We also agreed on the way of concatenating particular texts. Did plenty of notes in the spreadsheet and then we started a final, major refactor on the apps. I won’t lie – it wasn’t the best time of my life. The work had to be done with extreme caution but at the same time, even monkey could do that.
A dream comes true
We ended up with a shared database of translations with just a few texts that were platform specific. We’ve decided to keep the separate.
At this point, if you’ve decided to use something else than Loco, it would be the end of it. Rest of the story is very tool specific.
It was a time to import translations to Loco. We took the android file, as in the test this is when the Loco import worked the best. You can set up various options in there, just test it out yourself. Loco lets you tag resources and this is the way we’ve chosen to separate platform specific resources. We’ve created the tags:
- shared – most of the texts was tagged with that – these are the texts used between all the platforms
- android – clearly Android specific texts
- ios – iOS specific texts
- plurals – a special category for plural resources – we were surprised but Loco handles that well too, more about that later
We started with Swedish database and then, in Loco, we translated it to English and Norwegian. At this point, we only needed a convenient way to export Loco resources, some other way than manually, each time the translations are updated. Luckily – Loco has a great API.
Awesome exporter
I’ve looked for existing tools and I found this script. It’s great but we needed something bit more specific to our use case which was:
- export resources with the specified tags
- export resources with the specified tags as plurals
- possibility of having a script inside project repository and executing in a build time
I forked it and ended up with more customizable one. You can grab it here. Check it’s readme for the information on how to use it.
Awesome exporter automated
If you want the export to happen automatically on each Android build you can modify your buuld.gradle. We eventually went for a manual execution of a script, for a better control. However, if you still wanna do that just create a separate script that passes arguments to loco_import.sh and execute it as follows:
//automatic strings import on build task importStringsFromLoco(type: Exec) { doFirst { commandLine "update_translations.sh" } } //OR run task before every build preBuild.dependsOn(importStringsFromLoco)
Current state
So – how does it look now? Let’s say we have to develop a new feature. First one to implement it decides on the concatenation and the keys and then puts that in Loco. The translators fill up missing languages and then we just export them using out exporter script. We keep our work consistent from now on. Our translator sometimes fix the typos in the localization when they realize there is such bug in the app and they don’t even notify us, as they know it’s gonna be released in the next release window.