C4 wave 3a, gospel-resources: four guards share one, English unchanged
All four of gospel's translators had the same shape — enLookup + a try/catch
around useI18n() + `v === key ? fallback : v` — and all four now call
useOptionalT('gospel', { fallback: enLookup }).
enLookup STAYS as each site's fallback rather than taking the helper's catalogue
default. The difference is real and it is not mine to decide: enLookup resolves
against the SHIPPED ENGLISH, while the catalogue default resolves through tStatic,
which honours the runtime locale. Adopting the default would make a bare host
render Arabic under ?lang=ar where it renders English today. That is arguably
better, and it belongs to gospel-plugin.
★ AND ONE FIX THAT CAME OUT OF READING THEIR CODE BEFORE REPLACING IT.
charts.vue's interpolate() substitutes only when a value is NOT NULL; my
fillParams substituted whenever the KEY WAS PRESENT. With { count: null } mine
would have rendered the string "null" into a sentence where theirs leaves
`{count}` visible. Theirs is right — a visible placeholder is a bug you can see,
"null" reads as content — so fillParams now matches, with assertions for the null
case and for the 0 case a truthiness test would have dropped.
resources-map.vue KEEPS two bare useI18n() calls, deliberately: they read the
LOCALE (`useI18n().locale.value`) for isEnglish and the engine's language tag, not
text. `locale` arrives as a ref or a plain string depending on the host —
PosterWall records the same variance — so normalising it belongs in a considered
helper change, not here. The register asserts EXACTLY two, so a third bare call
still fails.
check:no-free-identifiers earned its keep on this batch: replacing that file's
vue-i18n import while two calls still referenced useI18n left a name that is valid
JS until it runs. Caught before commit, both imports now present.
The per-site register in check:optional-t-equivalence gains four rows (59
assertions).
This commit is contained in:
parent
63ab6719d2
commit
e04121f16f
6 changed files with 50 additions and 29 deletions
|
|
@ -63,6 +63,10 @@ const hostI18n = (table) => () => ({ t: (k) => (k in table ? table[k] : k) })
|
|||
const { fillParams, qualify } = make(tStatic, throwingI18n)
|
||||
ok(fillParams('a {x} b', { x: 1 }) === 'a 1 b', 'fillParams interpolates')
|
||||
ok(fillParams('a {x} b', {}) === 'a {x} b', 'fillParams leaves an unknown placeholder visible rather than blanking it')
|
||||
ok(fillParams('a {x} b', { x: null }) === 'a {x} b',
|
||||
'fillParams leaves the placeholder for a NULL value -- a key-presence test would render the string "null" into the sentence')
|
||||
ok(fillParams('a {x} b', { x: 0 }) === 'a 0 b',
|
||||
'but 0 is a real value and must still substitute (a truthiness test would drop it)')
|
||||
ok(fillParams('plain') === 'plain', 'fillParams with no params is a no-op')
|
||||
ok(qualify('pplr', 'x') === 'pplr.x' && qualify('pplr', 'pplr.x') === 'pplr.x', 'qualify prefixes once, never twice')
|
||||
}
|
||||
|
|
@ -107,12 +111,31 @@ const SITES = [
|
|||
['library/components/hints/PinHint.vue', 'useOptionalI18n', 'PINHINT_FALLBACK[key]'],
|
||||
['plugin-designs/pplr-data-maps/src/composables/usePplrT.js', 'useOptionalT', null],
|
||||
['plugin-designs/upg-neighbor/src/composables/useNeighborT.js', 'useOptionalT', null],
|
||||
// wave 3a — gospel-resources (owner: gospel-plugin). All four fell back to enLookup, i.e. the
|
||||
// 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'],
|
||||
['plugin-designs/gospel-resources/resources-map.vue', 'useOptionalT', 'fallback: enLookup'],
|
||||
]
|
||||
// ★ 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
|
||||
// engine's language tag. That is a different fact from translating text: `locale` arrives as a ref OR
|
||||
// a plain string depending on the host (PosterWall.vue records the same variance), so normalising it
|
||||
// belongs in a deliberate helper change, not in a translator migration. Asserting EXACTLY 2 means a
|
||||
// third bare call — a new unguarded translator — still fails this gate.
|
||||
const ALLOWED_BARE = { 'plugin-designs/gospel-resources/resources-map.vue': 2 }
|
||||
for (const [file, helper, fallbackFrag] of SITES) {
|
||||
const code = codeOnly(rf(new URL('../' + file, import.meta.url), 'utf8'))
|
||||
const short = file.split('/').pop()
|
||||
ok(new RegExp(`\\b${helper}\\(`).test(code), `${short}: routes through ${helper}()`)
|
||||
ok(!/useI18n\(/.test(code), `${short}: no bare useI18n() left in CODE (comments explaining it are fine)`)
|
||||
const bare = (code.match(/useI18n\(/g) || []).length
|
||||
const allowed = ALLOWED_BARE[file] || 0
|
||||
ok(bare === allowed, allowed
|
||||
? `${short}: exactly ${allowed} bare useI18n() left, the documented locale read(s) -- found ${bare}`
|
||||
: `${short}: no bare useI18n() left in CODE (comments explaining it are fine)`)
|
||||
if (fallbackFrag) {
|
||||
ok(code.includes(fallbackFrag),
|
||||
`${short}: still passes its OWN fallback (${fallbackFrag}) -- migrating it to the catalogue would change what a bare host renders`)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,11 @@ import { tStatic } from '@map/i18n/index.js'
|
|||
/** `{n}` interpolation. Exported so a gate can test it without a Vue setup scope. */
|
||||
export function fillParams(str, params) {
|
||||
if (!params) return String(str)
|
||||
return String(str).replace(/\{(\w+)\}/g, (m, k) => (k in params ? String(params[k]) : m))
|
||||
// ★ `!= null`, NOT `k in params`. Gospel's interpolate() had it right and my first version did not:
|
||||
// with { count: null } a key-presence test renders the STRING "null" into the sentence, while this
|
||||
// leaves `{count}` visible. A visible placeholder is a bug you can see and fix; "null" reads as
|
||||
// content. Found by comparing against charts.vue before migrating it, not after.
|
||||
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 either 'x.y' or 'pplr.x.y'. */
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
* DATA: fetched at runtime from assets/tables/ (people-group-resources.json is 1.2MB;
|
||||
* inlining it into the IIFE would block first paint for every profile in the bundle).
|
||||
*/
|
||||
import { useI18n } from 'vue-i18n'
|
||||
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'
|
||||
|
|
@ -64,14 +64,10 @@ const enLookup = (key) =>
|
|||
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
|
||||
let tr = (key, params) => interpolate(enLookup(key) ?? String(key), params)
|
||||
try {
|
||||
const i18n = useI18n()
|
||||
tr = (key, params) => {
|
||||
const v = params ? i18n.t(key, params) : i18n.t(key)
|
||||
return v === key ? interpolate(enLookup(key) ?? key, params) : v
|
||||
}
|
||||
} catch (_) { /* no i18n on this host */ }
|
||||
// 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 })
|
||||
|
||||
|
||||
const profileConfig = inject('profileConfig', ref(null))
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
* the table's own meta so the method travels with the number.
|
||||
*/
|
||||
import { computed, inject, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useOptionalT } from '@map/composables/useOptionalT.js'
|
||||
import { EN_MESSAGES } from '@map/i18n/index.js'
|
||||
import DataTable from '@map/components/tables/DataTable.vue'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
|
@ -37,11 +37,10 @@ const assetsBase = computed(() => resolveAssetsBase(profileConfig.value))
|
|||
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 || {})
|
||||
let tr = (key) => enLookup(key) ?? String(key)
|
||||
try {
|
||||
const i18n = useI18n()
|
||||
tr = (key) => { const v = i18n.t(key); return v === key ? (enLookup(key) ?? key) : v }
|
||||
} catch (_) { /* no i18n on this host: the shipped English catalogue is the fallback */ }
|
||||
// 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 })
|
||||
|
||||
const TABS = [
|
||||
// ★ THE DOXA TABLE LEADS, because it is the question the Driver actually asked: which of
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useOptionalT } from '@map/composables/useOptionalT.js'
|
||||
import { EN_MESSAGES } from '@map/i18n/index.js'
|
||||
import { resourceRows } from './gospel-resource-rows.js'
|
||||
/**
|
||||
|
|
@ -46,11 +46,10 @@ import { resolveAssetsBase } from '@map/utils/embedBase.js'
|
|||
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 || {})
|
||||
let tr = (key) => enLookup(key) ?? String(key)
|
||||
try {
|
||||
const i18n = useI18n()
|
||||
tr = (key) => { const v = i18n.t(key); return v === key ? (enLookup(key) ?? key) : v }
|
||||
} catch (_) { /* no i18n on this host */ }
|
||||
// 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 })
|
||||
|
||||
|
||||
const profileConfig = inject('profileConfig', ref(null))
|
||||
|
|
|
|||
|
|
@ -71,7 +71,8 @@
|
|||
* disagree, and the 5-country string above parses to exactly its 5 real names.
|
||||
*/
|
||||
import { computed, inject, ref, shallowRef, markRaw, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useI18n } from 'vue-i18n' // still needed: two guarded LOCALE reads below (isEnglish, the engine tag)
|
||||
import { useOptionalT } from '@map/composables/useOptionalT.js'
|
||||
import { applyPeopleGroupList } from '@map/lists/peopleGroupList.js'
|
||||
import { EN_MESSAGES } from '@map/i18n/index.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
|
|
@ -784,11 +785,10 @@ const pinsByIso = computed(() => {
|
|||
broken-looking label is not graceful degradation, it just moves the bug. */
|
||||
const enLookup = (key) =>
|
||||
String(key).replace(/^gospel\./, '').split('.').reduce((o, k) => (o == null ? o : o[k]), EN_MESSAGES.gospel || {})
|
||||
let t = (key) => enLookup(key) ?? String(key)
|
||||
try {
|
||||
const i18n = useI18n()
|
||||
t = (key) => { const v = i18n.t(key); return v === key ? (enLookup(key) ?? key) : v }
|
||||
} catch (_) { /* no i18n on this host: the English catalog above is the fallback */ }
|
||||
// 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 t = useOptionalT('gospel', { fallback: enLookup })
|
||||
/* 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