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:
- 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. - Types must be compatible.
%1$svs%1$dis a mismatch.{count}vs{count, number}is a mismatch.pluralis compatible withplainandnumber;selectwithplain. - In a plural entry, forms other than
othermay omit the count placeholder ("One item"is idiomatic), but only for count-like names (count,n,num,number,total) or numeric printf types.othermay not. - 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 Polishfew/manycompare against Englishother. - 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:
- i18next JSON, Rails YAML, Android, stringsdict, ARB/ICU: all CLDR categories of the locale (
pl: one, few, many, other;ja: other). - gettext PO: only the categories reachable by whole numbers, because that is what
npluralscounts (pl: one, few, many).msgstr[i]is mapped to the i-th category in CLDR order, somsgstr[1]of a Polish entry isfew.
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.