Skip to content

Translation config (generate / generate --resume) ​

Use translate in i18nprune.config.* as a roster of backends (translate.providers) plus a default id (translate.primary) when the CLI flag and provider env omit.

Discover backends: run i18nprune providers (or --json) for ids, env var names, and minimal translate.{ primary, providers } examples from @i18nprune/core.

Canonical shape ​

FieldMeaning
primaryDefault TranslationProviderId when --provider and I18NPRUNE_TRANSLATE_PROVIDER are unset (must match an enabled row in providers).
providersNon-empty array; each element is { id: '…', … } (discriminated union). enabled: false ignores a row for merges (rich init leaves optional backends scaffolded off). rateLimit — optional maxConcurrency, rpm, rps, intervalMs. Duplicate id values are rejected.
policyOptional translate-policy block. Parse merges TRANSLATE_POLICY_DEFAULTS; maxAttempts defaults to providers.length. Keys: routing, onRateLimit, onTransientFailure, onQuotaExceeded, onAuthFailure, onProviderUnavailable, onIdentityOutput, onIncompleteRun, maxAttempts, handoff. .strict() rejects unknown keys.
workersnumber (1…64): max parallel translateLeaf jobs when CLI/env omit --workers (1 = serial; default when omitted).

Row fields by id ​

idExtra fields
googleoptional enabled, rateLimit
mymemoryoptional contactEmail, enabled, rateLimit
libreoptional baseUrl, enabled, rateLimit
deeploptional apiKey, enabled, rateLimit
llmoptional apiKey, baseUrl, model, enabled, rateLimit

Example:

typescript
export default defineConfig({
  source: 'en',
  localesDir: 'locales',
  src: 'src',
  functions: ['t'],
  translate: {
    primary: 'llm',
    providers: [
      { id: 'google' },
      {
        id: 'llm',
        baseUrl: 'https://api.openai.com/v1',
        model: 'gpt-4o-mini',
        // apiKey: prefer env — I18NPRUNE_TRANSLATE_LLM_API_KEY
      },
    ],
    policy: { routing: 'single', onRateLimit: 'backoff', onTransientFailure: 'retry' },
  },
});

Run i18nprune init --rich for every namespace, including translate.providers stubs and policy.

translate.policy (outcome → verb) ​

Orchestration is forward-only: each failure class maps to a policy key whose value is a single verb (retry, backoff, fallback, prompt, abort, flag). Defaults are defined in TRANSLATE_POLICY_DEFAULTS (see source). Verb meanings below.

KeyDefaultAllowed verbs
routingsinglesingle | auto
onRateLimitbackoffbackoff | retry | fallback | abort
onTransientFailureretryretry | fallback | abort
onQuotaExceededfallbackfallback | prompt | abort
onAuthFailureabortabort | prompt
onProviderUnavailablefallbackfallback | abort
onIdentityOutputflagflag | fallback | abort
onIncompleteRunconfirmconfirm | write | discard
maxAttemptsproviders.lengthpositive integer
handoffautoauto | on | off

providers[] order is the auto chain when routing: 'auto' — see init template comments.

Mid-run handoff (catalog pool) ​

When policy resolves to prompt and the host is interactive (handoff matrix §8 in the plan doc), runGenerate offers the built-in provider catalogue (not the same as reordering providers[] only): google, mymemory, libre, deepl, llm, excluding the failing id. deepl appears only if apiKey resolves (I18NPRUNE_TRANSLATE_DEEPL_API_KEY or the deepl row). llm requires apiKey, baseUrl, and model. libre without a URL can use the public demo origin when picked from the handoff list.

--json route detail ​

Each finished target includes markedForReview (already present) and providerAttempts[] rows: legacy outcome (success / rate_limited / network_error / non_retryable_error) plus, on failures, translateFailureOutcome — the seven-variant classifier string (rate_limited, quota_exceeded, transient_network, …) for tooling and dashboards.

When at least one target was finalized as a partial write (see onIncompleteRun below), the payload also includes partial: true, resumeHint: "generate --resume", and markedForReview (sum across those targets). Each such row sets partial: true on targetResults[].

Order (first wins):

  1. --provider (generate, including generate --resume)
  2. I18NPRUNE_TRANSLATE_PROVIDER
  3. translate.primary
  4. Built-in default (google)

Credential fields merge only from the translate.providers row whose id matches the resolved backend. Rows with enabled: false are skipped.

When policy.routing is 'auto', the CLI resolves an ordered chain: the pin (--provider / env) or translate.primary first, then every other enabled provider row (deduped, typically config file order). After retryable failures (429, transient network errors, etc.), the next provider continues generate (full or --resume) using the same in-memory locale: paths already translated by the previous backend are not sent again; only remaining strings hit the next provider. routing: 'single' locks to one id for that run (no chain).

Routing combinations (mental model) ​

Treat routing and --provider / env as:

Config routingCLI / env provider pinEffective chain (example providers google, mymemory)
single(none)[translate.primary] only
single--provider mymemory['mymemory'] only (config primary ignored for ordering)
auto(none)[primary, …other enabled rows in file order]
auto--provider mymemory['mymemory', …other enabled rows] (mymemory first, then fallbacks)
autoenv I18NPRUNE_TRANSLATE_PROVIDER=mymemorySame as row above

Retryable failure ⇒ advance one step in the chain and continue without discarding successful leaf translations from earlier attempts.

Skipped rows: enabled: false never appears in the chain. Rows missing required secrets (DeepL apiKey, libre baseUrl, llm triple) fail assertTranslationProviderCredentialsReady when that id’s turn arrives — the run stops with a USAGE error (see below); they are not silently skipped.

Partial runs (onIncompleteRun) ​

When generate cannot finish every string leaf for a target (chain exhausted, non-retryable error, or interrupt), core applies translate.policy.onIncompleteRun:

VerbBehavior
discardThrow the last error; no locale file is written for that target.
writeFinalize in-memory JSON (same normalization path as a full success) and write (--dry-run still exercises the path without touching disk).
confirmInteractive CLI: prompt to write or abort. --yes, non-interactive runs, and --json map to write (loss of completed translations is worse than auto-writing a partial file). SDK / runGenerate without GenerateRunHooks.onIncomplete: confirm defaults to write in core.

Missing apiKey / baseUrl / model ​

Before each provider attempt, the CLI validates required fields (after env merge). If anything required is missing, it throws I18nPruneError with code: 'USAGE' and issueCode: i18nprune.translate.missing_credentials — immediate hard stop (no translation). In --json mode, generate maps that to an issues[] row via usageIssueFromI18nPruneError (same pattern as other usage failures). Human runs log the error message on stderr; there is no separate warning-only path for misconfigured backends.

Example commands (copy/paste) ​

Assume locales/en.json, locales/, and src/ match your project.

A — Auto fallback (MyMemory first, Google second), config-driven

bash
# policy.routing: 'auto', translate.primary: 'mymemory', providers include google + mymemory (both enabled)
i18nprune generate --target ja --metadata --workers 8 --yes

B — Same roster, but force Google first for this run

bash
i18nprune generate --target ja --metadata --provider google --workers 8 --yes

C — Single routing: only primary, no fallback even if many providers exist

bash
# policy.routing: 'single'
i18nprune generate --resume --target de --metadata --workers 4 --yes

D — Resume all targets with env pin + auto chain

bash
export I18NPRUNE_TRANSLATE_PROVIDER=mymemory
i18nprune generate --resume --all --metadata --yes

Precedence — same field again (env vs file) ​

If a matching I18NPRUNE_TRANSLATE_* env var is non-empty, it supersedes the same field on the active row. Tables: Environment variables (translation backends).

Parallel translation (--workers) ​

Source (CLI wins first)Meaning
--workers <n>Hard override (clamped 1…64).
I18NPRUNE_TRANSLATE_MAX_WORKERSEnv baseline when flag omitted.
translate.workersConfig baseline (integer ≥ 1) when env + flag omit.

With --workers > 1, core uses a bounded parallel translate pool then ordered replay (same final locale JSON as --workers 1). Details: CLI verbosity and this page's policy notes.

Providers with strict quotas (429) ​

Free tiers (mymemory, etc.) return 429 quickly. i18nprune enforces provider rateLimit pacing using rpm, rps, and intervalMs. If quotas still trip, reduce --workers, lower rpm / rps, widen spacing, retry, or change --provider.

When no throttle fields are provided, i18nprune applies conservative per-provider defaults (including maxConcurrency) and may warn when a requested --workers value is reduced to a safer effective cap.

When MyMemory returns quota text (e.g. NEXT AVAILABLE IN ...), i18nprune parses that wait window and includes it in failure messaging / issue payloads so operators can retry at a concrete time.

Source of truth ​

  • Zod: packages/cli/src/config/schema.ts — translate.workers must be a bare integer 1…64 (or omit for 1). Objects / concurrency keys are not accepted.
  • Merge: resolveTranslationProviderOptions (packages/cli/src/shared/translator/resolveProvider.ts).
  • Concurrency: resolveTranslateMaxParallel (@i18nprune/core) · resolveCliTranslateMaxParallel.
  • Troubleshooting: Issues — translate.