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.

Cihat Gündüz
Feb 11 · 7 min read

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) and Localizable.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 to Localizable.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:

  1. Call NSLocalizedString with your localization key.
  2. Switch to the Localizable.strings file.
  3. Add a new key and provide an initial translation for the dev language.
  4. 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:

  1. Add a new Swift file to your project (e.g. named BartyCrouch.swift)
  2. 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:

  1. Call BartyCrouch.translate with your localization key and one or multiple translations.
  2. 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