How it works
The translation pipeline end to end, from reading the source locale to updating the lock file.
A run is a pipeline: read the source locale, diff each target against the lock file, send only what
needs work to the provider, check what comes back, write, and record the result. This page walks
that pipeline so you know exactly what happens when you run verbatra translate.
A run starts from your validated config; see Config file for where it is found
and what it contains. Everything below is the same whether the run came from the CLI, the SDK, or
watch.
Read the source locale
The format adapter reads the source locale file into a neutral set of keyed entries, so the rest of
the pipeline works the same for every format. A missing source file fails the run with
SOURCE_UNREADABLE; a file the adapter cannot parse fails with SOURCE_INVALID. While reading, the
adapter also flags source keys whose values are invalid for the format's message syntax (invalid
ICU): those keys ride along but are set aside instead of being sent to the provider in a broken
state.
Diff against the lock baseline
This diff exists so a run is incremental: the provider is called only for strings that actually need work. verbatra reads the existing target locale file (an absent file counts as empty) and diffs the source against it, using the lock file as the baseline of what was translated before. Every source key lands in one of four buckets:
- missing: the target does not have the key yet.
- changed: the source value no longer matches the hash recorded in the lock.
- unchanged: the source value still matches the recorded hash.
- orphaned: the key is in the target but no longer in the source.
Only missing and changed keys become translation candidates. Unchanged keys are left alone.
Orphaned keys are reported and, by default, left in place; with --prune (or prune: true in the
config) exactly those keys are removed from the target file and the lock, and nothing else.
Batch to the provider
Candidates go to your configured provider in sequential sub-batches of at most maxBatchSize
(default 50), so one oversized request cannot take down a whole locale. Each sub-batch carries the
entries plus your optional glossary and tone. If a sub-batch call fails (a rate limit, a
timeout, a revoked key), its keys are withheld for this run and picked up again next run while the
other sub-batches keep making progress. See Providers for how each provider
applies the inputs.
Validate the response
Provider output is never trusted as free text. For the LLM providers (Anthropic, OpenAI, Gemini, and openai-compatible), the response is schema-bound data, validated before anything else happens: a response that is malformed, or that contains a key that was never requested, fails the sub-batch, and a requested key that comes back missing or duplicated is re-requested once; if it is still unresolved after that, the key is treated as not translated this run and retried on the next one. DeepL is a machine-translation API without this response layer, but its results go through the same integrity checks below.
Check placeholder and ICU integrity
A translation that passed schema validation can still be broken, so verbatra recomputes its own accept-or-reject decision from the candidate value itself: it never trusts the provider's report. Each accepted candidate must carry the same placeholders as its source, and for the ICU formats it must still be a valid message. A candidate that fails either check is withheld instead of written, surfaces on the run summary, and is retried next run. See Translation safety for the full safety model.
Write through the adapter
Accepted translations are merged into the target and written by the format adapter, atomically, in document key order: a key already in the target keeps its position, and a new key appends where the source puts it. An interrupted write never leaves a half-finished locale file, and a formatter or code reviewer sees stable, minimal diffs.
Update the lock
After a locale is written, its lock entries are refreshed with the current source hashes, with one deliberate exception: a key withheld this run keeps its prior hash, so the next run still sees it as needing work. Each locale's write happens under a cross-process write lock, so two overlapping runs never diff against a stale baseline or pay for the same provider call twice. The details live in The lock file.
One locale at a time, failures as data
Each target locale runs on its own: one locale failing does not stop the others. The run returns a
summary per locale (translated, unchanged, orphaned, withheld, flagged for review, notices), and a
dry run (--dry-run) produces the same summary shape after the diff step, without constructing a
provider or writing anything.