---
title: "Languages and locales"
description: "Which languages verbatra can translate, how locale codes reach each provider, and how plurals, scripts, regional variants and right-to-left text are handled."
type: "concept"
---
# Languages and locales

Which languages verbatra can translate, how locale codes reach each provider, and how plurals, scripts, regional variants and right-to-left text are handled.

<AvailableFrom version="0.12.0" />

verbatra has no language list of its own: it accepts any valid BCP 47 locale code, so you can use
the languages your provider supports, and for a human-only project, all of them. verbatra spells
each code the way the provider expects, holds every translation to its language's plural rules, and
warns you before a run when a provider cannot serve a locale.

## Locale codes [#locale-codes]

Every locale in your config must be a valid BCP 47 tag, such as `fr`, `pt-BR`, `sr-Latn` or
`es-419`; for files named `pt_BR.json`, write `pt-BR` and set `files.localeStyle` to `posix`. The
full rules are under [Locale codes](https://verbatra.kreitz-webdev.de/docs/config-file.md#locale-codes).

The code from your config is the one verbatra stores everywhere: file paths,
[the lock file](https://verbatra.kreitz-webdev.de/docs/the-lock-file.md) and [the cache](https://verbatra.kreitz-webdev.de/docs/the-cache.md). A provider-specific spelling
exists only inside a request. Respelling a code you already translate, such as `pt_BR` to `pt-BR`,
keeps its history; see [A respelled locale code](https://verbatra.kreitz-webdev.de/docs/the-lock-file.md#a-respelled-locale-code).

## Coverage by provider [#coverage-by-provider]

How well a language translates is the provider's matter; each row links to the vendor's own list.

| Provider | Languages | Code sent | Glossary | Tone |
| --- | --- | --- | --- | --- |
| `anthropic` | No fixed list. [Claude multilingual support](https://platform.claude.com/docs/en/build-with-claude/multilingual-support) | As written, plus English language, script and region names | Locale terms in the prompt | In the prompt |
| `openai` | No fixed list. [Using the OpenAI API in different languages](https://help.openai.com/en/articles/6742369-how-do-i-use-the-openai-api-in-different-languages) | As written, plus names | Locale terms in the prompt | In the prompt |
| `gemini` | [Gemini language support](https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/google-models#languages-gemini) | As written, plus names | Locale terms in the prompt | In the prompt |
| `opencode` | Whatever the Zen model you choose covers | As written, plus names | Locale terms in the prompt | In the prompt |
| `openai-compatible` | Whatever the model you run covers | As written, plus names | Locale terms in the prompt | In the prompt |
| `deepl` | [DeepL supported languages](https://developers.deepl.com/docs/getting-started/supported-languages) | Normalized to DeepL's codes | A pre-created DeepL glossary by `glossaryId` only, for 32 languages | DeepL formality for some languages, not on a free-tier key |
| `google-translate` | [Cloud Translation language support](https://docs.cloud.google.com/translate/docs/languages) (the NMT list applies to the Basic, v2 tier verbatra uses) | Normalized for Chinese and Norwegian, otherwise as written | None | None |
| `libretranslate` | The models installed on your server; [the upstream list](https://docs.libretranslate.com/) shows what is available to install | The language alone, except `pt-BR`, `zh-Hans` and `zh-Hant` | None | None |
| `none` | Not applicable: no provider is called | Nothing is sent | Not applicable | Not applicable |

What that means in practice:

- **LLM providers** have no closed list. They attempt any language you name, and quality falls off
  where training data is thin, so a lower-resource target deserves a human review.
  [Review flags](https://verbatra.kreitz-webdev.de/docs/translation-safety.md#review-flags) point at the most suspicious results.
- **DeepL and Google Cloud Translation** have a closed list, checked before a run.
- **LibreTranslate** serves whatever models your server installed.
- A `tone` or glossary a provider cannot honor is reported as a notice; see
  [Tone and glossary across providers](https://verbatra.kreitz-webdev.de/docs/providers.md#tone-and-glossary-across-providers).

### How each provider receives the code [#how-each-provider-receives-the-code]

DeepL and Google Cloud Translation get their own spelling of some codes, and DeepL refuses a bare
`en` or `pt` target before any request. LLM providers get the code plus the
[English names](https://verbatra.kreitz-webdev.de/docs/translation-safety.md#language-script-and-region-names) of the language, script
and region. `localeMap` in the provider's `options` overrides the code per locale; the full mapping
is under [Locale codes sent to the provider](https://verbatra.kreitz-webdev.de/docs/providers.md#locale-codes-sent-to-the-provider).

## Checking support before a run [#checking-support-before-a-run]

Before anything is spent, verbatra judges every configured locale against the provider, and
[`verbatra doctor --locales`](https://verbatra.kreitz-webdev.de/docs/cli/doctor.md#locale-support) prints the verdicts:

- **supported**: the code verbatra sends is on the provider's table, or the provider is an LLM.
- **unverified**: only its base language is listed, the code comes from an explicit `localeMap`
  entry, or the provider is a LibreTranslate server whose models are known only from its live list.
  It is translated, with the warning `LOCALE_UNVERIFIED_BY_PROVIDER`.
- **unsupported**: neither the code nor its base language is listed. `translate`, `watch` and a
  single-key retranslation refuse the whole run with
  [`LOCALE_UNSUPPORTED_BY_PROVIDER`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_unsupported_by_provider).

Leave the locale out with `--locales`, or, if the provider added the language later, map it in
`localeMap`: an explicit entry is trusted. The shipped tables are copies of the vendors' lists
(DeepL with 125 codes and Google with 198, both of 2026-09-28); `verbatra doctor --live` checks the
current list.

Nothing is refused for an LLM provider. A target outside the languages LLMs are known to translate
well gets the warning `LOCALE_NOT_WELL_TESTED`, a nudge to review its output. The list matches the
base language, so `pt-BR` counts as Portuguese: Arabic (`ar`),
Chinese (`zh`), Dutch (`nl`), English (`en`), French (`fr`), German (`de`), Hindi (`hi`),
Indonesian (`id`), Italian (`it`), Japanese (`ja`), Korean (`ko`), Polish (`pl`), Portuguese (`pt`),
Russian (`ru`), Spanish (`es`), Swedish (`sv`), Turkish (`tr`), Ukrainian (`uk`) and Vietnamese
(`vi`).

## Scripts and regional variants [#scripts-and-regional-variants]

A script or region subtag changes what the provider is asked for, not how verbatra stores anything:

| Your locale | DeepL | Google Cloud Translation | LibreTranslate | LLM providers |
| --- | --- | --- | --- | --- |
| `pt-BR` / `pt-PT` | `PT-BR` / `PT-PT` | `pt-BR` / `pt-PT` | `pt-BR` / `pt` | Portuguese (Brazil) / Portuguese (Portugal) |
| `es-419`, `es-MX` | `ES-419` | `es-419`, `es-MX` | `es` | Spanish (Latin America), Spanish (Mexico) |
| `zh-Hans` / `zh-Hant` | `ZH-HANS` / `ZH-HANT` | `zh-CN` / `zh-TW` | `zh-Hans` / `zh-Hant` | Chinese (Simplified) / Chinese (Traditional) |
| `sr-Latn` / `sr-Cyrl` | `SR` for both | `sr-Latn` / `sr-Cyrl` | `sr` for both | Serbian (Latin) / Serbian (Cyrillic) |

DeepL and LibreTranslate keep a script only for Chinese, so `sr-Latn` and `sr-Cyrl` get whatever
script the engine writes for Serbian; an LLM provider, told the script by name, is the dependable
choice. The `LENGTH_RATIO_OUTLIER` flag weights characters by script, so compact Chinese, Japanese
or Korean text is not flagged as short, and a glossary term in a script without spaces between
words is matched by containment.

## Plurals [#plurals]

verbatra reads the plural categories a language needs, ordinals included, from the CLDR rules in
Node.js's ICU data: English has two cardinal categories, Russian and Polish four, Arabic and Welsh
six, Japanese only `other`. For a locale Node.js has no rules for, the run notice assumes `one` and
`other`, while the integrity gate, `check` and `doctor` require only `other`; the `plural-rules`
check of [`verbatra doctor`](https://verbatra.kreitz-webdev.de/docs/cli/doctor.md) names such a locale.

How a plural is kept correct depends on the format:

- **i18next** keys each form with a CLDR suffix (`items_one`, `items_few`). When the source lacks a
  category the target needs, the run reports `PLURAL_CATEGORIES_INCOMPLETE`; with
  `generatePlurals: true` and an LLM provider, verbatra writes the missing forms. See
  [Add a language](https://verbatra.kreitz-webdev.de/docs/add-a-language.md#languages-with-more-plural-categories).
- **next-intl and ARB** keep a whole ICU plural in one value, and a value whose arms do not fit the
  target language is refused; see
  [Plural arms per target language](https://verbatra.kreitz-webdev.de/docs/translation-safety.md#plural-arms-per-target-language).
- **Apple, Android and gettext** round-trip exactly the forms each locale has, and a missing form has
  to be added by hand. gettext keeps its `Plural-Forms` header as written.

### Plural warnings [#plural-warnings]

[`verbatra check`](https://verbatra.kreitz-webdev.de/docs/cli/check.md#plural-categories) and `verbatra doctor` warn about every plural
that lacks a category its language uses, in each format listed above except gettext, whose forms
follow its own header; plurals in other formats are not checked. DeepL, Google Cloud Translation and
LibreTranslate translate existing forms but never generate new ones.

## Right-to-left languages [#right-to-left-languages]

Arabic, Hebrew, Persian, Urdu and other right-to-left languages need no special handling: a value
is read, sent, checked and written back as it is, with no direction marks added, removed or
reordered. What that means for you:

- **Set direction in your app, not in the strings**, with the `dir` attribute, CSS or your
  platform's layout direction. The [inline markup check](https://verbatra.kreitz-webdev.de/docs/translation-safety.md#inline-markup-parity)
  refuses a translation that adds a `dir` attribute the source does not have.
- **Direction marks are ordinary text** and may be dropped; check the translations that rely on
  one. Unclosed embeddings, overrides or isolates (when the source closes its own) or added
  overrides raise [`BIDI_CONTROLS_CHANGED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#bidi_controls_changed).
- **A changed placeholder order** only raises `INTEGRITY_REORDERED`, because another order is often
  correct in another language.
- **Studio shows values right to left** for every locale whose script runs that way, by the
  browser's direction or the locale's likely script (`ku-Arab` right to left, `ku` left to right). Only the display changes.
- **[`verbatra pseudo --mode bidi`](https://verbatra.kreitz-webdev.de/docs/cli/pseudo.md) renders your source right to left** in an
  `ar-XB` pseudolocale, exposing left-to-right layout assumptions before any translation exists.
  Mirrored images and icons are yours to check.

## Low-resource languages and local models [#low-resource-languages-and-local-models]

For a language your hosted provider covers poorly:

- **Run a local model through `openai-compatible`.** It has to be an instruction-tuned chat model
  that returns one JSON object; see [Choosing a local model](https://verbatra.kreitz-webdev.de/docs/providers.md#choosing-a-local-model).
- **Use a dedicated translation model outside verbatra** and bring the result back through the
  [workbook round-trip](https://verbatra.kreitz-webdev.de/docs/manual-translation.md), which runs the same integrity checks. None of
  these plugs into `openai-compatible`, since they are not chat models:
  [MADLAD-400](https://huggingface.co/google/madlad400-3b-mt) (Apache-2.0, over 400 languages),
  [TranslateGemma](https://huggingface.co/google/translategemma-4b-it) (55 languages, Gemma terms of
  use), and [NLLB-200](https://huggingface.co/facebook/nllb-200-distilled-600M) (about 200
  languages, but CC-BY-NC-4.0, so not for a commercial product). Check each model's license
  yourself.
- **Have a person translate.** The [human-only workflow](https://verbatra.kreitz-webdev.de/docs/human-only-workflow.md) works for any
  language.
