Achieving Compile-Time Localization Validation
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
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:
let’s declare the following ShoppingCartStrings
Enum:
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
:
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:
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 💪