---
title: "Übersetzungen ausführen"
description: "translate, watch und die Einstiegspunkte rund um einen Lauf, dazu Pseudolocales und typisierte Kataloge, alles aus deinen eigenen Node.js-Skripten aufrufbar."
type: "reference"
---
# Übersetzungen ausführen

translate, watch und die Einstiegspunkte rund um einen Lauf, dazu Pseudolocales und typisierte Kataloge, alles aus deinen eigenen Node.js-Skripten aufrufbar.

## Übersetzen und beobachten [#übersetzungen-ausführen]

### translate [#translate]

Der Einmal-Flow: die Quelle lesen, jede Ziel-Locale mit der Lock-Baseline vergleichen, die
fehlenden und geänderten Keys an den Provider schicken, die Integritätsprüfungen ausführen, die
Locale-Dateien schreiben und das Lock aktualisieren. Nutze ihn für Skripte, Build-Schritte und
CI-Jobs.

```ts title="Signatur"
function translate(
  input: TranslateInput,
  deps?: TranslateDeps,
): Promise<RunSummary>;
```

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `config` | `VerbatraConfig` | yes | The resolved project config, normally from loadConfig. |
| `cwd` | `string` | no | Directory the `files.pattern` is resolved against, and where the lock-file, the translation memory, and the run-status file live. Defaults to the project root of the config object loadConfig returned, else the process working directory; a copied or rebuilt config loses that root, so pass `cwd` from resolveProjectRoot. |
| `locales` | `readonly string[]` | no | Restrict the run to a subset of the configured target locales, which is how you translate one locale at a time against a rate-limited provider. Defaults to every configured target. The result keeps the configured order whatever order the subset is given in, and any locale named here that is not a configured target fails the whole run with `UNKNOWN_LOCALE` before anything is read or spent. An explicit empty array selects no locale at all rather than every locale. Locales left out are untouched: their files, their lock entries, and their translation-memory entries all stay as they were. The run-status file always describes the most recent run alone, so a subset run narrows it to that subset. |
| `dryRun` | `boolean` | no | Compute the whole run but write nothing and call no provider. The returned RunSummary reports what would have happened, which makes this safe to run in CI to preview work without spending anything. Defaults to false. In human-only mode (provider `none`) it reads the translation memory, so the plan matches a live run. |
| `estimate` | `boolean` | no | Compute a pre-run cost estimate and return it on RunSummary.estimate. Implies `dryRun`: an estimate constructs no provider, reads no API key, makes no network call, and writes no file, so it is safe to run anywhere. Defaults to false. The figure bounds the plan rather than the invoice: every provider call a live run schedules is counted, plural generation included, and the prompt is measured from the request payload that would be sent. Provider-side retries, a repair round, and a language that expands past the allowance can each push a live run above it; EstimateCaveatCode names them all. It carries a currency amount only when the config supplies a `rates` block covering the configured provider and model; otherwise it reports the quantity and says why the money is missing. See RunEstimate. |
| `prune` | `boolean` | no | Remove keys that no longer exist in the source. Defaults to the config's `prune`, then to false, so orphaned keys are reported but kept unless removal is asked for explicitly. |
| `generatePlurals` | `boolean` | no | Generate the plural categories a target language requires rather than translating each category separately. Defaults to the config's `generatePlurals`, then to false. Takes effect only for the `i18next-json` format with an LLM provider; any other format, a machine-translation provider, and provider `none` generate nothing. |
| `onLockWait` | `LockWaitListener` | no | Called while waiting on another process's write lock, so a CLI can explain a stall instead of appearing to hang. Never called for a lock this process holds itself, such as the lock-file guard a sibling locale holds on a concurrent run. Fires first once a wait has lasted a second. The SDK writes no output itself. |
| `onProgress` | `ProgressListener` | no | Called as locales and sub-batches start and finish, for progress reporting. The SDK writes no output itself. |
| `lockAcquireTimeoutMs` | `number` | no | How long, in milliseconds, to wait for a locale's write lock before that locale fails with `LOCK_CONTENDED`, and for the lock-file guard before a locale whose respelled state could not be moved fails with `LOCALE_STATE_NOT_CARRIED_OVER`. Both waits happen before any provider call. Defaults to ten minutes. It does not bound the lock-file guard a locale takes to record its result once its target file is written: that wait always allows the ten-minute default, so a written file is not left unrecorded. Not used on a dry run, which takes no lock. |
| `concurrency` | `number` | no | How many locales to run at once. Must be an integer of at least 1; defaults to 1. On a live run it cannot be combined with a token budget, configured or passed as `maxTokens`: the ceiling would still hold, but which locale loses its remaining work would depend on the order the locales interleave, so the run would not be reproducible. A whole-run error raised once locales are running starts no further locale, and the locales in flight finish and release their write locks before `translate` rejects, so the rejection arrives after the slowest one. |
| `cache` | `boolean` | no | Consult and update the translation memory, fuzzy reuse included. Defaults to true. Turning it off forces every key through the provider, which is what to do when you want to re-pay for a fresh translation. A dry run never reads the memory, whatever this says, except in human-only mode (provider `none`), where the memory is all a live run would use. The memory is the local CACHE_FILE_NAME file; `false` is the CLI's `--no-cache`. |
| `humanEdits` | `"overwrite" \| "protect" \| "suggest"` | no | Overrides the config's `humanEdits` for this run only (see HumanEditsPolicy). Set `overwrite` to retranslate stale keys a person wrote, as the CLI's `--include-human` flag does. Keys matching the config's `pinnedKeys` stay protected whatever this says. A dry run reads the provenance file too, unless this resolves to `overwrite`. |
| `maxTokens` | `number` | no | A token ceiling for this run alone, as the CLI's `--max-tokens` flag sets it. It is always a hard stop: a provider request that would pass it is withheld rather than sent, whatever the config's `budgetBehavior` says, and its keys are listed in LocaleSummary.budgetWithheld. When the config also sets `maxTokens`, the lower of the two applies, so this can tighten the configured budget but never loosen it. Must be a whole number of at least 1. Defaults to the config's `maxTokens` and `budgetBehavior` alone. |
| `signal` | `AbortSignal` | no | Cancels the run when aborted. No further locale starts, and a wait for a write lock stops. The locales running at the time send no further request, and an in-flight request is abandoned rather than counted as a provider failure, unless the provider reported a failure of its own. What already arrived is still written and recorded, the keys left over get no lock-file entry and stay pending for the next run, the run status is recorded, and every lock is released. The call then resolves rather than rejects: the result carries RunSummary.cancelled, each locale kept from starting is `failed` with the `RUN_CANCELLED` error code, and locales stopped while running carry a `RUN_CANCELLED` notice and are `partial`, or `failed` when no key landed and others were withheld. A dry run only stops early. Defaults to none. |

**Rückgabe:** [`RunSummary`](https://verbatra.kreitz-webdev.de/de/docs/sdk/run-summary.md)

**Wirft:** [`UNKNOWN_LOCALE`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#unknown_locale), [`LOCALE_UNSUPPORTED_BY_PROVIDER`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#locale_unsupported_by_provider), [`UNKNOWN_FORMAT`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#unknown_format), [`CONCURRENCY_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#concurrency_invalid), [`CONCURRENCY_BUDGET_CONFLICT`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#concurrency_budget_conflict), [`MAX_TOKENS_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#max_tokens_invalid), [`LOCK_TIMEOUT_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#lock_timeout_invalid), [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#locale_path_collision), [`SOURCE_UNREADABLE`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#source_unreadable), [`SOURCE_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#source_invalid), [`PROVIDER_CONSTRUCTION_FAILED`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#provider_construction_failed), [`NETWORK_POLICY_VIOLATION`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#network_policy_violation), [`CONFIG_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#config_invalid), [`LOCK_FILE_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#lock_file_invalid), [`PROVENANCE_FILE_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#provenance_file_invalid)

**Beispiel:** [Übersetzen und die Zusammenfassung lesen](https://verbatra.kreitz-webdev.de/de/docs/sdk-quickstart.md#übersetzen-und-die-zusammenfassung-lesen)

Was die Tabelle nicht abdeckt:

- `cache` ist der lokale [Übersetzungsspeicher-Cache](https://verbatra.kreitz-webdev.de/de/docs/the-cache.md) (`verbatra.cache.json`, ebenfalls exportiert als `CACHE_FILE_NAME`).
- `humanEdits` (ab 0.12.0): Standardmäßig bleiben veraltete Keys, die ein Mensch geschrieben,
  importiert oder außerhalb von verbatra geändert hat, erhalten und werden in
  `LocaleSummary.protected` gelistet. Siehe
  [Menschliche Übersetzungen schützen](https://verbatra.kreitz-webdev.de/de/docs/protecting-human-translations.md).
- `onProgress` bekommt die unten beschriebenen [Fortschrittsereignisse](#fortschrittsereignisse).
- `maxBatchSize` und `budgetBehavior` sind nur über die Konfiguration steuerbar; es gibt keine
  Übersteuerung pro Lauf.

Probleme des gesamten Laufs werfen einen `SdkError`; eine einzelne fehlschlagende Locale wirft nie,
sondern landet als `status: "failed"` auf dem Eintrag dieser Locale, während der Lauf weitergeht.
Siehe [die Anatomie der `RunSummary`](https://verbatra.kreitz-webdev.de/de/docs/sdk/run-summary.md#die-anatomie-der-runsummary).

### Fortschrittsereignisse [#fortschrittsereignisse]

<AvailableFrom version="0.12.0" />

`onProgress` erhält ein `ProgressEvent`, eine nach `type` unterschiedene Union. Eine Minor-Version kann Typen ergänzen, ignoriere also einen `type`, den du nicht kennst.

| `type` | Wann | Felder |
| --- | --- | --- |
| `locale-started` | die Arbeit an einer Locale beginnt | `locale`, `localeIndex`, `totalLocales` |
| `locale-planned` | nachdem das Translation Memory befragt wurde und bevor der erste Batch gesendet wird; nur wenn ein Provider aufgerufen wird | `locale`, `keys`, `batches`, `cacheHits` |
| `sub-batch` | direkt bevor ein Übersetzungs-Batch versucht wird | `locale`, `batchIndex`, `totalBatches` |
| `provider-retry` | ein eingebauter Provider wiederholt eine Anfrage, die mit einem wiederholbaren Status fehlschlug | `attempt`, `delayMs?`, `status?` |
| `repair` | ein LLM-Provider fordert die Keys erneut an, die seine erste Antwort ausgelassen hat | `locale`, `keys` |
| `split-retry` | ein Batch, dessen Ausgabe abgeschnitten wurde, wird in zwei Hälften wiederholt | `locale`, `keys` |
| `batch-finished` | ein Batch ist fertig, ob erfolgreich oder nicht | `locale`, `batchIndex`, `totalBatches`, `durationMs`, `usage?` |
| `writing` | direkt bevor die Datei einer Locale geschrieben wird | `locale` |
| `locale-finished` | die Arbeit an einer Locale endet | `locale`, `status`, `translated`, `localeIndex`, `totalLocales` |
| `run-finished` | alle Locales sind fertig | `localesCompleted`, `localesFailed` |
| `change-detected` | nur `watch`: eine Änderung an der Quelldatei hat sich gesetzt | `paths` |
| `idle` | nur `watch`: ein Lauf ist fertig und keiner steht an | keine |

`provider-retry` nennt keine Locale, weil ein Provider den ganzen Lauf bedient. Gemini meldet die Wartezeit und den Status, Anthropic, OpenAI, OpenCode und openai-compatible melden den Versuch, sobald ihr Client ihn startet, und DeepL, Google Cloud Translation und LibreTranslate melden nichts. Ein Dry Run und eine Schätzung senden nur `locale-started`, `locale-finished` und `run-finished`. Die `--json`-Fortschrittsdatensätze der CLI bleiben bei den vier ursprünglichen Typen: `locale-started`, `sub-batch`, `locale-finished` und `run-finished`.

`extract`, `diff` mit `unused` und `doctor` mit `literals` nehmen ein eigenes `onProgress`. Es erhält ein `ScanProgressEvent`, `{ type: "files-scanned", scanned, total }`, einmal nach jeder Quelldatei der Anwendung, die der Scan liest.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `type` | `"files-scanned"` | yes | Discriminant, reserved so later scan events can join a union. |
| `scanned` | `number` | yes | How many files have been scanned so far. |
| `total` | `number` | yes | How many files the scan will read in total. |

Eine eigene `CreateProvider`-Fabrik erhält ein drittes Argument, `CreateProviderHooks`, sobald ein Lauf Fortschritt meldet. Rufe dessen `onRetry` mit `{ attempt, delayMs?, status? }` auf, dann erscheinen die Wiederholungen deines Providers als `provider-retry`-Ereignisse.

### resolveDryRun [#resolvedryrun]

<AvailableFrom version="0.11.0" />

Beantwortet, ob eine Menge von Lauf-Optionen zu einem Dry Run führt. Sie nimmt `dryRun` und
`estimate`, die du gleich an `translate` übergibst, und liefert zurück, ob dieser Lauf einen
Provider konstruiert, eine Datei schreibt oder Geld ausgibt. Das ist die einzige Stelle, an der
entschieden wird, dass `estimate` ein `dryRun` impliziert. Greif also darauf zurück, wenn etwas rund
um einen Lauf das vorab wissen muss, statt die Implikation zu wiederholen und davon abzudriften.

```ts title="Signatur"
function resolveDryRun(input: {
  readonly dryRun?: boolean | undefined;
  readonly estimate?: boolean | undefined;
}): boolean;
```

**Rückgabe:** `boolean`

```ts title="Beispiel"
import { resolveDryRun, translate, type VerbatraConfig } from "@verbatra/sdk";

declare const config: VerbatraConfig;

const options = { dryRun: false, estimate: true };
resolveDryRun(options); // true

const summary = await translate({ config, ...options });
```

### watch [#watch]

Beobachtet die Quell-Locale-Datei und führt `translate` bei jeder entprellten Änderung neu aus. Es
feuert beim Start sofort einen initialen Lauf, danach einen Lauf pro beruhigter Änderung. Läufe
sind serialisiert: Änderungen während eines Laufs fallen zu einem einzigen Folgelauf zusammen.

```ts title="Signatur"
function watch(
  input: WatchInput,
  deps?: WatchDeps,
): Promise<WatchController>;
```

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `config` | `VerbatraConfig` | yes | The resolved project config, normally from loadConfig. |
| `cwd` | `string` | no | Directory the `files.pattern` is resolved against, and where each run's lock-file, translation memory, and run-status file live. Defaults to the project root of the config object loadConfig returned, else the process working directory; a copied or rebuilt config loses that root, so pass `cwd` from resolveProjectRoot. |
| `locales` | `readonly string[]` | no | Restrict every run of the session to a subset of the configured target locales. Validated once when watching starts, so an unconfigured locale throws `UNKNOWN_LOCALE` before any watching begins rather than failing on each run. An explicit empty array selects no locale at all. |
| `debounceMs` | `number` | no | How long to coalesce rapid source changes before running. Defaults to 300 milliseconds. |
| `onRun` | `(result: WatchRunResult) => void` | yes | Called after every run with its outcome. This is the only way to observe a watch session, since watch itself resolves as soon as watching starts. |
| `onReady` | `(() => void)` | no | Called once when the session's startup checks have passed and the watcher is attached, right before the initial run starts, so a caller can announce the session ahead of that run's output. |
| `onLockWait` | `LockWaitListener` | no | Called while waiting on another process's write lock, never for one this process holds. |
| `onProgress` | `ProgressListener` | no | Called as each run plans, sends and writes its locales, and as the watcher detects a change or goes idle, for progress reporting. |
| `lockAcquireTimeoutMs` | `number` | no | How long, in milliseconds, to wait for a locale's write lock, before any provider call, before that locale fails with `LOCK_CONTENDED` on the run's summary. Defaults to ten minutes. The lock-file guard a locale takes to record its result after writing its target file always allows the ten-minute default instead. |
| `concurrency` | `number` | no | How many locales to run at once. Defaults to 1. Validated once when watching starts, and applied to every run. |
| `cache` | `boolean` | no | Consult and update the translation memory, fuzzy reuse included. Defaults to true. |

**Rückgabe:** `WatchController`

**Wirft:** [`UNKNOWN_LOCALE`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#unknown_locale), [`LOCALE_UNSUPPORTED_BY_PROVIDER`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#locale_unsupported_by_provider), [`CONCURRENCY_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#concurrency_invalid), [`LOCK_TIMEOUT_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#lock_timeout_invalid), [`CONCURRENCY_BUDGET_CONFLICT`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#concurrency_budget_conflict), [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#locale_path_collision), [`SOURCE_UNREADABLE`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#source_unreadable), [`NETWORK_POLICY_VIOLATION`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#network_policy_violation), [`PROVIDER_CONSTRUCTION_FAILED`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#provider_construction_failed), [`CONFIG_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#config_invalid)

**Beispiel:** [Beobachten in einem langlaufenden Prozess](https://verbatra.kreitz-webdev.de/de/docs/programmatic-api.md#beobachten-in-einem-langlaufenden-prozess)

`debounceMs` ist standardmäßig 300. `onRun` wird einmal pro Lauf mit einem `WatchRunResult`
aufgerufen: `{ status: "succeeded", summary }` oder
`{ status: "failed", error: { code, message, causeCode?, hint? } }`, wobei `causeCode` den Code des
Fehlers nennt, den der Fehlschlag umhüllt, und `hint` der nächste Schritt ist, den `errorHint`
liefert. `onReady` wird einmal aufgerufen, nachdem die Startprüfungen bestanden sind und direkt vor
dem ersten Lauf, damit du die Sitzung vor dessen Ausgabe ankündigen kannst. Wirft `onReady`, wird
der Watcher geschlossen, und `watch` lehnt mit genau diesem Fehler ab, bevor ein Lauf startet. Das
SDK loggt nichts; `onRun` ist die einzige Ausgabe. `concurrency`, `cache`, `onProgress`,
`onLockWait` und `lockAcquireTimeoutMs` werden an jeden Lauf durchgereicht und verhalten sich genau
wie bei `translate`. `locales` grenzt jeden Lauf der Sitzung auf diese Teilmenge ein und wird einmal
beim Start geprüft, nicht pro Zyklus.

Gibt einen `WatchController` mit einer Methode zurück, `stop()`, die den Watcher schließt und den
laufenden Lauf abwartet. Jeder Code aus der Zeile **Wirft** wird schon beim Start zurückgewiesen,
bevor der Watcher existiert, sodass `watch` selbst fehlschlägt, statt einen Controller
zurückzugeben. Zwei davon wiegen für eine Sitzung schwerer als für einen einzelnen Lauf: Eine
`concurrency` über 1 gegen eine Konfiguration, die `maxTokens` setzt, wirft immer
`CONCURRENCY_BUDGET_CONFLICT`, weil es keinen Dry-Run-Watch gibt, und ab 0.12.0 wirft ein Provider,
der sich nicht erzeugen lässt, etwa weil seine API-Key-Variable fehlt, einmal
`PROVIDER_CONSTRUCTION_FAILED`, statt jeden Lauf scheitern zu lassen. Jeder Fehlschlag nach dem
Start kommt über `onRun` an, und das Beobachten geht weiter.

### releaseHeldLocks [#releaseheldlocks]

<AvailableFrom version="0.12.0" />

Löscht jede Schreibsperre, die der aktuelle Prozess hält, für einen Prozess, der gleich beendet
wird, ohne seine laufenden Durchläufe abzuschließen: Die CLI ruft es auf, wenn ein Interrupt
`translate` oder `import` stoppt, und wenn ein zweiter Interrupt `watch` hart beendet. Die
Durchläufe, die diese Sperren genommen haben, verlieren ihren Schutz, ruf es also nur direkt vor dem
Beenden des Prozesses auf. Es nimmt keine Argumente und lehnt nur dann mit einem `AggregateError`
ab, der die Pfade der Sperren nennt, wenn sich eine gehaltene Sperre nicht löschen ließ, nachdem
jede Sperre versucht wurde. Eine Sperre, die ein noch laufender Vorgang währenddessen nimmt oder
nehmen will, wird mit `RUN_CANCELLED` abgelehnt und hinterlässt keine Datei; auf eine solche
Übernahme wartet es höchstens drei Sekunden.

```ts title="Signatur"
function releaseHeldLocks(): Promise<void>;
```

**Rückgabe:** `void`

Eine Sperre, die trotzdem liegen bleibt, weil ein Prozess hart beendet wurde, braucht es nicht: Ein
wartender Durchlauf auf derselben Maschine erkennt, dass der eingetragene Prozess nicht mehr läuft,
und übernimmt die Sperre selbst. Siehe [die Schreibsperre](https://verbatra.kreitz-webdev.de/de/docs/the-lock-file.md#die-schreibsperre).

## Pseudolokalisierung [#pseudolokalisierung]

### pseudolocalize [#pseudolocalize]

<AvailableFrom version="0.11.0" />

Baut allein aus den Quellstrings eine künstliche Locale, sodass abgeschnittene Layouts und Strings,
die nie für die Übersetzung extrahiert wurden, auffallen, bevor überhaupt ein API-Key existiert.
Jeder Wert wird akzentuiert, um etwa ein Drittel seiner übersetzbaren Länge verlängert und in die
Begrenzungszeichen `[` und `]` gefasst, während Platzhalter in jeder Syntax unangetastet bleiben,
die die Adapter kennen, einschließlich ICU-Plural- und -select-Zweigen, Markup-Tags und
Escape-Sequenzen. Es konstruiert keinen Provider, liest keinen API-Key und stellt keine
Netzwerkanfrage.

```ts title="Signatur"
function pseudolocalize(
  input: PseudolocalizeInput,
  deps?: PseudolocalizeDeps,
): Promise<PseudolocalizeResult>;
```

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `config` | `VerbatraConfig` | yes | The resolved project config, normally from loadConfig. |
| `cwd` | `string` | no | Directory the `files.pattern` is resolved against. Defaults to the project root of the config object loadConfig returned, else the process working directory; a copied or rebuilt config loses that root, so pass `cwd` from resolveProjectRoot. |
| `mode` | `"accented" \| "bidi"` | no | The transform to apply. Defaults to `accented`. `bidi` wraps every word of translatable text in a right-to-left mark and override (`U+200F U+202E` before, `U+202C U+200F` after), so it renders right to left while the stored letters keep their order; placeholders, ICU syntax, markup, digits and punctuation stay outside the override, and nothing is accented or padded. |
| `locale` | `string` | no | The pseudolocale's BCP-47 code. Defaults to `en-XA` in `accented` mode and to `ar-XB` in `bidi` mode, a code that resolves to right-to-left text direction, so an application that derives its layout direction from the locale flips without further setup. In `bidi` mode an `i18next-json` plural keyed by suffix also gets every CLDR category the pseudolocale's language needs and the source lacks (for `ar-XB`, `_zero`, `_two`, `_few` and `_many`), filled from the source's `other` form, so no count falls back to another language. It must not be the source locale or any configured target locale, compared case-insensitively, so a pseudolocale can never stand in for a real translation. |
| `out` | `string` | no | Directory the pseudolocale file is written under, relative to `cwd`. Defaults to `.verbatra-local/pseudo`, which `verbatra init` already adds to `.gitignore`. The configured `files.pattern` is expanded inside it, so the file keeps the layout the application expects. It must name a directory inside `cwd`: an absolute path, one that climbs out with `..`, and `cwd` itself are all refused, as is a directory under which the expanded pattern would place the pseudolocale file beside a configured locale file, so a generated pseudolocale never lands outside the project or beside the real translations where nothing ignores it. The same holds after symbolic links are resolved, so a linked directory cannot carry the file outside `cwd` or into the directory of a configured locale file. |

**Rückgabe:** `PseudolocalizeResult`

**Wirft:** [`PSEUDO_OUTPUT_CONFLICT`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#pseudo_output_conflict), [`UNKNOWN_FORMAT`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#unknown_format), [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#locale_path_collision), [`SOURCE_UNREADABLE`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#source_unreadable), [`SOURCE_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#source_invalid), [`TARGET_UNWRITABLE`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#target_unwritable)

**Beispiel:** [Eine Pseudo-Locale bauen](https://verbatra.kreitz-webdev.de/de/docs/programmatic-api.md#eine-pseudo-locale-bauen)

Jeder erzeugte Wert muss dasselbe Integritäts-Gate passieren wie eine Provider-Übersetzung. Ein
Wert, der das nicht schaffen würde, wird wortwörtlich aus der Quelle kopiert und in `copied`
genannt, statt kaputt geschrieben zu werden; der Eintrag ist also weiterhin vorhanden und wird
weiterhin gerendert.

<AvailableFrom version="0.12.0" />

`mode` wählt die Transformation aus `PSEUDO_MODES`: `accented`, der Standard, oder `bidi`, das
stattdessen jedes Wort übersetzbaren Textes in eine Rechts-nach-links-Marke und -Überschreibung
fasst und Platzhalter, ICU-Syntax, Markup, Ziffern und Satzzeichen draußen lässt, ohne etwas zu
akzentuieren. Im Modus `bidi` ist `locale` standardmäßig `ar-XB`, ein Code, der zur Richtung rechts
nach links aufgelöst wird; wie seine arabischen Pluralregeln auf die Pluralformen deiner Quelle
treffen, steht bei [`verbatra pseudo`](https://verbatra.kreitz-webdev.de/de/docs/cli/pseudo.md).

`locale` ist standardmäßig `en-XA` und `out`
`.verbatra-local/pseudo`, was `verbatra init` bereits in `.gitignore` einträgt; das konfigurierte
`files.pattern` wird darin expandiert, die Datei behält also das Layout, das die Anwendung
erwartet. Eine Pseudo-Locale, die eine konfigurierte Locale benennt oder auf einer konfigurierten
Locale-Datei landen würde, wird mit `PSEUDO_OUTPUT_CONFLICT` zurückgewiesen, bevor irgendetwas
gelesen oder geschrieben wird; sie kann also weder eine echte Übersetzung überschreiben noch für
eine einstehen. Ebenso ein `out`, das absolut ist, aus `cwd` herausklettert oder `cwd` selbst
benennt, etwa `.`, und, wenn `deps.fs` `realpath` implementiert wie die Voreinstellung, eines,
das nach dem Auflösen symbolischer Links so landet, im Verzeichnis einer konfigurierten
Locale-Datei oder auf einer anderen Datei, von der das Projekt abhängt. Weil die Datei
außerhalb von `files.pattern` liegt, gibt `translate` nie Geld für sie aus, und `check` und `diff`
melden sie nie als abgedriftet.

Gibt ein `PseudolocalizeResult` zurück: die `locale`, den `mode`, den absoluten `path`, wie viele `entries`
verarbeitet wurden, wie viele `transformed` wurden, die `copied` Keys und ob überhaupt etwas
`written` wurde. Die Transformation ist deterministisch, ein zweiter Lauf über unveränderte Quellen
schreibt also nichts neu und meldet `written` als false. Bei `xliff` und `apple-xcstrings`, deren
Writer nur ein bereits vorhandenes Dokument patchen, wird die Ausgabe bei jedem Schreibvorgang neu
aus der Quelle kopiert, damit ein nach dem ersten Lauf hinzugefügter Key weiterhin in der
Pseudo-Locale ankommt; bei einer kopierten XLIFF-Datei wird `target-language` auf die Pseudo-Locale
umgeschrieben.

## Typisierte Kataloge [#typisierte-kataloge]

### generateTypes [#generatetypes]

<AvailableFrom version="0.11.0" />

Deklariert den Quellkatalog eines Projekts als TypeScript: eine Union aller Keys, die er enthält,
und pro Key die Argumente, die dessen Nachricht interpoliert. Typisiere eine Übersetzungsfunktion
gegen das Ergebnis, und aus einem vertippten Key oder einem fehlenden Interpolationsargument wird
ein Compile-Fehler statt eines Lookup-Fehlers zur Laufzeit. Es kostet nichts: Es wird kein Provider
konstruiert, keine API-Key-Umgebungsvariable gelesen und keine Netzwerkanfrage gestellt.

```ts title="Signatur"
function generateTypes(
  input: GenerateTypesInput,
  deps?: GenerateTypesDeps,
): Promise<GenerateTypesResult>;
```

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `config` | `VerbatraConfig` | yes | The resolved project config, normally from loadConfig. |
| `cwd` | `string` | no | Directory the `files.pattern` and the output path are resolved against. Defaults to the project root of the config object loadConfig returned, else the process working directory; a copied or rebuilt config loses that root, so pass `cwd` from resolveProjectRoot. |
| `out` | `string` | no | Where to write the declaration, relative to `cwd`. Defaults to DEFAULT_TYPES_PATH. Refused with `TYPES_OUTPUT_CONFLICT`, before anything is read or written, when it names no file, is absolute, climbs out of `cwd`, does not end in `.ts`, `.mts` or `.cts`, or names a configured locale file, the lock file, the provenance file, the translation-memory cache, a file verbatra searches for its configuration, the GenerateTypesInput.configPath file, or the GenerateTypesInput.glossaryPath file. When the file-system port implements `realpath`, the same checks run again after symbolic links are resolved. Names are compared case-insensitively. A generating run also refuses to replace an existing file there unless that file begins with the header line verbatra writes, after any leading byte order mark and blank lines, and refuses one too large to verify. |
| `configPath` | `string` | no | The configuration file `config` was loaded from, absolute or relative to `cwd`. It is refused as the output path even when its name is not one verbatra searches for. |
| `glossaryPath` | `string` | no | The glossary file the config names, absolute or relative to `cwd`, normally the `path` of a file-backed LoadedConfig.glossary. It is refused as the output path. |
| `check` | `boolean` | no | Compare instead of writing. The run reports whether the file on disk matches what a fresh generation would produce and leaves every file untouched. |

**Rückgabe:** `GenerateTypesResult`

**Wirft:** [`TYPES_OUTPUT_CONFLICT`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#types_output_conflict), [`TYPES_UNWRITABLE`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#types_unwritable), [`UNKNOWN_FORMAT`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#unknown_format), [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#locale_path_collision), [`SOURCE_UNREADABLE`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#source_unreadable), [`SOURCE_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#source_invalid)

**Beispiel:** [Einen typisierten Katalog erzeugen](https://verbatra.kreitz-webdev.de/de/docs/programmatic-api.md#einen-typisierten-katalog-erzeugen)

Gelesen wird eine Datei (der Quell-Locale-Katalog), geschrieben eine Datei (die Deklaration); eine
Ziel-Locale-Datei wird nie angefasst. Die Keys sind genau das, was der Format-Adapter ohnehin schon
erzeugt hat, in Dokumentreihenfolge, zwei Läufe über einen unveränderten Katalog schreiben also
byteidentische Bytes. Die Argumente stammen aus den Platzhalter-Tokens, die der Adapter extrahiert
hat, außer in den ICU-Nachrichtenformaten (`next-intl-json` und `arb`): Dort wird jede Nachricht mit
demselben ICU-Parser gelesen, den der Adapter verwendet. Jeder Key wird als String-Literal in
Anführungszeichen ausgegeben; einer, der einen Punkt, ein reserviertes Wort, eine führende Ziffer,
ein Anführungszeichen oder gar nichts enthält, wird also wortwörtlich deklariert, statt verworfen
oder neu aufgeteilt zu werden, und ein Key, den der Adapter in `excludedLeafPaths` gemeldet hat,
wird überhaupt nicht deklariert.

Was nicht behauptet wird, zählt ebenso viel wie das, was deklariert wird. Eine Nachricht ohne
Platzhalter bekommt eine Form, bei der jedes übergebene Argument ein Typfehler ist; eine, deren
Platzhalter ihre Argumente benennen, bekommt eine Objektform; eine mit nummerierten oder anonymen
Platzhaltern bekommt ein readonly-Tupel. Ein ICU-Argument, das nur einige `select`- oder
`plural`-Zweige verwenden, wird als optionales Mitglied deklariert. Argumenttypen stammen
ausschließlich aus dem, was der Katalog festhält: `number` für ein ICU-Argument vom Typ `number`,
`plural` oder `selectordinal` und für einen numerischen Formatter oder eine numerische
Konvertierung, `Date | number` für ein ICU-Argument vom Typ `date` oder `time` und einen
i18next-Formatter `datetime`, `string` für ein ICU-Argument vom Typ `select` und eine
printf-String-Konvertierung, und sonst ein Alias auf `string | number`, nie ein großzügiges `any`.
Eine unescapte i18next-Interpolation (`{{- name}}`) wird unter dem Namen hinter ihrem Präfix
deklariert. Eine Nachricht, die der Adapter als ungültig gemeldet hat, eine, die ihre Argumente
zugleich zu benennen und zu nummerieren scheint, und eine mit einem Platzhalter-Index außerhalb des
Bereichs werden mit ihren Argumenten in `unresolved` deklariert, statt still als argumentlos zu
gelten. Plural-Geschwister werden nie zusammengefasst: Jedes wird für sich deklariert, und die vom
Adapter als Plural markierten werden aufgelistet.

`out` fällt auf
`DEFAULT_TYPES_PATH` (`verbatra-types.d.ts`) zurück. Bevor irgendetwas gelesen oder geschrieben
wird, weist `TYPES_OUTPUT_CONFLICT` ein `out` zurück, das keine Datei benennt (auch eines, das auf
ein Pfadtrennzeichen endet), absolut ist, mit `..` aus `cwd` herausklettert, `cwd` selbst benennt (`.`) oder nicht auf
`.ts`, `.mts` oder `.cts` endet, und eines, das eine konfigurierte Locale-Datei, die Lock-Datei, den
Übersetzungsspeicher-Cache, eine Datei, in der verbatra nach seiner Konfiguration sucht,
`configPath`, die Konfigurationsdatei, aus der `config` geladen wurde, oder `glossaryPath` benennt,
die Glossardatei, auf die sie zeigt; Namen werden ohne Rücksicht auf Groß- und Kleinschreibung
verglichen, und wenn `deps.fs` `realpath` implementiert, wie es die Voreinstellung tut, laufen
dieselben Prüfungen noch einmal auf dem Ziel, auf dem der Pfad nach dem Auflösen symbolischer Links
landet. Ein generierender Lauf weist mit demselben Code außerdem zurück, eine bestehende Datei zu
ersetzen, die nicht mit der Kopfzeile beginnt, die verbatra schreibt, und lässt diese Datei
unangetastet. Die vollständige Liste steht auf der Seite zu [`verbatra types`](https://verbatra.kreitz-webdev.de/de/docs/cli/types.md). Mit
gesetztem `check` wird überhaupt nichts geschrieben, nichts wegen seines Inhalts zurückgewiesen, und
der Lauf meldet nur, ob die committete Datei noch passt, und genau das braucht ein CI-Gate.

Gibt ein `GenerateTypesResult` zurück: den absoluten `path`, den `sourcePath`, aus dem es gebaut
wurde, wie viele `keys` deklariert wurden und wie viele davon mindestens ein Argument nehmen, das
verbatra bestimmen konnte (`withArguments`, das einen Key aus `unresolved` nie mitzählt), die
`unresolved` Keys sowie die `excluded` und `plural` Keys, ob etwas `written` wurde, ob die Datei auf
der Platte beim Start `stale` war, ob sie ganz fehlte (`missing`, eine fehlende Datei ist auch
`stale`) und ob dies ein `check`-Lauf war.
