The Lingo.dev CLI translates Xcode String Catalogs (.xcstrings) through a configured localization engine. String Catalogs are Apple's modern localization format, introduced in Xcode 15, that stores all languages in a single JSON file. The CLI mutates this file in place - no per-locale directories needed.
This guide walks through localizing an iOS app end-to-end: configuring the CLI, translating locally, and automating with the GitHub App so translations ship on every push.
Demo repository
Clone or fork lingodotdev/ios-app-localization-example to follow along. The repository contains a working Xcode project with String Catalogs and a Lingo.dev CLI configuration.
How String Catalogs Work#
Before Xcode 15, iOS localization required managing separate .strings and .stringsdict files across [locale].lproj/ directories. String Catalogs replace this with a single Localizable.xcstrings file that Xcode maintains automatically.
When you mark a string as localizable in SwiftUI or UIKit, Xcode detects it during build and adds an entry to the String Catalog. Each entry tracks the source string, its translations for every configured locale, and an optional comment field that provides context to translators.
| Aspect | Legacy .strings | String Catalogs .xcstrings |
|---|---|---|
| File count | One per locale per table | One file, all locales |
| Format | Key-value text | Structured JSON |
| Plural support | Separate .stringsdict file | Built-in plural rules |
| Xcode integration | Manual export/import | Automatic detection |
| Translator notes | Not supported | Comment field per entry |
The CLI detects the .xcstrings format from the file extension, parses this JSON structure, translates each entry through the localization engine, and writes translations back into the same file - preserving comments, plural rules, and metadata.
Prerequisites#
Create a localization engine
Every translation sends content through a localization engine - the configuration that determines which LLM model, glossary, brand voice, and rules apply. Create one in the Lingo.dev dashboard and generate an API key.
Verify Node.js
The CLI requires Node.js 22 or higher:
node -vEnable localization in Xcode
In your Xcode project, go to Project Settings > Info > Localizations and add your target languages. Xcode creates the String Catalog entries for each locale you add. See Apple's localization documentation for details.
Install and Configure the CLI#
Install the CLI, authenticate, then set up the project. See the Quickstart for the full walkthrough.
npm install -g @lingo.dev/cli
lingo loginRun lingo init in your project root and answer the prompts (source locale, target locales, and the file pattern pointing at your String Catalog), then lingo link to bind the project to your organization and engine. Together these write a .lingo/config.json:
{
"orgId": "org_...",
"engineId": "eng_...",
"sourceLocale": "en",
"targetLocales": ["es", "fr", "de", "ja"],
"files": [{ "pattern": "MyApp/Localizable.xcstrings" }]
}Commit .lingo/config.json - it's the source of truth for what gets translated. The .xcstrings format is detected from the file extension. Because String Catalogs store all locales in a single file, no locale placeholder is needed in the pattern: the CLI reads the source-language entries and writes all target languages back into the same file. See the configuration reference for the full schema.
Multiple String Catalogs
If your project uses multiple String Catalog files (for example, one per framework target), add a files entry for each:
{
"files": [
{ "pattern": "MyApp/Localizable.xcstrings" },
{ "pattern": "MyAppWidgets/Localizable.xcstrings" }
]
}Translate Locally#
From your project root, run the first translation:
lingo push --backfill-missingThe CLI reads your String Catalog, translates every missing entry through your localization engine, waits for the run to finish, and writes results back into the .xcstrings file. Open the file in Xcode to see translations populated for each configured locale.
After you edit source strings, a plain lingo push translates only the delta - entries whose source hasn't changed are skipped server-side, tracked via the lockfile:
lingo pushTranslator Notes#
String Catalogs support a comment field per entry that the CLI includes in translation requests. These comments provide context to the localization engine - disambiguating terms, specifying tone, or describing where a string appears in the UI.
In Xcode, select a string in the String Catalog editor and add a comment in the inspector panel. The comment is stored in the .xcstrings JSON:
{
"sourceLanguage": "en",
"strings": {
"Set": {
"comment": "Refers to a collection of items, not the verb",
"localizations": { }
}
}
}The CLI sends this comment alongside the string, steering the model toward the correct interpretation. "Set" without context could become a verb in many languages - the comment eliminates that ambiguity. See Translator Notes for more patterns.
Plurals#
String Catalogs handle plural forms natively using CLDR plural rules. When you define a plural variation in Xcode, the String Catalog stores rules for each plural category (zero, one, two, few, many, other) that the target language requires.
The CLI preserves this structure during translation and generates the correct plural categories for each target locale. English uses two categories (one and other), but Arabic needs six, Polish needs four, and Japanese needs one. The localization engine handles these differences automatically.
Automate with the GitHub App#
Install the Lingo.dev GitHub App on your repository for continuous localization - no CI runner, API-key secret, or lockfile to manage. Once installed and pointed at your .lingo/config.json (with its engineId), it reacts to pushes and pull requests automatically: it detects changed source strings, translates them through your engine, and commits the updated .xcstrings back to the branch or opens a pull request.
Prefer running it yourself?
You can also run lingo push from your own CI job (any runner with Node.js) and commit the results, authenticating with LINGO_API_KEY. See CI/CD Workflows for the runner-based patterns.
Verify Before Deploy#
Use lingo check as a deployment gate to ensure no untranslated strings ship to production. It reports missing or outdated translations and exits with a non-zero status when work remains:
lingo checkAdd it as a separate CI step before your build.
