Localizing mobile apps at Futured
I'm gonna tell you a story. It'll be about a history of how we managed translations in our Android applications throughout the long years of mobile development at Futured.
I will also try to sell you a brand new free Gradle plugin at the end, but let's not focus on that now.
Once upon a time...
Our story began many years ago when people still used Java for Android development, and AsyncTask was still around. Also, for those in the know of Android development, "string resources" are quite a common term. These XML-formatted files serve as the language library for your app, housing translations supporting the diverse languages your app communicates in.
Our clients would send us translations the old-fashioned way: on a parchment. Well, ok, it was always some sort of randomly structured Word or Excel-like document, but it seemed ancient.
We then played the exciting game of "Hand-Transfer The Strings XML Resources." When I retrospectively sum it up, I might have spent hours on riveting copy-pasting, one string at a time, hoping not to miss or switch up any translation. It was precision labor, manual, painstaking, and horribly inefficient. Not to mention, errors were our constant companions, sneaking in with a misplacement or two.
When clients wanted to make a granular change, they always sent a Slack DM or e-mail, asking us to change this and there, on this and other screen, and so on. And there you go again, replacing values in XML files.
POEditor: Welcome to the Industrial Revolution
We soon realized this was not the way, so we left the Stone Age and entered the Industrial Revolution with a web tool called POEditor. A paid service that was essentially a fancy interface for translation entry, allowing you to download entire translation files to be copied into your project.
It was an improvement. We had roles, user management, which brought some order to the chaotic world of translators, proofreaders, and developers.
Clients could add their bits directly, and we didn't have to keep an eagle eye tracking the arrival of every new document with new revisions of translations. However, it wasn't without flaws: downloading generated XMLs and copying files remained our burden, and let's not forget, it had a price tag attached.
Automation Begins: Command Line Script era
Our evolutionary journey continued as we took automation on board. We were still hitching a ride with POEditor, but we brought in a command line tool called poesie, to lighten our load. This clever little script managed to automate downloading string resources from POEditor directly to our projects, reducing our manual efforts.
However, our wallet was still feeling the pressure as POEditor continued to charge for its services. And being a company with “no vendor lock” as one of our fundamental values, we did not want to lock our clients into using this tool. Although we were paying for everything, if the client decided to move development elsewhere, they would have to find another solution to manage translations.
Moreover, POEditor treated Android and iOS translations equally - without acknowledging the individual needs of each platform. There was no specific design to differentiate between strings exclusively for Android and those for iOS, so by default, we would end up having iOS-specific strings in our XMLs just to never be used by our apps. To overcome this, we had to get a little bit creative by filtering them by specific keys before generating string resource files.
The last downside was the requirement to install the 'poesie' tool in your development environment. Each developer or CI workflow needed to have this in order to use it.
IntelliJ IDEA Plugin and Google Spreadsheets: Finally, Freedom!
We moved into the golden age several years ago when we discovered this awesome Spreadsheet Localizer IDE plugin by our colleagues from Ackee. It was the magic wand that made our budgets sigh in relief. Google Spreadsheets became our new playground. It lets us place the translations into Google Sheets, which then would be automatically downloaded and converted into Android string resource files.
Google Spreadsheets have proven to be quite versatile when handling translations, offering several key advantages:
- Universality. Everyone from management people to developers is familiar with how Google Spreadsheets work, making the platform easy to navigate and use.
- Customization. Google Spreadsheets enables you to store a range of metadata alongside translations, including notes and comments. Moreover, you can harness its functions to calculate various metrics from your strings and much more - endless possibilities.
- Auditable Change History. A complete history of alterations is available, proving invaluable if an accidental change is made or if the metaphorical goblin comes and rewrites something they shouldn't.
- Granular permissions. Google Spreadsheets allows you to set specific user permissions, providing you with the control to prevent mishaps. For instance, you can limit your client to commenting with new suggestions rather than risk them changing everything and potentially disrupting your string placeholders.
- Oh yeah, and it’s free.
Screenshot from Google Sheet-managed translations for our internal app called Futured Coin
So, naturally, things were bright and sunny. Our clients and translators could continue editing while we pulled the strings directly into our project. We gradually migrated all projects from POEditor to this solution.
The sole drawback of this approach was the reliance on the IDE plugin for the bulk of the work. It couldn't be invoked from the command line, necessitated installation in every version of Android Studio in use, and didn't lend itself to continuous integration workflows. However, despite these limitations, the advantages mentioned earlier significantly outweighed these minor drawbacks.
It was an unlimited, client-friendly, license-free party. We reveled in the joy of no longer having to pay for string management.
Kotlin Multiplatform, and MOKO resources: Beyond the Future
As time passed, Futured began to embrace the shift in the technological world, and we set sail towards Kotlin Multiplatform (KMP). We'd been using KMP for a while to share various aspects of logic across apps, and in one of our latest projects, Preventivka, we decided it was time to go all in. We pushed beyond logic — sharing strings, color, and image resources with the aid of the incredible moko-resources library. Sharing almost everything in KMP saved us a tremendous amount of development time – but that's a story for another blog post.
When we started using the MOKO library, we hit a wall when synchronizing string resources from our beloved Google Spreadsheets. Our tried-and-true method of synchronizing string resources from Google Spreadsheets, catalyzed by the IDE plugin, began to falter in the face of KMP development.
The IDE plugin - our old friend - was unfortunately out of step in the KMP era for two main reasons:
- First, to use the IDE plugin, you need to invoke it directly from the IDE (either Android Studio or IntelliJ IDEA). While it worked like a charm for our Android-loving half, it wasn't quite in sync with our iOS colleagues, given its IDE-dependent nature. And so, we craved a tool that could be summoned from the command line, freely playing well with all.
- Secondly, while the IDE plugin was fluent in generating Android strings, it didn’t quite understand the requirements for the MOKO library. Sure, MOKO accepted the Android-based XML format, but they required a unique titling and directory setup. This discrepancy added just another layer of complexity to the conundrum.
So what's a dev to do?
Our solution: Write our own tool, catering to our needs. It should be backward compatible with Ackee's Google Sheets format so we can use it even for our existing Android-only projects.
The result? A free-to-use, open-source tool. Introducing Sheet Happens! 💩, a nifty Gradle plugin that eases the process of localizing your Android and Kotlin Multiplatform (KMP) applications. This open-source tool leverages the power of Google Spreadsheets to generate XML string resources.
With this Gradle plugin, we got a chance to improve a few things we had been missing in the IDE plugin, so now:
- You can invoke the translation synchronization using the Grade task and see the output in the console. With the previous solution, we just pressed a button in IDE and waited for something to happen. If something went wrong, we did not know -- no output in the console or anything.
- Being a Gradle task lets you incorporate string synchronization into various workflows you might have set up, for example, as part of some Git hook or having it run on CI.
- You can split string and plural resources into separate files or specify file names if you wish to for better separation.
- You can extract the same language into multiple resource folders. This is particularly useful for apps where you want to provide the same translation to users in countries with similar languages instead of falling back to the default language (looking at you 🇨🇿 & 🇸🇰).
- You can apply the plugin per feature module; this allows you to split your translations across many Google Sheets, where each module gets just the translations it is supposed to access.
The Here and Now
Here we are, living in a digital age where managing translations is as easy as editing a Google Sheet, and it doesn't cost a penny.
We realize that Google Sheets might not be the magic potion to solve everyone's localization needs. And yes, some paid services offer more sophisticated tools and fancy bells and whistles. But here's the truth: it works for us and our clients at present, and so we'll stick to this for the time being.
So go ahead and
./gradlew makeSheetHappen ! Give our plugin a whirl. It's the least hectic localization you’ll ever do!