Check catalog

Every finding carries a rule id, a severity (error, warning, info), the file, a line when the format has one, the locale and the key. Severity is overridable per rule in [severity], and off disables a rule. Examples below are taken from examples/webapp.

Rule Default What it flags
parse-error error A catalog file could not be parsed (bad JSON, unterminated PO string, malformed XML...). The file is skipped.
missing-key error Key exists in the base catalog but not in a target. Android array items are keys like sort_options[2].
extra-key warning Key exists in a target but not in the base.
empty-translation warning Present but blank (an untranslated PO msgstr "", an empty JSON string). Other checks skip it.
untranslated warning Target text identical to source. Skipped for the same language (en-GB vs en), for texts without two letters once placeholders and tags are removed, and for anything matching [untranslated] allow (regex on the text) or keys (glob on the key).
placeholder-mismatch error Placeholder set or type differs from the source (below).
icu-syntax error Message is not valid ICU MessageFormat. Only reported for ICU catalogs (ARB, or any catalog containing a {x, plural, ...}-style message; force with icu = true).
markup-mismatch error Multiset of tags differs. <b>x</b> vs x reports <b>, </b>; <1> / </1> (react-i18next Trans) work the same; attributes are ignored.
whitespace-mismatch warning Leading or trailing whitespace differs from source.
punctuation-mismatch warning Trailing . ? ! : ; ... class differs. Full-width and script variants are equivalent: . 。 । ۔, ? ? ؟, ! !, : :.
length-expansion warning Translation longer than source by more than the percentage configured for the key glob in [length].
plural-incomplete error A plural lacks categories the locale needs (CLDR).
plural-invalid warning A plural uses a category the locale never selects, or a PO form index beyond nplurals.
plural-shape error Source is plural but the translation is a plain string, or the reverse.
duplicate-value info Several base keys share the same text: consolidation hint.
stale-fuzzy warning Fuzzy PO entry (or XLIFF needs-review); stronger message when #| msgid shows the source changed.
unused-key warning Base key never referenced in scanned source.
missing-in-catalog error Key used in source but absent from the base catalog.
hardcoded-string warning Text node or placeholder/title/alt/aria-label/label attribute in JSX/HTML not going through a translation call.

Placeholders

All of these syntaxes are understood at once, so mixed projects work:

Syntax Name Type
{{count}}, {{count, number}} (i18next) count none
%{user}, %<n>d (Rails) user / n none
%(name)s (Python) name from conversion
%s, %d, %1$s, %2$d (printf, Android, iOS %@) position (1, 2...) string / int / float / hex...
{name}, {n, number}, {d, date, short}, {n, plural, ...} (ICU) argument name plain, number, date, plural...

Rules of comparison:

  1. Names present in the source must be present in the translation, and no new names may appear. Positional printf placeholders may be reordered (%2$d von %1$s) because they are keyed by position.
  2. Types must be compatible. %1$s vs %1$d is a mismatch. {count} vs {count, number} is a mismatch. plural is compatible with plain and number; select with plain.
  3. In a plural entry, forms other than other may omit the count placeholder ("One item" is idiomatic), but only for count-like names (count, n, num, number, total) or numeric printf types. other may not.
  4. For a plural, the compared source text for a form is the source's form of the same category, falling back to other. This makes Polish few/many compare against English other.
  5. A translation that fails to parse as ICU is reported once as icu-syntax; placeholder comparison is skipped for it to avoid cascading noise.

Worked example (locales/de/translation.json, source "Hello, {{name}}!"):

error   placeholder-mismatch  L4  [de] missing placeholder 'name'
error   placeholder-mismatch  L4  [de] unexpected placeholder 'nme' (not in source)

Plural completeness

Which categories are required depends on how the format stores plurals:

Details in icu-and-plurals.md.

Source scanning

Usage extraction runs per family: js (JSON/YAML/ARB catalogs), gettext (PO), android, ios. A family is only analysed when at least one usage of it was found, so a project without code for a catalog type gets no unused-key noise. Keys with ns: prefixes lose the namespace. Dynamic keys (t(`status.${s}`), t('pre.' + x)) mark every base key starting with the literal prefix as used. Add your own regexes under [scan] patterns with a named group key.