CLI for managing Xcode String Catalog (.xcstrings) files. Query, inspect, and update translations from the terminal.
Install locally from source — this links the xcstrings command globally:
git clone https://github.com/onmyway133/string-catalog-cli.git
cd string-catalog-cli
bun install
bun run build
bun linkAfter pulling changes, re-run bun run build to update the linked command.
Or install from npm:
npm install -g string-catalog-cliAll commands take a path to an .xcstrings file as the first argument. Add --json to any read command for compact, machine-readable output (single line, no pretty-printing, so it stays cheap for AI agents to read).
Writes preserve the file's existing formatting (Xcode's "key" : value style, key order, trailing newline), so git diff only shows what actually changed.
In Xcode catalogs the key is usually the source text and the source-language entry is often omitted. The CLI treats such keys as having the key itself as source text, so they don't show up as "missing" in the source language.
List all languages with translation coverage.
Detailed statistics broken down by state (translated, needs_review, new, stale).
List all string keys. Paginated with --limit (default 100) and --offset.
Case-insensitive substring search over key names.
Show source text, comment, and all translations (including every plural form) for one key. Use -- before keys starting with -, with all options before it: xcstrings get App.xcstrings --json -- "-foo".
Source text and comment for many keys in one call. Use --stdin to pass a JSON array or one key per line.
xcstrings source App.xcstrings "Cancel" "Delete %@?" --json
jq -c '[.items[].key]' page.json | xcstrings source App.xcstrings --stdin --jsonEvery key with its source text and value in one language, for reviewing translation quality. Add --compare <lang> to show a second language next to it. Skips stale keys unless --include-stale. Paginated with --limit (default 100) and --offset.
xcstrings values App.xcstrings --language pl --compare de --limit 50 --jsonList keys with missing translations. Skips shouldTranslate: false keys and keys Xcode marked stale (no longer in code, use --include-stale to include them). Paginated with --limit (default 100) and --offset.
xcstrings missing App.xcstrings --language de --with-source --limit 50 --json
# {"total":71,"offset":0,"limit":50,"hasMore":true,"items":[{"key":"Delete %@?","source":"Delete %@?","comment":"Confirm deletion"}, ...]}--with-source includes the source text and comment, which is everything needed to translate a batch in one call.
List keys marked stale by Xcode's extraction state or by translation state.
Find translations that break at runtime. Each issue has a kind:
| kind | Meaning |
|---|---|
extra-placeholder |
Translation reads an argument the source doesn't have (garbage or crash) |
wrong-placeholder-type |
e.g. %d where the source has %lld |
malformed-placeholder |
Stray %, e.g. @% typed instead of %@ |
missing-placeholder |
Translation drops a placeholder |
raw-key |
An ID-style key (settings.title, onProPlan) shows up as the value, or has no source text at all |
empty |
Empty value |
Positional and sequential specifiers are treated as equivalent (%@ %lld matches %2$lld %1$@), and plural one/zero forms may drop the count. Stale keys are skipped unless --include-stale. Filter with --language and --kind. Exits with code 1 when issues are found.
xcstrings check App.xcstrings --json --limit 0 # just the counts per kind
xcstrings check App.xcstrings --language de --kind raw-key,emptyUpdate translations from inline JSON, a JSON file, or - for stdin.
xcstrings update App.xcstrings translations.json
xcstrings update App.xcstrings - < translations.json
xcstrings update App.xcstrings '{"data":[{"key":"Cancel","translations":[{"language":"de","value":"Abbrechen"}]}]}'- Unknown keys are skipped (a typo would otherwise create a junk key). Pass
--createto add new keys. - Every written translation is checked against the source placeholders and mismatches are reported.
--strictrefuses to save if any are found. - Localizations using device variations or substitutions are protected:
updaterefuses to overwrite them (it would destroy them) and reports them. --dry-runreports what would change without saving.- Exits with code 1 if keys were skipped or protected, or
--strictblocked the save.
JSON format (a bare array without the data wrapper also works):
{
"data": [
{
"key": "Hello %@",
"translations": [
{ "language": "de", "value": "Hallo %@" },
{ "language": "fr", "value": "Bonjour %@", "state": "needs_review" }
],
"comment": "Greeting with user name"
}
]
}state defaults to translated. For plural strings, use pluralForms instead of value:
{
"data": [
{
"key": "%lld items",
"translations": [
{
"language": "de",
"pluralForms": {
"one": "%lld Element",
"other": "%lld Elemente"
}
}
]
}
]
}Preserve these in all translations:
| Placeholder | Meaning |
|---|---|
%@ |
String / object |
%d, %lld |
Integer |
%f |
Float |
%1$@, %2$@ |
Positional (order can change per language) |
bun install
bun run build
node dist/cli/index.js --helpbun test- super-use-xcstrings — Claude Code skill that teaches agents to use this CLI