From ef8b86029ad0c69f35a34fb0fd3d1214fd58d8bd Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sat, 26 Sep 2026 18:55:37 +0000 Subject: [PATCH] C4 wave 3d, all-people-groups: the {t, te, locale} form, English preserved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This site needed more than `t`, so it is the first to take useOptionalI18n: it used te() to choose between the catalogue and its inline EN map, and i18n.locale to pick a number format for toLocaleString. EN stays the fallback, passed in. It is an inline English map rather than the catalogue, so routing it through tStatic would change what a bare host renders. gospel-plugin and exp-shepherd each CHOSE the locale-honouring default for their own bundles; all-people-groups has no owner to ask — studio agreed to treat it as unowned — so the conservative reading wins and it renders exactly what it rendered. An unowned file is the wrong place to make a product decision by default. Two equivalences worth stating rather than assuming: - the old code asked te() first and only then t(); the helper calls t() and falls back when it returns the key unchanged. Same outcome either way — present key gives the translation, absent key gives EN — and it no longer depends on te() being implemented by the host. - keys arrive BARE here ('hint', 'pray'), and qualify() prefixes 'allPeopleGroups.' exactly as the hand-written `const full = 'allPeopleGroups.' + key` did. locale needs unwrapping: it is a REF when the host installed i18n and a plain string when it did not. PosterWall.vue records the same variance, which is why the helper hands it back as-is rather than pretending one shape. --- .../all-people-groups/all-people-groups.vue | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/plugin-designs/all-people-groups/all-people-groups.vue b/plugin-designs/all-people-groups/all-people-groups.vue index 42b9c9ca8..32afd3715 100644 --- a/plugin-designs/all-people-groups/all-people-groups.vue +++ b/plugin-designs/all-people-groups/all-people-groups.vue @@ -30,7 +30,7 @@ * answers. A slow or unreachable API costs the black layer, never the map. */ import { inject, ref, computed, onMounted, onBeforeUnmount } from 'vue' -import { useI18n } from 'vue-i18n' +import { useOptionalI18n } from '@map/composables/useOptionalT.js' import { useMapInstance, engineGlobal } from '@map/composables/useMapInstance.js' import { resolveMapProvider, setProjectionOn } from '@map/composables/useMapProvider.js' import { useMapData } from '@map/composables/useMapData.js' @@ -108,18 +108,26 @@ const EN = { title: 'All People Groups', locations: '{n} group–country locatio frontier: 'Frontier — nothing started', unreached: 'Unreached — work beginning', reached: 'Reached — church is there', hint: 'Click a row to see all of them · click again for everything', loading: 'Loading every people group…', loadError: 'The map could not be loaded.', pray: 'Pray for them →', population: 'pop {n}' } -let i18n = null -try { i18n = useI18n() } catch (_) { /* no i18n on this host: English below */ } -function tr(key, vars) { - const full = 'allPeopleGroups.' + key - // vue-i18n treats {n} as ITS placeholder: pass the values to t(), or it blanks them out before any - // later substitution sees them (the legend read "group–country locations" with no number). - if (i18n && i18n.te && i18n.te(full)) return i18n.t(full, vars || {}) - let v = EN[key] - if (vars) for (const k in vars) v = String(v).split('{' + k + '}').join(String(vars[k])) - return v -} -const uiLocale = computed(() => (i18n && i18n.locale && i18n.locale.value) || 'en') +// C4 wave 3d: the guard is library/composables/useOptionalI18n now — this site needs more than `t`, +// so it takes the {t, te, locale} form rather than useOptionalT. +// ★ EN STAYS THE FALLBACK, passed in. It is an inline English map, not the catalogue, so routing this +// to tStatic would change what a bare host renders. gospel-plugin and exp-shepherd each CHOSE the +// locale-honouring default for their own bundles; all-people-groups has no owner to ask +// (studio agreed to treat it as unowned), so the conservative reading wins: preserve what it renders. +// The keys arrive BARE here ('hint', 'pray'), and qualify() adds the 'allPeopleGroups.' prefix, which +// is exactly what the hand-written `const full = 'allPeopleGroups.' + key` did. +// ★ ONE EQUIVALENCE WORTH STATING: the old code asked te() first and only then t(). The helper calls +// t() and falls back when it returns the key unchanged. Same outcome both ways — present key: the +// translation; absent key: EN — and it no longer depends on te() being implemented. +const { t: tr, locale: i18nLocale } = useOptionalI18n('allPeopleGroups', { + fallback: (full) => EN[String(full).replace(/^allPeopleGroups\./, '')] +}) +// locale arrives as a REF when the host installed i18n and a plain string when it did not +// (PosterWall.vue records the same variance), so unwrap both shapes. +const uiLocale = computed(() => { + const l = i18nLocale + return (l && typeof l === 'object' && 'value' in l ? l.value : l) || 'en' +}) const fmt = (n) => { try { return Number(n).toLocaleString(uiLocale.value) } catch (_) { return Number(n).toLocaleString() } } // ─── The four categories (the Driver's palette) ──────────────────────────────