@rio-cloud/i18n
v1.0.1
Published
Deterministic CLI workflow for AI-assisted Web, JVM, iOS, and KMP translations
Keywords
Readme
I18n
This package provides the reusable, deterministic i18n-flow CLI for AI-assisted Web, JVM, iOS, and Kotlin
Multiplatform translations. It initializes projects, creates or updates translations, validates the results, and
offers supporting catalog and maintenance tools.
Consumer projects keep their app-specific configuration in a .i18nrc.json file at the project root.
Backend projects using Spring MessageSource or Java ResourceBundle should start with the
JVM backend guide.
iOS apps using locale-specific Localizable.strings resources should start with the
iOS guide.
Compose Multiplatform apps using composeResources should start with the
Kotlin Multiplatform guide.
Table of contents
- Main idea
- Platform guides
- Installation and entry points
- Typical developer flows
- Why this approach
- Source of truth
- Important config sections
- Job manifests and generated output
- CLI options
- Example commands
- Repository structure
- Repository skill
- Publishing
- License
Main idea
The workflow groups its commands by purpose. Most developers primarily use the translation commands.
Translation
translate- creates missing translations only
- does not overwrite existing target translations
- after adding source keys, simply run
i18n-flow translate; locale or key parameters are not required - automatically uses locale-oriented requests for large backfills and multi-locale requests for small deltas
improve- reviews existing target translations against the corresponding messages from the configured source locale
- gives the AI both the source text and the current target translation as its basis
- leaves the source locale unchanged unless
--include-source-localeis passed; with that option, the source is improved first and its updated text is then used to improve the selected target locales - runs in conservative mode by default
- changes a translation only when an objective correction is required
- use
--mode reviseto actively improve wording, clarity, consistency, and style
retranslate- regenerates existing translations from the current English source text
- use this when the source label in
en-GBchanged and targets need to be updated accordingly
validate- checks all existing target translations against the mandatory translation policy
- reports missing translations without treating them as invalid
- does not call AI or change translation files
Catalog and setup
initconfigures a project and installs the repository-scoped i18n skill.select-ai-modelupdates the model and reasoning of the current AI runner in an initialized project.install-skill- installs the packaged workflow instructions at
.agents/skills/i18n/SKILL.md - preserves an existing installed skill unless
--updateis passed
- installs the packaged workflow instructions at
inspect-ui-components- finds UI component usages described by an external i18n catalog
- maps localized props to canonical catalog keys and messages
- produces a human-readable or JSON task without changing project files
sync-shared-messages- finds shared catalog message keys that are referenced in project source files
- copies their source text and available reviewed translations into the configured locale files
- is run automatically by
translate; the explicit command is useful for previews and CI checks
migrate-to-catalog-keys- migrates explicit shared-message and UI-component
legacyKeysin source and locale files - removes migrated legacy locale entries and supports
--dry-run
- migrates explicit shared-message and UI-component
Maintenance
remove-unused-keys- removes messages that are not statically referenced in project code from every locale JSON file found in the
directory configured by
targetPathPattern - conservatively preserves key families constructed with template literals or string concatenation
- use
--keepfor indirect dynamic keys that cannot be inferred statically - use
--unsafeto opt into aggressive exact-reference-only cleanup
- removes messages that are not statically referenced in project code from every locale JSON file found in the
directory configured by
harmonize-keys- adds the configured prefix to unprefixed translation keys and normalizes supported legacy key forms
- updates exact JavaScript/TypeScript references and unambiguous static prefixes of trailing dynamic keys
- detects conflicting normalized keys before changing files and supports
--dry-run
cleanup-phrase- removes obsolete root-level Phrase YAML configuration files after migration
- removes
pull-translation(s)andpush-translation(s)scripts frompackage.json - supports an interactive confirmation,
--yes,--dry-run, and JSON output
Diagnostics
test-catalog-connectionverifies access to the configured i18n catalog.test-ai-connectionverifies the configured AI runner.
translate, improve, and retranslate run end-to-end:
- build a job manifest
- call the configured Codex CLI model
- validate the AI output
- write the updated target locale files
Platform guides
- Web/JSON projects: continue with the setup and configuration reference in this README.
- JVM/Java-properties projects: see the JVM backend guide.
- iOS/Apple-strings projects: see the iOS app guide.
- Kotlin Multiplatform/Compose-XML projects: see the KMP guide.
Installation and entry points
The folder is packaged as @rio-cloud/i18n and exposes the brand-neutral i18n-flow executable. Use the package
runner for your project:
| Package manager | Initialize | Run the package script | Run the binary directly |
| --- | --- | --- | --- |
| npm | npx @rio-cloud/i18n init | npm run i18n-flow -- <command> | npm exec -- i18n-flow <command> |
| pnpm | pnpm dlx @rio-cloud/i18n init | pnpm run i18n-flow -- <command> | pnpm exec i18n-flow <command> |
| Yarn | yarn dlx @rio-cloud/i18n init | yarn run i18n-flow <command> | yarn i18n-flow <command> |
| Bun | bunx @rio-cloud/i18n init | bun run i18n-flow -- <command> | bunx i18n-flow <command> |
With npm or Bun, put every i18n-flow argument after the -- separator. For example, use
npm run i18n-flow -- help or npm run i18n-flow -- --help; otherwise npm handles --help itself.
The remaining examples use the package-manager-neutral form i18n-flow <command>.
Built-in help
The executable has built-in documentation, so the most relevant guidance is available directly in the terminal:
i18n-flow help
i18n-flow translate --help
i18n-flow improve --help
i18n-flow help web
i18n-flow help examples
i18n-flow help configInteractive init flow
In an interactive terminal, the Inquirer wizard discovers locale files and guides the developer through source, targets, locales, the catalog Git URL or local path, AI runner, and Codex model and reasoning settings. Chalk highlights the summary and result.
Run the wizard with:
i18n-flow initThe CLI first detects the likely platform from project files. Gradle or Maven builds and Java properties message bundles
indicate JVM; Xcode projects and .lproj/Localizable.strings resources indicate iOS;
commonMain/composeResources/values/strings.xml indicates Kotlin Multiplatform; package.json and JSON locale files
indicate Web. An unambiguous interactive result is shown without exposing the underlying file paths and must be
confirmed. Rejecting it, or conflicting signals, opens the platform selection with Web as the default. Non-interactive
setup uses an unambiguous detection and otherwise falls back to Web. Use --platform web, --platform jvm,
--platform ios, or
--platform kmp to bypass detection. The Web setup then follows this flow. Values supplied as CLI options skip the
corresponding question.
- Source translation file
- The wizard searches the project for
en-GB.json,en-US.json, oren.jsonsource candidates. - One matching source file is selected automatically.
- If multiple candidates exist, choose
Select the source translation file. - If no candidate can be inferred, pass
--source-file <path>.
- The wizard searches the project for
- Target locale path pattern
- Enter the target file pattern.
- Default:
{locale}.jsonnext to the selected source file, for examplesrc/features/translations/{locale}.json. - If
{locale}is omitted, the wizard normalizes the value to a locale pattern.
- Source locale
- Enter the locale represented by the source file.
- Default:
en-GB.
- Translation key prefix
intl-msg:— recommended and selected by default.Custom prefix— prompts for a complete project-specific prefix including its separator.No prefix— preserves unprefixed translation keys.
- Target locales
- Existing locale files matching the target path pattern are detected and proposed as the target locales.
- Confirm the detected locales to continue, or decline to customize them in the checklist. Detected locales are preselected when customizing.
- If none are detected, choose one or more locales from the checklist:
bg-BG,cs-CZ,da-DK,de-DE,el-GR,es-ES,et-EE,fi-FI,fr-FR,hr-HR,hu-HU,it-IT,lt-LT,lv-LV,nb-NO,nl-NL,pl-PL,pt-BR,pt-PT,ro-RO,sk-SK,sl-SI, andsv-SE. All are initially selected.
- Shared translation rules (i18n-catalog)
- Enter the Git URL of the shared catalog. A local path remains supported for catalog development.
- Default:
ssh://[email protected]:7999/riofront/i18n-catalog.git. --catalog <source>,I18N_CATALOG_URL, orI18N_CATALOG_PATHcan provide the value non-interactively.--catalog-ref <ref>orI18N_CATALOG_REFselects a branch or tag; otherwise the remote default branch is used.- Before writing setup files, initialization verifies that the selected remote and ref are readable with the current
Git/Bitbucket credentials.
--dry-rundoes not access the network.
- AI runner
Codex CLI— selected by default. Continue with:- Initialization reads the available model and reasoning-effort catalog from the signed-in Codex CLI. The
interactive model recommendation prefers the newest available Terra or Luna generation, with Terra preferred
when both families have the same generation. It recommends
mediumreasoning when the selected model supports it, otherwise the model's own default effort. Custom modelremains available for models outside the discovered catalog.- Explicit
--modeland--reasoning-effortoptions take precedence over automatic selections. - Before writing setup files, initialization performs a small AI request to verify that the configured Codex CLI
can reach the selected model. If catalog discovery fails, setup warns and uses
gpt-5.6-terrawithmediumreasoning; the connection check still runs and must succeed before setup files are written. Failure of that connection check stops setup with an update/sign-in hint instead of exposing the raw CLI error.--dry-runstill performs the local Codex catalog discovery, but skips the generation connection check. If the Codex executable cannot be started for discovery, initialization stops with a concise command-path error.
- Initialization reads the available model and reasoning-effort catalog from the signed-in Codex CLI. The
interactive model recommendation prefers the newest available Terra or Luna generation, with Terra preferred
when both families have the same generation. It recommends
AWS Bedrock— confirm or replace the default modeleu.anthropic.claude-sonnet-4-6and Regioneu-central-1, then optionally enter an AWS profile. The runner uses the AWS SDK credential chain, including credentials refreshed withaws sso login. Initialization verifies the Bedrock connection before writing setup files;--dry-runskips this network check. Bedrock projects install the optional runtime once withnpm install @aws-sdk/client-bedrock-runtime; other projects do not receive it.Custom AI— continue with one adapter template:Provider SDK— selected by defaultDirect API (API key)
- Package script
- Confirm
Add an "i18n-flow" script to this project's package.json? (recommended)to add the single script"i18n-flow": "i18n-flow". - Decline to leave the existing
scriptssection unchanged and run the binary directly.
- Confirm
- Configuration summary and confirmation
- Review source, target pattern, prefix, locales, catalog, runner, model, and optional adapter.
- Confirm
Create this configuration?before files are written. - When
.i18nrc.jsonalready exists, confirm its replacement. Existing project-context files are preserved unless their individual replacement prompts are confirmed.
After confirmation, initialization creates or updates:
.i18nrc.json.i18n/context/project.md.agents/skills/i18n/SKILL.md- an ignored
.i18n/cache/checkout when a catalog Git URL is configured - the selected Custom AI adapter template, when applicable
- the optional
i18n-flowpackage script and the pinned@rio-cloud/i18ndevelopment dependency inpackage.json .gitignorerules for local state and job artifacts
With --yes or --json, the interactive questions are skipped. The automatic Codex model and reasoning effort come
from the signed-in CLI's default model entry and its default effort. If discovery fails or the CLI reports no default
model, initialization warns and uses gpt-5.6-terra with medium reasoning; the usual connection check still has to
succeed. Explicit --model or --reasoning-effort values override the corresponding automatic choice. Other
automatic defaults are Codex CLI, intl-msg:, and the shared Bitbucket catalog URL. Detected target locales are used;
otherwise the default locale list above is selected. The i18n-flow package script is added unless
--no-package-script is passed.
For automated setup, use --yes; use --dry-run or --json for a machine-readable plan/result. Initialization writes
.i18nrc.json, project context templates, the repository-scoped skill, the optional i18n-flow package script, the
pinned development dependency, and local artifact ignore rules. Shared domain data remains in the catalog checkout.
The generated .i18n/context/project.md is the extension point for project-specific product and audience knowledge,
tone and form of address, required terminology, and terms that must not be translated. Project rules extend the shared
catalog and take precedence when they conflict with a catalog default. This supports deliberate exceptions such as a
driver-facing app using informal German du while the shared catalog defaults to formal Sie. Add any additional
project context files explicitly to contextPaths.
There are two supported ways to start the workflow after initialization:
- run the optional package script with the syntax shown in the table above
- run the
i18n-flow <command>binary directly through the package manager
Both entry points use the same deterministic CLI implementation. The CLI owns discovery, selection, batching, manifests, JSON schema creation, validation, and target-file writes. The configured AI runner only receives a bounded prompt and schema and returns structured translation data.
JVM projects
For Spring MessageSource and Java ResourceBundle setup, bundle strategies, locale mappings, configuration, and
supported commands, see the JVM backend guide. The explicit quick start is
i18n-flow init --platform jvm.
iOS projects
For .lproj/Localizable.strings discovery, encoding preservation, Phrase migration, configuration, and supported
commands, see the iOS app guide. The explicit quick start is
i18n-flow init --platform ios.
Change the configured AI model
Run i18n-flow select-ai-model in an initialized Web, JVM, iOS, or KMP project to update ai.model and
ai.reasoningEffort. The runner and all other configuration settings are preserved. Use --config <path> to update
an alternative configuration file.
--model and --reasoning-effort take precedence over interactive selection. If either value is missing, the CLI
asks for it. Codex discovers its model catalog and offers the same model and reasoning recommendations as init;
custom models also allow manual reasoning input. Other runners ask for missing values as text.
i18n-flow select-ai-model
i18n-flow select-ai-model --model gpt-6-luna --reasoning-effort medium
i18n-flow select-ai-model --model gpt-6-luna --yes--yes is allowed only with the Codex runner and at least one missing value. It automatically selects the catalog's
default model and/or the selected model's default reasoning, as during init. If a custom model has no catalog
reasoning default, it uses medium. Passing both values together with --yes, or using --yes with another runner,
is an error. Without a terminal, pass both values or use the permitted Codex --yes mode.
Before saving, the CLI tests the selected settings with the same minimal AI request as init. All runners,
including custom commands, must pass this test. Codex discovery failures stop the command before the final test.
A discovery or connection failure leaves the configuration unchanged.
AI runner boundary
The actual AI translation call is made through .i18nrc.json > ai. With runner: "codex-cli", execution depends on
the local codex binary and its authentication state. With runner: "bedrock", the AWS SDK invokes the configured
model through Bedrock's Converse API and resolves authentication through the standard AWS credential chain. Runners do
not discover or write translation files.
To integrate another executable, use the command runner. It receives a versioned JSON request over stdin and must
return { "output": ..., "metadata": ... } over stdout. Provider-specific behavior remains isolated in runners/.
When Custom AI is selected during initialization, the CLI asks whether to use a provider SDK or a direct HTTP API with
an API key. It then creates and configures .i18n/ai-adapter-sdk.mjs or .i18n/ai-adapter-api.mjs and asks the developer
to complete the provider-specific parts. An existing adapter is not overwritten. For automated setup, select the template
with --custom-ai-mode sdk or --custom-ai-mode api-key.
Additional API or internal agent runners can implement the same interface without changing the deterministic workflow.
Built-in runners and their initialization behavior are registered centrally in src/cli/runners/configuration.mjs, so
new providers do not require separate configuration branches in every platform initializer.
Typical developer flows
1. New locale or missing translations
- Add or confirm the target locale in
.i18nrc.json - Make sure you are logged into Codex locally
- Run
i18n-flow translate - Let the AI fill only missing keys
2. New UI feature with new English keys
- Add new keys to the English source file
- Run
i18n-flow translate - No locale or key parameters are needed; only the missing translations are selected automatically
- Multiple locales are translated in one AI call when they fit within the configured batch limits
- Existing manual translations stay untouched
3. Improve existing translations
- Run
i18n-flow improveand select the keys and locales interactively, or pass them as CLI options - For every selected target translation, the AI compares its current text with the corresponding text from the
configured source locale, for example
en-GB - For a focused request, use for example
i18n-flow improve --locale de-DE --keys 'intl-msg:app.*' - The default
conservativemode changes only translations that require an objective correction - Pass
--mode revisewhen the AI should also actively improve wording, clarity, consistency, and style - Add
--include-source-localeto improve the source locale first and use that updated source text as the reference for the selected target locales
4. English source text changed
- Update the label in the configured English source file
- Select keys with
--keysor use--all - Run, for example,
i18n-flow retranslate --locale de-DE --keys 'intl-msg:app.*' - Existing target translations for those keys are regenerated from the updated source text
5. Harmonize existing translation keys
i18n-flow harmonize-keys migrates keys in the configured source locale and every existing locale listed in
targetLocales to the configured prefix. A key without a prefix receives it directly, for example
home.title becomes intl-msg:home.title. Supported legacy forms such as intl-msg.home.title are normalized as
well. An unknown prefix is preserved as a key namespace rather than interpreted as a typo: with intl-msg: configured,
app:save becomes intl-msg:app.save.
The command also scans .js, .jsx, .ts, .tsx, .mjs, .cjs, .mts, and .cts files below the project root.
It updates exact references to migrated keys. If the dynamic part is only a trailing suffix and the static prefix can be
derived unambiguously from migrated locale keys, that prefix is updated too:
formatMessage({ id: 'home.title' }); // -> 'intl-msg:home.title'
const key = `home.${section}`; // -> `intl-msg:home.${section}`
const otherKey = 'app:' + action; // -> 'intl-msg:app.' + actionThe migration is intentionally text-based rather than syntax- or usage-aware. Its main limitations are:
- Unknown or misspelled prefixes are not corrected. For example,
intl-mesg:homeis treated as a namespace instead of a typo forintl-msg:home. - Dynamic prefixes are changed only when matching migrated locale keys imply one unambiguous replacement. Fully indirect keys, a dynamic beginning, or template literals with additional static text after the interpolation are left unchanged.
- Source references outside the supported JavaScript/TypeScript extensions are not changed. Configuration entries,
generated
.i18nfiles, HTML, Vue, Svelte, Markdown, and other formats remain untouched. - Because replacement is text-based, matching text in comments or unrelated strings can also be changed. Review the preview before writing.
- Only the configured source file and existing
targetLocalesfiles participate. A source reference with no matching old key in those locale files cannot be migrated.
Run i18n-flow harmonize-keys --dry-run first to review the key mapping and affected files. The command detects
conflicting normalized keys with different values before it writes any files.
Why this approach
The workflow is intentionally built around a small local CLI instead of sending whole translation files to an AI manually.
- The root
.i18nrc.jsonis this repository's single source of truth for source files, target files, locales, AI runtime settings, batching, formatting, and checks similar to what we are known from Phrase. - AI calls are scoped to explicit jobs. A run translates, improves, or retranslates selected keys instead of rewriting entire locale files.
- Existing target translations are preserved by
translate, so manual fixes and reviewed translations are not overwritten accidentally. - Prompts include catalog and project context, UI style rules, output constraints, and protected terminology.
- The AI response is constrained with a JSON schema and validated before it is written back.
- Placeholder checks catch common breaking changes such as missing
{name},%s, or${value}placeholders. - Protected terminology can be checked against the source and target text.
- Each run writes a manifest and state summary so the requested keys, locales, model, duration, validation findings, and written files stay inspectable.
The main tradeoff is that the AI can preserve structure and follow context, but it cannot guarantee perfect wording, domain nuance, or consistency across all future changes without review and good catalog data.
Source of truth
The root .i18nrc.json is the app-specific configuration for:
- the project platform
- translation key prefix
- source locale and source file
- target locales
- target file pattern
- state file location
- which catalog checkout and project context files should be given to the AI
- which Codex CLI settings should be used
- JSON formatting rules
- translation checks
- globally ignored keys
The current source locale is:
"sourceLocale": "en-GB"That means:
translatereads missing source keys from the configuredsourceFileretranslatealways uses the current English source text from that same source file
Important config sections
Core paths
{
"platform": "web",
"prefix": "intl-msg:",
"sourceLocale": "en-GB",
"sourceFile": "src/features/translations/codex/en-GB.json",
"targetLocales": ["de-DE", "fr-FR"],
"targetPathPattern": "src/features/translations/codex/{locale}.json",
"stateFile": ".i18n/state.json"
}Meaning:
platformselects the platform-specific command, initialization, help, and resource adapters. New Web configurations useweb; existing configurations without this property remain compatible and are treated as Web projects.prefixis the complete text prepended byharmonize-keys, including any separator. The defaultintl-msg:produces keys such asintl-msg:home.title. For example,my-app.producesmy-app.home.title.sourceLocaleis the locale of the source messages. The current workflow expectsen-GB.sourceFileis the JSON file that contains the source messages.targetLocalesis the default locale scope for commands when no CLI locale override is provided. Locale files matchingtargetPathPatternbut missing from this list are ignored. Before each task, the CLI prints a warning asking the user to add those locales to the configuration manually.targetPathPatternis the path to target locale files.{locale}is replaced with each target locale.stateFilestores the latest run summary. Job manifests are written next to it underjobs/.
Relative paths are resolved against the config file directory and project root. For non-src/ paths, the CLI also tries resolving under src/.
AI context
{
"catalog": {
"url": "[email protected]:company/i18n-catalog.git"
},
"contextPaths": [".i18n/context/project.md"]
}Meaning:
catalog.urlidentifies the shared GitLab or Bitbucket repository.catalog.refoptionally selects its branch or tag; when omitted, the remote default branch is used. The CLI clones it into the ignored.i18n/cache/catalog/directory and fetches updates before use. Initialization requires successful remote access even if a cache already exists. During later commands, a failed update falls back to an existing valid cache with a warning.catalog.pathremains available for a checked-out local catalog. A CLI--catalogsource has highest precedence, followed byI18N_CATALOG_PATH,I18N_CATALOG_URL, and the configured source.- Technical output and integrity policies for JSON, keys, placeholders, ICU structures, and markup are built into the workflow and do not depend on the catalog.
contextPathsaccepts project-specific Markdown files or directories. New configurations reference.i18n/context/project.mddirectly. Add further project files explicitly when needed. When a directory is configured, every direct.mdfile in it is loaded in alphabetical order.- Catalog guidance, terminology, protected terms, writing style, approved corrections, and shared messages are loaded first; project-local Markdown files are loaded afterwards. Project context extends the shared defaults and wins on conflicts involving terminology, protected terms, product names, audience, form of address, tone, or preferred wording. A project protected-term definition overrides an identically named catalog term. Neither catalog nor project context can override the built-in output-integrity rules for JSON, keys, placeholders, ICU structures, and markup.
The catalog also contains UI component contracts used by inspect-ui-components and shared messages synchronized into
client translation files. Other catalog artifacts are translation context rather than client translation output.
Use i18n-flow test-catalog-connection to diagnose the configured catalog independently. For remote sources, the
command checks the selected Git ref in a temporary checkout, validates catalog.json, and leaves both project files
and the managed catalog cache unchanged. Pass --json for machine-readable CI output, or use --catalog and
--catalog-ref to test an override.
AI runtime
{
"ai": {
"runner": "codex-cli",
"command": "codex",
"model": "gpt-5.6-terra",
"reasoningEffort": "medium",
"sandbox": "read-only",
"timeoutMs": 300000
}
}AWS Bedrock example:
{
"ai": {
"runner": "bedrock",
"model": "eu.anthropic.claude-sonnet-4-6",
"region": "eu-central-1",
"profile": "rio-development",
"maxTokens": 8192,
"timeoutMs": 600000
}
}region defaults to eu-central-1; --aws-region, AWS_REGION, and AWS_DEFAULT_REGION override it in that order.
profile is optional and otherwise resolved through the standard AWS credential chain. Authenticate named IAM Identity
Center profiles with aws sso login --profile <profile>. Do not store
AWS access keys or session tokens in .i18nrc.json. The Bedrock SDK is an optional peer dependency and is loaded only
when the Bedrock runner executes. The selected model must support Converse tool use with a forced named tool
(toolChoice.tool). The runner submits the output schema as the submit_result tool and reads its structured input
as the result, avoiding free-text JSON formatting errors. Property names with unsupported characters (such as :) or
more than 64 characters receive temporary aliases in the tool schema and are restored in the response. Locale files
keep their original keys.
Meaning:
runnerselectscodex-cli,bedrock, orcommandas the execution backend for AI translationcommandpoints to the local Codex binary or custom adapter executable when that runner needs onemodelselects the Codex model or Bedrock model/inference-profile ID. New Codex projects use the signed-in CLI's default model in automatic setup; Bedrock defaults toeu.anthropic.claude-sonnet-4-6.reasoningEffortselects the Codex reasoning level. Model choices and model default efforts come from the signed-in CLI catalog during initialization.regionconfigures AWS Bedrock and defaults toeu-central-1;profileandmaxTokensare optionalsandboxlimits whatcodex execmay do while generating translationstimeoutMssets the request timeout per locale call
For typical product UI translations, prefer the newest available Terra or Luna model and medium reasoning when that
effort is supported. Use a Luna model for large, simple backfills where cost is the priority, and a Sol model for
particularly important or terminology-heavy content. Compare the available models with a representative set of 50–100
keys before using a cheaper model for a large translation run.
Translation
{
"translation": {
"batchSize": 80,
"deltaLocalesPerCall": 6,
"maxParallelLocales": 4
}
}Meaning:
batchSizelimits the number of translations in an AI request. For a large backfill it is applied per locale. For an automatically bundled multi-locale request it limits the total key-locale combinations in that request.deltaLocalesPerCalllimits how many localestranslate,retranslate, orimprovemay bundle into one automatic multi-locale request. Its default is6. Setting it to1disables multi-locale batching and creates a separate AI request for every locale. For example, one missing source key across 24 locales normally produces four chunks at the default value, providedbatchSizeallows all six key-locale combinations per chunk.maxParallelLocalescontrols how many locale or locale-chunk AI calls can run at the same time.
Recommended starting point:
| Situation | batchSize | deltaLocalesPerCall | maxParallelLocales |
| --- | ---: | ---: | ---: |
| Normal project usage | 80 | 6 | 4 |
| Rate limits or unstable runner | 40 | 3 | 1–2 |
| Fast runner and small labels | 100 | 6–8 | 4–6 |
Change one setting at a time. Reduce batchSize when requests become too large or hard to retry, reduce
deltaLocalesPerCall when multi-locale requests time out, and reduce maxParallelLocales when the runner reports
rate limits. Higher values can reduce wall-clock time but do not improve translation quality by themselves.
Batching
Batching applies to all AI-backed commands.
- It is controlled by
translation.batchSize. translateinspects missing keys, whileretranslateandimproveinspect the selected existing keys per locale. All three choose locale-oriented requests for large workloads and multi-locale requests when multiple locales fit safely into one request.- A multi-locale request never exceeds
translation.batchSizekey-locale combinations ortranslation.deltaLocalesPerCalllocales. - Large locale workloads are processed in locale-first order. Once the command advances to the next locale, the previous locale is complete.
improveandretranslatecontinue automatically and bundle small selected-key workloads across locales. Large workloads stay locale-first. Passing--limitintentionally stops after one bounded batch.
This locale-first order makes progress predictable: once the command moves to the next locale, the previous locale is
complete. A one-off run with --limit keeps the requested multi-locale scope and stops after one bounded batch.
Batching is used for practical reliability and cost control:
- It keeps prompts small enough to stay within model context limits.
- It avoids asking the model to reason about too many unrelated labels at once.
- It limits the blast radius of a failed AI call. A failed batch can be retried without redoing all locales.
- It makes long backfills resumable because each batch writes a manifest and then updates only the requested missing keys.
- It keeps runtime predictable.
batchSizecaps how many missing keys a locale can process in one batch. - Automatic multi-locale chunks can run in parallel through
translation.maxParallelLocales.
There is no strategy option. If three new keys are missing in 24 locales, translate groups locales automatically to
reduce startup and prompt overhead. If many keys are missing, it keeps locale requests separate to preserve a focused
prompt and predictable retries. The chosen execution strategy is recorded in the job manifest and shown in the output.
Parallelisation is controlled by translation.maxParallelLocales.
- It lets independent locale or locale-chunk requests run at the same time.
- It reduces wall-clock time for large translation jobs without increasing the size of each individual prompt.
- It keeps failures isolated because each parallel worker still handles a bounded batch or locale chunk.
- It is useful when many locales need the same kind of update and the Codex/API runtime can handle multiple requests concurrently.
The value should stay conservative. Higher parallelism can make a run faster, but it can also increase local CPU and memory usage, hit rate limits sooner, or make failures harder to read in the console output.
JSON formatting
{
"jsonIndent": 4,
"sortKeys": false,
"newlineAtEnd": true
}Meaning:
jsonIndentcontrols JSON indentation for files written by the CLI.sortKeyssorts object keys deeply before writing when set totrue.newlineAtEndcontrols whether written JSON files end with a trailing newline.
These settings are used for target translation files, job manifests, and state files written by the CLI.
Checks
{
"checks": {
"protectedTerms": "contextual"
}
}Meaning:
protectedTermsaccepts"contextual"or"strict"and defaults to"contextual".contextualsupplies protected-term context to the AI and reports deterministic findings without blocking the job.strictadditionally fails when a protected term appears in the source but is missing verbatim from a target translation.
Protected-term findings are always recorded in the manifest. Placeholder mismatches are violations of the mandatory
translation policy and always block the affected job. The deterministic protected-term check is lexical and cannot
decide whether a potentially ambiguous name such as Perform or Exceptions was used as a product name or as a common
word; use strict only when that tradeoff is acceptable.
Ignored keys
Keep ignoredKeys near the end of .i18nrc.json, where a growing list does not interrupt the core path and runtime
settings:
{
"ignoredKeys": [
"intl-msg:legal.notice",
"intl-msg:glossary.serviceDescription.*"
]
}Exact keys and * patterns are excluded globally from translate, improve, and retranslate. harmonize-keys
leaves them unchanged, and remove-unused-keys preserves them. For example,
intl-msg:glossary.serviceDescription.* ignores every key below that prefix.
Job manifests and generated output
Short answer
The generated files under .i18n/jobs/ are records of individual command runs. .i18n/state.json is only a small
pointer to the most recently written record. It is a state file, not a statistics file.
They are not a translation memory, do not track which source texts changed, and are not read to decide what a later command should translate. Every command recalculates its work from the current source and target translation files.
The potentially confusing part is that the CLI also builds a job manifest in memory before calling AI. That in-memory plan supplies the selected texts, keys, locales, context, and settings for the current AI request. Once the run finishes, the CLI writes the completed manifest to disk as an audit and debugging record. The saved JSON file is not fed back into the AI on the next run.
.i18nrc + CLI options + current translation files + catalog
│
▼
job plan created in memory
│
▼
current AI request and validation
│
▼
translations written to locale files
│
▼
completed job manifest saved for diagnostics
│
▼
state.json points to that most recent manifestGenerated job artifacts
The generated job manifests and state file serve different purposes:
| Concept | Purpose | Created by |
| --- | --- | --- |
| In-memory job manifest | Concrete plan used to build and execute the current AI request | CLI, during the current run |
| .i18n/jobs/<mode>-<timestamp>.json | Saved plan and result record for diagnostics and auditing | CLI, after a batch is prepared or completed |
| .i18n/state.json (stateFile) | Pointer and compact summary for the most recently saved manifest | CLI, when a manifest is saved |
The resolved CLI options become part of the manifest. For example, --locale, --keys, --context, --model,
--reasoning-effort, and the improve-specific --mode determine the concrete locale scope, key scope, instructions,
improve mode, and AI settings recorded for that batch.
A manifest contains the information needed to understand what the CLI selected and what happened, including:
- command mode, resolved improve mode when applicable, timestamp, locales, and keys
- source and target file references
- source texts, current target texts, and placeholders for the selected entries
- catalog and project context files
- resolved AI settings and the automatic batching strategy
- validation findings
- after a successful AI run: request count, duration, provider metadata, and updated-key counts
The lifecycle of a manifest is:
- The CLI combines
.i18nrc.jsonand CLI options. - It reads the current translation files, discovers the applicable keys and locales, and creates one bounded batch plan in memory.
- With
--dry-run, it writes that plan without updatingstateFileand stops before calling AI. - During a normal run, the CLI uses the in-memory plan to build the current AI request. It validates and writes the
returned translations, adds
aiRunresult metadata, and saves the completed manifest. - A long command can create multiple manifests because every processed batch has its own plan and result record.
stateFilepoints to the most recently written one.
By default, manifests are written to .i18n/jobs/<mode>-<timestamp>.json. When stateFile uses another directory,
the jobs/ directory is created next to that state file.
The saved manifest is useful for reviewing the selected scope, diagnosing unexpected output, understanding which model and context were used, and retaining CI/debugging evidence. It is not consumed by the client application, is not read by later translation commands, and is not the source of truth for translations.
How later commands determine their work
translatecompares the current source file with the current target locale files and selects missing string values.improveuses the source text and current target translation for each selected key. With--include-source-locale, it improves the source locale first and uses the updated source text for the selected target locales.retranslateuses the currently selected keys and current source and target texts. It does not automatically detect source-text changes from previous manifests. Existing placeholder or protected-term violations in the selected target texts do not block retranslation because those values are about to be replaced. Newly generated translations still pass the complete mandatory policy before any locale file is written.- terminology and approved wording come from the current catalog and project context, not from previous jobs.
Deleting .i18n/jobs/ or .i18n/state.json therefore does not remove translations and does not prevent future runs.
It only removes the diagnostic records and the pointer to the most recent record.
--dry-run does not create a queued or paused job. There is currently no command that executes an existing
manifest later. To continue or retry work, run the original translation command again; it recalculates the remaining
scope from the current source and target files. --out <path> only changes where the generated manifest is written.
Should manifests be committed?
Normally, no. Job manifests and stateFile are transient runtime artifacts. They can change on every run, contain
source and target text plus execution metadata, and create noisy repository history. i18n-flow init therefore adds
.i18n/state.json and .i18n/jobs/ to .gitignore automatically. An explicit --out path should also remain
untracked unless a team deliberately wants to preserve one diagnostic artifact. If stateFile is moved from its
default location, add the custom state file and its adjacent jobs/ directory to .gitignore manually.
Commit these project inputs and outputs instead:
.i18nrc.json- project context files under
.i18n/context/ - custom AI adapter source code, without credentials
- source and target translation files
For CI, prefer uploading manifests as temporary build artifacts when they are useful for troubleshooting instead of committing them to the repository.
Removing unused keys
remove-unused-keys reviews keys without exact source references before changing locale files. In an interactive
terminal, it opens a checkbox list. Reliable dynamic-prefix matches and plural families with a statically referenced
base key are treated as used and do not appear. Keys with no inferred reference are preselected; ambiguous dynamic
matches and unreferenced plural keys remain unselected and are annotated as uncertain dynamic reference or
plural key. Press a to toggle all candidates, or use Space to select individual keys.
Use --dry-run to print the complete annotated candidate list without changing files. Non-interactive write mode
requires --yes and removes only the safely preselected candidates. Use --all --yes to explicitly remove every
candidate, including dynamically referenced and plural-family keys. --keep <a,b> and configured ignoredKeys exclude
keys from the review entirely.
CLI options
Most commands support the same selection and execution options:
--config <path>uses a different config file instead of.i18nrc.json.--locale <locale>or--locales <locale1,locale2>overrides the configured locale scope.--keys <key1,key2>overrides the configured key scope, including when selecting only one key.--context <text>adds one-off instructions for the AI, for example a preferred tone. The instructions are stored in the job manifest and apply to every AI request in that run. They do not override translation policy or mandatory terminology context.--model <model>overrides the configured AI model for one translation run. The selected model is stored in the job manifest and passed to the configured runner.--reasoning-effort <level>overrides the configured reasoning effort for one translation run. Codex CLI accepts the level names supported by the installed CLI and model; the value is passed to Codex for validation. Bedrock and custom-command runners acceptnone,low,medium,high,xhigh, ormax. The selected value is stored in the job manifest.improve --mode <conservative|revise>selects the review behavior. Conservative mode changes text only when an objective correction is required. Revise mode actively improves wording, clarity, consistency, and style while keeping the existing translation as its basis.- When
improveruns interactively without a CLI key and locale scope, it asks which keys and target locales to improve before showing the plan and overwrite confirmation. --allselects every source key. This is especially useful withimproveandretranslate;--limitcan still constrain a one-off run.--limit <number>overridestranslation.batchSizefor the current run.--keys <a,b>accepts exact keys and anchored*patterns such asintl-msg:app.*.improve --include-source-localeimproves the configured source locale first. Target locales in the same batch are then improved using the updated source texts. Other commands reject this modifier.--out <path>writes the generated manifest to an explicit path.--dry-runbuilds and writes the manifest without calling AI, changing translations, or updatingstateFile.improveandretranslateask for confirmation before overwriting translations. In non-interactive environments, pass--yesto confirm the run.--dry-rundoes not require confirmation.
Shared-message sync
shared-messages catalog artifacts define reusable application messages that are not tied to a UIKIT component, such
as the generic error fallback. Catalog keys do not contain the project prefix. For a project configured with
"prefix": "intl-msg:", the catalog key common-message.error.generic.headline therefore becomes
intl-msg:common-message.error.generic.headline.
Before planning a translate job, i18n-flow scans JavaScript and TypeScript source files for exact references to
catalog shared-message keys. For every used key it:
- copies the catalog
sourceinto the configured source-locale file; - copies a target value only when the catalog contains a reviewed translation for that locale;
- leaves all other target locales missing so the normal translation job generates them;
- preserves unrelated project messages and ignores shared catalog messages that the project does not reference.
This makes a template reference such as the following sufficient; developers do not need to copy catalog messages or know which locale files contain them:
<FormattedMessage id='intl-msg:common-message.error.generic.headline' />Run the sync explicitly to apply catalog values without starting an AI translation, or preview it without writing:
i18n-flow sync-shared-messages
i18n-flow sync-shared-messages --dry-run
i18n-flow sync-shared-messages --dry-run --jsonThe sync treats catalog values as canonical and updates a differing project value. translate --dry-run also previews
the automatic sync in memory but does not change locale files.
Catalog-key migration
Shared messages and UI-component message definitions can declare proven predecessor keys as legacyKeys. Migrate
Phrase-era locale entries and their JavaScript or TypeScript references together with:
i18n-flow migrate-to-catalog-keys --dry-run
i18n-flow migrate-to-catalog-keysThe migration replaces exact quoted legacy references in source files, writes the canonical catalog value when one is
available (otherwise preserving the existing legacy locale value), and removes migrated legacy locale entries. It only
uses explicit catalog mappings and is idempotent. sync-shared-messages and translate do not perform this source-code
migration implicitly.
UI component inspection
inspect-ui-components reads ui-component-contract artifacts from an external i18n catalog and scans JavaScript and
TypeScript client source files. Component and prop knowledge comes from the catalog; the CLI contains no hard-coded
UIKIT component list.
The catalog source is read from .i18nrc.json. Override it for a single run through --catalog <source>. A local
checkout can also be supplied through I18N_CATALOG_PATH; remote deployment configuration can use
I18N_CATALOG_URL and I18N_CATALOG_REF:
i18n-flow inspect-ui-components --catalog ../i18n-catalog
i18n-flow inspect-ui-components --catalog [email protected]:company/i18n-catalog.git --json
i18n-flow inspect-ui-components --fixThe result reports hard-coded, missing, already aligned, and custom-expression props. Catalog keys are prefix-free; the
configured project prefix is applied to every reported expectedKey and when looking up project messages. The result
also includes canonical catalog messages and their current values in the configured project locale files. The command is
read-only by default and does not call AI; its JSON output is intended as a structured task for a client AI agent.
A component contract can define multiple semantic variants for the same physical prop. The inspector resolves a variant from an already canonical key, a referenced project message with the same catalog value, or a hard-coded catalog value:
{
"TreeOption": {
"label": {
"variants": {
"showEmptyGroups": "treeOption.label.showEmptyGroups",
"showGroups": "treeOption.label.showGroups"
}
}
}
}When exactly one variant matches, the result contains its canonical expectedKey and variant name. When the intended
meaning cannot be inferred safely, the prop is reported as ambiguous-variant with all candidate keys and remains
unchanged by --fix.
With --fix, the command copies every available canonical catalog message to its configured project locale file. It also
normalizes a component prop expression when that expression contains exactly one non-canonical message key with the
configured prefix. Hard-coded values, missing props, and ambiguous expressions remain findings because the CLI cannot
safely infer or introduce the consuming application's i18n access pattern. Use --fix --dry-run to preview the counts
without changing source or translation files.
Example commands
Available commands:
i18n-flow init
i18n-flow install-skill
i18n-flow inspect-ui-components
i18n-flow sync-shared-messages
i18n-flow migrate-to-catalog-keys
i18n-flow validate
i18n-flow translate
i18n-flow improve
i18n-flow retranslate
i18n-flow remove-unused-keys
i18n-flow harmonize-keys
i18n-flow cleanup-phrase
i18n-flow test-catalog-connection
i18n-flow test-ai-connection
i18n-flow helpExamples:
# Create a structured localization task for catalog-supported UI components
i18n-flow inspect-ui-components --catalog ../i18n-catalog --json
# Apply available catalog messages and safely replace non-canonical keys
i18n-flow inspect-ui-components --fix
# Verify Bitbucket/Git read access and the catalog manifest without changing the cache
i18n-flow test-catalog-connection
# Replace the installed repository skill with the version shipped by this package
i18n-flow install-skill --update
# Validate every existing target translation without changing files or calling AI
i18n-flow validate
# Validate one configured locale
i18n-flow validate --locale de-DE
# Normal feature workflow: translate every newly added or otherwise missing key
i18n-flow translate
# Build the manifest only and skip the AI call
i18n-flow translate --dry-run
# Translate selected labels with one-off AI instructions
i18n-flow translate --locale de-DE --keys 'intl-msg:batteryAnalyticsSurvey.*' --context 'The text should be informal'
# Improve existing translations for selected keys
i18n-flow improve --locale de-DE --keys 'intl-msg:app.*'
# Actively revise wording, clarity, consistency, and style
i18n-flow improve --locale de-DE --keys 'intl-msg:app.*' --mode revise
# Improve selected German translations once with the quality-focused model
i18n-flow improve --locale de-DE --keys 'intl-msg:slider.batteryAnalyticsSurvey.*' --model gpt-5.6-sol
# Use additional reasoning for a difficult translation run
i18n-flow improve --locale de-DE --keys 'intl-msg:slider.batteryAnalyticsSurvey.*' --reasoning-effort high
# Improve the complete German locale
i18n-flow improve --locale de-DE --all
# Retranslate existing translations because the English source changed
i18n-flow retranslate --keys intl-msg:foo.bar --locale de-DE
# Retranslate the complete Brazilian Portuguese locale from the current English source
i18n-flow retranslate --locale pt-BR --all
# Preview key normalization without changing files
i18n-flow harmonize-keys --dry-run
# Preview obsolete Phrase configuration and package scripts after migration
i18n-flow cleanup-phrase --dry-run
# Remove the reviewed Phrase artifacts without an interactive prompt
i18n-flow cleanup-phrase --yes
Prefer a focused locale and key selection for improve. --all selects every source key in the selected locale scope;
it can take a long time, make many AI requests, and consume substantially more AI tokens. Use it only when a complete
review is intended.
Repository structure
Production code under src/ has three top-level areas:
core/- platform-independent jobs, prompts, policies, validation, ports, and workflow orchestration
- imports neither
clinor a concrete platform
cli/- the unified
i18n-flowbinary, argument parsing, command handlers, terminal UI, and replaceable AI runners
- the unified
platforms/- platform registries and concrete resource, discovery, initialization, help, and asset integrations
- the Web platform owns the JSON resource adapter and repository-scoped frontend skill
- the JVM platform owns Java-properties bundles, explicit locale/file mappings, and default-bundle strategies
- the iOS platform owns Apple
.stringsparsing, encoding preservation, and.lprojlocale mappings
Tests and fixtures live separately under the root-level test/ directory.
Repository skill
i18n-flow init installs the packaged skill at .agents/skills/i18n/SKILL.md without replacing an existing file.
This keeps the AI instructions versioned with the client repository and makes catalog-backed UI component conventions
available while code is written. Run i18n-flow install-skill to install it separately, or
i18n-flow install-skill --update to explicitly replace an installed copy with the version from the package.
The installed metadata.version is generated from the @rio-cloud/i18n package version. Without --update, the
installer reports whether the checked-in skill is current, outdated, or from an older unversioned installation.
Publishing
This package is intended for RIO projects and is published under the public @rio-cloud scope on npmjs.org. Publishing
requires an npm account with permission to publish packages in that scope and the configured npm two-factor
authentication or publishing token.
Before publishing, update the version in package.json and package-lock.json, add the release notes to
CHANGELOG.md, and commit those changes. Then run the release from the repository root:
npm login
npm whoami
npm publishThe package's publishConfig selects npmjs.org and public access, so no additional registry or access flags are needed.
npm publish runs npm run test-all through prepublishOnly and stops before publishing if type checking, formatting,
or tests fail. Each version can be published only once. After a successful release, verify it with:
npm view @rio-cloud/i18n versionLicense
Licensed under the Apache License 2.0.
