Repository navigation
Description
Found during #678 (the #666/#667 date-filter slice), where it nearly shipped.
The hole
web/src/i18n/catalogParity.test.ts enforces that en, es and tl carry the same key set for every namespace in TRANSLATED_NAMESPACES. That is a real guard and it works — a missing or extra key fails.
It says nothing about what the strings mean. So a translated string can:
- call a UI element a name that element never uses in that locale, or
- contradict another string in the same locale about the same thing,
and pass catalogParity, typecheck, the full Vitest suite, CodeQL, and review.
What actually happened
A help-text rewrite in #678 changed the word for the expiry field. Nothing caught it:
| locale | the UI label (inventory.expiryLabel) |
what the help text said |
|---|---|---|
en |
Expiry |
"expiry" ✓ |
es |
Vencimiento |
"caducidad" in one string, "vencimiento" in another — it disagreed with itself |
tl |
Expiry |
"pagkaluma" — becoming old / obsolete, which is not an expiry date |
A Tagalog user would have read help text naming a field something the field never calls itself. Caught only because the driver happened to grep how the catalog says "expiry" elsewhere — not by any gate.
This is also the class the repo already worries about: help text exists to point at controls, and it silently stopped pointing.
Why the existing guards can't see it
catalogParity— key sets only, by construction.i18n.test.ts/enums.test.ts— structure and enum coverage, not prose.badgeCase.test.ts(AGENTS.md: three conventions earned while shipping #651 + #652 #662) — precedent that this repo does pin string content when it matters, but only for casing.- Review — three locales rewritten in one pass; a reviewer who does not read Tagalog cannot catch it, and the driver's own summary described the change correctly while the text said something else.
Proposal
A small guard pairing help/glossary strings that name a UI element against that element's own label key, per locale. Concretely: a table of { helpKey, labelKey } pairs, asserting the help string contains the label's term in each locale.
Deliberately narrow to start — the handful of help strings that name a specific labelled control. A broad "every noun must match" check would be unmaintainable and would fight legitimate grammar (Spanish inflects, Tagalog uses different particles).
Open question for whoever picks this up, and it is the crux: the assertion has to survive inflection. "vencimiento" appears inside "fecha de vencimiento", which is fine, but a stemming-free includes() will eventually fight a locale that declines the word. If that turns out to be unworkable, the honest fallback is to document the pairs and check them in review — say so rather than shipping a guard that has to be skipped later.
Not in scope
- Native-speaker review of
es/tlgenerally. Those ship machine-drafted pending review by policy (i18n infrastructure (SPA foundation): react-i18next + resolution + string sweep (tracker) #182); this is about a string contradicting the same catalog, which is checkable mechanically. - The specific wording fixed in feat(web): date-range filters on audit and expenses, and the stock lot filter gets its bounded toolbar (#666, #667, #653) #678 — already corrected there (
2fd1f3c4).
Related
#182 (translate-now policy), #662 (badgeCase.test.ts — precedent for pinning string content), #678 (where this surfaced).
Activity
feat(sales): mark discounted lines, total the discount, and show it i…
fix(audit): show the price a line sold for, not its list price (#759)
test(web): pin the 99.99% ceiling vector and name the approver per lo…
fix(i18n): tl says nakabinbin for outstanding, in one word across the…
chore(main): release 0.1.0 (#542)
fix(dashboard): give recent sales real columns and make both charts r…
fix(reports): rate the lay figure on the houses that reported, not th…
fix(reports): rate eggs against the same flocks their hen-days come from
fix(dashboard): tell a day with no entry from a day that laid no eggs (…
fix(i18n): tl glossary uses the standard passive of ilagay (#813)
fix(web): serialize banner operations, fix a vacuous Login test, and …
fix(web): serialize banner operations, fix a vacuous Login test, and …
fix(web): use the actual Tagalog nav labels in the inspector glossary…
docs: unslop the Help and glossary prose this PR changes