Reference / CLI
verbatra check
Report which keys are missing or stale per locale without writing files or calling a provider, and fail CI while any locale is out of sync.
Report, per target locale, how many keys are missing, stale, or up to date, and exit with a code a CI step can branch on. check runs the read and diff verbatra translate runs before calling a provider, then stops: no provider call, no file written, no API key needed.
Synopsis
verbatra check [flags]Examples
# fail the build when translations drifted
verbatra check
# machine-readable status for two locales
verbatra check --locales de,fr --json
# also list source strings translated more than one way
verbatra check --consistency
# fail on broken placeholders, markup and ICU, and on warnings too
verbatra check --qa --strict
# fail while machine translations wait for approval
verbatra check --require-reviewed
# fail on emails, API keys or card numbers in the source
verbatra check --sensitive
# check the one file an editor or agent just changed
verbatra check --file locales/de.json --jsonFlags
| Flag | Argument | Default | Effect |
|---|---|---|---|
--locales | <list> | all configured targets | check only this comma-separated subset; an unconfigured locale fails with UNKNOWN_ and an empty list with INVALID_, both exit 2 |
--consistency | none | off | also list source strings translated more than one way; report only, see Consistency report |
--qa | none | off | also run the quality check over every committed translation; exit 1 on an error |
--file | <path> | off | check only this locale file, see One file; not with --locales, --consistency, --require-reviewed or --sensitive |
--severity | error or warning | warning | with --qa or --file, the lowest severity to report |
--strict | none | off | with --qa or --file, also exit 1 on a warning or incomplete plural; not with --severity error |
--require-reviewed | none | off | also exit 1 while a machine-written translation is not approved |
--sensitive | none | off | also scan the source file and glossary for sensitive content; exit 1 on any finding |
Also takes the global flags --cwd, --config and --json.
A key is missing when the source has it and the target does not, and stale when its source string changed since it was last translated. A locale is in sync when it has neither. For the key lists behind the counts, use verbatra diff.
Behavior
Consistency report
--consistency lists, per target locale, every source string that two or more keys translate differently: the source, each distinct translation, and the keys holding it. It never changes the exit code, a value, or the review queue. The comparison rules:
- Values are compared after Unicode NFC normalization,
\r\nfolded to\n, and outer whitespace trimmed. Inner whitespace and letter case count. - Only up-to-date keys are compared, and a translation equal to its own source is left to
EQUALS_.SOURCE - Keys whose description, meaning, plural flag, or gettext
msgctxtdiffer are never grouped. - Plural forms are compared per form: the
oneandotherforms of one key are never grouped with each other.
Known false positives: i18next context variants (invite_, invite_), gettext entries marked #, fuzzy, plural-style keys in a format without plurals (yaml, ngx-translate-json, xliff, properties, ini, resx), and the plural forms of a custom: format. Give such keys a differing description or meaning, or ignore the finding. With --json, each locale carries inconsistencies: { source, context?, description?, meaning?, isPlural, pluralForm?, translations: [{ value, keys }] }[].
Protected keys
A stale key a person wrote, imported, or changed outside verbatra, or a key matching pinnedKeys, is one translate leaves for a person (see Protecting human translations). check still counts it and shows the protected share, for example 2 stale (1 protected). --json carries it as protected per locale; when verbatra.provenance.json is unreadable, only pinned keys count.
Plural categories
Every check lists the plurals that lack a CLDR plural category their target language uses, such as a Polish Android <plurals> with only one and other:
plural categories (warning: exit 1 only under --qa --strict)
pl: 2 plurals missing CLDR categories
cart {count} plural: missing few, many
files: missing few, manyThe categories come from the CLDR rules built into Node.js (see language support). Formats checked:
| Format | Plural forms read |
|---|---|
i18next-json | the _, _, ... key suffixes, and _ plurals against the ordinal rules |
android-xml | the <item quantity> entries of each <plurals> |
apple-strings | the plural rules in the .stringsdict beside the .strings file |
apple-xcstrings | the variations.plural categories of each locale |
next-intl-json, arb | every ICU plural and selectordinal, including nested ones |
other is always required. An exact arm such as =1 never stands in for a category, select arms are never checked, and only a plural the target holds at least one form of is checked. It is a warning: a plain check keeps its exit code, and --qa --strict fails on it. verbatra doctor reports the same plurals. With --json, each locale carries incompletePlurals, ordered by key:
interface IncompletePlural {
code: "PLURAL_CATEGORIES_INCOMPLETE";
key: string; // base key, or the ICU message's key
argument?: string; // the ICU plural's argument, such as count
ruleType: "cardinal" | "ordinal";
missing: ("zero" | "one" | "two" | "few" | "many" | "other")[]; // in CLDR order
}Quality check
--qa runs the checks a translate run applies to provider output over the values already in your locale files, stale or not, including extra plural forms the target language needs. Findings come in two severities:
- error: the value fails the integrity gate with
placeholder,markup,icu,degenerate, orempty. Details list dropped tokens as-{{name}}and added ones as+{{nom}}. - warning: the value carries a review reason computed from the value, its source and your config (glossary,
maxLength), so none that depends on the provider or the cache; forFOREIGN_, the finding'sPLACEHOLDER_ CHANGED detailsname each dropped or changed token, such as-{name}. A value with an error gets no warnings.
qa: 1 error, 1 warning
de: 1 error, 1 warning in 42 values checked
greeting: error placeholder (-{{name}}, +{{nme}})
title: warning EQUALS_SOURCEOrphaned and missing keys are skipped, and source keys that are not valid ICU are listed once under invalidSourceKeys. Protected, pinned and rejected values are checked like any other. With --json, result.qa holds the totals and each locale its own qa:
interface CheckQaSummary { errors: number; warnings: number; invalidSourceKeys: string[] }
interface LocaleQaReport { checked: number; errors: number; warnings: number; findings: QaFinding[] }
type QaFinding =
| { key: string; severity: "error"; reason: "placeholder" | "markup" | "icu" | "degenerate" | "empty"; details?: string[] }
| { key: string; severity: "warning"; reason: string; details?: string[] }; // one of the review reasons aboveRequire reviewed translations
--require-reviewed fails with exit 1 while a value whose origin is machine, memory, fuzzy, or agent is not approved in the committed verbatra.provenance.json, where Studio and the MCP tools record decisions. An approval from before the source text changed, or before the value changed, does not count.
review: failed [REVIEW_REQUIRED] 2 machine-written translations not approved
de: 2 unreviewed: checkout.title, checkout.totalWith --json, result.review is { reviewed, unreviewed, code? }, where code is REVIEW_, or REVIEW_ when the provenance file is corrupt or from a newer verbatra (the gate then fails). Each locale carries review.unreviewed, the keys in source order.
Sensitive content
--sensitive runs the sensitive content guard scan over each source key (key name, value, description, meaning) and the glossary of each checked locale, with the detectors, patterns and allow list of sensitiveData (default detectors without the block), whatever its mode and provider, none included: values still leave the machine through export, XLIFF and TMX handoffs. It reports keys and detectors, never matched text outside a key name, and fails with exit 1 on any finding.
The email detector skips reserved example domains, so test with a real-looking address.
sensitive: 1 key and 0 glossary terms hold content that looks sensitive
support.contact: email in valueWith --json, result.sensitive is { findings, glossaryTerms }, each finding { key, fields, detectors }; detectors may hold pattern-timeout-<n>.
One file
--file <path> checks one locale file, for editor and agent hooks after an edit. It reads that file, verbatra.limits.json new (if unusable, ignored with a notice on stderr, as under --qa; doctor names the problem) and, for a target locale, the source file, nothing else. The path is resolved against --cwd and matched against files.pattern to find its locale.
- A file that does not parse is one
syntaxerror with the adapter's code (INVALID_,JSON INVALID_, ...) and the line and column when the parser reports a position.YAML - A target file is checked like
--qa, plural categories included; a source file for syntax only. Anapple-xcstringscatalogue is checked for every target locale. - A path that is no configured locale's file fails with
NOT_and exitA_ LOCALE_ FILE 2.
With --json, result reuses the --qa report, so a hook parses one shape:
interface CheckFileSummary {
file: string; // relative to the project root
role: "source" | "target" | "catalogue";
locales: {
locale: string;
incompletePlurals: IncompletePlural[];
qa: { checked: number; errors: number; warnings: number; findings: (QaFinding | QaSyntaxFinding)[] };
}[];
qa: CheckQaSummary;
}
interface QaSyntaxFinding {
severity: "error"; reason: "syntax"; code: string; message: string;
line?: number; column?: number; // when the parser reports a position, counted from 1
}Missing and stale keys are not part of a file check.
JSON output
Schema: check-envelope.json validates the whole envelope; Schemas lists the others.
--json prints one envelope whose result is the status document. Without --qa, --require-reviewed or --sensitive, inSync is true exactly when the command exits 0:
interface CheckSummary {
inSync: boolean;
locales: LocaleCheckSummary[];
qa?: CheckQaSummary; // only with --qa
review?: CheckReviewSummary; // only with --require-reviewed
sensitive?: CheckSensitiveSummary; // only with --sensitive
}
interface LocaleCheckSummary {
locale: string;
missing: number; // in source, absent from target
stale: number; // source changed since last translated
upToDate: number;
inSync: boolean; // missing === 0 && stale === 0
emptySource?: number; // source value empty: never missing or stale
provenance?: ProvenanceSummary; // counts by origin and review state
protected?: number;
incompletePlurals?: IncompletePlural[];
inconsistencies?: InconsistencyGroup[]; // only with --consistency
qa?: LocaleQaReport; // only with --qa
review?: LocaleReviewReport; // only with --require-reviewed
}A source key whose value is empty or whitespace only, such as one extract added without a default, has nothing to translate yet: it is counted in emptySource instead of missing or stale, never makes a locale out of sync, and the terminal output names the count.
Terminal feedback
check shows verbatra: checking the locales... done (0.1s) on stderr, or verbatra: checking locales/de.json... with --file. --quiet drops it. See Terminal output.
Exit codes
| Code | Meaning |
|---|---|
0 | in sync, and no finding the flags given turn into a failure |
1 | a locale has a missing or stale key, --qa or --file found an error (or a warning or incomplete plural under --strict), --require-reviewed found an unapproved translation, or --sensitive found sensitive content |
2 | could not run: a whole-run error such as SOURCE_ or NOT_, or a usage error such as INVALID_, INVALID_, or INVALID_ for an empty --file or one combined with --locales, --consistency, --require-reviewed or --sensitive |
Exit 1 means the result is not clean, not that the run failed; the report is still printed. On a project whose source file is not in place yet, verbatra doctor still reports. See Exit codes for the contract.
Related
verbatra difflists the exact keys behind these counts.- CI and exit codes shows
checkas a drift gate. - The lock file explains how staleness is detected.