Translating Intlayer in place
Intlayer is a good solution for applications that keep translations in typed content dictionaries, or that use a translation management system and human translation. translate-intlayer is for that audience. It fills the missing and stale locale leaves inside your *.content.ts dictionaries, in place. The dictionaries stay the source of truth, Intlayer stays at runtime, and ai-i18n-tools acts as the translation engine (provider selection, glossary, translation context, and model fallback).
Keep Intlayer, or move to source-string calls?
| Your audience | Command | Guide |
|---|---|---|
| Typed content dictionaries, a translation management system, or human translation. Intlayer stays at runtime. | translate-intlayer | this page |
Source-string calls, t('…'), with the source-language text kept with the code that uses it. The source language can be any language. | migrate-intlayer | Migrating from Intlayer to source-string calls |
translate-intlayer is an ongoing task you re-run whenever you add source strings or edit existing ones. migrate-intlayer is a one-time move for the other audience. Each rewritten call carries the source-language string with the code that uses it, which simplifies maintenance and reduces the complexity of that code. That language is sourceLocale, and it can be any language.
What translate-intlayer does
- Parses
*.content.tsdefault exports and collects everyt({ locale: '…' })leaf. Locale values can be plain strings or Intlayerinsert('…')insertion nodes. - Seeds the UI block's
ui.stringsJsonwith the source text, then runs the same engine astranslate-uifor the missing and stale locales. - Writes the new per-locale strings back into each
t({ … })object. Edits are surgical: only the missing or stale locale lines change, so your formatting, key order, and comments are preserved. Existing translations are left as written. When a leaf usesinsert('…'), new locale values are wrapped ininsert(…)too, so the leaf stays callable at runtime. - Preserves placeholders. Intlayer's insertion syntax
{{token}}is passed through unchanged. Single-brace{token}placeholders are also supported: they become{{token}}for translation and return to{token}on write. - Dry run with
--dry-run: it reports the missing/stale counts without calling an LLM or writing anything.
Missing vs. stale
A leaf + locale is translated when either:
- The locale is absent from the leaf (missing), or
- The source-locale text changed since the last run for that locale (stale). The source language is
sourceLocaleand can be any language.
Staleness is tracked per leaf in cacheDir, so editing one English string re-translates only that leaf on the next run; everything else stays cached. A translation that was already out of date on the very first run is accepted as current — only later edits mark it stale.
Translate your project
Install
ai-i18n-tools(see Installation). If your project has noai-i18n-tools.config.jsonyet, scaffold one:bashai-i18n-tools init -P geminiSet
sourceLocaleandtargetLocalesto the locales your dictionaries use, and pointui.sourceRoots,ui.stringsJson, andui.flatOutputDirat your source and a catalog path — the same keystranslate-uiuses, see UI strings — Step 1: Initialise. The catalog can live in a scratch directory (for example.i18n/); it is used as the translation cache and you never hand-edit it.Dry run first to see what would be filled:
bashai-i18n-tools translate-intlayer src --dry-runApply it (needs a provider API key):
bashai-i18n-tools translate-intlayer srcRe-run whenever you add a new string or edit an English one. Only the new or changed leaves are translated. Scope a run with
-l de,fror--ui-blockwhen needed.
Run the example
The intlayer-translate example is a small Vite + React app whose dictionaries ship with English and Brazilian Portuguese; Spanish and Simplified Chinese are missing on purpose. pristine/ is a read-only reference; src/ is the working copy. pnpm reset returns src/ to that starting state (and clears the catalog and cache) so you can repeat the exercise.
npx degit wsj-br/ai-i18n-tools/examples/intlayer-translate intlayer-translate
cd intlayer-translate
pnpm install
pnpm reset
pnpm translate:dry
pnpm translatepnpm dev runs the app on http://localhost:3093. Switch the locale to es or zh-Hans before translating and the strings fall back to English; after pnpm translate the filled translations appear. Edit an English string and re-run pnpm translate to see only that leaf refresh.
Full walkthrough: examples/intlayer-translate/README.md.
Command
ai-i18n-tools translate-intlayer [paths...] [-l <codes>] [--ui-block <selector>] [--dry-run] [-j <n>]Requires features.translateUIStrings and the UI block's ui.stringsJson (same as translate-ui). --dry-run does not call an LLM.
| Option | Meaning |
|---|---|
[paths...] | Files/dirs/globs to scan (default: ui.sourceRoots) |
-l, --locale <codes> | Comma-separated target locales to fill (default: the block's targetLocales) |
--ui-block <selector> | UI block: zero-based index, description, or stringsJson path (default: block 0) |
--dry-run | Report missing/stale counts only; no LLM call, no writes |
-j, --concurrency <n> | Max parallel target locales (default: config concurrency or 4) |
See also: CLI — UI strings, Migrating from Intlayer to source-string calls