Achieving Compile-Time Localization Validation

Skwiggs
Urban Sports Club Engineering
4 min readSep 2, 2019

This article is written with XCode & Swift in mind. With that being said, notions & ideas explored below can be applied to any programming language / IDE. I leave that part as an exercise to the reader ;)

guard let templateString = NSLocalizedString("hello", tableName: "Greetings", comment: "") else { print("Missing localization"); return }

Sounds familiar ?

Let’s face it: Localization sucks. It’s all String-based. It’s messy. And even with the best effort put into arranging your .strings files, you always end-up forgetting which key to use, or with duplicates all over the place.

Not only that, but it’s also nearly impossible to keep track of changes made over time, since XCode won’t help you in the slightest if some key were to disappear or are otherwise renamed.

Now, what if I were to tell you there is a better way ? What if I told you the following is possible ?

label.text = Greetings.hello("world").localized()
// returns "Hello, world" in English,
// "Hallo, world" in German, etc.

And

Localization Managers

Before we do anything, we need to find a generic structure that can be reused for the many localized fields our app contains.

As with most things in programming, we’ll want some sort of Manager to make our logic reusable. At OneFit, I opted for a protocol which I called LocalizationManager

LocalizationManager protocol

As you can see, it’s quite straightforward. It also comes with its own convenient default implementation, which I’m sure you’re very familiar with.

The power of this protocol shines when you implement it with Enums; Swift Enums are among the most powerful and versatile of them all, and the perfect tool in this situation.

For instance, let’s say we are working on a shopping app, and have a few localized strings for the shopping cart. Given this example strings file:

LocalizedShoppingCart.strings

let’s declare the following ShoppingCartStrings Enum:

ShoppingCartStrings.swift

I’ve added the actual english values of these keys as documentation so that it shows up when we’re using auto-completion 👌

Now, that enum on its own is not going to do much. To fix that, all we need to do is to make it conform to LocalizationManager :

ShoppingCartStrings+LocalizationManager

And just like this, we’ve gotten ourselves a very convenient way of getting localized strings. All you need to do is call ShoppingCartStrings.itemsMultiple(count: 3).localized() and you’ve got a formatted & localized string! Pretty neat.

Now this is all fine and dandy, but what about all the remaining strings files & keys, I hear you say. “Do I have to write all of them manually ?”

Fear not, there are good news; I wrote a Swift script that generates these Enums automatically!

Not only that, but as shown at the beginning of this article, it also adds specific warnings when keys or even files are missing.

I explain how to set that up in the next section, so tag along!

Automatic Localization Manager Generation

To get started, you’ll need a copy of the script. Download it and save it in your project’s root folder (where your .xcodeproj / .xcworkspace file lives)

Once that’s done, go to your target settings, add a new build phase and name it “Generate LocalizationManagers”, then copy & paste the following script invocation:

Generate Localization Manager Build Phase

Make sure to place that phase before the Compile Source phase, as it will otherwise mess up the build process.

Then, go ahead and build your project. The script will be invoked and a bunch of Swift source files will be generated in the output folder you specified. Don’t forget to link them to your project (necessary for each file that wasn’t previously added. The first time, that’s all of them).

Once all that’s done, you’re finally ready to remove all previous calls to NSLocalizedString and replace them with their new equivalent.

Next time your strings files change, you’ll immediately get compiler warnings/errors 💪

Going beyond

By default, the script uses en.lproj as the main localization base file. All enums will be generated following its structure.

For now, the only way to change this is by editing the script directly, but who knows, in the future this might be supported right from the input parameters.

The script also highlights any issues with your strings files; any keys that are not present for each supported language will raise a warning in your main language’s strings file.

The same happens if an entire strings file is missing.

TL;DR

Download the script: https://gist.github.com/MrSkwiggs/9cffc243a77a0b3088ab019fa0939b5e

Add a build phase: https://gist.github.com/MrSkwiggs/e5b28fea20ca47fbbb97242ac4949a23/

Build. Add Generated Files to Project. Profit 💪

--

--