[Cobalt Matters]

Strings Uniformization Between iOS, Android & React Native Platforms: “To String or Not to String”

When developing apps for customers from a multitude of countries, we find ourselves hearing the following sentence more often than not: “We want our application to support multiple languages, as we target people all over the globe”. Or something along those lines. 😄

So, naturally, in this era of optimality, we have been trying to find the most efficient solution for this problem. This article aims to explain our findings on what works best when dealing with this situation, how we handle it (in most cases) and some ideas on what is not exactly optimal about our approach.

First of all, the main issue is that, in most cases, we need to develop both an iOS and an Android application. And since developing apps for the two requires different approaches so is the handling of translations (from hereon referred to as localization strings or localizables).

Needless to say, if we choose to write the application in React Native, the approach will differ as well. We will cover all three cases, so fear not. 💪

Our main goal was to have a single source for our localizables; something that is human readable, easy to edit and easy to understand at first glance. 

Also, another important issue is that it should be easy to export it to the project: be it iOS, Android or React Native.

Ok, so all this sounds great and all, but how can we do it? Well, we found a neat little command line tool called babelish. It allows, among other things, to export the contents of a Google Sheet into project friendly files for iOS, Android and React Native.

The infamous Google Sheet

Without further ado, let’s jump headfirst into localizations. You can’t learn how to swim by reading books about it, after all. But before doing the head first dive, testing the water is a good idea, believe us. So checking out our sample translations Google Sheet would be a good idea, just so it would be easier to wrap your head around what’s to come up.

Localization, on all platforms, works with key-value pairs. What we need is a string “key” which will be used by the application to choose which exact translated “value” should be displayed on the screen. The “keys” in our case are the contents of the Variable Name column. The “values” are the contents of English, French, German and Spanish columns. These are the actual values which will be displayed on the screen if the device language is the corresponding one.

Even though the Google Sheet is indeed easy to read and pretty easy to understand, there is still one issue: how can the person reading it be sure where exactly in the application will each string be displayed? Here’s where the Description column comes in and thankfully babelish has a feature precisely for this situation. We can choose to tell babelish to ignore the contents of any one column from the Google Sheet. This gives us the freedom to write precious meta-information for each translation. For example, for an item with the key homepageButton, we could either write in the Description column that “title of the only button from the Homepage” or provide a screenshot with the exact location of the translation on screen. So. Neat! 🤩

The dreaded babelish config file

In order for babelish to do what we want it to do and not what it wants (which would be… nothing 😄), we need a config file to give these indications.

For more info, check the babelish sample on GitHub.

Export strings to .csv (Base2CSV)

  • filenames: array of all the Localizable.strings file paths you have in your project
  • headers: needed for .csv export file. Will be the name of the column headers for when .csv will be exported to a (Google Sheets) table
  • csv_filename: the name of the .csv export file

Import strings from .csv (CSV2Base)

  • filename: the name of the .csv import file OR the name of the Google Sheets file
  • sheet: optional; number; the sheet index of the Google Sheets file (aka filename) from which you want babelish to import translations
  • fetch: optional; boolean; default is false

*if set to false, babelish will search for filename locally and import strings from it

*if set to true, babelish will download all sheets from the Google Sheets file with the name provided in filename parameter

*if sheet is missing, the downloaded sheets will look like translations_{SheetName}.csv. babelish will export all contents from the downloaded .csv files to output_dir, overwriting older values of same key-value pairs if necessary.

*if sheet is NOT missing, babelish will download just the contents of the sheet from the specified index in a .csv file named translations.csv. babelish will export all contents from the downloaded .csv files to output_dir

  • langs: the name of the column header from .csv import file followed by the corresponding name of the .lproj where the Localizable.strings file exists
  • csv_separator: depending on what table editor you use, the .csv file will have different separators; the default is , but Apple Numbers, for example, uses ;
  • output_dir: the path where the .lproj files are
Cobalt Matters Strings Uniformization

How we work with it

This being said and all these steps being taken, all we need to do in order to import our Google Sheet into the project is use our friendly neighbourhood babelish. Long story short is that all we need to do is run the command babelish csv2strings (iOS) / babelish csv2android (Android) / babelish csv2json (React Native) from the Terminal in our project’s root directory. And voilà: app localized!

Good practices

Organizational stuff

  1. Include the babelish export/import files in .gitignore.
  2. Keep your sheet ordered alphabetically by variable key (e.g. Variable Name).
  3. If you ever need translated strings with other strings or numbers included inside them, we found the following format works best:

 – {x} for strings

 – {n} for numbers

 – {f} for floating point numbers

For disambiguation, check ourOwnAppDescription from the sample Google Sheets file.

iOS

  1. Keep your string keys inside a separate String extension file like so:

2. For a cleaner code, a good idea is to use the following String extension:

3. Keep all translations (eg var localized and func localized) in a single .swift file

Android

  1. Always keep the constants in a different strings file, so they don’t interfere with frequent translations changes.
  2. Set the translatable flag to true for all the strings that don’t require translation. This prevents warnings and other unwanted errors that might appear.

React Native

Keep each language in its own json file and use a different file that unifies them using the “require” function for each one and the ‘react-native-localization‘ library.

Issues we encountered

But like everything in life, if something sounds too good to be true, it probably is. Programming and development, in general, is an iterative process, new problems appearing with every new implemented feature. It is our job as programmers to figure out how to solve and fix all the bugs that find their way in the development process. We also encountered some issues regarding the conversion from the CSV file to the native strings files, that we’ll cover briefly:

iOS

Since babelish is a tool built by an iOS developer it’s not so hard to believe that not too many issues arose. Kudos to François Benaiteau for creating it. That being said, we’ll move on to the other more problematic platforms.

Android

The first big issue that we encountered on Android was the & character. Since the Android XML requires that the ampersand (and other special characters (like < and >) to be escaped, were left with XML errors. A practical guide on how to escape these special characters can be found on the Android developer’s official website.

Another special character, the apostrophe (used intensively in French language, for instance), is completely ignored by babelish, leaving us more work to do in order to escape this character.

And last but not least, and not the worst issue exactly, babelish does not create the strings for the required strings.xml file. It remains our job to copy and paste into this file the default strings that we want our up to be translated it, usually in English. Cool, right? 😁

React Native

We had a little fun with React Native also, mainly because the backspace from the escaped newline character \n was being doubled. It leaves us with \\n, which is not recognized as a valid json sequence.

Also, the double quotes used in multiple strings were replaced with \\\”, which is kind of strange behaviour to begin with. But who am I to judge the mighty powers of babelish? After all, we should be thankful because it frees us from a lot of work.

Fixing these issues

Since this is just the first iteration of our babelish process, needless to say, that we still left some issues open; those being the ones described above. For now and since it doesn’t require a tremendous amount of work, we solve them manually. Meaning we replace all bad characters manually in a text editor, using global Search and Replace. Not optimal but we found that automating this process would require time we’re not willing to spend. Yet.

Final thoughts

Localizing strings is fun and all, but at the end of the day, you have to think about what really matters to you: time or a clean code structure. You’ll definitely spend some time at early stages of development with the localization process, but in the long run, you’ll come to realise that keeping all the strings in one place is the best approach, even if you are not planning to translate your app in another language.

Eager for some practice with the newly learned skills? Why not localize your strings in Japanese in your existing app today using Babelish, or start a fresh new project just for the sake of practising? Who knows what a productive day can bring?

All rights reserved Cobalt Sign.