Reference / CLI
verbatra diff
Show the keys a run would add or re-translate per locale, and the orphaned keys it would leave, without writing files or calling a provider.
List the exact pending keys per target locale: the ones a run would add, the ones it would re-translate, and the ones now orphaned. diff runs the same read and diff as verbatra translate, then stops: no provider call, no file written, no lock file update, no API key needed.
Synopsis
verbatra diff [flags]Examples
# list the pending keys per locale (exit 1 if any are pending)
verbatra diff
# only the German and French locales
verbatra diff --locales de,fr
# machine-readable key lists for tooling or a PR comment
verbatra diff --json
# also list the source keys your code no longer names
verbatra diff --unusedFlags
| Flag | Argument | Default | Effect |
|---|---|---|---|
--locales | <list> | all configured targets | diff only this comma-separated subset; an unconfigured locale fails with UNKNOWN_ and an empty list with INVALID_, both exit 2 |
--unused | none | off | also list the source-locale keys no call site under the extract roots names; see Unused source keys |
Also takes the global flags --cwd, --config and --json.
Behavior
Pending and orphaned keys
Each locale lists four kinds of key:
| List | Meaning |
|---|---|
missing (shown as add) | in the source, absent from the target: a run would add it |
changed (shown as re-translate) | the source text changed since the last translation: a run would re-translate it |
orphaned | in the target, gone from the source: reported only |
emptySource (shown as empty source) | the source value is empty or whitespace only: nothing to translate until you write it, never pending |
A locale has pending changes when it has at least one missing or changed key. Orphaned keys never count as pending, because a default run leaves them in place; only verbatra translate --prune removes them. Where verbatra check gives the counts, diff gives the key lists.
Unused source keys
--unused answers a different question from orphaned: which keys in the source locale file does your application no longer name? It scans the roots of the extract block with the reader verbatra extract uses and lists every source key no static reference names. The two lists are never merged. The scan stays read-only: nothing is removed, and no API key is read. Deleting a key is your call.
The scan models the i18next runtime. These count as a reference:
- a
t(...),$t(...)or<object>.t(...)call anywhere under the roots, also through an alias such asconst { t: translate } = useTranslation(),const tr = i18n.t,t.bind(i18n)ori18n.getFixedT(...); - a static
keyPrefixor thirdgetFixedTargument, which also covers the prefixed form of every static key in that file, and a statici18nKeyonTransor in an object; - a namespace-qualified key (
common:nav.homecoversnav.hometoo), a natural-language key, and every entry of a static key array; - a plural or context variant of a referenced key (
t("items", { count })coversitems_andone items_), and every key under a referenced parent.other
Keys are compared in the catalog format's own encoding, so a flat dotted JSON key, a gettext plural or msgctxt entry, and an Android <plurals> entry all match the key your code names. A template literal with a static head, such as t(`nav.${page}`), lists the keys under nav. as possiblyDynamic, apart from unused, and never fails the run.
The report carries a status: complete (every reachable key was bounded, so unused is trustworthy), unreliable (a listed key may still be in use; unreliableBecause names each reason with its count and sites) or not-run (nothing is listed; reason says why):
| Reason | status | When |
|---|---|---|
dynamic-keys | unreliable | a key with no static part: t(key), a template literal with no static head, or a concatenation |
dynamic-key-prefix | unreliable | a keyPrefix or getFixedT prefix that is not a static string |
trans-without-key | unreliable | a Trans element without a static i18nKey |
aliased-translate-function | unreliable | t assigned to anything but a plain variable, such as this.tr = t |
translate-function-escapes | unreliable | a translate function used anywhere but a direct call, its binding, a local alias, t={t} on Trans or Translation, or a hook dependency array: renderRow(t), { t }, return t, export default t |
unrecognised-translate-source | unreliable | a t or $t from a source the scan does not model, such as a wrapper hook returning useTranslation(...), hook options built with a spread, or an exported getFixedT result |
template-files-not-scanned | unreliable | .vue, .svelte, .html, .htm, .astro, .hbs, .handlebars, .ejs, .pug, .njk, .liquid, or .mdx files under the roots (the first 20 are listed, all are counted) |
incomplete-scan | unreliable | a file that is too large, unreadable, or not readable to its end (also an unclosed JSX element or a quoted string running into the end of its line), or a directory that cannot be listed |
EXTRACT_ | not-run | no extract block |
EXTRACT_ | not-run | an SDK caller's file system cannot list directories |
FRAMEWORK_ | not-run | the format is next-intl-json, vue-i18n-json, or ngx-translate-json, whose runtimes the scan does not model |
NO_ | not-run | there is no source file under the roots |
NO_ | not-run | the scanned files name no key and hold no dynamic key site while the catalog is not empty, which usually means the roots miss your code |
The recognised translate sources are useTranslation(...) destructured into a local, not exported binding with static arguments; a local i18n.getFixedT(...) with static arguments; a direct call on a member t (i18n.t("key"), this.props.t("key")) and its local alias; <Translation>{(t) => ...}</Translation>; withTranslation("ns")(Component) with props.t(...); and import { t } from "i18next". Any variable or member named t or $t is judged, so an unrelated frame.t + 1 can make a report unreliable. In .tsx, .jsx and .js files, JSX text and quoted attribute values are read as markup, never as code.
Each listed key shows its decoded spelling (a gettext msgctxt entry as menu|Open); --json also carries the catalogKey as the adapter spells it. A key used only outside the scanned source (a server template, a CMS) belongs in extract.unused.ignore: a pattern matches the decoded key, and a matched key is listed under ignored, never dropped.
Protected keys
Missing and changed keys that translate would leave for a person (a person wrote, imported or changed the value outside verbatra, or the key matches pinnedKeys) are listed again under protected; see Protecting human translations. They stay in add and re-translate, since they are still pending. When verbatra.provenance.json is corrupt or from a newer verbatra, only pinned keys are listed.
JSON output
Schema: diff-envelope.json validates the whole envelope; Schemas lists the others.
--json prints one envelope whose result is a DiffSummary. hasPendingChanges is true exactly when the command exits 1 for pending keys:
interface DiffSummary {
hasPendingChanges: boolean;
locales: LocaleDiff[];
unused?: UnusedKeysReport; // only with --unused
}
interface LocaleDiff {
locale: string;
missing: string[]; // would be added
changed: string[]; // would be re-translated
orphaned: string[]; // reported only
emptySource?: string[]; // source value empty: never pending
hasPendingChanges: boolean; // missing.length > 0 || changed.length > 0
changedOrigins?: Record<string, KeyOrigin>; // absent when the provenance file is unreadable
protected?: string[]; // pending keys translate would leave alone
}Terminal feedback
diff shows one line while it works (with a running file count when --unused scans the source) and suggests the next command:
verbatra: diffing and scanning the source... done (0.8s)
next: verbatra translate (send the pending keys to your provider)When every pending key is protected, it suggests verbatra studio instead; under provider: { id: "none" } it suggests verbatra export. These lines go to stderr, and --quiet drops them. See Terminal output.
Exit codes
| Code | Meaning |
|---|---|
0 | no locale has a key to add or re-translate, and no complete --unused scan found an unused key |
1 | at least one locale has a missing or changed key, or a complete --unused scan found an unused key |
2 | could not run: a whole-run error, an unconfigured --locales value (UNKNOWN_), or a usage error such as an empty --locales list (INVALID_) |
Orphaned, possibly dynamic and ignored keys, and an unreliable or not-run report, never cause 1 on their own. See the exit-code contract.
Related
verbatra checkgives the per-locale counts behind these lists.verbatra translateapplies the pending changes;--pruneremoves orphaned keys.- The config file documents the
extractblock andunused.ignore. - CI and exit codes shows
diffin a pipeline.