My new project: Tact, a simple chat app.

How to use string catalogs across Swift package modules

April 16, 2024

Apple platforms have always supported localizing your apps. String Catalogs are a new addition to the toolkit that were released at WWDC 23 with Xcode 15. They compile down to good old strings files, but the frontend is nicer.

I was curious how to use the new string catalogs in a modularized project that has all its features, including UI, split into different Swift Package modules, that seems to be a good practice these days. It’s quite straighforward, but there are some nooks and crannies that were not obvious to me, so I did a small experiment around that.

String catalogs are just a nicer frontend to good old string files. While this post is about the new string catalogs, most of the concepts apply to string files as well.

TL;DR

Check out the toy project.

String catalogs toy

Following is a review of some of the concepts and tips around this topic.

Basic API

I will use examples from a SwiftUI app.

In a top-level app target, you simply use this API to display text.

Text("Some string")

If you have a key called Some string in your strings catalog, this will display a localized string.

If your code lives in a SPM module and you have a strings file or catalog in the same module, you’d use this code:

Text("Some string", bundle: .module)

This tells the system to load the string resources from the bundle of the current module.

Using strings from another module

How do you use strings from another module?

The use case for this might be that you are providing all your strings from a single module, both to other modules and the top-level app target.

I didn’t find a standard approach for this. So what I came up with is that you put your strings in a separate module with a strings catalog file, and then have this piece of code in the module:

public struct StringsExporter {
  public static var bundle: Bundle { Bundle.module }
}

What this does is it simply exports the bundle resources of this module to be used by other modules.

At the call site in another module, you will then do this:

Text("ChildHello", bundle: StringsExporter.bundle)

This tells SwiftUI to load the string resources from the specified bundle, and seems to work fine.

Allow mixed localized resources

I set up my demo app project so that it does not have any localized strings or other localized resources in the top level app target. This works fine, but you have to do this one extra thing if you use this approach: you have to set CFBundleAllowMixedLocalizations to true in your top-level app Info.plist.

Mixed localized resources

If you don’t do this, the localizations provided by the modules are filtered by whatever localizations are present in the top level app target. The localizations that are not present in the app target will simply be discarded. If you set this key, this filtering won’t be done, and whatever localizations are provided by modules will be used.

There is a thread in Swift forums that discusses the risks of this. If you are using modules from other vendors, you may end up in situations like “Today is Montag, le 1er décembre.” But if you are controlling all the code, it should be fine.

Conclusion

Xcode strings catalogs work fine across module boundaries if you add the glue to export and import them, and allow mixed resources if your top-level app target does not include all localizations. String catalogs can be used flexibly across modules and string tables. All projects can find an approach that suits their needs and fits their project setup.