Swift scripting on the rescue of Localizable strings files management on iOS

How using SwiftGen, Lokalise.co and Swift scripts are helping us managing Localizable.strings & Localizable.stringsdict files efficiently.

Jean-Charles Sorin
Apr 11, 2019 · 7 min read

Managing strings translations with Lokalise.co

Since 2018 at Back Market, the engineering team decided to manage translation strings with a new solution. The team compared a lot of tools, like POEditor or Transifex. After some weeks of benchmarking and workshops, a competitor takes the point: Lokalise.co.

Image for post
lokalise.co

For the mobile software team, on iOS and Android, Lokalise.co is a great solution, particularly with the Over The Air feature. With the iOS or Android SDKs, translations can be updated without submitting a new binary. In a world of release deployment, it makes much sense!

You can also directly download Localizable.strings & Localizable.stringsdict files from the platform. There are two ways to do it:

  • The Build & Download feature: directly from the website, which give you all keys and strings files available for a dedicated platform;
  • The API, via /files: it gives you the opportunity to download specific files for a dedicated platform.

Both are the same but not the methods.

Let’s begin with what we use and how we structure our project for Localization.

Use SwiftGen for safety translation code

It’s been many years I am using SwiftGen across iOS projects, and one of the great features I like is generating the L10n interface(s).

SwiftGen gives you the opportunity to manage resources like storyboards, assets, strings, colors, fonts and many others.

The purpose is simple: using constants and safety functions to get resources across your project. If a resource disappears, your code doesn’t compile anymore (if you were using the key). Pretty efficient to detect regressions.

For instance, if we delete a common_ok string key in the code below:

Image for post
Guarantee that any translation is still existing across your project is a must to have.

Configuring and using SwiftGen is pretty easy, but it’s not the final purpose here. To go further, check the documentation.

Create a dedicated Localization.framework for your app

Now we can safely manage localizable keys on our iOS project, the final purpose for your iOS app architecture is to isolate translation management from a dedicated framework or module. That’s why on our iOS & Android app we create a dedicated Localization framework.

Image for post
Our Localization module.

It’s mighty, because of:

  • Single responsibility principle: it only manages strings translations.
  • Third party substitution: tomorrow, if we switch from Lokalise.co SDK to another, only an update on the Localization.framework will be necessary.

To use SwiftGen with Lokalise.co, we have to update the default strings template for the SDK call. Here we use the flat-swift3.stencil template of SwiftGen:

That’s all. Now our Localization module:

  • Manages local strings;
  • Can fetch OTA updates from Lokalise.co (keep in mind that Lokalise.co keys take the overriding priority over local ones if key available on both);
  • Safely expose Localizable keys with SwiftGen across the iOS project.

Sounds great!

However, two things are missing:

  • Automatically update Localizable.strings file at any time to be aligned with the last Lokalise.co version;
  • Manage plurals: SwiftGen does not manage plurals .stringsdict files by default, and Lokalise.co use them.

Update our Localizable.strings file: time for Swift scripting!

The issue and fallback to think about

To make Lokalise.co working correctly with OTA updates, you have to:

  1. Generate a bundle version of your available keys;
  2. Make the bundle version available on preprod/production. Note that, with this feature, it will be useful to rollback or A/B test translations from a version to another:

After the setup, when your app starts and you call the dedicated SDK methods for OTA updates, your app receive the last version of your Localizable.strings for the next start. Keep this in mind!

Moreover, you are encouraged to freeze previous bundles for previous app versions. Therefore, some updated values (not keys) will produce crashes with String(format:). Like an expected format value ”Version %s” instead of ”Version %@”) or any translator error.

Bundle featurr sounds great but:

  • What about the first start, or network issues?
  • What if, for any reason, Lokalise.co does not respond anymore? You got the point. We need to keep the local Localizable.strings file up-to-date too.
  • Moreover, we need safety generated SwiftGen keys to use them across the project, because nobody got time for this type of line:
[[Lokalise sharedObject] localizedStringForKey:@"key" value:@"default value" table:@"table name"];

We’ve already done it before by updated the stencil template.

To download the last Localizable files via the Lokalise.co API, we can make a bash script, or a Ruby script, or Python script.

But

Image for post
SWIFT.AT.ANY.TIME

Create the Lokalise.co Swift script downloader 🚀

Make shell commands call possible 🤓

To interface with the shell from Swift code, we have to write two utility methods. They are the key for script scripting with bash commands.

These two methods are the base for Swift scripting: use bash commands inside your Swift code.

Create the API download calls with URLSession & Codable ⬇️

We use Foundation, so we have access to the URLSession API and it is sufficient for our use cases.

  • URLSession configuration:
Prepare the Lokalise.co APIs interfaces.
  • Lokalise.co download requests:
Two requests here: get the Lokalise file info, and get the .zip file.
  • Writing file logic:
After we download the .zip file, we have to write it on disk.

Multi-languages support 🇫🇷

Your app supports multi-languages and a base language. To copy all your supported Localizable variants, we create two properties: one for the base language, another for the languages listing:

We can add future language compliance just by adding a key/value.

Create the zip unarchiving and copy files code 👯‍♀️

Now we’ve downloaded the .zip file (AWS URL) from the Lokalise response, we can unzip it:

No more simple than unzipping a file…

The unarchived folder contains multiple _language_.lproj folders with Localizables.strings and .stringsdict files inside. We have to copy them in the destination of our Localization framework.

Copy files method:

We copy the base language and the other to the target destination.

Use the command line parameters 🚀

If we use the command line tool, why not using parameters 🤓? Let’s write it to get APIToken and ProjectID directly from the command line:

Really simple, uh?

Clean & make it beautiful 🤩

Seems good but some beautiful details are missing:

  1. Cleaning and deleting unzipped files
  2. Create console logs to follow script work in your Terminal
  3. Use emojis to make it «alive» and visible
  4. Use Semaphore for asynchronous logic

Let’s write the entire script now 🚀:

Run it! 👨‍💻

Image for post
Launch the script, and enjoy!

Now, each time a developer or a translator update your translations on Lokalise.co, just run your Swift script to put them automatically in your project 🚀.

Much better: create an alias for it ;)

To be informed on updated keys by translators on your project, don’t hesitate to use integrations. We use the Slack integration, and it’s very useful to follow changes every day. You can directly access to a specific updated or added key via a link.

Create a Swift script Build Phase to generate plurals

Wait, SwiftGen doesn’t manage plurals?

Yes, SwiftGen does not manage plurals (yet):

You can create a dedicated template or an evolution for it, but since our first Lokalise.co Swift script, we can’t wait writing another one, particularly my colleague Arnaud 🍻.

Let’s Swift it!

The L10n extension for plurals

What we want in terms of Swift code is this type of line:

L10n.Plurals.numberOfBananas(bananaCount)

To do so, we have to create an extension on our previously generated file, L10n.swift:

Create the script

What do we want?

  • Specify access control: public, private, etc.
  • Execute it via a Build Phase: each time we re-build the Localization.framework, generate the related plurals if needed. To do so, you have to prefix your script with
#!/usr/bin/env xcrun — sdk macosx swift
  • Generate the L10n+Plurals.swift file extension
  • Manage errors and console logging

Let’s go straight to the point with the entire script 🤓.

Now, just update the SwiftGen Build Script Phase in your project:

… and build it!

Image for post
Print from your script, you will know exactly what happens during the build phase.

Et voilà!

With those two scripts, our productivity getting better by managing Localizable strings files on our iOS project. Our code is safer, fully automated and generated in Swift!

Moreover, any member of your team can improve it and make it owns.

Those scripts are available as Gists on GitHub here and here, as displayed in this Medium post. Don’t hesitate to propose code evolution/solutions.

Thanks for reading, and thanks to my colleague Arnaud for the L10nPlurals Swift script 👨‍💻.

Want to go further?

You can make a lot with Swift scripting, but I hope you’ve already done rockets with it 🚀. That’s why we love Swift on iOS, Swift Server Side & Linux. `Unlock the power of Swift right now.

We hope you’ve liked this post, don’t hesitate to comment 💬 or clap 👏 if you like it!

If you want to join our Bureau of Technology or any other Back Market department, take a look here, we’re hiring! 🦄

Image for post
www.backmarket.com

Back Market Engineering

Creative engineers building a less wasteful world …

Thanks to Antonin Morel

Jean-Charles Sorin

Written by

Engineering Manager @BackMarket — Mobile Team - www.backmarket.com - iOS engineering & personal growth.

Back Market Engineering

Creative engineers building a less wasteful world - www.backmarket.com

Jean-Charles Sorin

Written by

Engineering Manager @BackMarket — Mobile Team - www.backmarket.com - iOS engineering & personal growth.

Back Market Engineering

Creative engineers building a less wasteful world - www.backmarket.com

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app