diff --git a/plugin-designs/my-upg-100-list/poster-wall.vue b/plugin-designs/my-upg-100-list/poster-wall.vue index ee164d4d7..5938b5cc3 100644 --- a/plugin-designs/my-upg-100-list/poster-wall.vue +++ b/plugin-designs/my-upg-100-list/poster-wall.vue @@ -39,7 +39,7 @@ * IDEA-POSTER-EXPORT-404-ON-DROPPED-FOLDER-TRADEOFF. */ import { computed, inject, ref } from 'vue' -import { useI18n } from 'vue-i18n' +import { useOptionalT } from '@map/composables/useOptionalT.js' // The page's people-group LIST (library/lists/): every people-group fetch below keeps only its groups. import { applyPeopleGroupList } from '@map/lists/peopleGroupList.js' import PosterWall from '@map/components/poster/PosterWall.vue' @@ -160,11 +160,22 @@ async function fetchListRecords(list) { // copy a reader reads FIRST was the untranslated part. The travels inside the // translated string so each language can emphasise its own words, which is not something a // wrapper around an English sentence could do. -// Guarded like every other useI18n() here: it throws on a host without vue-i18n. -const humanizeKey = (k) => String(k).split('.').pop() - .replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/^./, (c) => c.toUpperCase()) -let t = (k) => humanizeKey(k) -try { t = useI18n().t } catch (_) { /* no i18n on this host */ } +// Guarded like every other useI18n() here: it throws on a host without vue-i18n. useOptionalT stores +// that guard once (host i18n -> tStatic -> the key itself) instead of a fourth hand-rolled copy. +// +// ★ NO humanizeKey FALLBACK, and the reason is a MEASUREMENT, not a preference. That fallback rendered +// a key's last segment title-cased, so a bare host showed "Upg100 Wall Intro" where a real sentence +// exists. It was kept on the theory that these keys were not fully catalogued — I said so to +// regions-plugin, and it was FALSE: this file's three keys are present in all ELEVEN locales, 0 gaps +// (enumerated 2026-09-27; they are the only three, all string literals, no computed keys). +// So the fallback was shielding nothing and costing the translation. tStatic honours the READER'S +// locale with English as the last resort, which is strictly better than an English-ish word derived +// from an identifier. +// ⚠ IT GUARDS A BARE HOST, NOT A MISSING KEY — those are different failures and I had them confused. +// Switching changes what seven vue-i18n-less bundles render, so it is a product decision: taken by +// dx-shepherd (UPGHK, 2026-09-27) as carrying out the Driver's standing goal that the production maps +// be localized. Not a consolidation I was entitled to make on my own. +const t = useOptionalT('messages') const intro = computed(() => t('messages.upg100WallIntro')) const heading = computed(() => t('messages.upg100WallHeading')) const wallTag = computed(() => t('messages.upg100WallTag')) diff --git a/plugin-designs/my-upg-100-list/upg-100-mfe.vue b/plugin-designs/my-upg-100-list/upg-100-mfe.vue index f9501aa2f..1fafb0ecf 100644 --- a/plugin-designs/my-upg-100-list/upg-100-mfe.vue +++ b/plugin-designs/my-upg-100-list/upg-100-mfe.vue @@ -34,7 +34,7 @@ * whole MFE (both panes) is what gets shared/embedded. */ import { ref, computed, inject, onMounted, onBeforeUnmount } from 'vue' -import { useI18n } from 'vue-i18n' +import { useOptionalT } from '@map/composables/useOptionalT.js' import { buildPaneColumn, PANE_GRID_STYLES } from '@map/layouts/pane-wrapper/pane-wrapper.js' import { useShadowStyles } from '@map/composables/useShadowStyles.js' import ViewSelect from '@map/components/ViewSelect.vue' @@ -46,12 +46,19 @@ import ShareButton from '@map/components/map-controls/ShareButton.vue' // wrapper never did. The chrome a reader sees FIRST was the untranslated part. // // ⚠ GUARDED, like every other useI18n() call in this codebase: it THROWS on a host that has not -// installed vue-i18n, and this component is mounted by hosts we do not control. The fallback -// humanises the key so a bare host shows "Select View", never a blank or a raw dotted key. -const humanizeKey = (k) => String(k).split('.').pop() - .replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/^./, (c) => c.toUpperCase()) -let t = (k) => humanizeKey(k) -try { t = useI18n().t } catch (_) { /* no i18n on this host */ } +// installed vue-i18n, and this component is mounted by hosts we do not control. useOptionalT holds +// that guard once — host i18n first, then tStatic, then the key — instead of a hand-rolled copy here. +// +// ★ THE humanizeKey FALLBACK IS GONE ON PURPOSE. It rendered a key's last segment title-cased, so a +// bare host showed "Select View" — which reads fine in English and is simply WRONG for every other +// reader, because it cannot be translated: it is derived from the identifier, not from a catalogue. +// tStatic honours the reader's locale and still ends at English. The four keys this file uses +// (tabs.map / prayerResources / selectView / views) are present in all ELEVEN locales, 0 gaps, +// enumerated 2026-09-27 — so nothing here was relying on the fallback. +// Product decision, taken by dx-shepherd (UPGHK) under the Driver's standing goal that the production +// maps be localized: switching changes what seven vue-i18n-less bundles render, which is not a call a +// consolidation gets to make for itself. +const t = useOptionalT('tabs') const cfg = inject('profileConfig', ref(null)) const tk = inject('mapboxToken', ref(''))