Migrate Localizable.strings to a String Catalog (.xcstrings)
Updated · GLabs325
Since Xcode 15, Apple has been steering everyone to String Catalogs: one JSON file for every language, with plurals and device variations. The migration takes two clicks. The diff that follows is what needs your attention.
Before you migrate
Two precautions that prevent most bad surprises:
- A clean Git tree, on a dedicated branch. Everything worth checking afterwards is in the diff, so you want that diff on its own.
- A build that passes before the migration. Xcode fills the catalog by scanning your code at build time. If the project doesn't compile, the catalog stays incomplete and you won't know why.
The migration itself
In the project navigator, right-click Localizable.strings, then Migrate to String Catalog. Xcode lists the files and targets involved; the exact wording can change between Xcode versions, the idea doesn't.
The fr.lproj/Localizable.strings, de.lproj/Localizable.strings… files are replaced by a single Localizable.xcstrings holding every language. Comments from the .strings files become each key's comment.
What the file looks like
A String Catalog is JSON. A simple key:
{
"sourceLanguage" : "en",
"strings" : {
"welcome_user" : {
"comment" : "Home screen greeting",
"localizations" : {
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bon retour, %@ !"
}
}
}
}
},
"version" : "1.0"
}
And a plural, stored under variations:
"items_left" : {
"localizations" : {
"fr" : {
"variations" : {
"plural" : {
"one" : { "stringUnit" : { "state" : "translated", "value" : "%lld article restant" } },
"other" : { "stringUnit" : { "state" : "translated", "value" : "%lld articles restants" } }
}
}
}
}
}
Note the formatting: a space before each colon, the way Xcode writes it. A tool that rewrites the file as "standard" JSON will produce a huge diff without changing a single translation.
What to review in the diff
Keys marked Stale. After a build, Xcode flags keys it can no longer find in your code. They aren't necessarily dead: a key built at runtime (NSLocalizedString(variable, comment: ""), a key read from a config file) is invisible to the scan. Check before deleting.
New keys extracted from code. The build can add strings you never put in Localizable.strings, typically SwiftUI Text("…") literals, which are localizable by default. They arrive untranslated, so the percentage Xcode shows for each language drops. That's expected.
States. Every value has a state: translated, needs_review, or new for a string nobody has translated yet. Three months from now, that's how you'll know what was actually reviewed.
Format specifiers. A SwiftUI string such as Text("\(count) items left") produces the key %lld items left. If your old .strings used %d for the same sentence, you now have two near-duplicate keys; keep the one your code really uses.
What about older iOS versions?
No need for iOS 17. The catalog is a source format: at build time, Xcode compiles it into regular .strings files inside the app bundle. Your deployment target doesn't change.
Editing the catalog outside Xcode
Xcode's editor is fine for a handful of strings. To review a whole language, translate in bulk or check placeholders, a dedicated tool helps. LocalePilot opens .xcstrings files and writes them back in Xcode's style: an unchanged catalog comes out byte-identical, an edit only changes its own lines, and an edited value moves to the translated state. Plurals and device variations are editable; keys with substitutions (%#@count@) and keys marked shouldTranslate: false are kept as they are, for you to edit in Xcode.
Frequently asked questions
Do String Catalogs require iOS 17?
No. Xcode compiles the catalog into regular localization files at build time; your app's deployment target doesn't change.
What does Stale mean in a String Catalog?
Xcode couldn't find the key in your code at the last build. Make sure it isn't built at runtime before deleting it.
How do I undo the migration?
Migrate on a dedicated branch with a commit right before: reverting in Git brings the .strings files back.