diff --git a/plugin-designs/regions-dashboard/regions-list.vue b/plugin-designs/regions-dashboard/regions-list.vue index de7b5844b..e5479734d 100644 --- a/plugin-designs/regions-dashboard/regions-list.vue +++ b/plugin-designs/regions-dashboard/regions-list.vue @@ -44,6 +44,8 @@ import { applyPeopleGroupList } from '@map/lists/peopleGroupList.js' import { SHADOW_BOX_SIZING_RESET } from './src/shadowReset.js' import { splitLastWord } from './src/useLastWordHighlight.js' import { useRegionsI18n } from '@map/i18n/useRegionsI18n.js' +import { countryName } from '@map/utils/countryName.js' +import { useOptionalT } from '@map/composables/useOptionalT.js' // ar renders right-to-left. RTL_LOCALES is the shared set and :dir on our own root is the // house pattern (doxa-simple-map.vue:18, :246, :1346) — an EMBEDDED element must not reach for // document.documentElement.dir, which belongs to the host page. @@ -129,6 +131,12 @@ const isRTL = computed(() => RTL_LOCALES.has(i18nLocale.value)) // invisible inside the composite, where the tab strip names the view, and plainly missing on // /doxa-maps/regions-dashboard/regions-list/index.html, which is a page in its own right. const { t, tf } = useRegionsI18n() +// ★ A SECOND TRANSLATOR, DELIBERATELY. useRegionsI18n's t() resolves DX-life's English sentences as +// keys inside the `regions` namespace only, which is what this page is cloned from. A dotted key in +// another area — messages.countryPosterTooMany — misses there and would render the literal string +// "messages.countryPosterTooMany" on the wall. tKey is vue-i18n's own lookup, for keys that live +// outside this page's namespace. +const tKey = useOptionalT('') const headTitle = computed(() => splitLastWord(t('Regions'))) // ★ REGISTER BEBAS NEUE AT DOCUMENT LEVEL, not in the shadow root: @font-face is document-scoped @@ -311,10 +319,27 @@ const POSTER_BY_ISO3 = new Map((posterData.countries || []).map(c => [String(c.i // Every country in the region gets a card, in PosterWall's own shape. A country with no // generated poster is NOT dropped — it is passed with a reason, which is what the wall's // `unsupported` slot is for, so the grid never silently loses a country. +// ★ EACH CARD NAMED IN THE READER'S LANGUAGE. item.label is the card's accessible name, the print +// HUD's title, and the title on the printed list, deck and packet — and country-posters.json carries +// it in English only, so every card was English in every locale. Same fix as the country-posters +// wall, and the same two caveats travel with it: +// · ENGLISH KEEPS THE DATA'S OWN NAMES. Intl's English differs from them for 32 of these 175 +// countries, and countryName.js records that swap as a product decision rather than a refactor. +// · THE SHEET GENERATOR STILL GETS THE ORIGINAL RECORD. The poster itself is English by recorded +// decision, so only the card's label is localised — postersFor's result feeds the wall, while +// countrySheet resolves its own country entry from the same json by iso3. +// The i18n locale REF is read, not a snapshot, so a runtime language switch renames the cards. +function localisedPoster(rec) { + const locale = i18nLocale.value || 'en' + if (/^en\b/i.test(locale)) return rec + const name = countryName(rec.iso3, locale) + return name && name !== rec.iso3 ? { ...rec, label: name } : rec +} + function postersFor(region) { return (region.countries || []).map((c) => { const rec = POSTER_BY_ISO3.get(String(c.code || '').toUpperCase()) - if (rec) return rec + if (rec) return localisedPoster(rec) return { id: `country-${String(c.code || '').toLowerCase()}`, iso3: c.code, @@ -325,10 +350,14 @@ function postersFor(region) { }) } -// Their own not-yet reason travels WITH the record, so the number the wall prints can never -// drift from the data — country-poster-wall.vue:63 does exactly this. India is the only -// country in the dataset that carries one (855 people groups, no sheet); China is fine at 3x4. -const posterUnsupported = (c) => c.unsupportedReason || null +// ★ THE REASON IS TRANSLATED, THE PRESENCE OF ONE IS STILL THE DATA'S. The record carries its own +// English note ("India has 855 people groups — too many…"), and returning it put one untranslated +// sentence on the wall in every locale. The record still DECIDES whether a country is unsupported; +// only the wording comes from the catalogue now. messages.countryPosterTooMany exists in all +// eleven, and needs no number in it: the card already shows the count directly above. +// India is the only country in the dataset carrying one (855 people groups, no sheet); China is +// fine at 3x4. My own not-yet reason, for a country with no record at all, is already through t(). +const posterUnsupported = (c) => (c.unsupportedReason ? tKey('messages.countryPosterTooMany') : null) // One region's wall is already inside a region section, so it needs no second level of // collapsing and no grouping: everything in it is that one region.