Skip to content

JoshuaSullivan/local-localizer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

local-localizer

A macOS command-line tool that machine-translates an iOS/macOS String Catalog (.xcstrings) or legacy .strings file into multiple locales using the on-device Apple Foundation Models framework.

Translations run entirely on-device. No API keys, no network, no per-call cost.

Disclaimer. Translations are produced by an AI model (Apple's on-device Foundation Models). They will contain errors: wrong words, awkward phrasing, missed nuance, broken format specifiers, mistranslated brand names. The rate of error varies by language, domain, and prompt. Always have a native speaker review the output before shipping. The author of this tool makes no representations or warranties about the correctness, fitness, or safety of any translation it produces and accepts no responsibility for any consequence of using its output. By running this tool you agree that you are responsible for reviewing and validating every translation it generates. See the LICENSE file for the full disclaimer of warranties.

Requirements

  • macOS 26 or later
  • Apple Silicon
  • Apple Intelligence enabled (System Settings → Apple Intelligence & Siri)
  • Xcode 26 toolchain (for building from source)

Install

swift build -c release
cp .build/release/local-localizer /usr/local/bin/

Or use SwiftPM's installer (puts it in ~/.swiftpm/bin, which you'll need on PATH):

swift package experimental-install

Usage

local-localizer <input> [options]

String Catalog (.xcstrings)

In-place modification of the input file. All locales live in the same JSON file.

local-localizer Resources/Localizable.xcstrings
local-localizer Resources/Localizable.xcstrings --locales fr,de,ja
local-localizer Resources/Localizable.xcstrings --output /tmp/translated.xcstrings

Legacy .strings

Per-locale outputs are written to sibling <locale>.lproj/ directories next to the input.

local-localizer Project/en.lproj/Localizable.strings
# produces Project/fr.lproj/Localizable.strings, Project/de.lproj/..., etc.

The input must live inside an .lproj directory. Source language is inferred from the parent directory name (en.lprojen, Base.lprojen); pass --source-locale to override.

Options

Flag Default Purpose
<input> — (required) Path to a .xcstrings or .strings file
--locales the nine defaults below Comma-separated locale identifiers
--source-locale inferred Source language for legacy .strings (ignored for .xcstrings)
--overwrite off Re-translate keys even if a translation already exists
--output in-place .xcstrings only: write to a different path
--temperature 0.2 Sampling temperature, 0.0–2.0
--dry-run off Print the work plan, don't call the model or write files
-v, --verbose off Include source prompts in the progress log
--glossary none Path to a JSON glossary file (see below)
--tone none Default tone: formal, informal, neutral, professional, polite
--state needs_review State to write into new translations: translated or needs_review
--check off Validate only — exit 0 if everything is current, 1 if anything is missing or needs review
--keys all Comma-separated list of keys to translate (others ignored)
--keys-from none Path to a newline-separated list of keys to translate
--concurrency min(9, locale count) Maximum simultaneous in-flight model calls

Default locales

Display name Identifier
French fr
German de
Spanish es
Italian it
Brazilian Portuguese pt-BR
Simplified Chinese zh-Hans
Traditional Chinese zh-Hant
Japanese ja
Korean ko

Glossary

A JSON file with three optional sections:

{
  "doNotTranslate": ["Microsoft Teams", "OneDrive", "Flipgrid"],
  "tone": {
    "default": "professional",
    "de": "formal",
    "fr": "formal",
    "ja": "polite"
  },
  "termMappings": {
    "de": { "Premium": "Pro" },
    "fr": { "Sign in": "Se connecter" }
  }
}
  • doNotTranslate — terms (typically brand and product names) that must appear verbatim in every translation.
  • tone — per-locale tone preference. The default key applies to any locale not explicitly listed. Per-locale entries override the global --tone flag. For German/French/Italian/Spanish, formal selects the polite second-person form (Sie/vous/Lei/usted). For Japanese/Korean, polite selects the polite verb forms.
  • termMappings — per-locale forced translations. When the source contains the left-hand term, the model is instructed to render it as the right-hand term in that locale.

Plural variations

.xcstrings plural-variation entries (auto-generated by Xcode for any source string with %lld) are translated into the correct CLDR plural categories per target locale:

  • French / Spanish / Italian / Portuguese: one, many, other
  • German: one, other
  • Chinese / Japanese / Korean: other only
  • Russian: one, few, many, other
  • Arabic: zero, one, two, few, many, other

Locales not in the hardcoded table fall back to one/other with a warning.

Format specifiers (%@, %lld, etc.) are preserved across all plural forms — every translation keeps the literal placeholder.

Resumability and state

.xcstrings translations are written with a state field. v2 honors all four states:

  • translated — current; skipped on subsequent runs
  • needs_review — re-translated on next run (Xcode marks this when source changes)
  • new or absent — translated
  • stale — skipped silently

New machine translations default to state: "needs_review" to honestly reflect that they should be reviewed before shipping. Pass --state translated to opt back to v1 behavior.

For legacy .strings, already-translated keys are skipped by file presence (no state field). Pass --overwrite to force re-translation.

Examples

Validate before shipping (CI use):

local-localizer Resources/Localizable.xcstrings --check && echo "ready to ship"

Translate only specific keys after a code review changed their wording:

local-localizer Resources/Localizable.xcstrings --keys onboarding_title,onboarding_subtitle --overwrite --state translated

Translate with brand-name preservation and formal European tone:

local-localizer Resources/Localizable.xcstrings --glossary glossary.json --locales de,fr,it,es

Faster runs against many locales:

local-localizer Resources/Localizable.xcstrings --concurrency 9

Notes and limitations

  • Hand-review the output. These are machine translations. Default state is needs_review for exactly this reason — flip to translated only after a native speaker confirms.
  • Plural support is .xcstrings-only. Legacy .stringsdict plural files are out of scope.
  • device variations and substitutions in .xcstrings are skipped with a warning.
  • Multi-line sources may collapse to a single line in some target languages — the on-device model occasionally prefers a single sentence over preserving exact line layout.
  • Comments matter. The developer comment is sent to the model as disambiguation context. Strings like "Open" or "Mark" translate noticeably better with a comment like "Verb, button label" than without.
  • Concurrency speedup is modest (~15-25% in practice). The on-device model serializes much of its inference, so adding parallel calls helps less than it would against a cloud API.

About

A command line app that uses Apple Intelligence to translate strings files.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages