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_credentialsimmediate 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.tstranslate.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.