The Missing String Catalogs FAQ for Localization in Xcode 15

Discover the game-changing implications of Apple's new feature, String Catalogs, which replaces traditional localization files and streamlines the localization process. From automatic key extraction to safety checks, find out why developers should be excited about this powerful tool in Xcode 15.

The Missing String Catalogs FAQ for Localization in Xcode 15
Photo by Camylla Battani / Unsplash

In WWDC23 Apple introduced a new feature to Xcode that sherlocked large parts of my RemafoX app. Of course, I tested the new feature in detail and asked all my questions to the team who built it in the related Slack activity. They actually did a great job with String Catalogs, so much so that I will reimagine my app since they've taken away most of the low-level work that I did in my app in the past.

But most people I talked to since Dub Dub are still unaware of the implications String Catalogs have on their projects. So I figured I should answer the most frequent questions to make it more clear how amazing String Catalogs really are.

What are String Catalogs? What about Strings(dict) files?

String Catalogs are files with the ending .xcstrings and store their content in a custom JSON format. The exact format is not documented (filed feedback to change that: FB12264877) and as a developer, you won't ever need to fiddle with any JSON code because Xcode 15 ships with a visual editor:

String Catalogs replace both .strings and .stringsdict files and therefore support pluralization out-of-the-box. Unlike .strings(dict) files that are placed under locale-specific folders like en.lproj, String Catalogs encapsulate the translations of all supported languages in one file. This allows for safety checks like showing the progress of specific translations right in Xcode so you are aware of any translations missing (replacing the RemafoX Linter). Likewise, it will also add new keys to all languages automatically for you, saving you a lot of time (replacing the RemafoX Normalizer). And more features could come in future updates, like a check if your localizations have the same parameters (e.g. %@) as your source language (a feature I had planned for RemafoX, filed feedback: FB12264614).

I have a project with Strings(dict) files. Is migration easy?

Yes, totally! Migrating to String Catalogs is easy as pie: Simply right-click one of your .strings(dict) files and press "Migrate to String Catalog...". This will open a modal with a list of all your .strings(dict) files in the project so you can choose which ones should be combined into one String Catalog file. If you have separate Strings files for different parts of your project, you can keep separate String Catalogs for them, there's no need to put everything into one String Catalog.

But my project supports older OS versions. Can I still migrate?

Yes, totally! Apple engineers have done something very smart here: While we as developers can make full use of all String Catalog's features, our apps won't see any new file format like .xcstrings. Instead, during the build process Xcode will convert the String Catalog back to plain old .strings and .stringsdict files, thus ensuring support for all OS versions you can potentially target. This means, once you can switch to Xcode 15 for your project, you can make full use of String Catalogs without ever looking back!

If you have been wishing for an SF Symbols-like app but for Localized Strings in code with official translations for all languages iOS is available in: I built exactly that and the feature is completely free!
Just download TranslateKit and check your menu bar. 🌐 👍

I'm using SwiftGen/RemafoX for safe Localization key references with compiler checks. Will I lose this safety?

No, not at all. Xcode not only introduces a new file format for your localizations, but it also comes with tools to automatically extract new localization keys from your project. If you use any of SwiftUI's localization APIs with LocalizedStringKey or LocalizedStringResource, they automatically get detected and added to your String Catalog. But this doesn't end with SwiftUI, it also works with plain Swift String APIs like String(localized:), plain Obj-C-style APIs like NSLocalizedString() (even custom names supported), and even Interface Builder files like .storyboard and .xib files. For Info.plist files you need to create a dedicated String Catalog named InfoPlist.xcstrings and extraction will even work there automatically.

Note that you won't get auto-completion for your keys in code like you now get for images and colors in your Asset Catalogs with Xcode 15. That's because in Asset Catalogs the source of truth lies in the catalog itself, where your assets are placed. But because Xcode automatically extracts any added localizations from your source code, the source of truth for your localizations is reversed here and lies in your code. Therefore it's impossible that you add a text to your code that lacks a counterpart in a Strings file (like it was possible before, leading to broken translations). Instead, whenever you add a localized String in your project, Xcode automatically creates a new translation key for all supported languages and you'll see in the Strings Catalog that your translations are incomplete.

The lack of a green checkmark for the German language indicates that the translation is incomplete.

What about typos? Can I change the key in development?

Xcode not only extracts new keys and adds them automatically to your Strings Catalog, but it will also make sure that any keys in your catalog that are no longer referenced in your project get deleted. It does this safely: If you have a key that you have not provided any translations for in any languages yet, then it will automatically delete the key if it no longer finds it in your project. This is what will typically happen when you have a typo in your key or simply want to improve the key during development and change it up. But if you do have some translations already, Xcode will instead mark the key in the String Catalog as "stale" with a yellow warning symbol. This makes it easy to spot keys no longer referenced, copy over their translations to the new key if needed and delete them afterward.

What if I don't want to localize a specific text in Code/IB file?

Currently, there seems to be no direct way to control the extraction from the source of truth. I filed a feedback for IB files in particular (FB12264777) because my tools RemafoX (and its predecessor BartyCrouch) support excluding there, therefore switching to String Catalogs might only be possible for people using those if there was some solution provided by Apple. While I also couldn't find an explicit way to mark a String in code as "non-translatable", in most SwiftUI views you will find an overload of the same API which takes a Text view instead of a String. For views where this applies, you can use Text(verbatim: "Your Text") to create a Text view whose text won't get extracted because the keyword verbatim marks it as non-translatable. But because an overload with a Text does not always exist, I filed a feedback for a more direct way to control extraction (FB12469163). For now, I found a workaround by simply using a String initializer in SwiftUI APIs that prefer the LocalizedStringKey overload, just pass something like String("Your Text").


The introduction of String Catalogs in Xcode 15 brings significant improvements to the localization workflow by simplifying the management of translation files. Developers can easily migrate their projects to String Catalogs and maintain the safety of localization key references, while still supporting older OS versions and having control over key changes and typos. Although there are some limitations and areas for improvement, String Catalogs are a huge step forward and have no real downsides compared to the old Strings file system. Migrate now!

Enjoyed this article? Check out TranslateKit!
A drag & drop translator for String Catalog files – it's really easy.
Get it now to machine-translate your app to up to 150 languages!
Want to Connect?
Follow me on 🐦 Twitter (X), on 🧵 Threads, and 🦣 Mastodon.