Xcode String Catalogs 101
Xcode String Catalogs
Localization, or l10n
, is an important step for any app to reach a broader market and be more inclusive.
Up to Xcode 14, localizing your Strings always felt like a chore, as they were saved in multiple plain-text key-value .strings
files that easily broke your build if there was a mistake.
Supporting variations on plural required .stringsdict
files that were property lists internally.
With Xcode 15, the IDE gained a versatile and easy-to-use way of handling localization: String catalogs.
Table of Contents
How to Localize an App
Regardless of the chosen language or framework, l10n
mostly boils down to using String-based identifiers to look up the correct translation instead of using String literals directly in your code.
For example, Java has Property files that append their Locale to their name, like TransactionComponent_de.properties
:
Xcode uses a similar system with its .strings
-files:
As you can see, there’s a lot more ceremony involved, like double quotes and semicolons.
That’s why you could break your build with a missing semicolon in a .strings
-file!
If you needed a variation based on counts, like “1 item” vs “2 items”, you needed to either handle it yourself with multiple key-value pairs, or create a .stringsdict
-file specifying rules based on the count.
A better way to do l10n
was overdue for a long time…
What’s a String Catalog?
String Catalogs replace the previous .strings
and .stringdict
files by using a new format and a nice visual editor in Xcode to manage multiple languages and variations.
The general concept isn’t new, Xcode uses other catalogs, like Asset catalogs, to manage images, colors, etc.
To add a String Catalog to your project, select File > New > File...
and then String Catalog
.
You can now start filling the Catalog by hand. But then, it would still be quite a chore, wouldn’t it?
Instead, we let Xcode do the heavy lifting and concentrate on developing our apps.
Letting Xcode Do The Work
The “Build Settings” have a new option under “Localization” called “Use Compiler to Extract Swift Strings”.
It does what it says on the box: it extracts all localizable Strings during each build and adds them to the catalog, or if already present, updates them.
However, extracting all Strings without any differentiation would make little sense, as it would pick up a lot of non-localizable String, too, like identifiers, notification names, NSPredicate
formats, etc.
Thankfully, the automatic extraction only picks up two types of Strings.
The first type is any String created via one of String
type’s init(localized: ...)
initializers.
The different initializers give you various options, like the default value, the bundle, the locale, etc.
The second type of automatically extracted Strings is any String literal used in SwiftUI:
If you don’t want a String literal to be interpreted as a possible localization key, you can use Text(verbatim: "...")
instead:
The automatic extraction works bi-directional on each build if you haven’t changed anything of that localization. Change any property, like the translation, its managed status, etc., and it won’t get removed automatically.
Localization Variations
Localization is a useful tool, even without bringing another language into the mix. By aggregating all your app’s Strings into a single location, they’re way easier to proofread and change if necessary.
But there’s still the issue of context-specific variations, like respecting counts. Creating an extra localization key for each variant is another chore Xcode 15 tries to eliminate by providing “variations”.
Vary By Plural
For example, displaying an item count in a shopping cart requires the differentiation between singular, plural, and zero items, even if you only want English:
- 0 items
- 1 item
- 2 items
Before String Catalogs, you either multiple localization and decide in your code which one to use, or use a .stringdict
file to retrieve the correct String format specifier, and then build your String.
It got the job done, but multiple steps were involved, making it hard to maintain and share with others.
String Catalogs, however, integrate an easy way to vary by counts in a single editor mask without changing your code (much).
First, Xcode needs a localizable String that uses String interpolation:
After the next build, you will find your String catalog has a new key called %lld items
.
What happened here is Xcode picked up the localized String and created a key with a format specifier, in this case, long long int
.
Now, choose “Vary By Plural” in the context menu:
This adds two sub-options: One, and Other.
The format specifier part is no longer text and can be positioned as needed. If you go to the context menu again, you can also add another option: Zero.
It might not be as flexible as .stringdict
rules, but it should cover most languages and scenarios without introducing unnecessary complexity.
Vary By Device
Varying localizations by singular/plural isn’t the only context that might require a different localization. That’s why there’s also “Vary By Device” where you can split the possible options by devices like iPhone, Apple Watch, Mac, etc., including Other so you don’t have to add each one.
The reasoning behind this additional localization axis is manifold.
Due to the available screen real estate, different texts might be required. Or the type of interaction must differ between devices. A button on an iPhone or iPad might be “Tap to learn more”, but on a Mac, using “Click to learn more” is the better choice.
And, of course, “Vary By Plural” and “Vary By Device” can be combined to cover all your bases.
Once again, almost all your localization needs (that depend on devices and plural) should be covered.
Managing Localizations
You most likely already noticed the little orange “NEEDS REVIEW” badge in one of the previous screenshots. This is the automatic review mechanism of String catalogs.
There are 4 possible states for automatically managed localizations:
- NEW: a new String that’s not translated yet. These appear in additional languages.
- NEEDS REVIEW: a String has changed, or was added by a variation, and should be checked if the localization is still correct.
- STALE: the String is no longer found in your code.
- TRANSLATED: Doesn’t have a badge, so no action is needed.
As keeping up with changing localizations can be cumbersome, any support from Xcode is welcome. For automatically managed key-values, Xcode is doing its best to keep the state up-to-date with the badges and a little warning about the language itself.
You can use this feature for manually managed ones, too, as they can be marked for review or reviewed via the context menu.
Where this really shines is when more than a single language is involved, as Xcode will show you how much of a language is translated, and even have little green checkmarks for a job well done.
Migrating Your Localizations to String Catalog
New apps are started every day, and many utilize the latest and greatest features available.
But what about existing apps already containing .strings
- and .stringdic
-files?
Well, there’s a “Migrate to String Catalog” option in the context menu that does most of the work.
The migration converts the .strings
-file into a String catalog with base localization set in the “Info” tab of the project settings.
Even though it creates keys, values, and even comments, it won’t change your actual code.
However, comments from a .strings
-file used to separate groups might end up at the wrong location, as there is no grouping in the String Catalog editor.
Still, using NSLocalizedString
instead of String(localized: ...)
works fine, so there’s no actual need to change your code (yet).
Using the String
initializer feels more Swifty, though, and I highly recommend using them, at least going forward with translations.
Behind The Scenes
Most Xcode’s fancy editors are just property lists or JSON files behind the scenes, and String Catalogs aren’t any different, as the new .xcstrings
files are just JSON files:
Being JSON makes it harder to be “human-readable”, but makes it easily “machine-readable”, which is why we got the fancy editor in Xcode in the first place.
Also, what I’m looking forward to is the possibility of additional tooling thanks to a more meaningful format, so .xcstrings
-files can be shared with translators who don’t or can’t use Xcode directly.
Conclusion
Making Localization easier is a step in the right direction, as it gives smaller and indie developers an easy-to-use tool for translations. Before that, you had to develop your own workflow or rely on third-party software/services to make it more manageable. Making it easier also (hopefully) means more apps will be translated in the future, so the whole ecosystem might get more inclusive.
Thanks to the new JSON-based filed format, many new tools will spring forth to integrate l18n
more tightly in CI/CD pipelines, like a GitHub action giving a detailed report on the current state of an app’s translation.
Even without an actual translation into another language, String catalogs aggregate all of the app’s Strings into a single place with review capabilities, making them a powerful new tool for my projects working with customers.
For me, Xcode isn’t my favorite IDE, at least compared to other IDEs like the different JetBrains ones or even Eclipse. But in my opinion, String Catalogs is a fantastic and well-thought-out addition that makes the previous chore of localization much more bearable and actually a joy to use.
Resources
String(localized: ...)
initializers (Apple Developer Documentation)- Localization (Apple Developer Documentation)
- Discover String Catalogs (WWDC 2023)