Drift & state
Per-key source hashes are how locallingo knows what changed — and what's safe to skip.
The idea#
Re-translating everything on every run is slow and expensive. locallingo instead records a small hash of the English value each key was translated from. On the next run it re-hashes the current source: if the hash still matches, the translation is up to date and is skipped; if it differs, the source text changed and the translation is outdated.
The result is that translate only touches keys that are genuinely missing or changed, and validate can tell you exactly which translations drifted.
The state files#
State lives under state_dir (default .i18n-state/), split into one JSON file per top-level namespace and locale — accounts.de.json, admin.sv.json — so diffs stay small and reviewable. Commit them: they're how CI and your teammates share the same view of what's current.
{
"accounts.show.title": { "source_hash": "3f2a9c11" }
}Detecting drift#
When you edit an English string, its hash changes. The next lingo validate compares the stored source_hash against the current value and reports the key as outdated for every target locale — so a source edit can't silently leave stale translations behind.
Protecting manual edits#
With validators.manual_edits enabled, state entries also carry a target_hash and a manual flag. If someone hand-tunes a translation, its target hash no longer matches and validate flags a manual_edit. Running lingo accept-edits stamps the current values as intentional (setting manual: true), and translate --force then leaves them alone.
Rebuilding state#
Adopting locallingo on an existing app, or making a batch of manual edits, can leave the state out of step with the files. lingo sync rewrites the state from the current translations so nothing reads as spuriously outdated.
lingo sync once when you first add locallingo to an app with existing translations — otherwise every key looks 'outdated' until its first translate.