L10nXcstrings.py generates Swift localization helpers from Xcode
Localizable.xcstrings catalogs and reports localization keys that are not used
from Swift code.
- Parses Xcode's JSON-based
.xcstringsformat. - Reads plain string units and nested catalog variation values, including plural branches.
- Uses
sourceLanguageby default, with--languageoverride support. - Fails when the selected language is missing for any key, unless
--allow-missingis passed. - Generates a Swift namespace enum with
public static varandpublic static funchelpers. - Converts keys such as
login.title_screentologinTitleScreen. - Escapes Swift keywords and reports identifier collisions.
- Adds stable hash suffixes for non-ASCII keys that cannot be represented by the ASCII Swift naming policy.
- Scans Swift source for explicit
L10n.keyusage, native localization calls such asString(localized:),NSLocalizedString, SwiftUI title-key views, and custom call/member names supplied from the CLI. - Writes unused original localization keys to
Unused.txt. - Verifies generated Swift with Swift 6 typechecking and runtime bundle-resource
tests when
swiftcis available.
brew tap disconnecter/l10n https://github.com/Disconnecter/homebrew-l10n
brew install l10n-xcstringsPlace your catalog at Localizable.xcstrings, then run:
l10n-xcstringsWith explicit paths:
l10n-xcstrings \
--input Resources/Localizable.xcstrings \
--output-swift Resources/Generated/Strings+Generated.swift \
--output-unused Unused.txt \
--source-dir Sources \
--ignore-dirs build DerivedData \
--table-name Localizable \
--bundle Bundle.module \
--localized-call ChipTitle \
--localized-member toolbarTitleGenerated Swift looks like this:
public enum L10n {
/// Sign in
public static var loginTitle: String {
return tr(key: "login.title")
}
/// Count %d
public static func countValue(_ p1: Int) -> String {
return tr(key: "count.value", p1)
}
}Unused keys are annotated in generated Swift and written to Unused.txt:
#warning("Unused key: loginTitle")
public static var loginTitle: String {
return tr(key: "login.title")
}| Parameter | Default | Description |
|---|---|---|
--input |
Localizable.xcstrings |
Path to the source .xcstrings file. |
--output-swift |
Generated/Strings+Generated.swift |
Path for generated Swift. |
--output-unused |
Unused.txt |
Path for original unused keys. |
--source-dir |
. |
Directory scanned for Swift usage. |
--ignore-dirs |
none | Space-separated directories to skip while scanning. |
--enum-name |
L10n |
Generated Swift namespace name. |
--language |
sourceLanguage or en |
Catalog language to read. |
--allow-missing |
off | Skip keys missing the selected language instead of failing. |
--table-name |
none | Localization table name passed to NSLocalizedString. |
--bundle |
Bundle.main |
Swift bundle expression passed to NSLocalizedString. Use Bundle.module for Swift packages. |
--localized-call |
built-in Swift/iOS title-key calls | Additional function/type call whose first string literal argument is a localization key. Repeat for multiple names. |
--localized-member |
built-in SwiftUI title-key members | Additional member call whose first string literal argument is a localization key. Repeat for multiple names. |
- Python 3.9+
- Xcode
.xcstringsJSON catalog - Swift code that references generated helpers as
L10n.keyor typed shorthand values
python3 -m pip install .[dev]
ruff check .
python3 -m unittest discover -s testsWhen swiftc is installed, the tests also typecheck generated Swift with a
small Swift caller under Swift 6.
- Literal percent signs in localized strings must be escaped as
%%. - Use
%@for Swift string arguments. C string placeholders (%s) are rejected because passing SwiftStringvalues to%scompiles but formats incorrectly. - Dynamic printf widths and precisions such as
%*dare rejected. - Plural placeholders such as
%#@items@are accepted. Concrete variation branches are used to infer a stronger Swift argument type when possible, and generated Swift usesString.localizedStringWithFormatso.stringsdictplural resources are resolved at runtime. - Positional placeholders must be complete and consistent, for example
%2$@ %1$d. - Version
0.0.6changes generated Swift from value enum cases to static namespace properties/functions. Existing callers such asL10n.title.stringshould migrate toL10n.title; associated-value cases should migrate to generated static functions such asL10n.countValue(3). - Release tags use bare semantic versions such as
0.0.6; the release workflow requires the tag to matchpyproject.toml.
MIT