Formats
Apple .xcstrings (iOS)
Translate Apple String Catalogs, the localization format of Xcode 15 and later.
String Catalogs (.xcstrings) are Apple's modern localization format. One catalog holds a table of strings with every locale inside, and Xcode keeps it in sync with your code on each build.
Install the format package:
npm install -D @eloqnt/format-apple-xcstrings
Then reference it from your configuration:
.eloqnt/config.ts
import {defineConfig} from '@eloqnt/cli';export default defineConfig({messages: {path: './MyApp/Localizable',locales: 'infer',sourceLocale: 'en',format: {codec: '@eloqnt/format-apple-xcstrings',extension: '.xcstrings'}}});
Since all locales live in one file, path points at the catalog itself (without the extension) and uses no {locale} placeholder. With locales: 'infer', the locales are read from inside the catalog, and sourceLocale should match the catalog's sourceLanguage.
Namespaces
Projects with several catalogs can use the {namespace} placeholder, which prefixes each message ID with its catalog name:
.eloqnt/config.ts
path: './MyApp/{namespace}';
This matches files like MyApp/Localizable.xcstrings and MyApp/InfoPlist.xcstrings.
Plurals
Plural variations appear as ICU messages in eloqnt/cli, using count as the variable:
MyApp/Localizable.xcstrings (excerpt)
"%lld songs" : {"localizations" : {"en" : {"variations" : {"plural" : {"one" : {"stringUnit" : {"state" : "translated", "value" : "%lld song"}},"other" : {"stringUnit" : {"state" : "translated", "value" : "%lld songs"}}}}}}}
The English entry above reads as {count, plural, one {{arg1} song} other {{arg1} songs}} in lint output and during translation. Each target locale receives the plural categories its language needs, which can differ from the source.
Substitutions
Multi-argument plurals via substitutions work the same way: each %#@name@ reference reads as a named plural in place, with %arg appearing as #. A catalog value of Found %#@count@ whose count substitution varies between %arg file and %arg files reads as Found {count, plural, one {# file} other {# files}}, and translations are written back into the substitution's variations.
Device variations
Device variations appear as separate strings: the other variant keeps the key as its ID, and every named device gets ::device. appended, e.g. Tap to continue::device.mac. Each variant is translated on its own (the device is passed along as context), and written back into the catalog's device variations.
Workflow states
Xcode tracks a workflow state for every value in a catalog, and eloqnt/cli reads it:
- A target value marked
neworneeds_reviewstill awaits translation or review, so it counts as missing.eloqnt lintreports it viamissing-translation, and the nexteloqnt translatewrites an up-to-date translation markedtranslated. This applies to target locales only, since the source text is authored in code, not translated. - An entry whose
extractionStateisstaleis no longer referenced by your code, soeloqnt lintreports it viaorphan-message. To fix this, delete the entry in Xcode's catalog editor, or switch it to manually managed if the string is used in ways Xcode can't see.
Entries marked as "Don't translate" (shouldTranslate: false) are skipped entirely.
Not supported
The following features are not supported:
stringSetentries: Their items are positional and unnamed, which is why most localization tools can't translate them reliably. To solve this, declare each phrase variant explicitly, e.g. inAppShortcut(phrases:), so that every phrase holds one value per language.srcPath: Source code analysis is currently limited tonext-intl.