Reference / CLI
verbatra doctor
Check whether a project is set up correctly and get every problem in one report, without calling a provider or reading an API key.
Check whether a project is set up correctly and get every problem in one report. doctor constructs no provider, writes no file, and reads an API key value only for --live, which sends it in one request for the provider's language list (LibreTranslate gets none). Run it after verbatra init or when another command fails.
Synopsis
verbatra doctor [flags]Examples
# report every setup problem at once
verbatra doctor
# validate a project in another directory, with an explicit config
verbatra doctor --cwd apps/web --config verbatra.config.ts
# machine-readable report for a CI preflight step
verbatra doctor --json
# list hardcoded user-facing strings, with no key set
verbatra doctor --literals
# what the provider supports for each target locale
verbatra doctor --locales
# the same, against the provider's current language list
verbatra doctor --live
# a data-flow manifest to attach to a DPA or NDA review
verbatra doctor --data-flow --jsonFlags
| Flag | Argument | Default | Effect |
|---|---|---|---|
--literals | none | off | scan the extract source roots for hardcoded user-facing strings instead of checking the setup (see Untranslated literals) |
--locales | none | off | after the checks, list per target locale the code sent, provider support, glossary and formality (see Locale support) |
--live | none | off | fetch the provider's current language list first, using no translation quota; implies --locales |
--data-flow | none | off | describe what is sent to which host and what is written locally instead of checking the setup (see Data flow) |
Also takes the global flags --cwd, --config and --json. --locales and --live cannot be combined with --literals (INVALID_, exit 2), and --data-flow with none of the three.
Behavior
What it checks
A setup run reports these checks, in this order. Plural rules, Plural completeness, Locale codes, Locale state and Length limits are informational and never fail: each prints [warn] when it names something and [ok ] otherwise.
| Check | Fails when |
|---|---|
| Configuration | no config file is found, or it fails validation |
| Format adapter | the configured format resolves to no file adapter |
| Provider | the configured provider.id resolves to no provider factory |
| API key environment variable | the variable that provider reads its key from is not set |
| Network policy | the effective network policy refuses the provider's host or a proxy it would use |
| Source locale file | the source locale file is missing at its resolved path, is not a regular file, or does not parse under the configured format |
| Plural rules | never; names the ICU and CLDR versions in use, and warns when a target locale has no ICU plural rules |
| Plural completeness | never; warns and names each target plural that lacks a CLDR category its language uses, judging a plural the target has no forms for by what a run would write there (a blank source form counts as absent, and plural generation only where it actually runs), deliberately stricter than the i18next-only notice of translate (at most ten; verbatra check lists the gaps in plurals a target already holds); skipped for a format that does not store plural forms by CLDR category |
| Locale codes | never; warns and names every valid but non-canonical code, such as en-us or iw, with the canonical form (see Locale codes) |
| Locale state | never; warns and names every locale the lock file, cache or provenance file holds state for that the config does not list, and whether translate carries it over |
| Length limits | never; warns when verbatra.limits.json cannot be used (a run ignores it and a limit cannot be set until it is fixed), names a limit for a key the source no longer has or one the config's maxLength overrides, and says so when the source could not be read to check the keys; passes when the file is absent (length limits) new |
| Locale support | the provider does not support the source or a target locale; warns when a locale carries a warning (see Locale support) |
Every check runs even when an earlier one failed. The one exception is the config: when it cannot be loaded, the checks that need it print as [skip] ("status": "skipped" in JSON), as does Plural completeness for a format without CLDR plural forms. A check that found something worth attention without failing prints [warn] ("status": "warn" in JSON) and never changes the exit code. A failed check also carries a fix, one imperative next step printed as a fix: line and sent as "fix" in JSON, so an agent can act without parsing detail.
A run with two problems:
verbatra doctor
[ok ] Configuration: Loaded /app/verbatra.config.ts.
[ok ] Format adapter: Format "i18next-json" resolves to an adapter.
[ok ] Provider: Provider "anthropic" resolves to a factory.
[fail] API key environment variable: The ANTHROPIC_API_KEY environment variable is not set.
fix: Set ANTHROPIC_API_KEY in the environment, or, with the CLI, in a .env file in the project directory.
[ok ] Network policy: any host (config: unset; VERBATRA_NETWORK_POLICY: unset). Provider "anthropic" connects to api.anthropic.com: permitted.
[fail] Source locale file: The source locale file was not found at /app/locales/en.json.
fix: Create the source locale file, or fix `files.pattern` and `sourceLocale` in the config so they point at it.
...
[warn] Locale codes: "iw" is canonically "he". Consider the canonical form in your config; file names follow the configured code and are never renamed for you.
[ok ] Locale support: Provider "anthropic" is an LLM and accepts any locale (well-tested list of 2026-09-28).
2 problems found (run verbatra doctor again after fixing them)Keys and files
- The API key is checked by name only:
doctorasks whether the variable is set, never what it holds. See Providers for each provider's variable. openai-compatiblefalls back to a placeholder key, so it fails only when the config names its ownprovider.options.apiKeyEnvVarand that variable is unset.libretranslatenever fails the check, since its key is optional.- With
provider: { id: "none" }, the Provider, API key and Network policy checks pass and Locale support is skipped. - A missing target file is fine:
translatecreates it. The source file is parsed, so a directory, an empty file or malformed content fails it. doctorloads the environment files first, so a key in a dotenv file counts as set; with--literalsit loads neither.
Locale support
The Locale support check asks whether the provider can translate each configured locale. DeepL and Google Cloud Translation are judged against a dated language table verbatra ships; an LLM provider accepts any locale. It fails on exactly the locale verbatra translate would refuse with LOCALE_, and otherwise prints [warn] when a locale carries a warning or --live could not fetch the list. Language support explains the verdicts.
--locales prints the verdict per locale after the checks:
locale support (deepl, language table of 2026-09-28, static)
en sent as EN source, supported
de sent as DE supported glossary: yes formality: yes
sv sent as SV supported glossary: yes formality: no
warning [FORMALITY_UNSUPPORTED_BY_PROVIDER] Provider "deepl" has no formality control for "sv", so the configured formal tone is not applied and the provider's default register is used.
chr sent as CHR unsupported glossary: no formality: noEach line names the locale, the code the provider receives (after localeMap, marked (localeMap)), the verdict, and whether a glossary and a formal or informal tone apply. A warning is advisory.
--live judges against the provider's current list instead (DeepL /v3/languages, Cloud Translation languages, LibreTranslate /languages). For LibreTranslate, which ships no table, it is the only way to get a verdict other than unverified, so Locale support shows [warn] until --live runs. The request is sent only when the key variable is set (LibreTranslate needs none) and the network policy permits the host; otherwise, or on failure, the shipped table is used and a live language list line says why.
Untranslated literals
--literals finds user-facing strings that never reached a catalog. It walks the source roots of your extract block, reads and never writes your source, constructs no provider, reads no key and loads no .env file. Only two checks run: Configuration and Untranslated literals. Each finding names file, line and column, with whitespace collapsed, JSX character references decoded, and at most 80 characters:
[fail] Untranslated literals: Scanned 42 source files: 2 untranslated literals found (1 suppressed).
src/components/Header.tsx:12:9 "Welcome back"
src/pages/settings.tsx:40:22 "Save changes"
suppressed (directive) src/pages/legal.tsx:8:5 "Acme Inc."Reported: JSX text in .tsx, .jsx and .js files; user-facing JSX attributes (alt, title, placeholder, label, aria-label and the other text-carrying aria-*); and any other string of two or more words.
Never reported:
- a string passed to
t(...),$t(...),i18n.t(...)or a renamedtfromuseTranslation, or rendered inside<Trans>or<Translation>; - object keys, import specifiers, class names, CSS values, test ids,
data-*and id or keyword attributes (aria-describedby,aria-labelledby,aria-controls,rel,sandbox,allow,autoComplete,referrerPolicy,crossOrigin), URLs, and strings without letters; - literals in a type position (the value before
asorsatisfiesis still checked), log messages, error messages (new ValidationError(...),throw Error(...)), comparison operands, and template literals with an expression; - direct arguments of
describe(zod),query,execute,prepare,format,parse,z.enum,setItem,getItemandremoveItem, and the first argument ofon,off,once,emit,addEventListenerand a membergetorset; copy nested deeper, and messages passed to functions likesetError, are still reported; - anything in test, story, declaration or config files.
To hold one literal back, put // verbatra-ignore-next-line above it ({/* verbatra-ignore-next-line */} in JSX) or // verbatra-ignore-line at the end of its line. A next-line directive targets the next line holding code and, on a JSX opening tag, all of that tag's attributes. For a string that is fine everywhere, list it under extract.literals.ignore with references decoded (Tom & Jerry). Suppressed literals are still listed with their reason.
The check fails on a finding and on a file it could not open, read to the end, or considered too large (the scan carries on). A project without an extract block fails it.
Data flow
--data-flow describes where this project's data goes instead of checking the setup, as evidence for a DPA or NDA review; Data handling explains each category. It reads no API key and makes no network request. Only two checks run: Configuration and Data flow. Data flow never fails: it prints [warn] when the network policy refuses a host or holds an invalid value, or when the source file cannot be read for the counts.
[ok ] Data flow: Provider "deepl" sends source-text, placeholder-markers, language-codes to api.deepl.com (permitted), api-free.deepl.com (permitted).
data flow (manifest version 1)
provider deepl (machine-translation)
network any host (no policy set)
destination api.deepl.com default paid-key before-construction permitted
destination api-free.deepl.com default free-key before-construction permitted
sent source-text, placeholder-markers, language-codes
api key required
placeholders masked
sensitive off
counts 3 source keys, 0 with a description or meaning, 1 with placeholders it cannot mask (withheld)
locale de sent as DE glossary terms: 0
local lock verbatra.lock.json (no source text, translations or personal data)
local provenance verbatra.provenance.json (reviewer names)
local cache verbatra.cache.json (source text, translations, gitignored by init)
local local-state .verbatra-local (source text, host name and process ID of a run holding a write lock, gitignored by init)
request language-list verbatra doctor --live sends the API key
request dns-lookup each connection to a destination host
agent mcp verbatra mcp redactable
agent studio-agent-tools verbatra studio --expose-agent-toolsThe manifest names the provider; each destination host, where it comes from (built in, provider.options.baseUrl or a *_ variable) and the policy verdict (permitted, deferred, refused or invalid-policy); the kinds of data sent, with the number of source keys, of keys carrying a description or meaning, and of keys whose placeholders the provider cannot mask (an estimate over the source keys: a run sends only missing or changed keys and may withhold more under sensitiveData: redact); the code and glossary term count per target locale; the local files with their project-relative paths, and whether they hold source text, translations or personal data; and the other requests and agent surfaces. DeepL is listed with both hosts, because the key that picks one is never read. A proxy in effect is named by its variable and host, never its credentials, and the fields list only what this config can send. With provider: { id: "none" } it states that nothing is sent. It names hosts, data categories and conditions, never a key value or file contents.
With --json the manifest is under result.dataFlow. Its version field changes only when a field is renamed or removed; new fields can appear without a bump, so ignore the ones you do not know. The SDK's dataFlowManifestSchema validates it.
JSON output
Schema: doctor-envelope.json validates the whole envelope; Schemas lists the others.
With --json, the envelope carries the report under result. ok is true exactly when the command exits 0:
interface DoctorResult {
ok: boolean; // true exactly when the command exits 0
checks: DoctorCheck[]; // one per check, always in the same order
literals?: LiteralScan; // only with --literals: findings, suppressed, diagnostics
dataFlow?: DataFlowManifest; // only with --data-flow: what goes where, version 1
locales?: LocaleCapabilityReport; // per-locale provider support, with or without --locales
}
interface DoctorCheck {
id:
| "config"
| "format-adapter"
| "provider"
| "api-key"
| "network-policy"
| "source-file"
| "plural-rules"
| "plural-completeness"
| "locale-codes"
| "locale-state"
| "limits" // 0.13.0
| "locales"
| "untranslated-literals"
| "data-flow";
title: string; // stable, human-readable name
status: "pass" | "warn" | "fail" | "skipped"; // only "fail" makes ok false
detail: string; // why it reached that verdict; never an API key value
fix?: string; // the next step, only on a failed check
}
interface LocaleCapabilityReport {
provider: string;
coverage: "listed" | "open"; // "open" for an LLM provider
tableVersion: string; // the table's date (YYYY-MM-DD)
tableOrigin: "static" | "live"; // "live" only when --live fetched the list
live?: { status: "refreshed" | "skipped" | "failed"; detail: string }; // only with --live
source: {
locale: string;
providerCode: string;
mapped: boolean;
support: "supported" | "unverified" | "unsupported";
warnings: { code: string; message: string }[];
};
locales: {
locale: string;
providerCode: string; // the code the provider receives
mapped: boolean; // true when localeMap set providerCode
support: "supported" | "unverified" | "unsupported";
glossary: boolean;
formality: boolean;
warnings: { code: string; message: string }[];
}[];
}result.locales is absent when the config could not be loaded, for an unknown provider, and for provider: { id: "none" }.
Terminal feedback
While it runs, doctor shows one stderr line, which --quiet drops: checking the setup, checking the setup and fetching the provider's language list under --live, describing the data flow under --data-flow, or scanning the source for literals with a file count under --literals. The report stays on stdout. See Terminal output.
Exit codes
| Code | Meaning |
|---|---|
0 | no check failed (a warn still exits 0) |
1 | at least one check failed, including an unsupported locale; with --literals, a literal was found or a file could not be scanned |
2 | could not run: a usage error, or an explicit --config path that does not exist |
A config the search does not find, or one that fails validation, is a failed check (exit 1), because reporting it is the command's job. See the exit-code contract.
Related
verbatra initscaffolds the config and.env.examplethatdoctorvalidates.verbatra checkis the drift gate to run once the setup is sound.- The config file documents every key
doctorvalidates. - Providers lists the API key variable each provider reads.
- Language support lists the language tables the Locale support check uses.