Skip to content
Skip to Content

Docs / Concepts

The translation cache

What verbatra.cache.json reuses between runs, how a fingerprint scopes each entry to its config, and why a cache problem never fails a run.

The translation cache, verbatra.cache.json, is a local translation memory keyed by content: it remembers the translation of a piece of source text, whichever key holds it, so the same text is never paid for twice. The lock file answers "is this key up to date?"; the cache answers "have we translated this text before?".

What it reuses

Before a key goes to the provider, verbatra looks up the hash of its current source entry: the text plus its description, meaning, plural flag and placeholders. A hit is free. Because the lookup is by content, reuse survives what the lock file cannot see:

  • A renamed key. Move home.title to landing.title and its translation is reused.
  • Duplicate content. Two keys with the same source text and context share one translation.

A reused value is not trusted blindly. It passes the same integrity gate against the current source as a fresh translation, and one that no longer fits is discarded and translated anew. Cache hits are reported per locale, apart from the keys translated this run.

A value a reviewer rejected is never reused for that key: the provenance file keeps a hash of the refused text, and a run skips any hit that matches it. The one gap is a provenance file the run cannot read (one from a newer verbatra, or a corrupt one under humanEdits: "overwrite"); the run then knows no rejection.

The fingerprint

Every entry is scoped to the context it was produced in: a fingerprint over the provider, the model, the tone, the glossary terms of that locale, and the provider's localeMap when it has entries. Change any of them and earlier entries stop matching, so a new tone or an edited glossary retranslates instead of serving a stale result. The glossary part is the terms verbatra would send to the provider, after any sensitiveData setting that withholds or redacts a term, whether or not the configured provider uses glossary terms (DeepL, Google Cloud Translation and LibreTranslate ignore them), so an edited term still retranslates. A term's caseSensitive flag is not part of it. Each locale has its own fingerprint, so editing only the German translation of a glossary term leaves the French entries matching.

The format is not part of the fingerprint; the integrity gate on every hit catches a value that no longer fits. With provider none, every entry goes into one human-only bucket that tone and glossary do not affect.

Fuzzy reuse

An exact hit needs the source text to hash to the same value as before, so editing one character makes it a miss. fuzzyCache is an opt-in fallback for that case: when a key misses, verbatra scores its source against the other entries of the same locale and fingerprint, and reuses the closest one at or above threshold. The score is one minus the character edit distance divided by the length of the longer string, so 1 is identical.

The threshold is not a safety setting

Character distance is not meaning. Dropping a "not", turning "cannot" into "can" or swapping one brand name for another are small edits that score above 0.9 and reverse what the sentence says. The threshold decides how much provider spend you avoid, not how safe a reuse is. Safety comes from rules that do not depend on the number:

  • It is off unless you turn it on.
  • Every fuzzy reuse is flagged for review with FUZZY_CACHE_REUSE, on every run, whatever it scored. If nobody works the review queue, leave it off.
  • Changed numerals refuse the reuse. If the numerals in the two sources differ in count, value or order, the entry is not a candidate. This covers every Unicode numeric character (m² to m³, ½ to ¼), but not numbers written as words.
  • A reuse does not settle the key. The lock keeps the key's old baseline, so it stays changed: every later run offers it again at no provider cost, and check keeps reporting it, until a real translation lands.
  • An unchanged source text never reaches the fuzzy layer. Editing only a description or a plural flag sends the key to the provider as usual.

What a fuzzy reuse still is not

A fuzzy reuse is a translation of text that is no longer the source, so it is never presented as an exact hit:

  • It passes the integrity gate like any other candidate; a near match whose placeholders no longer fit is refused, and the key goes to the provider.
  • It is reported apart from exact hits, as fuzzy-reused, with the key, the score and the earlier source text; the SDK carries the same on LocaleSummary.fuzzyHits.
  • It is never written back into the cache, so a later run cannot mistake it for an exact hit.

Scoring stays inside one fingerprint and is bounded: at most 256 candidates per key, and source text longer than 2,000 characters is skipped. With provider none, a fuzzy match is never applied.

The file

verbatra.cache.json is a local, regenerable file next to your lock file. Keep it out of version control: it is a cost optimization, not a shared baseline. verbatra init gitignores it, and translate, watch, import, tmx import and pseudo add the entry to an existing .gitignore that lacks it and say so on stderr.

The cache never fails a run. A missing, corrupt or oversized file becomes an empty cache and is rebuilt. A file from a newer verbatra is also treated as empty for the run, but left on disk untouched, with a notice. To start over, delete the file. For fuzzy scoring the file stores each source string beside its translations, so it grows roughly to the size of your source text.

Moving a memory between tools

verbatra tmx imports a TMX file from another translation tool into the cache and exports the cache as TMX. Imported units are stored under the current fingerprint, so they stop matching when the provider, model, tone, the glossary terms verbatra would send or localeMap changes, like everything else. One caveat to "delete it and it rebuilds": an imported memory is only as recoverable as the TMX file it came from, so keep that file.

Bypassing it

--no-cache on translate or watch (cache: false in the SDK) makes exactly the provider calls a run without a cache would, fuzzy reuse included, and leaves the file untouched. To keep exact reuse and drop only the fuzzy fallback, leave fuzzyCache out of the config. A dry run never writes the cache, and reads it only with provider none, so that plan matches a live run.

Limitation

Plural forms synthesized by generatePlurals are not cached, so they always come from the provider.

Edit on GitHub

On this page