From 03839179b78e99d3fb9add6b66ed8abda7d93747 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Tue, 29 Sep 2026 08:09:06 +0000 Subject: [PATCH] Speak the reader's language on the UPG-100 map, its wall and the print HUD 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. --- internal/check-hardcoded-english.mjs | 11 ++- internal/check-label-key-coverage.mjs | 55 +++++++++++-- .../components/legends/SemanticTreeLegend.vue | 21 +++-- library/components/poster/PosterWall.vue | 39 +++++---- library/composables/usePosterPrints.js | 55 ++++++++----- library/i18n/CATALOGUES.md | 7 ++ library/i18n/keyedError.js | 32 ++++++++ library/i18n/locales/ar/messages.json | 3 +- library/i18n/locales/ar/poster.json | 4 +- library/i18n/locales/ar/upgMap.json | 7 ++ library/i18n/locales/ar/wall.json | 28 ++++++- library/i18n/locales/de/messages.json | 3 +- library/i18n/locales/de/poster.json | 4 +- library/i18n/locales/de/upgMap.json | 7 ++ library/i18n/locales/de/wall.json | 28 ++++++- library/i18n/locales/en/messages.json | 3 +- library/i18n/locales/en/poster.json | 4 +- library/i18n/locales/en/upgMap.json | 7 ++ library/i18n/locales/en/wall.json | 28 ++++++- library/i18n/locales/es/messages.json | 3 +- library/i18n/locales/es/poster.json | 4 +- library/i18n/locales/es/upgMap.json | 7 ++ library/i18n/locales/es/wall.json | 28 ++++++- library/i18n/locales/fr/messages.json | 3 +- library/i18n/locales/fr/poster.json | 4 +- library/i18n/locales/fr/upgMap.json | 7 ++ library/i18n/locales/fr/wall.json | 28 ++++++- library/i18n/locales/hi/messages.json | 3 +- library/i18n/locales/hi/poster.json | 4 +- library/i18n/locales/hi/upgMap.json | 7 ++ library/i18n/locales/hi/wall.json | 28 ++++++- library/i18n/locales/it/messages.json | 3 +- library/i18n/locales/it/poster.json | 4 +- library/i18n/locales/it/upgMap.json | 7 ++ library/i18n/locales/it/wall.json | 28 ++++++- library/i18n/locales/pt/messages.json | 3 +- library/i18n/locales/pt/poster.json | 4 +- library/i18n/locales/pt/upgMap.json | 7 ++ library/i18n/locales/pt/wall.json | 28 ++++++- library/i18n/locales/ro/messages.json | 3 +- library/i18n/locales/ro/poster.json | 4 +- library/i18n/locales/ro/upgMap.json | 7 ++ library/i18n/locales/ro/wall.json | 28 ++++++- library/i18n/locales/ru/messages.json | 3 +- library/i18n/locales/ru/poster.json | 4 +- library/i18n/locales/ru/upgMap.json | 7 ++ library/i18n/locales/ru/wall.json | 28 ++++++- library/i18n/locales/zh/messages.json | 3 +- library/i18n/locales/zh/poster.json | 4 +- library/i18n/locales/zh/upgMap.json | 7 ++ library/i18n/locales/zh/wall.json | 28 ++++++- library/lists/peopleGroupRecords.js | 5 +- .../country-posters/country-poster-wall.vue | 5 +- .../my-upg-100-list/data/legend-tree.js | 28 ++++++- .../my-upg-100-list/poster-wall.vue | 38 ++++----- .../my-upg-100-list/upg-100-list.vue | 80 ++++++++++++++----- 56 files changed, 711 insertions(+), 127 deletions(-) create mode 100644 library/i18n/keyedError.js create mode 100644 library/i18n/locales/ar/upgMap.json create mode 100644 library/i18n/locales/de/upgMap.json create mode 100644 library/i18n/locales/en/upgMap.json create mode 100644 library/i18n/locales/es/upgMap.json create mode 100644 library/i18n/locales/fr/upgMap.json create mode 100644 library/i18n/locales/hi/upgMap.json create mode 100644 library/i18n/locales/it/upgMap.json create mode 100644 library/i18n/locales/pt/upgMap.json create mode 100644 library/i18n/locales/ro/upgMap.json create mode 100644 library/i18n/locales/ru/upgMap.json create mode 100644 library/i18n/locales/zh/upgMap.json diff --git a/internal/check-hardcoded-english.mjs b/internal/check-hardcoded-english.mjs index b1eef0171..d5c7ecebb 100644 --- a/internal/check-hardcoded-english.mjs +++ b/internal/check-hardcoded-english.mjs @@ -132,7 +132,12 @@ function localizedNames(code) { // tStatic is the library's own translator for code outside a component's setup (imported from // @map/i18n), and leaving it out meant `tStatic('grid.search') || 'English'` — correct, guarded, // fully translated code — read as a leak wherever it was used directly rather than behind a wrapper. - const names = new Set(['t', 'tf', '$t', 'tStatic']) + // keyedError(key, 'English', params) (library/i18n/keyedError.js) is a translator call too: its English is + // the FALLBACK of a catalogue key that errorText() shows in the reader's language, so it is judged like + // T(key, 'English') — quiet when the key resolves in every locale, a finding when it does not. As a plain + // call it would have counted every keyed error as a leak, and exempting it like new Error() would have + // hidden a typo'd key. + const names = new Set(['t', 'tf', '$t', 'tStatic', 'keyedError']) for (const m of code.matchAll(/\bconst\s+([A-Za-z_$][\w$]*)\s*=\s*(t|tf)\b\s*(?![(.])/g)) names.add(m[1]) // WRAPPERS ARE TRANSLATORS TOO. `const T = (key, en) => tStatic(key) || en` is the shape most of // these components use, and matching only `const X = t` missed every one of them — the wrapper's @@ -700,6 +705,7 @@ const PLANTED_BAD = `