i18n‑ai‑translate
AI‑powered localization for your translation catalogues. Automate translating single files or entire directories with ChatGPT, Gemini, Claude, or local Ollama models — while keeping translations accurate, formatting consistent, and placeholders intact. Works with i18next‑style JSON out of the box, plus Gettext .po, Java .properties, and iOS .strings.
_For a detailed walkthrough and advanced tips, see ADVANCED_GUIDE.md._
Why use it?
| Feature | What it means |
|---|---|
| Multi‑engine | Choose OpenAI, Google, Anthropic, or your own Ollama models |
| Fast | Parallel per-batch workers share one rate limiter; translate 20 locales concurrently |
| Safe | Translations verified against the source before being written |
| Diff‑aware | Only re‑translate keys you changed; existing translations are preserved |
| Check mode | Audit existing translations for drift, missing placeholders, or quality regressions |
| Format‑aware | i18next JSON, Gettext .po, Java .properties, iOS .strings — round‑tripped intact |
| Context-aware | --context flag injects product info so the model picks domain-appropriate terminology |
| Dry‑run | Preview updates before touching disk |
| Everywhere | Use as a CLI, GitHub Action, or Node library |
Quick start
1 · Install
npm i -g i18n-ai-translate # or yarn add i18n-ai-translate --dev
export OPENAI_API_KEY=••• # or GEMINI_API_KEY / ANTHROPIC_API_KEY
2 · Translate a file
i18n-ai-translate translate -i i18n/en.json -o fr \
-e chatgpt -m gpt-5.2
Need more languages? Pass multiple codes (-o fr es de) or -A for all 180+. Filenames like es-ES.json / pt-BR.json are accepted too — the language subtag is extracted automatically. Skip specific locales with --exclude-languages fr de (handy for locales you maintain by hand).
Other formats: besides i18next JSON, Gettext .po, Java .properties, and iOS .strings files work too — the format is inferred from the file extension (override with --file-format json|po|properties|strings). Non-translatable structure round-trips losslessly: PO comments, msgctxt, and plural forms; .properties comments, separators, and line continuations; .strings /* */ and // comments and quoting. Native placeholders (printf %s/%1$s/%@, MessageFormat {0}/{1}) are preserved across the translation. Works across translate (file + folder), diff, and check.
3 · Translate a folder
i18n-ai-translate translate -i i18n/en -o fr es de \
-e chatgpt -m gpt-5.2
Recursively translates every *.json file in en and writes the results to i18n/fr, i18n/es, and i18n/de.
4 · Translate only what changed
i18n-ai-translate diff \
-b i18n/en-before.json -a i18n/en.json \
-l en -e claude -m claude-sonnet-4-6
Preserves every existing translation; only added/modified keys are re-translated, only deleted keys are removed. Per-locale writes are persisted as each language finishes, so a mid-run crash doesn't discard completed work.
5 · Check an existing translation
i18n-ai-translate check -i i18n/en.json -o fr de \
-e chatgpt -m gpt-5.2 --format json
Runs the verification pipeline against your existing translations without writing anything. Emits a structured report of keys the model flagged. Exits non-zero if any issue is found, so you can gate CI on it.
6 · Keep PRs up‑to‑date
Add a one‑liner GitHub Action to auto‑translate whenever en.json changes:
- uses: taahamahdi/i18n-ai-translate@master
with:
json-file-path: i18n/en.json
api-key: ${{ secrets.OPENAI_API_KEY }}
CLI cheat‑sheet
translate -i <src> -o <lang…> [options] # Translate a file or folder
diff -b <before> -a <after> [options] # Re‑translate only edited keys
check -i <src> -o <lang…> [options] # Verify existing translations (no writes)
Common flags (all subcommands accept these unless noted):
| Flag | Default | Description |
|---|---|---|
-e, --engine |
chatgpt | chatgpt · gemini · claude · ollama |
-m, --model |
gpt‑5.2 | e.g. gemini‑2.5‑flash, claude‑sonnet‑4‑6, llama3.3 |
-l, --input-language |
from filename | ISO‑639‑1 code or English name (en, French) — BCP‑47 tags like pt-BR OK |
-r, --rate-limit-ms |
engine‑specific | Minimum gap between requests |
--concurrency |
2 | Batches to run in parallel within one language |
--language-concurrency |
1 | Target languages to translate in parallel (shares pool + rate limit) |
--tokens-per-minute |
off | Extra TPM cap across all workers; pair with --concurrency to stay under tier |
--context <string> |
— | Product/domain context, e.g. "a B2B invoicing SaaS" |
--glossary <path> |
— | JSON file: keep-verbatim terms + forced per-language translations |
--exclude-languages |
— | Locales to skip (for manually‑maintained targets) |
--no-continue-on-error |
continue | Abort on first key/batch failure instead of skipping |
--dry-run |
false | Don't write files, preview instead (translate/diff only) |
--cache [path] |
off | Reuse a translation memory across runs; skip unchanged strings (translate/diff) |
--file-format |
from extension | File format: json, po, properties, strings (translate/diff/check) |
--format |
table | table or json report output (check only) |
Full flag list: i18n-ai-translate <subcommand> --help.
Use as a library
import { translate, translateDiff, check } from "i18n-ai-translate";
const fr = await translate({
inputJSON: require("./en.json"),
inputLanguageCode: "en",
outputLanguageCode: "fr",
engine: "chatgpt",
model: "gpt-5.2",
apiKey: process.env.OPENAI_API_KEY,
context: "a music trivia game for Discord", // optional
concurrency: 4, // optional
});
const report = await check({
inputJSON: require("./en.json"),
targetJSON: require("./fr.json"),
inputLanguageCode: "en",
outputLanguageCode: "fr",
engine: "chatgpt",
model: "gpt-5.2",
apiKey: process.env.OPENAI_API_KEY,
});
// report.issues = [{ key, original, translated, issue, suggestion }]
Advanced topics
- Prompt modes:
csv(faster, GPT‑class models only) vsjson(structured output, works with weaker models too) - Custom prompts: swap in your own generation/verification prompts via
--override-prompt - Translation memory:
--cache [path]stores translations in a JSON file (default.i18n-ai-translate-cache.json) and reuses them on later runs, so unchanged strings are never re-sent to the model. The key is the source text + languages +--context— independent of engine/model, so the cache survives a provider switch. Library callers can pass their owncacheobject. - Glossary:
--glossary <path>points to a JSON file that steers terminology —doNotTranslatekeeps brand/product names verbatim, andtermsforces exact per-language translations:{ "doNotTranslate": ["Acme"], "terms": { "fr": { "Account": "Compte" } } }. The rules are injected into both the generation and verification prompts; only the run's target language is applied (with BCP-47 base-subtag fallback, soptcoverspt-BR). - Plural awareness: keys ending in
_one/_other/_few/_manyget a CLDR plural hint in JSON mode - Placeholders:
{{variables}}are preserved; customise delimiters with-p/-s - Rate-limit handling: per-engine defaults + exponential backoff;
--tokens-per-minuteadds TPM cap