Skip to content
Skip to Content

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 --json

Flags

FlagArgumentDefaultEffect
--locales<list>all configured targetscheck only this comma-separated subset; an unconfigured locale fails with UNKNOWN_LOCALE and an empty list with INVALID_LOCALES, both exit 2
--consistencynoneoffalso list source strings translated more than one way; report only, see Consistency report
--qanoneoffalso run the quality check over every committed translation; exit 1 on an error
--file<path>offcheck only this locale file, see One file; not with --locales, --consistency, --require-reviewed or --sensitive
--severityerror or warningwarningwith --qa or --file, the lowest severity to report
--strictnoneoffwith --qa or --file, also exit 1 on a warning or incomplete plural; not with --severity error
--require-reviewednoneoffalso exit 1 while a machine-written translation is not approved
--sensitivenoneoffalso 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\n folded 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 msgctxt differ are never grouped.
  • Plural forms are compared per form: the one and other forms of one key are never grouped with each other.

Known false positives: i18next context variants (invite_male, invite_female), 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:

Output
plural categories (warning: exit 1 only under --qa --strict)
  pl: 2 plurals missing CLDR categories
    cart {count} plural: missing few, many
    files: missing few, many

The categories come from the CLDR rules built into Node.js (see language support). Formats checked:

FormatPlural forms read
i18next-jsonthe _one, _few, ... key suffixes, and _ordinal_ plurals against the ordinal rules
android-xmlthe <item quantity> entries of each <plurals>
apple-stringsthe plural rules in the .stringsdict beside the .strings file
apple-xcstringsthe variations.plural categories of each locale
next-intl-json, arbevery 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, or empty. 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; for FOREIGN_PLACEHOLDER_CHANGED, the finding's details name each dropped or changed token, such as -{name}. A value with an error gets no warnings.
Output
qa: 1 error, 1 warning
  de: 1 error, 1 warning in 42 values checked
    greeting: error placeholder (-{{name}}, +{{nme}})
    title: warning EQUALS_SOURCE

Orphaned 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 above

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

Output
review: failed [REVIEW_REQUIRED] 2 machine-written translations not approved
  de: 2 unreviewed: checkout.title, checkout.total

With --json, result.review is { reviewed, unreviewed, code? }, where code is REVIEW_REQUIRED, or REVIEW_STATE_UNREADABLE 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.

Output
sensitive: 1 key and 0 glossary terms hold content that looks sensitive
  support.contact: email in value

With --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 syntax error with the adapter's code (INVALID_JSON, INVALID_YAML, ...) and the line and column when the parser reports a position.
  • A target file is checked like --qa, plural categories included; a source file for syntax only. An apple-xcstrings catalogue is checked for every target locale.
  • A path that is no configured locale's file fails with NOT_A_LOCALE_FILE and exit 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

CodeMeaning
0in sync, and no finding the flags given turn into a failure
1a 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
2could not run: a whole-run error such as SOURCE_UNREADABLE or NOT_A_LOCALE_FILE, or a usage error such as INVALID_QA_OPTION, INVALID_SEVERITY, or INVALID_OPTION 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.

Edit on GitHub

On this page