---
title: "Die Lock-Datei und die Herkunft"
description: "Was verbatra.lock.json und verbatra.provenance.json festhalten, wie Drift erkannt wird, wie parallele Läufe serialisiert werden und warum du beide committest."
type: "concept"
---
# Die Lock-Datei und die Herkunft

Was verbatra.lock.json und verbatra.provenance.json festhalten, wie Drift erkannt wird, wie parallele Läufe serialisiert werden und warum du beide committest.

`verbatra.lock.json` ist die Baseline, gegen die jeder Lauf vergleicht, sodass ein Lauf Aktuelles
überspringen und Abgedriftetes neu übersetzen kann. Daneben hält
[`verbatra.provenance.json`](#herkunft-wer-welchen-wert-geschrieben-hat) fest, wer oder was jeden
Wert geschrieben hat.

## Was sie festhält [#was-sie-festhält]

Für jede Ziel-Locale einen Hash pro übersetztem Key, gebildet aus dem Quelleintrag hinter der
aktuellen Übersetzung, plus ein Feld `version`: keine Übersetzungen, keine Secrets. Der Hash deckt
den Quellwert samt Beschreibung, Bedeutung, Plural-Flag und Platzhaltern ab, eine geänderte
Beschreibung macht den Key also ebenfalls veraltet. Er normalisiert Unicode auf NFC und Zeilenenden
auf LF, ein erneutes Speichern einer Quelldatei mit CRLF-Zeilenenden ändert also nichts.

## Wie Drift erkannt wird [#wie-drift-erkannt-wird]

Ein Quell-Key, der im Ziel fehlt, ist missing. Bei einem vorhandenen Key wird der aktuelle
Quell-Hash mit dem festgehaltenen verglichen: Ein Unterschied heißt, die Quelle hat sich geändert
und der Key wird neu übersetzt; eine Übereinstimmung heißt, er wird nie gesendet, ein zweiter Lauf
ohne Änderungen an der Quelle kostet also nichts. Die ganze Pipeline steht unter
[Wie ein Lauf funktioniert](https://verbatra.kreitz-webdev.de/de/docs/how-it-works.md).

### Geschützte Keys [#geschützte-keys]

<AvailableFrom version="0.12.0" />

Die Ausnahme ist ein geänderter Key, dessen Wert ein Mensch geschrieben oder importiert hat: Er ist
standardmäßig [geschützt](https://verbatra.kreitz-webdev.de/de/docs/protecting-human-translations.md) und bleibt veraltet, statt neu
übersetzt zu werden.

## Ein bestehendes Projekt übernehmen [#ein-bestehendes-projekt-übernehmen]

Locale-Dateien, die schon reviewte Übersetzungen enthalten, werden nicht neu übersetzt. Beim ersten
Lauf gibt es keine Lock-Datei, also kann nichts veraltet sein: Ein Key, den das Ziel hat, behält
seinen Wert, nur fehlende Keys werden übersetzt, und der Lauf hält jeden Quell-Hash als Baseline
fest, ohne Provider-Kosten.

Die Übernahme richtet sich nach dem Vorhandensein eines Keys, nie nach seinem Wert, und das hat zwei
Folgen:

- **Ein leerer oder unübersetzter Wert zählt als übersetzt.** Lösch den Key (ihn zu leeren reicht
  nicht), und der nächste Lauf übersetzt ihn.
- **Eine veraltete Übersetzung wird als aktuell festgehalten.** Korrigiere oder lösch sie vor dem
  ersten Lauf.

Dieselbe Regel behält handgeschriebene Pluralformen bei, `generatePlurals` ist bei der Übernahme also
sicher. Um all das vorab zu sehen, führ [`verbatra diff`](https://verbatra.kreitz-webdev.de/de/docs/cli/diff.md) oder `verbatra translate --dry-run` aus.

## Wie sie aktualisiert wird [#wie-sie-aktualisiert-wird]

- Ein vollständiger Lauf (`translate`, `watch` oder ein Import) ersetzt die Einträge einer Locale
  durch das Ergebnis des Laufs.
- Eine Aktion für einen einzelnen Key, etwa ein Retranslate aus Studio, mergt nur den Eintrag dieses
  Keys.

In beiden Fällen behält ein Key, der in diesem Lauf zurückgehalten wurde (eine gescheiterte
Integritätsprüfung oder ein gescheiterter Provider-Aufruf, eine Quelle mit ungültigem ICU), seinen
bisherigen Hash, sodass der nächste Lauf ihn erneut versucht. Verwaiste Keys bekommen keinen
Eintrag, und sortierte Keys halten die Diffs klein.

## Die Schreibsperre [#die-schreibsperre]

Zwei Läufe, die gleichzeitig dieselbe Locale anfassen, könnten beide gegen eine veraltete Baseline
vergleichen und doppelt bezahlen, deshalb hält jeder Schreibvorgang eine prozessübergreifende
Sperre für diese Locale unter `.verbatra-local/`. Ein zweiter Schreiber wartet und vergleicht dann
gegen eine Baseline, die das Ergebnis des ersten enthält. Verschiedene Locales blockieren sich nie
gegenseitig.

Was die Sperre garantiert:

- **Ein Timeout kostet nichts.** `--lock-timeout` begrenzt nur das Warten vor jedem
  Provider-Aufruf, eine Locale, die aufgibt, hat also nichts ausgegeben und nichts geschrieben; das
  Festhalten einer geschriebenen Datei wartet die vollen zehn Minuten des Standards.
- **Verwaiste Sperren blockieren dich nicht.** Eine Sperre, deren Prozess auf diesem Rechner nicht
  mehr existiert, wird sofort übernommen, und eine, die 30 Sekunden lang nicht aufgefrischt wurde
  (Halter frischen alle 10 auf), gilt als verwaist.
- **Ein Abbruch gibt die Sperren frei.** Ctrl-C oder SIGTERM während `translate` oder `import` und
  der zweite Abbruch, der `watch` zwangsweise beendet, geben jede Sperre frei, bevor der Prozess
  endet.
- **Eine verlorene Sperre stoppt den Schreibvorgang.** Jede Sperre trägt ein zufälliges Token, das
  vor jedem Schreibvorgang geprüft wird; hat ein anderer Lauf die Sperre übernommen,
  stoppt der Schreibvorgang mit [`LOCK_CONTENDED`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#lock_contended), und nichts
  wird geschrieben.

Was sie nicht abdeckt:

- **Eine Sperre von einem anderen Rechner** (ein geteiltes Netzlaufwerk) oder von einer
  verbatra-Version vor 0.12.0 wird nie automatisch übernommen; wie du sie entfernst, steht unter
  [Fehlerbehebung](https://verbatra.kreitz-webdev.de/de/docs/troubleshooting.md).
- **Eine lange Pause zwischen Prüfung und Schreiben.** Ein Lauf, der direkt nach der Token-Prüfung
  pausiert (ein schlafender Rechner, ein angehaltener Prozess), kann nach dem Fortsetzen noch einmal
  schreiben.
- **Hosts mit gleichem Namen.** Außerhalb von Linux ist der Hostname die einzige Rechneridentität,
  zwei Container mit einem Hostnamen, die sich `.verbatra-local/` teilen, können also die lebende
  Sperre des anderen für verwaist halten. Gib jedem seinen eigenen Hostnamen
  oder seine eigene Arbeitskopie.

## Ein umgeschriebener Locale-Code [#ein-umgeschriebener-locale-code]

<AvailableFrom version="0.12.0" />

Die Lock-Datei, der Cache und die Herkunftsdatei ordnen ihren Zustand nach dem Locale-Code deiner
Konfiguration. Schreibst du einen um, etwa `pt_BR` zu `pt-BR`, überträgt der nächste Lauf von
`translate` oder `watch` den alten Zustand einmalig und meldet
[`LOCALE_STATE_CARRIED_OVER`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#locale_state_carried_over). Eine Quelländerung vor
der Umbenennung wird trotzdem neu übersetzt, und gecachte Übersetzungen werden weiter
wiederverwendet.

Die Übertragung passiert nur, wenn eindeutig: `pt-BR` hat keinen eigenen Zustand, und die
Datei hält Zustand unter genau einer Unterstrich-Schreibweise davon (oder seiner gettext-Schreibweise,
etwa `sr@latin` für `sr-Latn`), ohne Beachtung der Groß- und Kleinschreibung. Zustand, den `pt-BR`
schon hat, wird nie überschrieben. Lässt sich die Übertragung nicht schreiben, läuft diese Locale
nicht und scheitert mit
[`LOCALE_STATE_NOT_CARRIED_OVER`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#locale_state_not_carried_over), sodass der
nächste Lauf es erneut versucht; die anderen Locales laufen wie gewohnt.

Bis dahin behandelt jedes Lesen den alten Zustand als den von `pt-BR`, ohne zu schreiben, ein
veralteter Key wird also als veraltet gemeldet. Ein Schreibvorgang für einen einzelnen Key (eine Bearbeitung, ein
Retranslate, eine Review-Entscheidung) und `verbatra import` übertragen den Zustand vorher auf
dieselbe Weise. Die Prüfung `locale-state` von [`verbatra doctor`](https://verbatra.kreitz-webdev.de/de/docs/cli/doctor.md) nennt
zurückgebliebenen Zustand.

## Fehlend oder beschädigt [#fehlend-oder-beschädigt]

Eine fehlende Lock-Datei ist ein [erster Lauf](#ein-bestehendes-projekt-übernehmen), kein Fehler.
Eine, die sich nicht parsen lässt, strukturell falsch, zu groß
oder in einer nicht unterstützten Version ist, lässt den Lauf mit
[`LOCK_FILE_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#lock_file_invalid) scheitern, statt überschrieben zu werden,
sodass du sie wiederherstellen kannst, statt die Baseline zu verlieren.

## Herkunft: wer welchen Wert geschrieben hat [#herkunft-wer-welchen-wert-geschrieben-hat]

<AvailableFrom version="0.12.0" />

`verbatra.provenance.json` hält fest, welcher Schreibpfad jeden Wert erzeugt hat, der jetzt in der
Locale-Datei steht, und ob ihn jemand reviewt hat. Eine ältere verbatra-Version, etwa eine in CI
gepinnte, ignoriert sie.

| Herkunft | Geschrieben von |
| --- | --- |
| `machine` | einer Provider-Antwort in `translate`, `watch` oder einem Retranslate eines einzelnen Keys, mit der `id` des Providers und, wenn bekannt, dem Modell |
| `memory` | einem exakten Cache-Treffer, einschließlich jedes Werts, den ein reiner Handarbeits-Lauf (Provider `none`) füllt |
| `fuzzy` | einem Treffer der [unscharfen Wiederverwendung](https://verbatra.kreitz-webdev.de/de/docs/the-cache.md#unscharfe-wiederverwendung) |
| `human` | der Bearbeitung eines Menschen über `editEntry` aus dem SDK, wie sie der Bearbeitungsdialog in Studio macht |
| `agent` | der Bearbeitung eines KI-Agenten über `editEntry` mit `actor: "agent"` |
| `import` | einer angenommenen Zeile aus `verbatra import` |
| `unknown` | einer Review-Entscheidung zu einem Wert ohne Eintrag, oder einer Herkunft, die dieses Release nicht kennt |

Zwei weitere werden beim Lesen abgeleitet: `unrecorded` ist ein Wert ohne Eintrag, wie jeder Key
nach einem Upgrade; `external` passt nicht mehr zum Hash seines Eintrags, weil ihn etwas außerhalb
von verbatra geändert hat, etwa der Pull Request eines Übersetzers, ein älteres verbatra oder ein Merge. Die Felder, die jedes Lesen liefert,
stehen in der [SDK-Referenz](https://verbatra.kreitz-webdev.de/de/docs/sdk/inspect.md#loadprovenance).

Was die Datei garantiert:

- **Stabile Diffs.** Kein übersetzter Text und keine Zeitstempel, ein sortierter Eintrag pro Zeile,
  ein Lauf, der nichts ändert, lässt die Datei also unverändert. Bei einem Merge-Konflikt nimm die
  Seite, die zur behaltenen Locale-Datei passt: Ein abweichender Eintrag liest sich als `external`,
  was den Wert vor Neuübersetzung schützt.
- **Frische Einträge für neue Werte.** Ein Schreibvorgang, der einen Wert ändert, ersetzt seinen
  Eintrag durch einen neuen, nicht reviewten; einer, der Wert und Quelle unverändert lässt, behält
  ihn.
- **Kein stilles Überschreiben.** Eine beschädigte Datei stoppt jeden Schreibvorgang mit
  [`PROVENANCE_FILE_INVALID`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#provenance_file_invalid), bevor sich eine
  Locale-Datei ändert; Berichte lassen ihre Herkunftsfelder stattdessen weg. Eine Datei von einer neueren
  verbatra-Version bleibt unangetastet, und eine, die zu groß zum Zurücklesen würde, wird nicht
  geschrieben
  ([`PROVENANCE_FILE_TOO_LARGE`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#provenance_file_too_large)).

Sie ist nicht fälschungssicher; um daraus einen Bericht zu machen, siehe
[Nachweis maschineller Übersetzungen festhalten](https://verbatra.kreitz-webdev.de/de/docs/provenance-evidence.md).

### Review-Entscheidungen [#review-entscheidungen]

<AvailableFrom version="0.12.0" />

Ein Eintrag kann eine Review-Entscheidung tragen, `approved` oder `rejected`, mit einem optionalen
Namen unter `reviewer`. [Studios Review-Seite](https://verbatra.kreitz-webdev.de/de/docs/review-in-studio.md), die [Review-Funktionen des SDK](https://verbatra.kreitz-webdev.de/de/docs/sdk/review.md) und die
[MCP-Review-Tools](https://verbatra.kreitz-webdev.de/de/docs/cli/mcp.md) schreiben sie; die MCP-Tools geben die Entscheidung eines Menschen
weiter und verlangen dessen Namen. Eine Entscheidung gilt für genau
den Wert, den der Reviewer gesehen hat, und wird abgelehnt, wenn er sich geändert hat.

- **Eine Freigabe** ändert nur die Herkunftsdatei und liest sich wieder als nicht reviewt, sobald
  sich der Quelltext ändert.
- **Eine Ablehnung** entfernt den Wert und seinen Lock-Eintrag, sodass der Key als missing gilt, bis
  der nächste Lauf von `translate` oder ein Mensch einen neuen Wert schreibt. Der Eintrag behält
  einen Hash des entfernten Texts und der Cache-Eintrag wird verworfen, damit ein Lauf nicht denselben Text zurückbringt. XLIFF kann keine einzelne
  Übersetzung weglassen, eine Ablehnung wird dort also mit
  [`REVIEW_REJECT_UNSUPPORTED`](https://verbatra.kreitz-webdev.de/de/docs/error-codes.md#review_reject_unsupported) abgelehnt.

Nur Werte der Maschinenklasse landen in der Review-Warteschlange und im `--require-reviewed`-Gate. Die `byReviewState`-Zähler in `check` und `lock.state` erfassen jeden Wert nach seiner aufgezeichneten Entscheidung, gleich welcher Herkunft: Ein von einer Person geschriebener oder importierter Wert ohne Entscheidung zählt dort als `unreviewed`, ohne in der Warteschlange zu stehen. Lies sie zusammen mit `byOrigin`.

Die Entscheidungen stehen in dieser committeten Datei, also sehen die Review-Warteschlange und
[`verbatra check --require-reviewed`](https://verbatra.kreitz-webdev.de/de/docs/cli/check.md#freigegebene-übersetzungen-verlangen) auf
jedem Rechner denselben Zustand, und der Name unter `reviewer` ist öffentlich; verbatra trägt ihn
nie ein.

## Committe sie [#committe-sie]

Committe `verbatra.lock.json` und `verbatra.provenance.json` mit deinen Locale-Dateien und
bearbeite sie nie von Hand: sie sind die Baseline, gegen die dein Team und deine CI vergleichen, und das, was ein [Rollback](https://verbatra.kreitz-webdev.de/de/docs/recovery-and-rollback.md) wiederherstellt.
