The ratchet excused any value equal to en that held at most one plain
word, so "Settings", "Appearance", "Readability" or "Users" copied into
another locale were never counted, Japanese and Arabic included. Buttons,
tabs and section titles are mostly one word, so a feature could still add
26 English copies of its labels and stay green.
An identical value is now excused only when nothing ordinary is left
after dropping placeholders, markup, addresses, a listed set of product
names (BRAND_NAMES, which now holds one-word names such as Atlas, Immich
and Mapbox) and unit symbols (UNITS). A word that really is the locale's
own, German "Status" or French "Transport", carries a `// same-as-en`
marker on its declaration, which the catalogue reader now reports. The
marker is honoured only in Latin-script locales: a Latin word left in
Japanese or Russian is English whatever it says. Every locale folder
must be on LATIN_LOCALES or NON_LATIN_LOCALES, so a new language cannot
pass the rule unclassified.
The baseline is re-recorded from the narrower rule: the unmarked copies
go from 1252 to 3238, the marked fallbacks stay at 3157, and no entry
went down. This is the measured state under the new rule, not a raised
gate; translating, or marking a word that is genuinely the same, lowers
it with --update as before.
BudgetPanel titled its add button with budget.addCategory (since April)
and the trip form labelled its cover remove button with common.remove
(since August). Neither key was ever in en, and t() returns an unknown
key unchanged, so both rendered as the raw key in every language. Both
are added to all 27 locales.
Locale parity compares the locales with en, never en with the code, so
nothing could catch this. client/scripts/i18n-keys.mjs (npm run
lint:i18n-keys) now resolves every literal key src/ names against en:
the first argument of t(), tHtml() and tr() (both sides of a conditional
included), <TransHtml html>, the fallback of translateApiError and any
*Key property or prop. A template key, a prefix with the rest appended
and the help registry's key builders pass when their fixed parts match
an en key; the one template with no fixed part is on an allow-list that
names the keys it resolves to, and an entry nothing uses fails. With
--unused it lists the en keys nothing reaches, as information only.
en is read with shared/scripts/i18n-catalog.mjs, a reader for the
locale tables as text that the parity tooling will share. It refuses a
value that is not one string literal instead of skipping the key.