Apple String Catalog (.xcstrings) is a JSON-based file format introduced in Xcode 15 for storing localization data of iOS, iPadOS, macOS, watchOS, tvOS, and visionOS apps. Unlike the older Apple Strings (.strings) and Apple Stringsdict (.stringsdict) formats, which keep one language per file, a String Catalog holds every language of the app in a single file.
Xcode keeps the catalog in sync with the code. It extracts new strings at build time, marks strings that disappeared from the code as stale, and tracks the translation state of every string. Plurals and device-specific variants, which previously required a separate .stringsdict file, are part of the format.
{
"sourceLanguage" : "en",
"strings" : {
"GeneralLearnMore" : {
"comment" : "Link to the documentation",
"extractionState" : "manual",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mehr erfahren"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Learn more"
}
}
}
},
"Localizely" : {
"shouldTranslate" : false
},
"Save" : {
"extractionState" : "stale",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Speichern"
}
}
}
},
"Welcome %@!" : {
"comment" : "At runtime, the placeholder is replaced with the user's name",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Willkommen %@!"
}
}
}
}
},
"version" : "1.0"
}The example shows the two kinds of keys found in a catalog. GeneralLearnMore is a symbolic key with an explicit English text, as used with .strings files. Save and Welcome %@! are strings extracted from the code, where the key is the English text itself, so the catalog needs no localization for the source language.
A String Catalog is a JSON document with three top-level properties: sourceLanguage, the language the code is written in, strings, an object with one entry per string key, and version, the version of the format. Each entry under strings can contain the following properties.
comment, a note for translators, usually taken from the code.extractionState, how Xcode tracks the entry: manual for strings added by hand in the catalog editor, stale for strings that are no longer found in the code, migrated for strings that came from .strings and .stringsdict files, and extracted_with_value for strings whose source text is defined in the code and kept in sync by Xcode.shouldTranslate, set to false for strings that must stay the same in every language, such as brand names.localizations, the translations keyed by language code. Each localization holds either a single stringUnit with a value and a state, or variations by plural form or device, and optionally substitutions.Placeholders use the same format specifiers as Apple Strings files, such as %@ for objects, %lld for integers, %.2f for floating-point numbers, and positional forms like %1$@ when the order of placeholders differs between languages. See the Apple Strings page for an overview of the available specifiers.
Plural messages are stored as variations of type plural, with one string unit per plural form: zero, one, two, few, many, and other. Each language uses only the forms defined by its plural rules, and other is always required.
"%lld items" : {
"localizations" : {
"en" : {
"variations" : {
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld item"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld items"
}
}
}
}
}
}
}A string can have a different text per device family, for example to say “tap” on iPhone and “click” on Mac. Device variations are stored as variations of type device, with keys such as iphone, ipad, mac, applewatch, appletv, and applevision, plus other for the remaining devices. A device variant can in turn contain plural variations.
"Tap to continue" : {
"localizations" : {
"en" : {
"variations" : {
"device" : {
"mac" : {
"stringUnit" : {
"state" : "translated",
"value" : "Click to continue"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "Tap to continue"
}
}
}
}
}
}
}Substitutions handle messages with more than one pluralized argument, such as “3 files in 2 folders”. The main string refers to each substitution with a %#@name@ placeholder, and each substitution defines its own plural variations, the position of the argument it consumes (argNum), and the format specifier of that argument. Inside a substitution, %arg stands for the argument value.
"%lld files in %lld folders" : {
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "%#@files@ in %#@folders@"
},
"substitutions" : {
"files" : {
"argNum" : 1,
"formatSpecifier" : "lld",
"variations" : {
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%arg file"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "%arg files"
}
}
}
}
},
"folders" : {
"argNum" : 2,
"formatSpecifier" : "lld",
"variations" : {
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%arg folder"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "%arg folders"
}
}
}
}
}
}
}
}
}Every string unit carries a state that Xcode shows in the catalog editor: new for a string that has not been translated yet, translated for a finished translation, and needs_review for a translation that should be checked again, for example because the source text changed. In the source language, new simply means that the value came from the code.
Localizely supports String Catalogs for import, export, and the GitHub, GitLab, and Bitbucket integrations. In the configuration file and in the API, the file type is ios_xcstrings.
Since one catalog contains all languages, importing a file takes the translations of the language you select, and the same file can be imported once per language. Exporting produces a single Localizable.xcstrings file with all selected languages, instead of one file per language.
The git integrations handle every language of the project at once. When Localizely pushes to the repository, the catalog that is already there is updated in place: existing keys keep their order and metadata, unknown properties are preserved, and the JSON is written the way Xcode writes it, so the commit contains only the translation changes. See the configuration file page for the setup, including projects with several catalogs.
String keys in Localizely are flat, so the structure that a String Catalog keeps inside one entry is encoded in the key. Plural forms need no encoding, since Localizely supports plural string keys natively. An entry is treated as plural when it has plural variations in any language.
| String Catalog | String key in Localizely |
|---|---|
| Entry, with or without plural variations | key |
| Device variation of an entry, for example for iPhone | key[device_iphone] |
| Text of an entry for the remaining devices | key[device_other] |
| Substitution of an entry, for example named “files” | key::files |
| Device variation of a substitution | key::files[device_iphone] |
Device keys are used only for entries that have device variations in at least one language. In that case, a language without device-specific text is stored under the other device key. The :: notation for substitutions is the same convention used for .stringsdict files.
The comment of an entry becomes the description of the string key. The extractionState and shouldTranslate properties, as well as argNum and formatSpecifier of substitutions, are stored with the key and written back on export. Xcode owns these properties, so when an existing catalog is updated, its values are kept.
The state of a string unit maps to the Reviewed flag of the translation in both directions. On import, translated becomes Reviewed and any other state becomes not reviewed, while the text of the source language counts as reviewed unless it is flagged needs_review. On export, a reviewed translation is written as translated and an unreviewed one as needs_review. Strings extracted from the code with a value keep the new state in the source language, so that Xcode continues to sync them with the code. A plural is reviewed only when all of its forms are reviewed. When a catalog is updated in place, states change only where the translation or its review status changed.
Language codes in the catalog, such as en, de, or pt-BR, are matched with the languages of your project regardless of case, and the underscore and dash notations are treated as equal. Languages that exist in the file but not in the project are skipped by the git integrations.
String Catalogs are available since Xcode 15. Apps that use them can still run on earlier OS versions, because Xcode compiles the catalog into .strings and .stringsdict resources at build time.
Xcode can convert existing files. Select the .strings and .stringsdict files in the project navigator, right-click them, and choose “Migrate to String Catalog”. Xcode creates a catalog with all languages of the project and marks the converted strings with the migrated extraction state.
The most common mistakes are editing the JSON by hand and breaking its structure, for example with a trailing comma, which makes Xcode reject the whole file, and leaving out the other plural form, which is required for every language. Since Xcode updates the catalog at build time, keeping the file formatted the way Xcode formats it avoids noisy diffs and merge conflicts.
Previous: Supported file formats
Read next: Professional Translation Services
Tired of manually editing translation files?
Our platform streamlines software localization for you.