C4 wave 3d, all-people-groups: the {t, te, locale} form, English preserved

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.
This commit is contained in:
Doxa Maps 2026-09-26 18:55:37 +00:00
parent e80c38c045
commit ef8b86029a

View file

@ -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) ──────────────────────────────