Reference / CLI
verbatra translate
Translate every target locale once, sending only new and changed keys to your provider, then write the results and exit.
Run one translation pass over every target locale, then exit. translate diffs the source against the lock file, sends only new or changed strings to the provider, runs the integrity checks, writes the locale files, and updates the lock. An unchanged string is never sent twice. A real run also adds missing entries to an existing .gitignore, see .gitignore entries.
Synopsis
verbatra translate [flags]Examples
# translate once using the config it finds
verbatra translate
# preview the keys that would be pruned, spending nothing
verbatra translate --prune --dry-run
# one locale at a time, which is what a rate-limited free tier needs
verbatra translate --locales de
# size and price the run without spending anything
verbatra translate --estimate
# stop before the run passes 50000 tokens
verbatra translate --max-tokens 50000
# also retranslate stale keys a person wrote
verbatra translate --include-humanFlags
| Flag | Argument | Default | Effect |
|---|---|---|---|
--locales | <list> | every configured target locale | translate only this comma-separated subset; an unconfigured locale fails with UNKNOWN_ and an empty list with INVALID_, both exit 2 |
--dry-run | none | off | report what a run would change without calling a provider or writing files; needs no API key |
--prune | none | the config's prune option, otherwise off | remove orphaned keys (in a target file, absent from source) from the written file and the lock |
--lock-timeout | <seconds> | 600 | wait this long for a held per-locale write lock; a whole number from 1 to 3600 |
--concurrency | <n> | 1 | translate up to n target locales at once; a whole number from 1 to 100, refused under a token budget |
--max-tokens | <n> | the config's maxTokens | hard token ceiling for this run, a positive whole number (INVALID_ otherwise); the lower of this and the config's maxTokens applies |
--no-cache | none | off (cache on) | bypass the translation-memory cache (verbatra.cache.json) for this run |
--include-human | none | off | also retranslate stale keys a person wrote or imported, for this run only; pinned keys stay untouched |
--estimate | none | off | estimate what the run would send and cost, then exit; implies --dry-run |
Also takes the global flags --cwd, --config and --json. Before it runs, translate loads the environment files.
Behavior
Warning
A run without --dry-run or --estimate sends every new or changed string to the provider, and a hosted provider bills for it: size the run with --estimate and cap it with --max-tokens.
Warning
An API key sent to a self-hosted server over plain http:// (not loopback) crosses the network unencrypted, see Keys are optional.
Dry run
--dry-run constructs no provider, so it needs no API key and cannot spend anything. It reads the source and target files, diffs them against the lock file, and reports exactly what a real run would change. It writes nothing (no locale file, no lock update, no run-status snapshot), with a machine provider neither reads nor writes the cache (under provider none it reads the translation memory, see below), and takes no write lock, so it never waits on a run in progress.
Note
By default a run deletes nothing: orphaned keys are reported and left in place. --prune, or prune: true in the config, deletes exactly those keys from the target files and the lock; --prune --dry-run previews them.
Cost estimate
--estimate adds a pre-run estimate to the dry run: how many keys would be sent, how many provider requests they would take, and the quantity the provider bills for (tokens for an LLM or an openai-compatible endpoint, source characters for DeepL, Google Cloud Translation or LibreTranslate; a self-hosted endpoint or server is not billed, so the estimate reports no API cost). A currency figure appears only when the config has a rates block covering the provider and model; otherwise the missing rate key is named instead of inventing 0.00. See Estimating cost.
A subset of locales
--locales de,fr narrows the run to part of the configured targets. The other locales keep their files, lock entries, and cache entries as they were. Use it on a rate-limited free tier to translate one locale at a time, or to re-run a single locale that came out partial. The run-status snapshot then describes that subset only.
Write locks and interrupts
A locale waits up to --lock-timeout for a contended write lock, then fails with LOCK_ while the other locales still run. A lock left by a verbatra process that is no longer running on this machine is reclaimed at once. The timeout bounds only that wait before any provider call: recording a written file in the lock file always waits up to the ten-minute default, so a written file is never left unrecorded. A wait longer than a second prints a line on stderr naming the lock and its holder (a lock-wait record under --json), which --quiet does not drop. Ctrl-C or SIGTERM releases the run's locks before the command exits.
Token ceiling
--max-tokens is always a hard stop: a provider request that would pass the ceiling is withheld, whatever budgetBehavior says, and its keys are listed as budgetWithheld. It is counted like a configured maxTokens budget and can tighten that budget but never loosen it. A run the ceiling cut short exits 1. Run --estimate first to pick a figure.
Parallel locales
--concurrency is refused with exit 2 when a token budget applies (from maxTokens or --max-tokens), because which locale loses its remaining work would depend on how the locales interleave; a dry run is exempt. If a whole-run error, such as a corrupt lock file, surfaces while locales are running, no further locale starts, and the running ones finish and release their locks before the command exits 2.
Keys a person wrote
A stale key whose value a person wrote, imported, or changed outside verbatra is kept by default: it is listed as protected, not sent to the provider, and stays stale for verbatra check. A stderr warning says how many were kept (pinned keys are not counted) and points to --include-human, which retranslates them for one run (humanEdits: "overwrite") and so prints no warning. Protected keys do not change the exit code, except in a human-only project. See Protecting human translations.
Human-only projects
In a human-only project (provider: { id: "none" }) no provider is called. Keys the translation memory covers are written, and every other missing or changed key is listed as unfilled and exits 3; a failed or partial locale still wins with 1. A dry run reads the memory here, so its plan shows the same cacheHits and unfilled keys, and --estimate reports nothing billed; both exit 3 the same way. Without --json, a stderr warning says how many keys need a person, and a next: hint points to verbatra export, where you hand the unfilled keys off.
Failed and partial locales
One locale failing is not fatal: the run finishes and exits 1. A partial locale exits 1 too: its file was written but keys are still missing, typically because a provider sub-batch failed or the integrity gate refused a translation. The human output lists every refused key under integrity-withheld with its reason (placeholder, markup, icu, degenerate, or empty); with --json, each locale carries them in integrityRefusals. Keys the run flagged for review follow the locale's N needs-review count under a needs-review: line, each with its review reasons; with --json they are in needsReview.
Unsupported locales
A locale the configured DeepL or Google Cloud Translation provider does not support refuses the whole run with LOCALE_ and exit 2, on a dry run and with --estimate too, before anything is spent. Leave it out with --locales, or see Language support. A glossary or tone the provider cannot apply only adds a notice (GLOSSARY_, FORMALITY_), as do LOCALE_ and, for an LLM, LOCALE_.
JSON output
Schema: translate-envelope.json validates the whole envelope; Schemas lists the others.
With --json, stdout carries one envelope whose result is a RunSummary; RunSummary lists every field, for the run and for each locale.
Terminal feedback
translate names the locales and provider before the run, and prints the elapsed time, tokens used, and the next command afterwards. On an interactive terminal the progress is one spinner line; when stderr is piped, every step is a plain line:
verbatra: translating 2 locales with gemini/gemini-2.5-flash
verbatra: translating de
verbatra: de batch 1/1
verbatra: de: writing
verbatra: de done, 12 translated
verbatra: translating fr
verbatra: fr batch 1/1
verbatra: fr: writing
verbatra: fr done, 12 translated
verbatra: run finished, 2 locales processed
[ok] done in 4.2s, 1830 tokens
next: verbatra check (confirm every locale is in sync)A dry run opens with dry run over 2 locales, no provider call, an estimate with estimating 2 locales, no provider call, and a human-only project with filling 2 locales from the translation memory (provider none). A dry run or an estimate ends each locale with would translate instead. A locale that kept only some keys ends with partly done, one that failed with failed, and the closing line adds how many failed. A dry run closes with dry run done in 4.2s, nothing written. An estimate closes with estimate done in 4.2s, nothing written new. These lines go to stderr, and --quiet drops them. See Terminal output.
Exit codes
| Code | Meaning |
|---|---|
0 | every target locale came out complete |
1 | the run finished, but a locale failed or came out partial |
2 | could not run: a config, source, format, provider, unsupported-locale, or lock-file error, or a usage error |
3 | human-only project: the run (a dry run or --estimate included) finished cleanly, but keys are left for a human translation |
130 | interrupted with Ctrl-C (SIGINT) after releasing the write locks |
143 | stopped with SIGTERM after releasing the write locks |
On an interrupt, stderr ends with verbatra: interrupted (SIGINT), released locks, or under --json with the record {"type":"interrupted","signal":"SIGINT","locksReleased":true}. If releasing the locks fails or does not finish within 5 seconds, the line says so instead (could not release locks: <reason> or lock release did not finish within 5s), and the JSON record carries "locksReleased":false with "reason":"failed" (plus message) or "reason":"timed-out" (plus deadlineMs). See the exit-code contract.
Related
- How it works explains the read, diff, translate, write pipeline.
- The cache explains what
--no-cachebypasses. - Translation safety covers the integrity checks and the needs-review queue.
verbatra diffpreviews the same pending keys read-only.- CI and exit codes shows how to branch on these codes in a pipeline.