The legend's region sections, its Deaf row and its one-country zones, the
wall's card names, the print progress and error messages, the map's status
badge and the Population column all rendered English in every locale. None
of them was visible to check:hardcoded-english: they were single words, .js
modules, Error() arguments, library components or number formatting.
- Legend: region rows through one REGION_KEY table shared with the wall
(poster.region_*, new region_deaf); zones through zoneTitle (tier A by
country, the Deaf row by its bloc name); node ids stay English-derived.
- Wall: card labels through zoneTitle, so the Show-on-map name, alt text,
tooltip, HUD title and printed title agree with the legend and packet.
- Errors a reader sees are keyedError(key, English, params);
errorText(e, t) shows them translated, and an unkeyed error still shows
its own message. Developer detail for a failed data load goes to the
console; the badge says upgMap.mapDataFailed.
- Population: Intl compact notation in the app's locale, same precision.
- Country Posters: the unsupported card's reason is
messages.countryPosterTooMany, not the data file's English note.
- Strings only the UPG-100 map shows move to a new upgMap area, so other
bundles do not carry them (country-posters 1,490,907 bytes).
- check:hardcoded-english judges keyedError's English as a fallback
(quiet when the key resolves everywhere, a finding when it does not);
check:label-key-coverage follows REGION_KEY to legend-tree.js and covers
the legend's table and its regionFor producer. Both self-tests two-sided.
poster-wall translates its section headings by looking the key up by the
English: one table maps a zone id to a heading, a second maps that heading
to a catalogue key, and the helper returns the raw English when the second
table has no entry. Two headings reachable from four zone ids had no key, so
they printed in English in all eleven locales. upg100 found and fixed that
one; this is so the next table cannot repeat it.
The hardcoded-English scan cannot see this class. There the English is a
lookup index rather than a rendered label, and the key is computed, so a
scan that reads keys written literally at the call site has nothing to
check. Resolving arbitrary key expressions is a static-analysis problem;
this does the decidable part — two object literals in one file where the
values of the first are the keys of the second, and coverage is a set
difference. The table list is declared, not discovered, because a heuristic
hunting for related-looking objects would be guessing about code it has
never read.
Two things the check has to get right and could quietly get wrong. It
compares the VALUES of the producing table, not its keys — the ids are
never translated, and reading them finds nothing while looking like a
check. And a label passed straight to the helper as a literal counts as
produced: three of the nine headings never appear in the table at all, and
"Other" is exactly the catch-all that gets added last and keyed never.
A renamed table fails loudly rather than comparing two empty sets and
calling it agreement. The self-test covers a covered table, a gap, a gap
reachable only through a literal argument, and the renamed case; poisoning
either parser or the object matcher trips it.
Verified against both real states: development's poster-wall fails naming
Americas and Europe, and upg100's fix passes.