Validation & quality

# 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.

```ruby
{
  "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.

> **Note:** Run `lingo sync` once when you first add locallingo to an app with existing translations — otherwise every key looks 'outdated' until its first translate.