verbatra doctor
Validate the project setup without calling a provider or reading an API key.
Available from 0.9.0
Answer one question before you run anything else: is this project set up correctly? doctor validates the config, the format adapter, the provider, the API key variable, and the source locale file, then reports every problem it found at once. It spends nothing: no provider is constructed, no network request is made, no file is written, and no API key value is ever read.
Reach for it on a fresh checkout, right after verbatra init, or whenever another command failed and you want the whole list rather than the first error. verbatra check is the cheapest validation once a project already works, but it reads the locale files and stops at the first whole-run error, so it cannot tell you what is wrong with a project that has no source file yet.
Synopsis
verbatra doctor [flags]Flags
| Flag | Argument | Default | Effect |
|---|---|---|---|
--cwd | <path> | current directory | resolve config and locale files from this directory |
--config | <path> | search for one | load this config file instead of searching for one |
--literals | none | off | scan the source roots of the extract block for hardcoded user-facing strings instead of checking the setup (see Untranslated literals) |
--json | none | off | print one JSON envelope on stdout carrying the report under result; the human-readable error line still goes to stderr |
What it checks
| Check | Passes when |
|---|---|
| Configuration | a config file was found and passes validation |
| Format adapter | the configured format resolves to a file adapter |
| Provider | the configured provider.id resolves to a provider factory |
| API key environment variable | the variable that provider reads its key from is set |
| Source locale file | the source locale file exists at its resolved path, is a regular file, and parses under the configured format |
Every check runs even when an earlier one failed, so one run reports every independent problem. The only exception is the config itself: when it cannot be loaded, the four checks that need it are skipped instead of reaching a verdict. Such a check prints as [skip] in the human report and carries "status": "skipped" in the --json envelope.
Four details are worth knowing:
- The API key is checked by name only.
doctorasks whether the variable is set, never what it holds. The value is not read, not printed, and never sent anywhere. See Providers for which variable each provider uses. - The
openai-compatibleprovider is the exception. It falls back to a placeholder key, so a missing variable is fine. It only fails when your config names its own variable throughprovider.options.apiKeyEnvVarand that variable is unset. - A missing target locale file is not a problem:
verbatra translatecreates it. Target files are not checked at all. - The source locale file is read and parsed, not merely probed for. A directory standing in for it, an empty file, and malformed content all fail this check with the same message
verbatra checkwould give, because those are exactly the cases that make every other command fail. When the configuredformatresolves to no adapter there is nothing to parse with, so the check falls back to existence alone and says so.
Like verbatra translate, doctor loads .env.local and then .env from the working directory before it looks at the environment, so a key kept in a dotenv file counts as set. With --literals it loads neither file.
Untranslated literals
Available from 0.11.0
verbatra doctor --literals looks for the one i18n bug no other check can see: a user-facing string that never made it into a catalog. It walks the source roots of your extract block and reports every hardcoded string that reads as user-facing text but does not go through a translation call. It reads your source and never writes it, constructs no provider, reads no API key variable, and loads no .env file, so it passes in a job that holds no secrets. In this mode only two checks run: the configuration and the literal scan.
Every finding names its file, line, and column. Its text has whitespace collapsed and, in JSX, character references such as & decoded, and it is cut to at most 80 characters:
verbatra doctor
[ok ] Configuration: Loaded /app/verbatra.config.ts.
[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."
1 problem found (run verbatra doctor again after fixing them)What counts as a finding:
- JSX text, such as
<p>Welcome back</p>, in.tsx,.jsx, and.jsfiles. - The value of a user-facing JSX attribute:
alt,title,placeholder,label,aria-label, and the other text-carryingaria-*attributes. - A string anywhere else that reads as prose, meaning two or more words. A single word outside JSX, such as an event name or an option value, is not reported.
What is never reported: a string passed to a recognised translation call (t(...), $t(...), i18n.t(...), or t renamed out of useTranslation, as in const { t: translate } = useTranslation(), from that line to the end of the enclosing block) or rendered inside <Trans> or <Translation>, an object key, an import specifier, a class name or CSS value, a test id or data-* attribute, an attribute that holds ids or a keyword rather than text (such as aria-describedby, aria-labelledby, aria-controls, rel, sandbox, allow, autoComplete, referrerPolicy, or crossOrigin), a URL, a literal in a type position (including one after as or satisfies; the value before them, as in "Welcome" as const, is still checked), a logging message, the message of a constructed error (new ValidationError(...)) or of a built-in error called without new (throw Error(...)), a comparison operand, a template literal carrying an expression, a string with no letters (punctuation, whitespace, a number, an emoji, a symbol), and anything in test, story, declaration, or config files.
Calls that take a query, a format, or a name rather than copy are skipped too: a string passed directly as an argument of describe (zod), query, execute, prepare, format, or parse, a string in the array passed directly to z.enum, every string argument of setItem, getItem, and removeItem, and the first argument of on, off, once, emit, addEventListener, and a member get or set call such as cookies().get(...). Only the direct arguments are skipped: copy nested deeper in such a call, such as in a callback (query(() => ({ message: "..." }))), an object, or JSX, is still reported. A function that only happens to end in Error, such as setError or showError, is not skipped, so the message you pass it is still reported.
To hold one literal back, put // verbatra-ignore-next-line above it (in JSX, {/* verbatra-ignore-next-line */}), or // verbatra-ignore-line at the end of its own line. A next-line directive targets the next line that holds code (blank lines and comment-only lines are skipped) and covers every literal that starts on that line. When a JSX opening tag starts there, it also covers all of that tag's attributes, even when the tag spans several lines. Text and nested elements on later lines are not covered, so give each of them its own directive. For a string that is fine everywhere, such as a brand name, list it under extract.literals.ignore in the config file. An entry matches the text as it is reported, with character references decoded, so write Tom & Jerry, not Tom & Jerry. A held-back literal is still listed as suppressed, with the reason, so nothing disappears silently.
The check fails when it finds a literal, and also when a file could not be scanned. A file the scanner cannot read to the end (an unterminated comment or template literal, or a JSX element that never closes or is closed by the tag of an element around it), cannot open at all, or considers too large is listed as not scanned and the rest of the scan carries on, but the run is never reported as clean. A generic function type like type Fn = <T>(x: T) => T or a type parameter list like <const T extends object = {}>(x: T) => x in a .tsx file is read as ordinary code, so it never fails the file, and an element with type arguments of any length, like <Table<Row>>, is still read as JSX. A project with no extract block fails the check with a message naming the block. With --json, the scan travels in the envelope under result.literals, split into findings, suppressed, and diagnostics.
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 in your source, with no key set
verbatra doctor --literalsA run with two problems looks like this:
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.
[fail] Source locale file: The source locale file was not found at /app/locales/en.json.
2 problems found (run verbatra doctor again after fixing them)Exit codes
| Code | Meaning |
|---|---|
0 | every check passed |
1 | at least one check failed (the full report is still printed); 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 |
Exit 1 means "it ran, and it found problems". Exit 2 is reserved for doctor being unable to run at all, which is why a missing config file found by search is a failed check and exit 1, while a --config path pointing at nothing is exit 2.
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.