---
title: "verbatra doctor"
description: "Prüfe, ob ein Projekt richtig eingerichtet ist, und erhalte jedes Problem in einem Bericht, ohne einen Provider aufzurufen oder einen API-Key zu lesen."
type: "reference"
---
# verbatra doctor

Prüfe, ob ein Projekt richtig eingerichtet ist, und erhalte jedes Problem in einem Bericht, ohne einen Provider aufzurufen oder einen API-Key zu lesen.

<AvailableFrom version="0.9.0" />

Prüfe, ob ein Projekt korrekt eingerichtet ist, und bekomm jedes Problem in einem einzigen Bericht. `doctor` konstruiert keinen Provider, schreibt keine Datei und liest den Wert eines API-Keys nur für `--live`, das ihn in einer Anfrage nach der Sprachliste des Providers mitsendet (LibreTranslate bekommt keinen). Nimm es nach [`verbatra init`](https://verbatra.kreitz-webdev.de/de/docs/cli/init.md) oder wenn ein anderer Befehl scheitert.

## Synopsis [#synopsis]

```bash
verbatra doctor [flags]
```

## Beispiele [#beispiele]

```bash
# 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 --json
```

## Flags [#flags]

| Flag | Argument | Standard | Wirkung |
| --- | --- | --- | --- |
| `--literals` | keins | aus | statt der Einrichtung die Quell-Roots des [`extract`-Blocks](https://verbatra.kreitz-webdev.de/de/docs/config-file.md#extract) nach hartcodierten, für Nutzer sichtbaren Strings durchsuchen (siehe [Nicht übersetzte Literale](#nicht-übersetzte-literale)) |
| `--locales` | keins | aus | nach den Prüfungen je Ziel-Locale den gesendeten Code, die Unterstützung durch den Provider, Glossar und Formality auflisten (siehe [Locale-Unterstützung](#locale-unterstützung)) |
| `--live` | keins | aus | zuerst die aktuelle Sprachliste des Providers abrufen, ohne Übersetzungskontingent zu verbrauchen; schließt `--locales` ein |
| `--data-flow` | keins | aus | statt der Einrichtung beschreiben, was an welchen Host geht und was lokal geschrieben wird (siehe [Datenfluss](#datenfluss)) |

Nimmt außerdem die [globalen Flags](https://verbatra.kreitz-webdev.de/de/docs/cli.md#globale-flags) `--cwd`, `--config` und `--json`. `--locales` und `--live` lassen sich nicht mit `--literals` kombinieren (`INVALID_OPTION`, Exit `2`), `--data-flow` mit keinem der drei.

## Verhalten [#verhalten]

### Was geprüft wird [#was-geprüft-wird]

<AvailableFrom version="0.12.0" />

Ein Einrichtungslauf meldet diese Prüfungen in dieser Reihenfolge. Plural rules, Plural completeness, Locale codes, Locale state und Length limits sind rein informativ und schlagen nie fehl: Jede zeigt `[warn]`, wenn sie etwas nennt, und sonst `[ok  ]`.

| Prüfung | Schlägt fehl, wenn |
| --- | --- |
| Configuration | keine Konfigurationsdatei gefunden wird oder sie die Validierung nicht besteht |
| Format adapter | das konfigurierte `format` zu keinem Datei-Adapter auflöst |
| Provider | die konfigurierte `provider.id` zu keiner Provider-Factory auflöst |
| API key environment variable | die Variable, aus der dieser Provider seinen Key liest, nicht gesetzt ist |
| Network policy | die wirksame [Netzwerkrichtlinie](https://verbatra.kreitz-webdev.de/de/docs/network-policy.md) den Host des Providers oder einen Proxy abweist, den er nutzen würde |
| Source locale file | die Quell-Locale-Datei unter ihrem aufgelösten Pfad fehlt, keine reguläre Datei ist oder sich im konfigurierten Format nicht parsen lässt |
| Plural rules | nie; nennt die genutzten ICU- und CLDR-Versionen und warnt, wenn eine Ziel-Locale keine ICU-Pluralregeln hat |
| Plural completeness | nie; warnt und nennt jeden Ziel-Plural, dem eine CLDR-Kategorie seiner Sprache fehlt, und beurteilt einen Plural ohne Formen im Ziel danach, was ein Lauf dort schreiben würde (eine leere Quellform zählt als fehlend, Plural-Generierung nur, wo sie wirklich läuft), bewusst strenger als der i18next-Hinweis von `translate` (höchstens zehn; [`verbatra check`](https://verbatra.kreitz-webdev.de/de/docs/cli/check.md#pluralkategorien) listet die Lücken in Plurals, die ein Ziel schon hat); übersprungen bei einem Format, das Pluralformen nicht nach CLDR-Kategorie speichert |
| Locale codes | nie; warnt und nennt jeden gültigen, aber nicht kanonischen Code wie `en-us` oder `iw` mit der kanonischen Form (siehe [Locale-Codes](https://verbatra.kreitz-webdev.de/de/docs/config-file.md#locale-codes)) |
| Locale state | nie; warnt und nennt jede Locale, für die Lock-Datei, Cache oder Provenance-Datei Zustand halten, die die Konfiguration aber nicht aufführt, und ob `translate` sie [übernimmt](https://verbatra.kreitz-webdev.de/de/docs/the-lock-file.md#ein-umgeschriebener-locale-code) |
| Length limits | nie; warnt, wenn `verbatra.limits.json` sich nicht verwenden lässt (ein Lauf ignoriert sie, und ein Limit lässt sich erst nach der Reparatur setzen), nennt ein Limit für einen Schlüssel, den die Quelle nicht mehr hat, oder eines, das das `maxLength` der Konfiguration überschreibt, und sagt es, wenn sich die Quelle zum Prüfen der Schlüssel nicht lesen ließ; besteht, wenn die Datei fehlt ([Längenlimits](https://verbatra.kreitz-webdev.de/de/docs/config-file.md#maxlength)) <AvailableFrom version="0.13.0" /> |
| Locale support | der Provider die Quell-Locale oder eine Ziel-Locale nicht unterstützt; warnt, wenn eine Locale eine Warnung trägt (siehe [Locale-Unterstützung](#locale-unterstützung)) |

Jede Prüfung läuft auch dann, wenn eine frühere fehlgeschlagen ist. Die eine Ausnahme ist die Konfiguration: Kann sie nicht geladen werden, erscheinen die Prüfungen, die sie brauchen, als `[skip]` (`"status": "skipped"` in JSON), ebenso Plural completeness bei einem Format ohne CLDR-Pluralformen. Eine Prüfung, die etwas Beachtenswertes findet, ohne fehlzuschlagen, erscheint als `[warn]` (`"status": "warn"` in JSON) und ändert nie den Exit-Code. Eine fehlgeschlagene Prüfung trägt außerdem ein `fix`, einen nächsten Schritt im Imperativ, ausgegeben als `fix:`-Zeile und in JSON als `"fix"` mitgeliefert, damit ein Agent handeln kann, ohne `detail` zu parsen.

Ein Lauf mit zwei Problemen:

```text output
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 und Dateien [#keys-und-dateien]

- Der API-Key wird **nur über den Namen** geprüft: `doctor` fragt, ob die Variable gesetzt ist, nie was sie enthält. Die Variable jedes Providers steht unter [Provider](https://verbatra.kreitz-webdev.de/de/docs/providers.md).
- `openai-compatible` fällt auf einen Platzhalter-Key zurück und scheitert nur, wenn die Konfiguration mit `provider.options.apiKeyEnvVar` eine eigene Variable benennt und diese nicht gesetzt ist. `libretranslate` lässt die Prüfung nie scheitern, weil sein Key optional ist.
- Mit [`provider: { id: "none" }`](https://verbatra.kreitz-webdev.de/de/docs/human-only-workflow.md) bestehen die Prüfungen Provider, API key und Network policy, und Locale support wird übersprungen.
- Eine fehlende **Ziel**-Datei ist in Ordnung: `translate` legt sie an. Die **Quell**-Datei wird geparst, also scheitert die Prüfung an einem Verzeichnis, einer leeren Datei oder fehlerhaftem Inhalt.
- `doctor` lädt zuerst die [Umgebungsdateien](https://verbatra.kreitz-webdev.de/de/docs/cli.md#umgebungsdateien), ein Key in einer dotenv-Datei zählt also als gesetzt; mit `--literals` lädt es keine der beiden.

### Locale-Unterstützung [#locale-unterstützung]

<AvailableFrom version="0.12.0" />

Die Prüfung Locale support fragt, ob der Provider jede konfigurierte Locale übersetzen kann. DeepL und Google Cloud Translation werden gegen eine datierte Sprachtabelle beurteilt, die verbatra mitliefert; ein LLM-Provider akzeptiert jede Locale. Sie scheitert genau an der Locale, die [`verbatra translate`](https://verbatra.kreitz-webdev.de/de/docs/cli/translate.md) mit `LOCALE_UNSUPPORTED_BY_PROVIDER` ablehnen würde, und zeigt sonst `[warn]`, wenn eine Locale eine Warnung trägt oder `--live` die Liste nicht abrufen konnte. [Sprachunterstützung](https://verbatra.kreitz-webdev.de/de/docs/language-support.md#unterstützung-vor-einem-lauf-prüfen) erklärt die Urteile.

`--locales` gibt nach den Prüfungen das Urteil je Locale aus:

```text output
  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: no
```

Jede Zeile nennt die Locale, den Code, den der Provider bekommt (nach [`localeMap`](https://verbatra.kreitz-webdev.de/de/docs/providers.md#an-den-provider-gesendete-locale-codes), dann mit `(localeMap)` markiert), das Urteil und ob ein Glossar und ein `formal`- oder `informal`-Tonfall greifen. Eine Warnung ist nur ein Hinweis.

`--live` urteilt stattdessen gegen die aktuelle Liste des Providers (DeepL `/v3/languages`, Cloud Translation `languages`, LibreTranslate `/languages`). Für LibreTranslate, das keine Tabelle mitbringt, ist das der einzige Weg zu einem anderen Urteil als `unverified`, daher zeigt Locale support `[warn]`, bis `--live` läuft. Die Anfrage geht nur raus, wenn die Key-Variable gesetzt ist (LibreTranslate braucht keine) und die [Netzwerkrichtlinie](https://verbatra.kreitz-webdev.de/de/docs/network-policy.md) den Host erlaubt; sonst, oder wenn sie scheitert, gilt die mitgelieferte Tabelle, und eine Zeile `live language list` sagt, warum.

### Nicht übersetzte Literale [#nicht-übersetzte-literale]

<AvailableFrom version="0.11.0" />

`--literals` findet für Nutzer sichtbare Strings, die nie in einem Katalog gelandet sind. Es durchläuft die Quell-Roots deines [`extract`-Blocks](https://verbatra.kreitz-webdev.de/de/docs/config-file.md#extract), liest deinen Quellcode und schreibt ihn nie, konstruiert keinen Provider, liest keinen Key und lädt keine `.env`-Datei. Nur zwei Prüfungen laufen: Configuration und Untranslated literals. Jeder Fund nennt Datei, Zeile und Spalte, mit zusammengefasstem Leerraum, aufgelösten JSX-Zeichenreferenzen und höchstens 80 Zeichen:

```text output
  [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."
```

Gemeldet werden: JSX-Text in `.tsx`-, `.jsx`- und `.js`-Dateien, für Nutzer sichtbare JSX-Attribute (`alt`, `title`, `placeholder`, `label`, `aria-label` und die anderen Text tragenden `aria-*`) und jeder andere String aus zwei oder mehr Wörtern.

Nie gemeldet werden:

- ein String, der an `t(...)`, `$t(...)`, `i18n.t(...)` oder ein umbenanntes `t` aus `useTranslation` geht oder in `<Trans>` oder `<Translation>` gerendert wird;
- Objekt-Keys, Import-Specifier, Klassennamen, CSS-Werte, Test-IDs, `data-*` sowie ID- oder Schlüsselwort-Attribute (`aria-describedby`, `aria-labelledby`, `aria-controls`, `rel`, `sandbox`, `allow`, `autoComplete`, `referrerPolicy`, `crossOrigin`), URLs und Strings ohne Buchstaben;
- Literale an einer Typposition (der Wert vor `as` oder `satisfies` wird weiter geprüft), Log-Meldungen, Fehlermeldungen (`new ValidationError(...)`, `throw Error(...)`), Vergleichsoperanden und Template-Literale mit Ausdruck;
- direkte Argumente von `describe` (zod), `query`, `execute`, `prepare`, `format`, `parse`, `z.enum`, `setItem`, `getItem` und `removeItem` sowie das erste Argument von `on`, `off`, `once`, `emit`, `addEventListener` und einem `get` oder `set` auf einem Objekt; tiefer verschachtelter Text und Meldungen an Funktionen wie `setError` werden weiter gemeldet;
- alles in Test-, Story-, Deklarations- und Konfigurationsdateien.

Um ein Literal zurückzuhalten, setz `// verbatra-ignore-next-line` darüber (in JSX `{/* verbatra-ignore-next-line */}`) oder `// verbatra-ignore-line` ans Ende seiner Zeile. Eine Next-Line-Direktive zielt auf die nächste Zeile mit Code und bei einem öffnenden JSX-Tag auf alle seine Attribute. Einen String, der überall passt, trägst du mit aufgelösten Referenzen (`Tom & Jerry`) unter `extract.literals.ignore` ein. Unterdrückte Literale werden trotzdem mit Grund aufgeführt.

Die Prüfung scheitert an einem Fund und an einer Datei, die sie nicht öffnen, nicht bis zum Ende lesen oder als zu groß einstuft (der Scan läuft weiter). Ein Projekt ohne `extract`-Block lässt sie scheitern.

### Datenfluss [#datenfluss]

<AvailableFrom version="0.12.0" />

`--data-flow` beschreibt statt der Einrichtung, wohin die Daten dieses Projekts gehen, als Nachweis für ein AVV- oder NDA-Review; [Datenverarbeitung](https://verbatra.kreitz-webdev.de/de/docs/data-handling.md) erklärt jede Kategorie. Es liest keinen API-Key und macht keinen Netzwerk-Request. Nur zwei Prüfungen laufen: Configuration und Data flow. Data flow schlägt nie fehl: Es zeigt `[warn]`, wenn die [Network Policy](https://verbatra.kreitz-webdev.de/de/docs/network-policy.md) einen Host ablehnt oder einen ungültigen Wert hat, oder wenn die Quelldatei für die Zählungen nicht lesbar ist.

```text output
  [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-tools
```

Das Manifest nennt den Provider; jeden Ziel-Host, woher er kommt (eingebaut, `provider.options.baseUrl` oder eine `*_BASE_URL`-Variable) und das Urteil der Policy (`permitted`, `deferred`, `refused` oder `invalid-policy`); die Arten gesendeter Daten, mit der Zahl der Quell-Keys, der Keys mit Beschreibung oder Bedeutung und der Keys, deren Platzhalter der Provider nicht maskieren kann (eine Schätzung über die Quell-Keys: Ein Lauf sendet nur fehlende oder geänderte Keys und kann unter `sensitiveData: redact` mehr zurückhalten); Code und Zahl der Glossarbegriffe je Ziel-Locale; die lokalen Dateien mit ihren projektrelativen Pfaden und ob sie Quelltext, Übersetzungen oder personenbezogene Daten enthalten; und die übrigen Requests und Agent-Schnittstellen. DeepL steht mit beiden Hosts drin, weil der Key, der einen davon wählt, nie gelesen wird. Ein wirksamer Proxy steht mit Variable und Host drin, nie mit seinen Zugangsdaten, und die Felder nennen nur, was diese Config senden kann. Mit `provider: { id: "none" }` sagt es, dass nichts gesendet wird. Es nennt Hosts, Datenkategorien und Bedingungen, nie einen Key-Wert oder Dateiinhalte.

Mit `--json` steht das Manifest unter `result.dataFlow`. Sein Feld `version` ändert sich nur, wenn ein Feld umbenannt oder entfernt wird; neue Felder können ohne Erhöhung dazukommen, ignoriere also die, die du nicht kennst. [`dataFlowManifestSchema`](https://verbatra.kreitz-webdev.de/de/docs/sdk/inspect.md#dataflowmanifestschema) aus dem SDK validiert es.

### JSON-Ausgabe [#json-ausgabe]

Schema: [`doctor-envelope.json`](https://verbatra.kreitz-webdev.de/schema/v1/doctor-envelope.json) validiert den ganzen Envelope; [Schemas](https://verbatra.kreitz-webdev.de/de/docs/cli/output.md#schemas) listet die übrigen auf.

Mit `--json` trägt der [Envelope](https://verbatra.kreitz-webdev.de/de/docs/cli/output.md#json-ausgabe) den Bericht unter `result`. `ok` ist genau dann wahr, wenn der Befehl mit `0` endet:

```ts
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` fehlt, wenn die Konfiguration nicht geladen werden konnte, bei einem unbekannten Provider und bei `provider: { id: "none" }`.

### Rückmeldung im Terminal [#rückmeldung-im-terminal]

<AvailableFrom version="0.12.0" />

Während es läuft, zeigt `doctor` eine Zeile auf stderr, die `--quiet` weglässt: `checking the setup`, unter `--live` `checking the setup and fetching the provider's language list`, unter `--data-flow` `describing the data flow` oder unter `--literals` `scanning the source for literals` mit Dateianzahl. Der Bericht bleibt auf stdout. Siehe [Terminalausgabe](https://verbatra.kreitz-webdev.de/de/docs/cli/output.md#terminalausgabe).

## Exit-Codes [#exit-codes]

| Code | Bedeutung |
| --- | --- |
| `0` | keine Prüfung fehlgeschlagen (auch ein `warn` endet mit `0`) |
| `1` | mindestens eine Prüfung ist fehlgeschlagen, auch bei einer nicht unterstützten Locale; mit `--literals`: ein Literal gefunden oder eine Datei nicht scannbar |
| `2` | konnte nicht laufen: ein Verwendungsfehler oder ein explizit angegebener `--config`-Pfad, der nicht existiert |

Eine Konfiguration, die die Suche nicht findet oder die die Validierung nicht besteht, ist eine fehlgeschlagene Prüfung (Exit `1`), denn genau das zu melden ist die Aufgabe des Befehls. Siehe [den Exit-Code-Vertrag](https://verbatra.kreitz-webdev.de/de/docs/cli/output.md#exit-codes).

## Siehe auch [#siehe-auch]

- [`verbatra init`](https://verbatra.kreitz-webdev.de/de/docs/cli/init.md) erzeugt die Konfiguration und die `.env.example`, die `doctor` prüft.
- [`verbatra check`](https://verbatra.kreitz-webdev.de/de/docs/cli/check.md) ist das Drift-Gate, sobald die Einrichtung steht.
- [Die Konfigurationsdatei](https://verbatra.kreitz-webdev.de/de/docs/config-file.md) dokumentiert jeden Key, den `doctor` validiert.
- [Provider](https://verbatra.kreitz-webdev.de/de/docs/providers.md) listet die API-Key-Variable, die jeder Provider liest.
- [Sprachunterstützung](https://verbatra.kreitz-webdev.de/de/docs/language-support.md#unterstützung-vor-einem-lauf-prüfen) listet die Sprachtabellen, die die Prüfung Locale support nutzt.
