Reference / Configuration
Providers
Every AI and machine translation provider verbatra supports, with its options and API key variable, plus the behavior all of them share.
Select a provider by id in the provider block of your config. Anthropic, OpenAI, Gemini, OpenCode and openai-compatible are language models; DeepL, Google Cloud Translation and LibreTranslate are machine-translation services; none turns machine translation off. Language coverage is on Languages and locales, and what each provider receives on Data handling and privacy.
Which one should I pick?
Every provider works with every format and command, and switching later means editing one id.
| If you want | Use |
|---|---|
| Free, for a small project with non-confidential strings | Gemini: a free API tier, whose requests Google may have humans review outside the EEA, Switzerland and the UK (terms). |
| The best quality for a production app | Anthropic or OpenAI, both paid. |
| High volume, a native glossary, no LLM | DeepL, free or paid tier. |
| The widest coverage from an API key on Google Cloud | Google Cloud Translation (Basic, v2). |
| Offline, no cost, nothing leaving your network | openai-compatible with LM Studio, Ollama or vLLM. |
| No language model, but self-hosted | LibreTranslate on your own server. |
| No machine translation at all | none: memory and humans only. |
Keys come from the environment
verbatra never reads a key from the config. Each hosted provider reads one variable:
| Provider id | Environment variable |
|---|---|
anthropic | ANTHROPIC_ |
openai | OPENAI_ |
gemini | GEMINI_ |
deepl | DEEPL_ |
google-translate | GOOGLE_ |
opencode | OPENCODE_ |
Set it in .env (which verbatra init gitignores), your shell, or your CI secret store, and never commit it. An unset or empty variable fails with MISSING_, naming the variable but never a value. openai-compatible and libretranslate usually need no key.
Anthropic
provider: {
id: "anthropic",
options: {
model: "claude-sonnet-4-6", // example model id
maxTokens: 4096,
},
}| Option | Type | Default | Effect |
|---|---|---|---|
model | string | required | The model id. |
maxTokens | positive integer | required | Caps one response's output tokens (limits); unrelated to the top-level budget. |
requestTimeoutMs | positive integer | 120000 | Bounds each request attempt (Request timeout). |
localeMap | map of locale to code | none | Overrides the code sent per locale (Locale codes). |
Reads ANTHROPIC_.
OpenAI
provider: {
id: "openai",
options: {
model: "gpt-5.4-mini", // example model id
maxOutputTokens: 4096,
},
}| Option | Type | Default | Effect |
|---|---|---|---|
model | string | required | The model id. |
maxOutputTokens | positive integer | required | Caps one response's output tokens. |
requestTimeoutMs | positive integer | 120000 | Bounds each request attempt. |
localeMap | map of locale to code | none | Overrides the code sent per locale. |
Reads OPENAI_.
Gemini
provider: {
id: "gemini",
options: {
model: "gemini-2.5-flash", // example model id
maxOutputTokens: 4096,
},
}| Option | Type | Default | Effect |
|---|---|---|---|
model | string | required | The model id. |
maxOutputTokens | positive integer | required | Caps one response's output tokens. |
requestTimeoutMs | positive integer | 120000 | Bounds each request attempt. |
localeMap | map of locale to code | none | Overrides the code sent per locale. |
Reads GEMINI_.
The free tier and its limits
A key from Google AI Studio needs no billing, but its per-minute and per-day caps can stop a first run over many locales: translate one locale at a time or lower maxBatchSize, and re-run for the rest.
DeepL
provider: {
id: "deepl",
options: {
glossaryId: "<your-glossary-id>",
},
}| Option | Type | Default | Effect |
|---|---|---|---|
glossaryId | string | none | A glossary you created in DeepL, applied natively. |
requestTimeoutMs | positive integer | 120000 | The minimum wait per attempt, up to six attempts. |
localeMap | map of locale to code | none | Overrides the code sent per locale. |
Reads DEEPL_; options may be {}. DeepL has no model and no output-token limit, and degrades with a notice rather than failing:
- Placeholders travel as numbered markers in an ignored tag (
Hello {{name}}asHello <x>{0}</x>) and are restored.PLACEHOLDER_withholds a value with a non-literal placeholder or other text holdingUNSUPPORTED <,>,{or}, and a result with a lost, repeated, changed or bare marker, an unknown entity or a stray tag. Markup alone goes as plain text. - Tone maps to formality (
formalmore,informalless,neutralomitted). A free-tier key sends no formality at all and reportsFORMALITY_. A paid key requestsDOWNGRADED prefer_ormore prefer_, so a language without formality, such as Swedish, falls back with the same notice instead of failing. The glossary is not applied.less
verbatra ships DeepL's dated language table and refuses an unlisted language with LOCALE_ before anything is spent. A glossaryId works only when both languages are on DeepL's glossary list (such as DE, EN, FR, JA, ZH).
Google Cloud Translation
provider: {
id: "google-translate",
options: {},
}| Option | Type | Default | Effect |
|---|---|---|---|
requestTimeoutMs | positive integer | 120000 | Bounds the whole call (no retries). |
localeMap | map of locale to code | none | Overrides the code sent per locale. |
Reads GOOGLE_, an API key for a Google Cloud project with the Cloud Translation API enabled. Only Basic (v2) works: Advanced (v3) needs a service account.
- Placeholders are masked as for DeepL, inside
<span translate="no">; line breaks, tabs and double spaces are withheld too. - Codes. Each code sent is checked as well-formed BCP 47 before any call (
en_fails withUS INVALID_). Regional codes are allowed; a pair Google rejects fails withREQUEST INVALID_from the API.REQUEST
verbatra ships Google's dated language table and refuses an unlisted language before anything is spent. A regional code whose language is listed only as a whole, such as de-AT, is sent as written with a warning.
OpenCode
Only OpenCode Zen models served over /chat/completions work here (the model table lists each model's endpoint). OpenCode keeps Go and the free Zen models for its own clients.
provider: {
id: "opencode",
options: {
model: "glm-5.3", // example: a Zen model served over Chat Completions
maxOutputTokens: 4096,
},
}| Option | Type | Default | Effect |
|---|---|---|---|
model | string | required | The model id. |
maxOutputTokens | positive integer | required | Caps one response's output tokens. |
requestTimeoutMs | positive integer | 120000 | Bounds each request attempt. |
localeMap | map of locale to code | none | Overrides the code sent per locale. |
Fixed endpoint, no baseUrl. Reads OPENCODE_.
openai-compatible
Any server speaking the OpenAI Chat Completions API: LM Studio, Ollama, vLLM, or a hosted API.
The server must serve Chat Completions at <baseUrl>/chat/completions with strict JSON schema output. Gateways serving only their own client apps do not work.
provider: {
id: "openai-compatible",
options: {
baseUrl: "http://192.168.178.74:1234/v1",
model: "qwen2.5-14b-instruct", // example: whatever your server exposes
maxOutputTokens: 1024,
},
}| Option | Type | Default | Effect |
|---|---|---|---|
baseUrl | http or https URL | required | The API root, usually ending in /v1. A URL ending in /chat/completions or /completions is rejected; one missing /v1 passes validation, then fails at request time. |
model | string | required | Whatever model your server exposes. |
maxOutputTokens | positive integer | required | Caps one response's output tokens. |
apiKeyEnvVar | variable name | none | The variable holding this server's key. |
requestTimeoutMs | positive integer | 120000 | Bounds each request attempt. |
localeMap | map of locale to code | none | Overrides the code sent per locale. |
For a hosted API such as Mistral:
provider: {
id: "openai-compatible",
options: {
baseUrl: "https://api.mistral.ai/v1",
model: "mistral-large-latest", // example: check Mistral's current model list
maxOutputTokens: 4096,
apiKeyEnvVar: "MISTRAL_API_KEY",
},
}The response uses the same strict schema as openai; the first balanced JSON object is taken from any surrounding prose or code fence and validated. verbatra init --provider openai-compatible --base-url <url> --model <name> writes this block (init), and --api-key-env-var <NAME> adds the variable's name.
Keys are optional
The key resolves in order:
apiKeyEnvVar: if set, its variable must hold a key, or the run fails withMISSING_.API_ KEY OPENAI_: used when set and non-empty.COMPATIBLE_ API_ KEY "local": a fixed, non-secret placeholder, which is what makes a keyless server work.
apiKeyEnvVar cannot name a hosted provider's variable, and the client never reads OPENAI_, so a hosted key never reaches a custom baseUrl.
A real key over plaintext http travels in cleartext
With a key and a non-loopback http:// URL, the key crosses your network unencrypted, without a warning, so use https:// for a server that needs a key.
Choosing a local model
Prefer an instruction-tuned, non-reasoning, multilingual model as large as your hardware runs well, such as qwen2.5-14b-instruct. Reasoning models and heavily quantized builds tend to wrap or truncate the answer, which fails as INVALID_ or OUTPUT_.
LibreTranslate
A self-hosted LibreTranslate server (Argos Translate, no language model). verbatra only calls its HTTP API, so the server's AGPL-3.0 license does not extend to your project.
provider: {
id: "libretranslate",
options: {
baseUrl: "http://localhost:5000",
},
}| Option | Type | Default | Effect |
|---|---|---|---|
baseUrl | http or https URL | required | The API root, without /translate. |
requestTimeoutMs | positive integer | 120000 | Bounds the whole call (no retries). |
localeMap | map of locale to code | none | Overrides the code sent per locale. |
Its languages are the models installed on your server:
docker run -p 5000:5000 libretranslate/libretranslate --load-only en,de,fr- Key.
LIBRETRANSLATE_is sent asAPI_ KEY api_only when set. A server started withkey --api-keysfails the run withMISSING_when it is unset, and withAPI_ KEY AUTH_when it rejects it.FAILED - Placeholders are masked as for DeepL, as bare
{0}markers; markup is kept, so only braces beside a placeholder count. - Markup. A value with HTML-style tags is sent in
htmlformat: text inside tags is translated, the tags stay (phrasing across a tag may suffer), and changed tags are refused. - Language check. Every target warns
LOCALE_on every run;UNVERIFIED_ BY_ PROVIDER verbatra doctor --locales --livechecks them against the server.
verbatra init --provider libretranslate --base-url <url> writes this block.
none
Disables machine translation by policy, for a rule that forbids sending strings to any service.
provider: { id: "none" }options may be omitted or {}. No provider is constructed, no key is read and no request is made; what each command does instead is on Run without machine translation.
Tone and glossary across providers
A request carries only its target locale's glossary terms, and every output is checked against them.
| Provider | tone | glossary |
|---|---|---|
| Anthropic, OpenAI, Gemini, OpenCode, openai-compatible | sent and honored | translations, forbidden renderings, do-not-translate terms and notes sent as binding data |
| DeepL | mapped to formality | GLOSSARY_; only glossaryId applies |
| Google Cloud Translation, LibreTranslate | FORMALITY_ for formal or informal | GLOSSARY_ |
Behavior every provider shares
Batching
A locale's work goes out in sequential sub-batches of at most maxBatchSize entries. A failed sub-batch is withheld and retried next run while the rest proceed. DeepL and Google Cloud Translation also split a sub-batch to fit their own request caps.
Retries
Rate limits and server errors are retried with backoff, by the client for OpenAI, OpenCode, openai-compatible, Anthropic and DeepL and by verbatra for Gemini. Google Cloud Translation and LibreTranslate do not retry. Gemini honours a stated wait up to 5 seconds and fails a longer one (an exhausted daily quota) at once with RATE_ naming the wait.
Request timeout
requestTimeoutMs (default 120000 ms) keeps a server that never answers from stalling a run and holding a locale's write lock. Raise it for a slow local model or a large maxBatchSize.
provider: {
id: "openai-compatible",
options: {
baseUrl: "http://192.168.178.74:1234/v1",
model: "qwen2.5-14b-instruct",
maxOutputTokens: 1024,
requestTimeoutMs: 300000, // 5 minutes
},
}- Per attempt. Each retry gets the full timeout. The LLM providers also retry a timed-out attempt, and all but Gemini a connection error, so a hung server takes up to three timeouts plus backoff.
- The error names the cause.
TIMEOUTmeans one attempt got no answer in time; repeated refused connections fail asPROVIDER_.ERROR - DeepL cannot abort a request in flight, so an interrupted run leaves it to settle at DeepL.
Locale codes sent to the provider
Files, the lock and the memory use your config's codes. Only the request is translated to what the provider expects:
| Provider | Source locale | Target locale |
|---|---|---|
| DeepL | The language alone: en-US as EN, zh-Hant-TW as ZH. | zh-Hant, zh-TW, zh-HK, zh-MO as ZH-HANT; zh-Hans, zh-CN, zh-SG as ZH-HANS. DeepL variants stay (EN-GB, EN-US, PT-BR, PT-PT, ES-419, FR-CA, FR-FR, DE-CH, DE-DE). Spanish with a Latin American or Caribbean region (MX, AR, CO, CL, PE, VE, EC, GT, CU, BO, DO, HN, PY, SV, NI, CR, PA, UY, PR, US, 419) as ES-419. Other regions and scripts are dropped: de-AT as DE. |
| Google Cloud Translation | zh-Hant (zh-HK, zh-MO) as zh-TW, zh-Hans (zh-SG) as zh-CN, nb as no; everything else as written. | Same as the source. |
| LibreTranslate | The language alone (de-AT as de), except pt-BR stays and Chinese goes as zh-Hans or zh-Hant (zh-TW as zh-Hant). | Same as the source. |
| Anthropic, OpenAI, Gemini, OpenCode, openai-compatible | As written. | Same as the source. |
An LLM also gets the language, script and region names. DeepL has no generic English or Portuguese target, so a target ending up as bare en or pt (such as en-AU) is refused before the run: map it to EN-GB, EN-US, PT-BR or PT-PT.
localeMap overrides the code per locale:
provider: {
id: "deepl",
options: {
localeMap: {
"en-AU": "EN-GB",
"fr-BE": "FR-FR",
},
},
}- Keys must be
sourceLocaleor a target locale as configured, or the config fails withCONFIG_. The map goes underINVALID options, for every provider exceptnone. - Values are non-empty strings of at most 64 characters, sent unchanged: as the code to a machine-translation provider, in place of the code to an LLM (
"pt-BR (Brazilian Portuguese)"works), whose language names still come from the key. A bareENorPTfor DeepL fails withINVALID_; a value the provider rejects fails its sub-batch.REQUEST - Effect on memory. Only new and changed strings go out with a new code. A non-empty map is part of the memory fingerprint, so changing it starts a separate bucket.
Checking a provider's language support
Every configured locale is judged before anything is spent:
| Provider | Judged against | Refused before the run | Warned about |
|---|---|---|---|
| DeepL | Its shipped language table, with glossary and formality support per language | An unlisted source or target language | A glossary for a pair outside the glossary languages, a tone without formality, a partly listed code |
| Google Cloud Translation | Its shipped language table | An unlisted language | Any glossary, a formal or informal tone, a regional code listed only as a language |
| LibreTranslate | Nothing before a run; the server's /languages with doctor --live | Never | Every target locale on every run, any glossary, a formal or informal tone |
| Anthropic, OpenAI, Gemini, OpenCode, openai-compatible | Nothing: an LLM accepts any language | Never | A language outside the well-tested list |
| none | Not applicable | Never | Never |
An unsupported locale is refused with LOCALE_ (exit 2) before any provider is built; a warning never refuses and appears as a notice. verbatra doctor reports each verdict, and its --live flag checks the provider's current list, but translate always uses the shipped table. For a language added since, map the locale in localeMap: an explicit code is sent with a warning, not refused.
Structured errors
A provider failure surfaces as a structured, secret-free error affecting only its own sub-batch. A PROVIDER_ names only the host and port of an openai-compatible or LibreTranslate baseUrl, and only translation.googleapis.com for Google, never the query carrying its key.
Output-token limits
The LLM providers cap each response's tokens; a model that stops at the cap raises OUTPUT_. verbatra re-splits a truncated sub-batch into halves, down toward one entry, and withholds only an entry that still overflows. To fix it for good, lower maxBatchSize or raise maxTokens (Anthropic) or maxOutputTokens. A reasoning model spends hidden reasoning tokens from the same budget, so give it generous headroom.
Review flags
A suspicious translation from any provider is flagged for review, not rejected. See review reasons and Review in Studio.
Edit on GitHub