---
title: "Edit and review"
description: "Read, edit, approve, reject and retranslate one key, read values in bulk or page by page, or review and retranslate many at once."
type: "reference"
---
# Edit and review

Read, edit, approve, reject and retranslate one key, read values in bulk or page by page, or review and retranslate many at once.

## Single-key operations [#single-key-operations]

These are the seams Verbatra Studio drives; use them to build your own review tooling. Those that take
a `locale` and a `key` resolve them fresh on every call and throw `UNKNOWN_LOCALE` or `UNKNOWN_KEY` when
either does not exist.

### keyValue [#keyvalue]

<AvailableFrom version="0.5.0" />

Reads one key's current source value and, when present, its current target value for one locale.
Read-only.

```ts title="Signature"
function keyValue(
  input: KeyValueInput,
  deps?: KeyValueDeps,
): Promise<KeyValueResult>;
```

| 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. |
| `locale` | `string` | yes | The target locale to read the translation from. Must be a configured target locale. |
| `key` | `string` | yes | The key to read. Must exist in the source resource. |

**Returns:** `KeyValueResult`

**Throws:** [`UNKNOWN_FORMAT`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_format), [`UNKNOWN_LOCALE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_locale), [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_path_collision), [`SOURCE_UNREADABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_unreadable), [`SOURCE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_invalid), [`UNKNOWN_KEY`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_key)

**Example:** [Edit and retranslate a single key](https://verbatra.kreitz-webdev.de/docs/programmatic-api.md#edit-and-retranslate-a-single-key)

Returns `{ source, target? }`; `target` is absent exactly when the key does not yet exist in that
locale, and when present comes with `provenance`, the key's interpreted `KeyProvenance`:
`{ origin, provider?, model?, reviewState, reviewer? }`.

<AvailableFrom version="0.12.0" />

The result also carries `description` when the source file gives the key one: an ARB
`@key.description`, an XLIFF note, or a gettext, Apple `.strings` or .NET `.resx` comment.

### keyContext [#keycontext]

<AvailableFrom version="0.12.0" />

Reads what a translator needs to write one key in one target locale: everything `keyValue` returns, plus the glossary terms that apply to the source text and the key's `maxLength`. Pass a `draft` to check it against those terms. Read-only. To re-read a file-backed glossary from disk, it takes `loaded` from `loadConfigWithMeta`, not a bare config.

```ts title="Signature"
function keyContext(
  input: KeyContextInput,
  deps?: KeyContextDeps,
): Promise<KeyContext>;
```

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `loaded` | `GlossaryConfig` | yes | The loaded config, as loadConfigWithMeta returns it, so a file-backed glossary is read. |
| `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. |
| `locale` | `string` | yes | The target locale the key is about to be written in. Must be a configured target locale. |
| `key` | `string` | yes | The key to read. Must exist in the source resource. |
| `draft` | `string` | no | A value the caller intends to write, checked against the applying glossary terms when given. |

**Returns:** `KeyContext`

**Throws:** [`UNKNOWN_FORMAT`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_format), [`UNKNOWN_LOCALE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_locale), [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_path_collision), [`SOURCE_UNREADABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_unreadable), [`SOURCE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_invalid), [`UNKNOWN_KEY`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_key)

An unreadable glossary does not throw: `glossary` is empty and `glossaryNotice` carries the error's code and redacted message. `draftCheck` says, per applying term, whether the draft uses the required translation and which forbidden renderings it uses.

### localeHistory [#localehistory]

<AvailableFrom version="0.12.0" />

Lists the recent git commits that touched the source or a target locale file, newest first. Read-only; it runs `git log` through the injectable `execFile`.

```ts title="Signature"
function localeHistory(
  input: LocaleHistoryInput,
  deps?: LocaleHistoryDeps,
): Promise<LocaleHistoryResult>;
```

| 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 git runs in. 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. |
| `limit` | `number` | no | The most commits to list. Rounded down and clamped to between 1 and LOCALE_HISTORY_LIMIT_CAP; defaults to LOCALE_HISTORY_LIMIT_DEFAULT when omitted or not a finite number. |

**Returns:** `LocaleHistoryResult`

**Throws:** [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_path_collision)

Each commit carries `hash`, `author` (the name after `.mailmap`, never the email), `authorDate`, `subject` and `touchedPaths`. `limit` defaults to `LOCALE_HISTORY_LIMIT_DEFAULT` (`50`), capped at `LOCALE_HISTORY_LIMIT_CAP` (`200`), rounded down and at least 1. Control and bidirectional formatting characters are stripped from `author` and `subject`; a commit whose metadata does not parse is left out. Without git or a repository, or when `git log` exceeds `LOCALE_HISTORY_TIMEOUT_MS` or `LOCALE_HISTORY_MAX_OUTPUT_BYTES`, the result is `{ available: false, reason }`, a reason from `LOCALE_HISTORY_UNAVAILABLE_REASONS`. See [Studio and MCP](https://verbatra.kreitz-webdev.de/docs/data-handling.md#studio-and-mcp).

### editEntry [#editentry]

<AvailableFrom version="0.5.0" />

Writes one human-typed correction for one key and locale. The candidate value passes the same
integrity gate as a provider translation before anything reaches disk; on acceptance the
locale file and the lock entry for that key are updated inside the locale's write lock. Never
calls a provider.

```ts title="Signature"
function editEntry(
  input: EditEntryInput,
  deps?: EditEntryDeps,
): Promise<EditEntryResult>;
```

| 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. |
| `locale` | `string` | yes | The target locale to write to. Must be a configured target locale. |
| `key` | `string` | yes | The key to edit. Must exist in the source resource. |
| `value` | `string` | yes | The new translation. It is accepted only if it passes the integrity gate. |
| `actor` | `EditEntryActor` | no | Who wrote the value, recorded as its origin in the provenance file: `human` for a person, `agent` for an AI agent acting through a tool. Defaults to `human`. The calling surface asserts it; nothing verifies it. |
| `onLockWait` | `LockWaitListener` | no | Called while waiting on another process's write lock for the locale, so a caller can explain a stall instead of appearing to hang. Never called for a lock this process holds itself. |
| `lockAcquireTimeoutMs` | `number` | no | How long, in milliseconds, to wait for the locale's write lock before failing with `LOCK_CONTENDED`. Defaults to ten minutes. It does not bound the lock-file guard taken to record the written value, which always allows the ten-minute default. |

**Returns:** `EditEntryResult`

**Throws:** [`UNKNOWN_FORMAT`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_format), [`UNKNOWN_LOCALE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_locale), [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_path_collision), [`SOURCE_UNREADABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_unreadable), [`SOURCE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_invalid), [`UNKNOWN_KEY`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_key), [`KEY_PINNED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#key_pinned), [`LOCK_CONTENDED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_contended), [`RUN_CANCELLED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#run_cancelled), [`TARGET_UNWRITABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#target_unwritable), [`LOCALE_STATE_NOT_CARRIED_OVER`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_state_not_carried_over), [`LOCK_TIMEOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_timeout_invalid), [`LOCK_FILE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_file_invalid), [`PROVENANCE_FILE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#provenance_file_invalid)

**Example:** [Edit and retranslate a single key](https://verbatra.kreitz-webdev.de/docs/programmatic-api.md#edit-and-retranslate-a-single-key)

Returns `{ accepted: true, value }`, or
`{ accepted: false, reason: "placeholder" | "markup" | "icu" | "degenerate" | "empty", value }` with
nothing written ([the integrity gate](https://verbatra.kreitz-webdev.de/docs/translation-safety.md#the-integrity-gate)). Build any
schema over the reasons from the exported `INTEGRITY_GATE_REASONS` tuple. `"empty"` refuses a blank
value for a source that has text; clear a key with the workbook's `[[CLEAR]]` sentinel instead. A
`placeholder` or `markup` refusal also carries `details`: each placeholder or tag behind it,
prefixed `-` when the candidate dropped it or `+` when it invented it.

The value is recorded in `verbatra.provenance.json` with the origin `human`; pass
`actor: "agent"` when an AI agent wrote it, so the record says so (the caller asserts it, nothing
verifies it). A corrupt provenance file throws `PROVENANCE_FILE_INVALID` before the value is gated
and nothing is written; one from a newer verbatra, or one the record would grow past the size
verbatra reads back, is left untouched and the value is written without a record.

<AvailableFrom version="0.12.0" />

With `actor: "agent"`, a key matching the config's `pinnedKeys` throws `KEY_PINNED` before anything is written. A person's edit of a pinned key is accepted.

<AvailableFrom version="0.12.0" />

`lockAcquireTimeoutMs` bounds the wait for the locale's write lock before the call fails with `LOCK_CONTENDED` (ten minutes by default), `onLockWait` reports that wait, and an invalid bound fails with `LOCK_TIMEOUT_INVALID` before anything is read.

### retranslateEntry [#retranslateentry]

<AvailableFrom version="0.5.0" />

Re-runs the provider for exactly one key and locale through the provider path and integrity gate
`translate` uses, and on acceptance writes the locale file and lock entry for that key only.

```ts title="Signature"
function retranslateEntry(
  input: RetranslateEntryInput,
  deps?: RetranslateEntryDeps,
): Promise<RetranslateEntryResult>;
```

| 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. |
| `locale` | `string` | yes | The target locale to retranslate into. Must be a configured target locale. |
| `key` | `string` | yes | The key to retranslate. Must exist in the source resource. |
| `includeHuman` | `boolean` | no | Replace the key's value even when a person wrote it: its origin is `human` or `import`, or it changed outside verbatra. Without it such a key is refused with `KEY_PROTECTED`, unless the config sets `humanEdits: "overwrite"`. A key matching `pinnedKeys` is refused whatever this says. Defaults to false. |
| `onLockWait` | `LockWaitListener` | no | Called while waiting on another process's write lock for the locale, so a caller can explain a stall instead of appearing to hang. Never called for a lock this process holds itself. |
| `lockAcquireTimeoutMs` | `number` | no | How long, in milliseconds, to wait for the locale's write lock before failing with `LOCK_CONTENDED`. The wait happens before the provider is called, so a timed-out call has spent nothing. Defaults to ten minutes. It does not bound the lock-file guard taken to record the written value, which always allows the ten-minute default. |
| `signal` | `AbortSignal` | no | Cancels the call when aborted before the provider has answered: a wait for the write lock stops, an in-flight provider request is abandoned, and the call rejects with `RUN_CANCELLED` having written nothing. An abort after the answer arrived does not stop the write. Defaults to none. |

**Returns:** `RetranslateEntryResult`

**Throws:** [`MACHINE_TRANSLATION_DISABLED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#machine_translation_disabled), [`LOCK_TIMEOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_timeout_invalid), [`UNKNOWN_FORMAT`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_format), [`UNKNOWN_LOCALE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_locale), [`LOCALE_UNSUPPORTED_BY_PROVIDER`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_unsupported_by_provider), [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_path_collision), [`SOURCE_UNREADABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_unreadable), [`SOURCE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_invalid), [`UNKNOWN_KEY`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_key), [`KEY_PINNED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#key_pinned), [`KEY_PROTECTED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#key_protected), [`PROVIDER_CONSTRUCTION_FAILED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#provider_construction_failed), [`NETWORK_POLICY_VIOLATION`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#network_policy_violation), [`CONFIG_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#config_invalid), [`LOCK_CONTENDED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_contended), [`RUN_CANCELLED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#run_cancelled), [`TARGET_UNWRITABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#target_unwritable), [`LOCALE_STATE_NOT_CARRIED_OVER`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_state_not_carried_over), [`LOCK_FILE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_file_invalid), [`PROVENANCE_FILE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#provenance_file_invalid), [`SENSITIVE_CONTENT_WITHHELD`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#sensitive_content_withheld)

**Example:** [Edit and retranslate a single key](https://verbatra.kreitz-webdev.de/docs/programmatic-api.md#edit-and-retranslate-a-single-key)

Returns `{ accepted: true, value, reviewReasons }` (the review reason codes of the new value) or
the refusal `editEntry` returns. Unlike `translate`, a provider failure here throws: a `ProviderError`, exported from `@verbatra/sdk`,
with a stable `code` such as `RATE_LIMITED` or `AUTH_FAILED`. Under `provider: { id: "none" }` it throws `MACHINE_TRANSLATION_DISABLED` first, before reading or locking anything.

The new value is recorded in `verbatra.provenance.json` as `machine`, with the `id` of the provider
that answered and the configured model. A provider from your own `createProvider` is recorded
under its own `id`, and without the model when that `id` differs from the configured one. A corrupt provenance file throws `PROVENANCE_FILE_INVALID` before the provider is called.

<AvailableFrom version="0.12.0" />

A key whose value a person wrote, imported, or changed outside verbatra throws `KEY_PROTECTED` inside the write lock, before the provider is called, unless `includeHuman: true` is passed or the config sets `humanEdits: "overwrite"`; under a provenance file from a newer verbatra, any existing value counts as protected. A key matching `pinnedKeys` throws `KEY_PINNED` before the provider is constructed, even with `includeHuman`.

### approveEntry [#approveentry]

<AvailableFrom version="0.12.0" />

Records that a person reviewed one key's current value in one locale and accepts it. It writes only
`verbatra.provenance.json`, never a locale file or the lock file, and calls no provider. The
approval also stores the lock file's source hash for the key, so it reads as unreviewed again once
the source changes; any later write that changes the value starts a fresh, unreviewed record.

```ts title="Signature"
function approveEntry(
  input: ReviewDecisionInput,
  deps?: ReviewDecisionDeps,
): Promise<ReviewDecisionResult>;
```

| 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. |
| `locale` | `string` | yes | The target locale the reviewed value belongs to. Must be a configured target locale. |
| `key` | `string` | yes | The key whose value was reviewed. Must exist in the source resource. |
| `expectedValue` | `string` | no | The translation the reviewer saw. The decision is refused when the key's current value is a different one, so a value nobody looked at is never approved or rejected. Pass exactly one of `expectedValue` and `expectedValueHash`. |
| `expectedValueHash` | `string` | no | The hash of the translation the reviewer saw, as the `valueMarker` dependency's ValueMarker.hash produced it, for a caller that only ever saw the value's redaction marker. The decision is refused when the key's current value hashes differently. |
| `reviewer` | `string` | no | Free text naming the reviewer, 1 to 64 characters with no control characters. It is stored in the committed provenance file, so it is public. When it is left out no new name is recorded, and a reviewer already recorded for the same decision on the same value is kept. |
| `onLockWait` | `LockWaitListener` | no | Called while waiting on another process's write lock for the locale, so a caller can explain a stall instead of appearing to hang. Never called for a lock this process holds itself. |
| `lockAcquireTimeoutMs` | `number` | no | How long, in milliseconds, to wait for the locale's write lock before failing with `LOCK_CONTENDED`. Defaults to ten minutes. It does not bound the lock-file guard taken to record the decision, which always allows the ten-minute default. |

**Returns:** `ReviewDecisionResult`

**Throws:** [`REVIEWER_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#reviewer_invalid), [`LOCK_TIMEOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_timeout_invalid), [`UNKNOWN_FORMAT`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_format), [`UNKNOWN_LOCALE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_locale), [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_path_collision), [`SOURCE_UNREADABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_unreadable), [`SOURCE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_invalid), [`UNKNOWN_KEY`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_key), [`LOCK_CONTENDED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_contended), [`RUN_CANCELLED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#run_cancelled), [`REVIEW_VALUE_CHANGED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#review_value_changed), [`LOCALE_STATE_NOT_CARRIED_OVER`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_state_not_carried_over), [`REVIEW_SOURCE_CHANGED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#review_source_changed), [`LOCK_FILE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_file_invalid), [`PROVENANCE_FILE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#provenance_file_invalid), [`PROVENANCE_FILE_UNWRITABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#provenance_file_unwritable)

**Example:** [Work through the review queue](https://verbatra.kreitz-webdev.de/docs/programmatic-api.md#work-through-the-review-queue)

`expectedValue` is the text the
reviewer saw, or pass `expectedValueHash` with the `valueMarker` that produced it: the call throws `REVIEW_VALUE_CHANGED` when the key has no value or a different one,
and `REVIEW_SOURCE_CHANGED` when the source changed since the value was written. `reviewer` is
optional free text of 1 to 64 characters with no control characters (`REVIEWER_INVALID`
otherwise), stored in the committed file and never filled in by verbatra. Returns
`{ locale, key, provenance }`. A value with no record yet, or one changed outside verbatra, is
recorded with the origin `unknown`.

### rejectEntry [#rejectentry]

<AvailableFrom version="0.12.0" />

Records that a person reviewed one key's current value in one locale and refuses it, and removes
it from the locale file and the lock file, so `check` and `diff` report it missing and the next
`translate` fills it again (under `provider: { id: "none" }` it stays missing until someone writes
one). The provenance record stays as `rejected` with a hash of the removed text, and this machine's
matching memory entry is dropped; a later `translate` skips any exact or fuzzy memory hit for the
key matching that hash, so no machine's memory brings the text back. Calls no provider.

```ts title="Signature"
function rejectEntry(
  input: ReviewDecisionInput,
  deps?: ReviewDecisionDeps,
): Promise<ReviewDecisionResult>;
```

| 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. |
| `locale` | `string` | yes | The target locale the reviewed value belongs to. Must be a configured target locale. |
| `key` | `string` | yes | The key whose value was reviewed. Must exist in the source resource. |
| `expectedValue` | `string` | no | The translation the reviewer saw. The decision is refused when the key's current value is a different one, so a value nobody looked at is never approved or rejected. Pass exactly one of `expectedValue` and `expectedValueHash`. |
| `expectedValueHash` | `string` | no | The hash of the translation the reviewer saw, as the `valueMarker` dependency's ValueMarker.hash produced it, for a caller that only ever saw the value's redaction marker. The decision is refused when the key's current value hashes differently. |
| `reviewer` | `string` | no | Free text naming the reviewer, 1 to 64 characters with no control characters. It is stored in the committed provenance file, so it is public. When it is left out no new name is recorded, and a reviewer already recorded for the same decision on the same value is kept. |
| `onLockWait` | `LockWaitListener` | no | Called while waiting on another process's write lock for the locale, so a caller can explain a stall instead of appearing to hang. Never called for a lock this process holds itself. |
| `lockAcquireTimeoutMs` | `number` | no | How long, in milliseconds, to wait for the locale's write lock before failing with `LOCK_CONTENDED`. Defaults to ten minutes. It does not bound the lock-file guard taken to record the decision, which always allows the ten-minute default. |

**Returns:** `ReviewDecisionResult`

**Throws:** [`REVIEWER_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#reviewer_invalid), [`LOCK_TIMEOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_timeout_invalid), [`UNKNOWN_FORMAT`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_format), [`UNKNOWN_LOCALE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_locale), [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_path_collision), [`SOURCE_UNREADABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_unreadable), [`SOURCE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_invalid), [`UNKNOWN_KEY`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_key), [`LOCK_CONTENDED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_contended), [`RUN_CANCELLED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#run_cancelled), [`REVIEW_VALUE_CHANGED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#review_value_changed), [`LOCALE_STATE_NOT_CARRIED_OVER`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_state_not_carried_over), [`TARGET_UNWRITABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#target_unwritable), [`REVIEW_REJECT_UNSUPPORTED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#review_reject_unsupported), [`REVIEW_RESTORE_FAILED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#review_restore_failed), [`LOCK_FILE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_file_invalid), [`PROVENANCE_FILE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#provenance_file_invalid), [`PROVENANCE_FILE_UNWRITABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#provenance_file_unwritable)

**Example:** [Work through the review queue](https://verbatra.kreitz-webdev.de/docs/programmatic-api.md#work-through-the-review-queue)

The input is checked like `approveEntry`,
except that a stale source does not block a rejection. Returns `{ locale, key, provenance }`. A
format that keeps a key it was not given, XLIFF, throws `REVIEW_REJECT_UNSUPPORTED` after
restoring the file.

Both calls take the locale's write lock with `lockAcquireTimeoutMs` and `onLockWait` as in
`editEntry`; `LOCK_TIMEOUT_INVALID` means the bound is not a whole number of milliseconds of at least
0. The timeout does not bound the lock-file guard taken to record the decision, which always allows
the ten-minute default.

Neither reports a decision it could not save: a provenance file from a newer verbatra, or one the
decision would grow past the size verbatra reads back, throws `PROVENANCE_FILE_UNWRITABLE` before
anything is written.

### createValueMarker [#createvaluemarker]

<AvailableFrom version="0.12.0" />

Creates the marker a surface uses to show a value without its text: `mark(value)` returns
`[redacted length=<n> hash=<h>]`, and `hash(value)` returns `h`, a 16-digit hex HMAC keyed with a
random salt (or the given `salt`), so a hash matches only under the same marker. Pass that marker as
`valueMarker` to `approveEntry` or `rejectEntry` to accept `expectedValueHash` in place of
`expectedValue`. `verbatra mcp --redact-values` creates one per server start.

```ts title="Signature"
function createValueMarker(salt?: Uint8Array): ValueMarker;
```

**Returns:** `ValueMarker`

## Bulk content reads [#bulk-content-reads]

<AvailableFrom version="0.10.0" />

### localeValues [#localevalues]

Reads every key's current source and target text across the requested target locales in one pass
over the files on disk, for instance to search values rather than key names: the bulk counterpart
to `keyValue`. Read-only.

```ts title="Signature"
function localeValues(
  input: LocaleValuesInput,
  deps?: LocaleValuesDeps,
): Promise<readonly LocaleValues[]>;
```

| 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. |
| `locales` | `readonly string[]` | no | Restrict the report to these target locales. Defaults to every configured target locale. |

**Returns:** `readonly LocaleValues[]`

**Throws:** [`UNKNOWN_FORMAT`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_format), [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_path_collision), [`SOURCE_UNREADABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_unreadable), [`SOURCE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_invalid), [`UNKNOWN_LOCALE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_locale)

**Example:** [Read every value in bulk](https://verbatra.kreitz-webdev.de/docs/programmatic-api.md#read-every-value-in-bulk)

An omitted `locales` covers every configured target locale.
Returns one `{ locale, keys, values }` per locale: `keys` lists every key in source order, then the
target-only ones, and `values` maps each key to `{ source?, target? }`. No `target` means not yet
translated there; no `source` means orphaned (gone from the source). `values` has no prototype, so
every key, even `__proto__` or `constructor`, is an own property and an absent key reads as
`undefined`.

Each pair with a `target` also carries `provenance`, the same `KeyProvenance` `keyValue` reports.

### localeValuesPage [#localevaluespage]

<AvailableFrom version="0.12.0" />

Reads one page of `localeValues`, optionally narrowed to exact `keys` or to a `query` matched against key, source and target text, ignoring case, and returns a cursor for the next page. The MCP server and Studio's `verbatra_locale_values` agent tool page with it. Read-only.

```ts title="Signature"
function localeValuesPage(
  input: LocaleValuesPageInput,
  deps?: LocaleValuesDeps,
): Promise<LocaleValuesPage>;
```

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `keys` | `readonly string[]` | no | Keep only these exact key names. |
| `query` | `string` | no | Keep only keys whose name, source text, or target text contains this text, ignoring case. |
| `limit` | `number` | no | The most entries the page holds, a whole number from 1 to PAGE_LIMIT_CAP. Defaults to PAGE_LIMIT_DEFAULT. |
| `cursor` | `string` | no | The `nextCursor` of the previous page. Omit it to read the first page. |
| `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. |
| `locales` | `readonly string[]` | no | Restrict the report to these target locales. Defaults to every configured target locale. |

**Returns:** `LocaleValuesPage`

**Throws:** [`UNKNOWN_FORMAT`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_format), [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_path_collision), [`SOURCE_UNREADABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_unreadable), [`SOURCE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_invalid), [`UNKNOWN_LOCALE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_locale), [`PAGE_CURSOR_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#page_cursor_invalid), [`PAGE_LIMIT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#page_limit_invalid)

Returns `{ locales, nextCursor? }`: each locale with an entry on the page carries `entries`, each `{ key, source?, target?, provenance? }` as `localeValues` reports it, ordered by locale and then by key. A page holds at most `limit` entries: `PAGE_LIMIT_DEFAULT` (200) by default, `PAGE_LIMIT_CAP` (1000) at most. Pass `nextCursor` back as `cursor`, with the same filters, until it is absent. Every call reads the files again, so a cursor whose key moved or is gone is refused rather than skipping or repeating entries.

A `cursor` is at most `PAGE_CURSOR_MAX_LENGTH` (512) characters. The MCP server and Studio accept a `query` of at most `LOCALE_VALUES_QUERY_MAX_LENGTH` (500) characters.

## Batch review and retranslation [#batch-review-and-retranslation]

<AvailableFrom version="0.12.0" />

Each of these runs the matching single-key call once per entry, in order, and returns `{ results }`
with one outcome per entry. A failed entry does not stop the batch: its outcome is
`{ locale, key, ok: false, code, message }`, with the code of the `SdkError`, adapter error, or
`ProviderError` and a redacted message. Any other error stops the batch with a
`BatchInterruptedError`. An empty `entries` list returns an empty `results`.

### approveEntries [#approveentries]

Runs `approveEntry` per entry with the same checks; `reviewer` applies to every entry. A successful
outcome is `{ ok: true, locale, key, provenance }`. `lockAcquireTimeoutMs` bounds each entry's
wait for its locale's write lock, and `onLockWait` reports that wait. An entry whose lock stays
taken past the timeout fails with `LOCK_CONTENDED`, and every later entry that needs the same lock
gets `{ locale, key, ok: false, skipped: true, code: "LOCK_CONTENDED", message }` instead of
waiting again; entries of other locales are still decided.

```ts title="Signature"
function approveEntries(
  input: ReviewEntriesInput,
  deps?: ReviewDecisionDeps,
): Promise<ReviewEntriesResult>;
```

| 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. |
| `entries` | `readonly ReviewBatchEntry[]` | yes | The entries to decide on, in the order they are carried out. |
| `reviewer` | `string` | no | Free text naming the reviewer, applied to every entry, with the rules of ReviewDecisionInput.reviewer. |
| `onLockWait` | `LockWaitListener` | no | Called while an entry waits on another process's write lock for its locale. Never called for a lock this process holds itself. |
| `lockAcquireTimeoutMs` | `number` | no | How long, in milliseconds, each entry waits for its locale's write lock before it fails with `LOCK_CONTENDED`, as ReviewDecisionInput.lockAcquireTimeoutMs does for one entry. Defaults to ten minutes per entry. |

**Returns:** `ReviewEntriesResult`

**Throws:** [`LOCK_TIMEOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_timeout_invalid)

### rejectEntries [#rejectentries]

Runs `rejectEntry` per entry, removing each rejected value. A
successful outcome is `{ ok: true, locale, key, provenance }`. A lock timeout skips the later entries
needing that lock, as in `approveEntries`.

```ts title="Signature"
function rejectEntries(
  input: ReviewEntriesInput,
  deps?: ReviewDecisionDeps,
): Promise<ReviewEntriesResult>;
```

| 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. |
| `entries` | `readonly ReviewBatchEntry[]` | yes | The entries to decide on, in the order they are carried out. |
| `reviewer` | `string` | no | Free text naming the reviewer, applied to every entry, with the rules of ReviewDecisionInput.reviewer. |
| `onLockWait` | `LockWaitListener` | no | Called while an entry waits on another process's write lock for its locale. Never called for a lock this process holds itself. |
| `lockAcquireTimeoutMs` | `number` | no | How long, in milliseconds, each entry waits for its locale's write lock before it fails with `LOCK_CONTENDED`, as ReviewDecisionInput.lockAcquireTimeoutMs does for one entry. Defaults to ten minutes per entry. |

**Returns:** `ReviewEntriesResult`

**Throws:** [`LOCK_TIMEOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_timeout_invalid)

### approveLocale [#approvelocale]

<AvailableFrom version="0.12.0" />

Approves a locale's whole review queue, or only its values with one of `origins`, in one write of
`verbatra.provenance.json` under the locale's write lock, each recorded as `approveEntry` records
one. It takes no expected value and approves what the queue holds when it runs, so show the
reviewer the queue first. Returns `{ locale, approved, sourceChanged }`: the keys approved, and the
queued keys left unreviewed because their source changed since they were written. Calls no
provider, and fails with `PROVENANCE_FILE_UNWRITABLE` rather than report approvals it could not save.

```ts title="Signature"
function approveLocale(
  input: ApproveLocaleInput,
  deps?: ApproveLocaleDeps,
): Promise<ApproveLocaleResult>;
```

| 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. |
| `locale` | `string` | yes | The target locale whose review queue is approved. Must be a configured target locale. |
| `origins` | `array` | no | Approve only the queued values with one of these origins, for a reviewer who has looked at, say, every fuzzy match. Defaults to every MachineClassOrigin. |
| `reviewer` | `string` | no | Free text naming the reviewer, with the rules of ReviewDecisionInput.reviewer. It is stored on every approved record. |
| `onLockWait` | `LockWaitListener` | no | Called while waiting on another process's write lock for the locale. Never called for a lock this process holds itself. |
| `lockAcquireTimeoutMs` | `number` | no | How long, in milliseconds, to wait for the locale's write lock before failing with `LOCK_CONTENDED`. Defaults to ten minutes. |

**Returns:** `ApproveLocaleResult`

**Throws:** [`REVIEWER_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#reviewer_invalid), [`LOCK_TIMEOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_timeout_invalid), [`UNKNOWN_FORMAT`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_format), [`UNKNOWN_LOCALE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#unknown_locale), [`LOCALE_LAYOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_layout_invalid), [`LOCALE_PATH_COLLISION`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_path_collision), [`SOURCE_UNREADABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_unreadable), [`SOURCE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#source_invalid), [`LOCK_CONTENDED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_contended), [`RUN_CANCELLED`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#run_cancelled), [`LOCALE_STATE_NOT_CARRIED_OVER`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#locale_state_not_carried_over), [`LOCK_FILE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_file_invalid), [`PROVENANCE_FILE_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#provenance_file_invalid), [`PROVENANCE_FILE_UNWRITABLE`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#provenance_file_unwritable)

### retranslateEntries [#retranslateentries]

Runs `retranslateEntry` per entry, so it makes up to one provider call per entry. It is not a
`translate` run: `maxTokens` and `budgetBehavior` do not apply, and the number of entries is the
only bound on what it spends. A completed outcome is `{ ok: true, locale, key, result }`, where
`result` is what `retranslateEntry` returned, including a value the integrity gate refused. A value
a person wrote fails with `KEY_PROTECTED` unless `includeHuman: true` is passed. The lock options
work as in `approveEntries`, and the wait ends before the provider is called.

```ts title="Signature"
function retranslateEntries(
  input: RetranslateEntriesInput,
  deps?: RetranslateEntryDeps,
): Promise<RetranslateEntriesResult>;
```

| 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. |
| `entries` | `readonly BatchEntry[]` | yes | The entries to retranslate, in the order they are carried out. |
| `includeHuman` | `boolean` | no | Replace values a person wrote too, as RetranslateEntryInput.includeHuman does for one entry. Defaults to false. |
| `onLockWait` | `LockWaitListener` | no | Called while an entry waits on another process's write lock for its locale. Never called for a lock this process holds itself. |
| `lockAcquireTimeoutMs` | `number` | no | How long, in milliseconds, each entry waits for its locale's write lock before it fails with `LOCK_CONTENDED`, as RetranslateEntryInput.lockAcquireTimeoutMs does for one entry. The wait happens before the provider is called. Defaults to ten minutes per entry. |

**Returns:** `RetranslateEntriesResult`

**Throws:** [`LOCK_TIMEOUT_INVALID`](https://verbatra.kreitz-webdev.de/docs/error-codes.md#lock_timeout_invalid)

<AvailableFrom version="0.12.0" />

An error that would fail every entry alike stops the provider calls: `MISSING_API_KEY`,
`AUTH_FAILED`, `RATE_LIMITED` (which covers an exhausted quota), `NETWORK_POLICY_VIOLATION`,
`PROVIDER_CONSTRUCTION_FAILED`, `CONFIG_INVALID`, or `MACHINE_TRANSLATION_DISABLED`. The entry that
hit it fails as usual, and every entry after it gets `{ locale, key, ok: false, skipped: true,
code, message }` naming that code, without another provider call.

A lock timeout skips the later entries needing that lock with `LOCK_CONTENDED`, as in
`approveEntries`; for a format that keeps all locales in one file, that is every later entry.

### BatchInterruptedError [#batchinterruptederror]

<AvailableFrom version="0.12.0" />

Thrown by `approveEntries`, `rejectEntries`, and `retranslateEntries` when an error that is not the
outcome of one entry, such as an unexpected file-system failure, stops a batch part way. `results`
holds the outcomes of the entries completed before it, in order, whose writes stay on disk; `entry`
names the `{ locale, key }` that was running, and `cause` is the original error. Entries after
`entry` did not run.

```ts title="Signature"
class BatchInterruptedError<R = ReviewBatchOutcome | RetranslateBatchOutcome> extends Error
```

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `results` | `readonly R[]` | yes | The outcomes of the entries completed before the batch stopped, in the order given. |
| `entry` | `BatchEntry` | yes | The entry the batch was carrying out when it stopped. |
