The lock file
What verbatra.lock.json records, how drift is detected, how concurrent runs are serialized, and why you commit it.
The lock file, verbatra.lock.json, exists so a run can tell what already got translated and skip
it: it is the baseline every run diffs against, and it is what makes verbatra incremental. This
page covers what it stores, how it changes, and what happens when it is contended, missing, or
corrupt.
What it records
For every target locale, the lock file stores one content hash per translated key, computed from
the source value that produced the existing translation. It contains no translations and no
secrets: just a version field and hashes. The hash normalizes Unicode to NFC and line endings to
LF, so re-saving a source file with different normalization or CRLF endings does not mark anything
as changed.
How drift is detected
When a run diffs a target locale, a source key absent from the target is missing. For a key the target does have, the current source hash is compared against the hash the lock recorded: a difference means the source drifted since the key was last translated, so it is changed and gets retranslated; a match means it is unchanged and never sent to the provider. That is why a second run with no source edits calls the provider for nothing, and why stale translations cannot hide behind a key that merely exists. The full pipeline is on How it works.
Adopting an existing project
Pointing verbatra at a repository whose locale files already hold reviewed translations does not retranslate them. On the first run there is no lock file, so no key can be detected as stale: a source key the target file already has is unchanged, is never sent to the provider, and keeps its existing value. Only keys the target file does not have are missing, and only those are translated. When nothing was accepted, pruned, or generated for a locale, its existing target file is not rewritten at all.
The run still records the current source hash for every key the target holds, so the first run is the adoption step: it establishes the baseline at zero provider cost, and from the second run on, source edits show up as drift the normal way.
Adoption is decided by key presence, never by value, which has two consequences worth knowing before that first run:
- An empty or untranslated value counts as translated. A target value that is an empty string, or that is still the untranslated source text, is a key that exists, so it is unchanged and never filled in. Scaffolding tools that pre-create locale files with empty string values run straight into this. The fix is to delete the key from the target file: emptying its value does not help, because an empty string is still an entry. Once the key is gone, the next run sees it as missing and translates it.
- A translation that was already out of date is recorded as current. The lock takes the source hash as it is now, so a translation that had already drifted from the source before you adopted the project is taken as up to date and will not be revisited. Fix or delete those keys before the first run.
Plural generation follows the same adoption rule. A plural form the target file already holds and
that verbatra did not generate itself is kept as it is, never regenerated and never sent to the
provider (a form the lock already tracks is still regenerated when its source changes), so
generatePlurals is safe to leave on while adopting a project that carries hand-written plural
forms; only forms genuinely absent from the target are synthesized.
You can see all of this before spending anything: verbatra diff lists the exact missing and
changed keys per locale, verbatra check reports the same as counts, and
verbatra translate --dry-run gives you the full run summary. None of the three calls a provider,
writes a locale file, or writes the lock.
How it is updated
The lock is updated per locale, and how depends on what ran:
- A full run (translate, watch, or a workbook import) replaces that locale's entries wholesale with the run's authoritative result.
- A single-key action (such as a retranslate from Studio) merges only that key's entry, leaving every other recorded key untouched.
In both cases one exception applies: a key withheld this run (a failed integrity check, a failed provider call, or an invalid-ICU source) keeps its prior hash, so the next run still sees it as needing work and retries it instead of recording it as done. Orphaned keys get no entry. The file is serialized with sorted keys, so its diffs stay stable and reviewable.
The write lock
Two runs touching the same locale at the same time (a second terminal, a CI job, a Studio action) could otherwise both diff a stale baseline and both pay for the same provider call. To prevent that, every write to a locale and its lock entries happens under a cross-process write lock scoped to that one locale; a second writer for the same locale waits, then re-reads the lock file fresh and diffs against a baseline that already includes the first writer's result. Different locales do not block each other.
When a run translates several locales at once and hits a whole-run error (in practice a corrupt lock file), it stops claiming new locales and waits for the ones already running to finish, so every lock the run took is released before the command exits. No locale is started after the run has already failed, and no lock is orphaned by the failure itself.
If a lock cannot be acquired, the run fails with LOCK_CONTENDED and names the lock file's path
(under the gitignored .verbatra-local/ directory). If no verbatra process is running, that file
was left behind by a killed one: delete it and retry.
Missing or corrupt
A missing lock file is not an error: it is a first run, and with no baseline nothing can be
detected as stale. Source keys the target file does not have are missing and get translated; keys
it does have are unchanged and are left untouched, as described under adoption above.
A lock file that is present but unparseable, structurally wrong, oversized, or at an unsupported
version fails the run with LOCK_FILE_INVALID instead of being silently overwritten, so you can
inspect or restore it rather than losing the baseline.
Commit it
Commit verbatra.lock.json alongside your locale files. It is the shared baseline your teammates
and your CI diff against; keeping it in version control is what makes incremental runs reproducible
across machines. Never hand-edit it.
Committing both is also what makes a rollback possible. Because the lock records source hashes and nothing about the translated values, reverting your locale files without this file leaves it claiming the reverted translations are current, and nothing will retranslate them. See Recovery and rollback.
Edit on GitHub