
Localization in Swift like a Pro
How to write safer localization code and save time by getting less distracted from your actual code writing task in Xcode.
The Status Quo in Xcode
As developers we know for a fact that context switches are inefficient. This does not only apply to CPUs though, it’s also true for the coding process itself. Often times we start writing code we need to immerse ourselves into, that’s why developer tools try to support us with any little task that might distract us from writing the actual code.
Xcode is a very good development environment in that it helps us app developers both with basic development tasks (like code completion, syntax highlighting, refactoring) and more complex tasks like defining device-agnostic UIs (Interface Builder) as well as several kinds of debugging and testing tools (View Hierarchy Debugger, UI / Performance Testing).
Missing Feature #1: Keeping Localizations in Sync
But at some tasks which are clearly not part of the coding process, Xcode lacks convenience. One such example is keeping localizations in sync between:
- Code Files (e.g.
NSLocalizedString
) andLocalizable.strings
files - Storyboards / XIBs and their Strings files
- The different locales of a Strings file
Apple actually does provide tools to update Strings files, namely ibtool
and the xcrun
scripts genstrings
and its successor extractLocStrings
. You can experience them in field within Xcode when you add a language to an existing Strings file: Xcode will automatically use existing keys in a source language to prefill the new languages Strings file. You will also have noticed that when you first localize a Storyboard or XIB file, Xcode automatically adds all localizable Views (like UILabel
) to the corresponding Strings files. But that's where Xcode's localization assistance stops: When you add a new label in a Storyboard for example, Xcode won't automatically add that to the Strings files.
Missing Feature #2: Resource Access
Another example where Xcode lacks convenience is resource access. To be more specific: Image Assets, Color Assets, Storyboards, XIBs and Localization Strings are typically all loaded in code by using a String literal like in the following example:
title = NSLocalizedString("onboarding.page-one.title", comment: "")
This dynamic string reference method is simple to use but lacks two important features:
- There is no compiler checks to ensure a resource is (still) available
- There is no autocompletion, leading to many misspellings & exact name lookups
Google solved resource loading much more gracefully. In Android Studio you can access resources like this with all the above features provided:
title = R.string.onboarding.page_one.title
Xcode disappoints in this perspective and still hasn’t any viable solution.
Enhancing Xcode using Build Scripts
Fortunately, you don’t need to rely on Apple to fix these issues. The Swift developer community found their own ways to enhance Xcode’s features to improve their workflow. One such way is by providing command line tools and running them automatically on every build of your app. This approach needs some initial setup and configuration but turns out to be pretty easy once understood and very powerful in terms of possibilities.
You can find detailed instructions on how to configure a build script here:
The tools we are going to use to add the two missing features to our Xcode workflow are BartyCrouch and SwiftGen. Go ahead and install them on your system using Homebrew:
brew install bartycrouch swiftgen
BartyCrouch
Remember Xcode includes tools for assisting with localization but only uses them in some rare situations? Well, BartyCrouch fixes that. It automatically searches your project for localizations and updates all Strings files incrementally. This works in all directions:
- New
NSLocalizedString
entries are added toLocalizable.strings
locales - New localizable views in Storyboards / XIBs are added to their Strings files
- Deleted localizable views in Storyboards / XIBs are removed from their Strings files
- All locales of a Strings file are synced with the keys of a source locale
But this isn’t all BartyCrouch does for you. It also sorts the keys within your Strings files alphabetically to automatically group together keys that have the same prefix and to minimize merge conflicts.
Also, BartyCrouch gives you the option to exclude specific views in Storyboards / XIBs from being localized at all. This is useful if you have labels whose values will be set in code programmatically and thus shouldn’t be translated. Simply add #bc-ignore!
to the Comment for Localizer
field within Interface Builder like described here.
To use BartyCrouch, simply add this build script to your project:
Make sure the build script is run before Compile Sources
by reordering per drag & drop.
But this isn’t all BartyCrouch can do for you …
Eliminate switching to Strings files entirely
The developers classical localization workflow in Xcode is like this:
- Call
NSLocalizedString
with your localization key. - Switch to the
Localizable.strings
file. - Add a new key and provide an initial translation for the dev language.
- Repeat step 3 for all supported locales, keeping translations in other languages empty.
By adding the above build script alone, BartyCrouch already eliminates the annoying steps to add new keys to your Strings files without further configuration. But you still need to switch to the Strings file(s) to provide some initial translations. You can even eliminate this step though with little configuration:
- Add a new Swift file to your project (e.g. named
BartyCrouch.swift
) - Replace its content with the following code (fix the TODO if needed):
That’s it!
From now on, instead of using NSLocalizedString
you should use BartyCrouch.translate
:
Note that within the translations
dictionary you can provide one or multiple initial translations. On the next build, BartyCrouch will not only add them to your Strings files to prevent you from needing to switch to them at all, but it will also automatically replace the BartyCrouch.translate
call with the a call to NSLocalizedString
. So the resulting code will still be original Foundation
macros as you know them. An optional comment
parameter can be provided as well, but isn't required like in NSLocalizedString
.
Finding common Strings file issues
If you’re a responsible developer, you’re already using SwiftLint (or another code linter) to enforce code style and prevent some common mistakes in code. Swift provides many warnings and compiler checks itself, so you’re in pretty good hands regarding code. But did you know that there can be issues with translations as well?
The most common issues are:
- Translation Ambiguation:
Multiple entries of the same key within a single Strings file. - Missing Translations:
Empty values for keys in untested locales of your project.
BartyCrouch has an integrated linter for Strings files which provides line-specific warnings right within Xcode for both of these issues. All you need to do is to run the lint
subcommand right after the update
subcommand in the build script:
Now when building your app you’ll see something like this when issues arise:

Configuring BartyCrouch
All the above features are turned on by default, so you don’t need to configure BartyCrouch at all to use them (just don’t forget the option -x
in the build script to make sure warnings are shown in Xcode).
You can completely customize BartyCrouch using a configuration file — I recommend using one at any rate to optimize BartyCrouch’s performance. You will definitely need one if your development language isn’t English as that’s the default language. The README has a good step-by-step configuration section, so I won’t repeat that here. Go check it out.
SwiftGen
Remember how Android Studio deals better with resource access than Xcode by providing compile time checks and autocompletion? Well, SwiftGen fixes that. It automatically searches your project for any specified type of resources and generates a Swift file containing an enum
with all your resources names. This way you both get compile-time checks and autocompletion when accessing them.
For the purpose of this article, we will only generate an enum for localization strings, refer to the README to use more options like statically referencing images, colors and Storyboards.
To configure SwiftGen, we first need to create an empty Strings.swift
file in our project. The contents of this file will be automatically replaced by SwiftGen, so it might be a good idea to place it separately from other code, for example within Resources/Generated
.
Next, create a file named swiftgen.yml
in the root of your project and add the following contents to it:
Replace both entries of path/to/your
with the correct paths of the appropriate files, then add this build script to your project:
Next, make sure the SwiftGen build script runs right after BartyCrouch.
After building your project the first time, you will from now on be able to replace calls to NSLocalizedString
with calls to the generated enum L10n
(which is short for Localization
but without the 10 characters between the beginning L
and end n
). Note that SwiftGen will automatically split your localization keys by .
and use -
or _
for camelCasing. Thus, using keys like ONBOARDING.PAGE_ONE.TITLE
or onboarding.page-one.title
will both be usable via a call to L10n.Onboarding.PageOne.title
.
Combine BartyCrouch & SwiftGen
Now you have added both missing features to Xcode, but they don’t work together well. When using the BartyCrouch.translate
call to create new localizations, BartyCrouch transforms that into a NSLocalizedString
call on the next build, and one still needs to replace this with a L10n
call. But BartyCrouch has us covered here, too:
Simply change the configuration option transformer
within the .bartycrouch.toml
file in the section [update.transform]
from foundation
to swiftgenStructured
. This will make sure that instead of NSLocalizedString
from Foundation
, BartyCrouch will transform the BartyCrouch.translate
to a call to SwiftGen's L10n
directly.
Pro Localization Workflow
As a result of all the previous steps, the new localization workflow for developers is like this:
- Call
BartyCrouch.translate
with your localization key and one or multiple translations. - Build your project (you can do multiple step 1’s before doing this).
Since you will do step 2 at some point anyways, it’s practically only a one-step process now.
Conclusion
By configuring BartyCrouch and SwiftGen for your project, you can streamline your localization workflow in Xcode, making it both less distracting for developers and safer in terms of preventing translation issues at the same time. This saves time and nerves.
This article was written by Cihat Gündüz,
main author of BartyCrouch