gospel takes the locale-honouring fallback, and it is their call that made it
gospel-plugin's decision, in their words: "most partners will drop a custom
element or an iframe and never install vue-i18n ... an Arabic ministry site
embedding this map serves its readers English while the bundle already carries all
11 catalogues. That is a bug, not a decision."
So all four gospel translators drop `{ fallback: enLookup }` and take the
helper's catalogue default, which resolves through tStatic: the reader's locale
first, English as the last resort. Their three conditions are met and each is
asserted, not assumed:
1. resources-map passes NO namespace — its keys span gospel.* and pins.*;
2. the fallback walks the catalogue ROOT, which tStatic does by construction;
3. params survive, via the null-safe fillParams.
★ THE ROW THEY ASKED FOR, and it is the only thing here that could catch the
regression this migration risked: a BARE host under ar must render
pins.approximate in ARABIC — not the key, not gospel.pins.approximate, not the
English. It reads the SHIPPED ar/ and en/ pins.json and rebuilds tStatic's own
resolution order over them, so it measures the real catalogue rather than a stub.
Three negatives beside it, plus a control that both catalogues really carry the
key, so the comparison is measuring a difference that exists.
enLookup is deleted from all four, and the EN_MESSAGES import from three —
data-tables keeps it, because it still builds a map from EN_MESSAGES.gospel.res at
:182. Checked before deleting rather than after: check:no-free-identifiers would
have caught a wrong removal, since removing something still used creates a name
that is valid JS until it runs.
Two corrections that came from other agents reading this work:
- studio: the JSDoc carried <area>.<key>-shaped examples, which
check:i18n-keys-exist reads as real keys and reports missing. They reworded it
twice because my file copy restored it. Reworded, with a standing note in the
file so a third copy cannot bring them back.
- the register said each site "takes the CATALOGUE fallback, as it always did".
True of the two composables, FALSE of gospel's four, which were moved here by
their owner. A false statement inside a gate message is worse than no message,
because a reader has no reason to doubt it. Now accurate per row.
check:i18n-keys-exist is red on messages.engineNotLoadedDetail. NOT mine and not
touched here: it is in cities-internet/connectivity-map.vue, whose own comment says
the key "arrives with upg100's ade12e4c", and the call already guards for its
absence.
This commit is contained in:
parent
0d0ee3cbed
commit
9816a4244f
7 changed files with 79 additions and 29 deletions
|
|
@ -147,7 +147,7 @@ and a change here can break other maps.
|
|||
`import … from '@map/composables/useMapSelection.js'`
|
||||
- **useMapTheme** — useMapTheme.js — shared light/dark theme for every map profile.
|
||||
`import … from '@map/composables/useMapTheme.js'`
|
||||
- **useOptionalT** — useOptionalT(namespace) — one guarded translator, for the twenty places that each stored the same
|
||||
- **useOptionalT** — ★ NO <area>.<key>-SHAPED EXAMPLES IN THIS FILE'S COMMENTS. check:i18n-keys-exist reads any such
|
||||
`import … from '@map/composables/useOptionalT.js'`
|
||||
- **usePosterPrints** — usePosterPrints.js — THE print actions every poster wall shares.
|
||||
`import … from '@map/composables/usePosterPrints.js'`
|
||||
|
|
|
|||
|
|
@ -115,15 +115,20 @@ const SITES = [
|
|||
// SHIPPED ENGLISH, not the locale-aware catalogue; each passes that enLookup in, so a bare host
|
||||
// still renders English. Switching them to the catalogue default would make a bare host honour
|
||||
// ?lang=ar — a real improvement, and gospel-plugin's to make, not this migration's.
|
||||
['plugin-designs/gospel-resources/charts.vue', 'useOptionalT', 'fallback: enLookup'],
|
||||
['plugin-designs/gospel-resources/data-tables.vue', 'useOptionalT', 'fallback: enLookup'],
|
||||
['plugin-designs/gospel-resources/resources-list.vue', 'useOptionalT', 'fallback: enLookup'],
|
||||
// ★ gospel-plugin CHOSE the locale-honouring catalogue fallback (2026-09-26) rather than keeping
|
||||
// enLookup's English: "most partners will drop a custom element or an iframe and never install
|
||||
// vue-i18n ... an Arabic ministry site embedding this map serves its readers English while the
|
||||
// bundle already carries all 11 catalogues. That is a bug, not a decision." So these four take NO
|
||||
// fallback, and row 7b proves the Arabic actually arrives.
|
||||
['plugin-designs/gospel-resources/charts.vue', 'useOptionalT', null],
|
||||
['plugin-designs/gospel-resources/data-tables.vue', 'useOptionalT', null],
|
||||
['plugin-designs/gospel-resources/resources-list.vue', 'useOptionalT', null],
|
||||
// ★ resources-map passes NO NAMESPACE: it uses keys in TWO areas (gospel.* and pins.*) and its
|
||||
// enLookup walks the catalogue ROOT to see both. useOptionalT('gospel', ...) would have qualified
|
||||
// pins.approximate into gospel.pins.approximate and broken the pins sentence — studio caught that
|
||||
// as a semantic conflict on main before it landed. The row asserts the '' explicitly so a later
|
||||
// edit cannot quietly re-add a namespace.
|
||||
['plugin-designs/gospel-resources/resources-map.vue', "useOptionalT(''", 'fallback: enLookup'],
|
||||
['plugin-designs/gospel-resources/resources-map.vue', "useOptionalT(''", null],
|
||||
]
|
||||
// ★ ONE FILE KEEPS TWO BARE useI18n CALLS ON PURPOSE, and the count is asserted rather than waived.
|
||||
// resources-map.vue reads the LOCALE twice (`useI18n().locale.value`) to decide isEnglish and the
|
||||
|
|
@ -151,10 +156,49 @@ for (const [file, helper, fallbackFrag] of SITES) {
|
|||
ok(code.includes(fallbackFrag),
|
||||
`${short}: still passes its OWN fallback (${fallbackFrag}) -- migrating it to the catalogue would change what a bare host renders`)
|
||||
} else {
|
||||
ok(!/fallback:/.test(code), `${short}: takes the CATALOGUE fallback, as it always did`)
|
||||
// ★ NOT "as it always did" for all of them. The two composables always resolved through the
|
||||
// catalogue; gospel's four used to resolve through enLookup's ENGLISH and were MOVED here by their
|
||||
// owner's decision. Saying "as it always did" for those four would be a false statement inside a
|
||||
// gate message, which is the one place a reader has no reason to doubt.
|
||||
const moved = file.includes('gospel-resources')
|
||||
ok(!/fallback:/.test(code), moved
|
||||
? `${short}: takes the locale-honouring CATALOGUE fallback (gospel-plugin moved it here from enLookup's English)`
|
||||
: `${short}: takes the CATALOGUE fallback, as it always did`)
|
||||
}
|
||||
}
|
||||
|
||||
// ── 7b. gospel-plugin's ASKED-FOR ROW, against the REAL catalogue ───────────────────────────────
|
||||
// Their words: "a BARE host (no vue-i18n) with ?lang=ar must render pins.approximate in Arabic — not
|
||||
// the key, not gospel.pins.approximate, not English." It covers their conditions 1 and 2 together: a
|
||||
// namespace would corrupt the key, and an EN-only fallback would serve English to an Arabic reader.
|
||||
// This reads the SHIPPED ar/ and en/ pins.json rather than a stub, and rebuilds tStatic's own
|
||||
// resolution order (messages[want] ?? messages.en) over them, so it measures the real catalogue.
|
||||
{
|
||||
const arPins = JSON.parse(rf(new URL('../library/i18n/locales/ar/pins.json', import.meta.url), 'utf8'))
|
||||
const enPins = JSON.parse(rf(new URL('../library/i18n/locales/en/pins.json', import.meta.url), 'utf8'))
|
||||
const messages = { ar: { pins: arPins }, en: { pins: enPins } }
|
||||
const realish = (want) => (path) => {
|
||||
const walk = (loc) => String(path).split('.').reduce((o, k) => (o == null ? o : o[k]), messages[loc])
|
||||
const v = walk(want); return typeof v === 'string' ? v : walk('en')
|
||||
}
|
||||
const { useOptionalT } = make(realish('ar'), throwingI18n)
|
||||
const t = useOptionalT('')
|
||||
const got = t('pins.approximate')
|
||||
ok(got === arPins.approximate,
|
||||
'GOSPEL ROW: a BARE host under ar renders pins.approximate in ARABIC from the real catalogue')
|
||||
ok(got !== 'pins.approximate' && got !== 'gospel.pins.approximate',
|
||||
'and it is neither the raw key nor a namespace-mangled key')
|
||||
ok(got !== enPins.approximate,
|
||||
'and it is NOT the English -- which is exactly what enLookup used to serve every partner host')
|
||||
// the same call must still reach English when the locale has no entry
|
||||
const tEnOnly = useOptionalT('')
|
||||
ok(typeof arPins.approximate === 'string' && typeof enPins.approximate === 'string',
|
||||
'CONTROL: both catalogues really do carry the key, so the three assertions above are measuring a difference that exists')
|
||||
const { useOptionalT: uMissing } = make(realish('zz'), throwingI18n)
|
||||
ok(uMissing('')('pins.approximate') === enPins.approximate,
|
||||
'ENGLISH REMAINS THE LAST RESORT: an unknown locale falls back to en, as gospel required')
|
||||
}
|
||||
|
||||
// ── 8. the fallback option and te/locale, behaviourally ────────────────────────────────────────
|
||||
{
|
||||
const { useOptionalT, useOptionalI18n } = make(tStatic, throwingI18n)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
/**
|
||||
* ★ NO `<area>.<key>`-SHAPED EXAMPLES IN THIS FILE'S COMMENTS. check:i18n-keys-exist reads any such
|
||||
* literal as a real catalogue key and reports it missing; studio had to reword mine twice because a
|
||||
* file copy restored them. Describe keys in words here, never in the shape of one.
|
||||
*
|
||||
* useOptionalT(namespace) — one guarded translator, for the twenty places that each stored the same
|
||||
* fact: HOW TO TRANSLATE WHEN THE HOST NEVER INSTALLED vue-i18n.
|
||||
*
|
||||
|
|
@ -10,15 +14,15 @@
|
|||
*
|
||||
* ★ WHY THIS DOES NOT TAKE A FALLBACK CATALOGUE, though the curation spec specified
|
||||
* `(namespace, fallbackCatalogue)`. Every existing copy imports its own English JSON
|
||||
* (`import enPplr from '@map/i18n/locales/en/pplr.json'`) and hand-walks it after stripping the
|
||||
* (its own per-area English JSON) and hand-walks it after stripping the
|
||||
* namespace prefix. That is REDUNDANT: `messages` in library/i18n/index.js is keyed
|
||||
* `messages[locale][area]` where `area` is the JSON's own filename, so `tStatic(area + '.' + key)` already
|
||||
* `messages[locale][area]` where `area` is the JSON's own filename, so tStatic given a fully-qualified key already
|
||||
* walks the same tree AND already falls back to English (`tryTree(want) ?? tryTree('en')`). The
|
||||
* second import was a second copy of the same bytes.
|
||||
*
|
||||
* ★ AND IT IS NOT A BUNDLE-SIZE REGRESSION, which was the reason to suspect the narrow import existed.
|
||||
* The build NARROWS library/i18n's catalogue glob per bundle to the areas `internal/i18n-areas.mjs`
|
||||
* finds in that bundle's source graph (see check:i18n-treeshake). A bundle that says 'pplr.' gets the
|
||||
* finds in that bundle's source graph (see check:i18n-treeshake). A bundle that references the pplr area gets the
|
||||
* pplr area and not the other thirty. Verified before writing this, because "the obvious dedup makes
|
||||
* every bundle carry every locale" would have been a real objection if the glob were still wide.
|
||||
*
|
||||
|
|
@ -29,7 +33,7 @@
|
|||
* Step 2 also covers the case i18n IS installed but has no entry: `i18n.t(key)` returns the key
|
||||
* unchanged, which every copy of this pattern already treated as "not found".
|
||||
*
|
||||
* @param {string} namespace the catalogue area, e.g. 'pplr' — used only to prefix bare keys
|
||||
* @param {string} namespace the catalogue area name (a bare area, no dotted key) — used only to prefix bare keys
|
||||
* @returns {(key: string, params?: object) => string}
|
||||
*/
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
|
@ -45,7 +49,7 @@ export function fillParams(str, params) {
|
|||
return String(str).replace(/\{(\w+)\}/g, (m, k) => (params[k] != null ? String(params[k]) : m))
|
||||
}
|
||||
|
||||
/** Prefix a bare key with its area, so callers may pass a bare key or one that already carries its area prefix. */
|
||||
/** Prefix a bare key with its area, so callers may pass a bare key or an already-qualified one. */
|
||||
export function qualify(namespace, key) {
|
||||
const k = String(key)
|
||||
return namespace && !k.startsWith(`${namespace}.`) ? `${namespace}.${k}` : k
|
||||
|
|
@ -55,7 +59,7 @@ export function qualify(namespace, key) {
|
|||
* ★ `fallback` EXISTS BECAUSE THE FIVE LIBRARY COMPONENTS DO NOT ALL FALL BACK TO THE CATALOGUE, and
|
||||
* swapping what they fall back to is a PRODUCT change, not a consolidation. Measured:
|
||||
* ApproxPinNote / FilterDropdown a literal fallback MAP
|
||||
* SemanticTreeLegend / PosterWall humanizeKey() — 'legend.columns.name' renders as 'Name'
|
||||
* SemanticTreeLegend / PosterWall humanizeKey() — a dotted legend column key renders as its last segment, title-cased
|
||||
* PinHint a one-key fallback map
|
||||
* Routing those through tStatic would change what seven bare-host bundles render (and, because tStatic
|
||||
* honours the runtime locale, would start translating strings that are English today). That may well be
|
||||
|
|
|
|||
|
|
@ -46,15 +46,12 @@
|
|||
* inlining it into the IIFE would block first paint for every profile in the bundle).
|
||||
*/
|
||||
import { useOptionalT } from '@map/composables/useOptionalT.js'
|
||||
import { EN_MESSAGES } from '@map/i18n/index.js'
|
||||
import { computed, inject, ref, watch } from 'vue'
|
||||
import BarChart from '@map/components/charts/BarChart.vue'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
||||
/* Guarded like every other useI18n() in this bundle — the host may never have installed it,
|
||||
and the shipped English catalogue is the fallback rather than a broken key. */
|
||||
const enLookup = (key) =>
|
||||
String(key).replace(/^gospel\./, '').split('.').reduce((o, k) => (o == null ? o : o[k]), EN_MESSAGES.gospel || {})
|
||||
/* ★ NUMBERS ARRIVE AS PARAMETERS, NEVER CONCATENATED. The subtitles carry a count, and a
|
||||
sentence built by gluing a number between two fragments cannot be translated — word order
|
||||
differs and several of these locales put the number where English does not. The catalogue
|
||||
|
|
@ -62,12 +59,13 @@ const enLookup = (key) =>
|
|||
THE FALLBACK HAS TO INTERPOLATE TOO. Without it a host that never installed i18n would render
|
||||
the placeholder literally — "Across all {n} languages" on screen — which is worse than the
|
||||
English sentence the fallback exists to protect. */
|
||||
const interpolate = (str, params) =>
|
||||
params ? String(str).replace(/\{(\w+)\}/g, (m, k) => (params[k] != null ? params[k] : m)) : str
|
||||
// C4: the guard is library/composables/useOptionalT.js now. enLookup STAYS as the fallback, passed
|
||||
// in, so a bare host renders the same ENGLISH it always did -- routing this to the catalogue default
|
||||
// would make a bare host honour ?lang=ar, which is gospel-plugin's call and not this migration's.
|
||||
const tr = useOptionalT('gospel', { fallback: enLookup })
|
||||
// C4 wave 3a, gospel-plugin's call (2026-09-26): take the LOCALE-HONOURING fallback rather than
|
||||
// enLookup's English. A partner host that never installed vue-i18n now gets the reader's language,
|
||||
// with English as the last resort. tStatic resolves 'gospel.<key>' against the same catalogue.
|
||||
const tr = useOptionalT('gospel')
|
||||
|
||||
|
||||
const profileConfig = inject('profileConfig', ref(null))
|
||||
|
|
|
|||
|
|
@ -35,12 +35,13 @@ const assetsBase = computed(() => resolveAssetsBase(profileConfig.value))
|
|||
so a translator called t() would be shadowed inside the one block that needs it most, and
|
||||
the labels would silently render as whatever the tab object stringifies to. Guarded like
|
||||
every other useI18n() call in this bundle: the host may never have installed i18n. */
|
||||
const enLookup = (key) =>
|
||||
String(key).replace(/^gospel\./, '').split('.').reduce((o, k) => (o == null ? o : o[k]), EN_MESSAGES.gospel || {})
|
||||
// C4: the guard is library/composables/useOptionalT.js now. enLookup STAYS as the fallback, passed
|
||||
// in, so a bare host renders the same ENGLISH it always did -- routing this to the catalogue default
|
||||
// would make a bare host honour ?lang=ar, which is gospel-plugin's call and not this migration's.
|
||||
const tr = useOptionalT('gospel', { fallback: enLookup })
|
||||
// C4 wave 3a, gospel-plugin's call (2026-09-26): take the LOCALE-HONOURING fallback rather than
|
||||
// enLookup's English. A partner host that never installed vue-i18n now gets the reader's language,
|
||||
// with English as the last resort. tStatic resolves 'gospel.<key>' against the same catalogue.
|
||||
const tr = useOptionalT('gospel')
|
||||
|
||||
const TABS = [
|
||||
// ★ THE DOXA TABLE LEADS, because it is the question the Driver actually asked: which of
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
<script setup>
|
||||
import { useOptionalT } from '@map/composables/useOptionalT.js'
|
||||
import { EN_MESSAGES } from '@map/i18n/index.js'
|
||||
import { resourceRows } from './gospel-resource-rows.js'
|
||||
/**
|
||||
* resources-list.vue — GOSPEL RESOURCES, by language.
|
||||
|
|
@ -44,12 +43,13 @@ import { resolveAssetsBase } from '@map/utils/embedBase.js'
|
|||
|
||||
/* Guarded like every other useI18n() in this bundle — the host may never have installed it,
|
||||
and the shipped English catalogue is the fallback rather than a broken key. */
|
||||
const enLookup = (key) =>
|
||||
String(key).replace(/^gospel\./, '').split('.').reduce((o, k) => (o == null ? o : o[k]), EN_MESSAGES.gospel || {})
|
||||
// C4: the guard is library/composables/useOptionalT.js now. enLookup STAYS as the fallback, passed
|
||||
// in, so a bare host renders the same ENGLISH it always did -- routing this to the catalogue default
|
||||
// would make a bare host honour ?lang=ar, which is gospel-plugin's call and not this migration's.
|
||||
const tr = useOptionalT('gospel', { fallback: enLookup })
|
||||
// C4 wave 3a, gospel-plugin's call (2026-09-26): take the LOCALE-HONOURING fallback rather than
|
||||
// enLookup's English. A partner host that never installed vue-i18n now gets the reader's language,
|
||||
// with English as the last resort. tStatic resolves 'gospel.<key>' against the same catalogue.
|
||||
const tr = useOptionalT('gospel')
|
||||
|
||||
|
||||
const profileConfig = inject('profileConfig', ref(null))
|
||||
|
|
|
|||
|
|
@ -74,7 +74,6 @@ import { computed, inject, ref, shallowRef, markRaw, onMounted, onBeforeUnmount
|
|||
import { useI18n } from 'vue-i18n'
|
||||
import { useOptionalT } from '@map/composables/useOptionalT.js' // vue-i18n stays: two guarded LOCALE reads below
|
||||
import { applyPeopleGroupList } from '@map/lists/peopleGroupList.js'
|
||||
import { EN_MESSAGES } from '@map/i18n/index.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import { addMapAttribution } from '@map/composables/useMapAttribution'
|
||||
import { engineUiLocale } from '@map/composables/useMapInstance.js'
|
||||
|
|
@ -786,8 +785,6 @@ const pinsByIso = computed(() => {
|
|||
/* Walks from the ROOT of the catalogue, not from its gospel subtree: this leaf now also reads
|
||||
`pins.*`, the shared people-group-pin wording, and an area-blind lookup would have fallen back
|
||||
to the key name for every one of those on a host with no i18n. */
|
||||
const enLookup = (key) =>
|
||||
String(key).split('.').reduce((o, k) => (o == null ? o : o[k]), EN_MESSAGES || {})
|
||||
/* The fallback interpolates too. vue-i18n does it for the translated path; without this the
|
||||
English arm would render "{names} — {n} groups share…" literally. */
|
||||
const fill = (str, params) => (params
|
||||
|
|
@ -800,7 +797,13 @@ const fill = (str, params) => (params
|
|||
// pins.approximate into gospel.pins.approximate on the translated path and sent the fallback looking
|
||||
// under EN_MESSAGES.gospel, where pins.* does not exist. studio caught it as a SEMANTIC conflict
|
||||
// before it landed; passing '' keeps every key exactly as the caller wrote it.
|
||||
const t = useOptionalT('', { fallback: enLookup })
|
||||
// C4 wave 3a, gospel-plugin's call (2026-09-26): take the LOCALE-HONOURING fallback. Their reason is
|
||||
// the bundle's purpose -- most partners drop an element or an iframe and never install vue-i18n, so
|
||||
// those hosts got enLookup's ENGLISH while the bundle already shipped all 11 catalogues. "That is a
|
||||
// bug, not a decision." tStatic honours the runtime locale and keeps English as the LAST resort.
|
||||
// ★ NO NAMESPACE: this leaf passes FULL keys in two areas (gospel.* and pins.*), so any prefixing
|
||||
// would ask for gospel.pins.approximate, which no catalogue has. studio caught that before it landed.
|
||||
const t = useOptionalT('')
|
||||
/* Whether to show the "these notes are in English" line at all. Read from the same i18n
|
||||
instance the labels come from, so the two can never disagree. */
|
||||
const isEnglish = (() => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue