diff --git a/internal/check-optional-t-equivalence.mjs b/internal/check-optional-t-equivalence.mjs index a38f03d54..e96e2f648 100644 --- a/internal/check-optional-t-equivalence.mjs +++ b/internal/check-optional-t-equivalence.mjs @@ -129,6 +129,12 @@ const SITES = [ // 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(''", null], + // wave 3b — cities-internet. All three were byte-identical in the guard block; each keeps its own + // enLookup fallback, so a bare host renders the same English. Their local fill() used `k in params`, + // so the shared fillParams also removes a way for them to print the literal "null". + ['plugin-designs/cities-internet/connectivity-list.vue', 'useOptionalT', 'fallback: enLookup'], + ['plugin-designs/cities-internet/connectivity-map.vue', 'useOptionalT', 'fallback: enLookup'], + ['plugin-designs/cities-internet/data-tables.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 diff --git a/plugin-designs/cities-internet/connectivity-list.vue b/plugin-designs/cities-internet/connectivity-list.vue index d16ef19ca..3ee15a021 100644 --- a/plugin-designs/cities-internet/connectivity-list.vue +++ b/plugin-designs/cities-internet/connectivity-list.vue @@ -34,7 +34,7 @@ import { computed, inject, ref } from 'vue' import { useShadowStyles } from '@map/composables/useShadowStyles.js' import data from './data/connectivity.json' -import { useI18n } from 'vue-i18n' +import { useOptionalT } from '@map/composables/useOptionalT.js' import enConnectivity from '@map/i18n/locales/en/connectivity.json' const profileConfig = inject('profileConfig', ref(null)) @@ -44,12 +44,14 @@ const profileConfig = inject('profileConfig', ref(null)) // shipped catalog is the fallback, never a blank or a key. Region and country names are data. const enLookup = (key) => String(key).replace(/^connectivity\./, '').split('.').reduce((o, k) => (o == null ? o : o[k]), enConnectivity) -const fill = (str, params) => (params ? String(str).replace(/\{(\w+)\}/g, (m, k) => (k in params ? params[k] : m)) : str) -let t = (key, params) => fill(enLookup(key) ?? String(key), params) -try { - const i18n = useI18n() - t = (key, params) => { const v = i18n.t(key, params || {}); return v === key ? fill(enLookup(key) ?? key, params) : v } -} catch (_) { /* no i18n on this host: the English catalog above is the fallback */ } +// C4 wave 3b: 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. gospel-plugin chose the +// locale-honouring catalogue default for their bundle instead ("a partner host that never installed +// vue-i18n serves its readers English while the bundle already carries all 11 catalogues"); the same +// choice is open to cities-internet's owner and is one word to make — it is not a migration's to take. +// The local fill() is gone with the guard: fillParams applies the same interpolation, except it leaves +// a placeholder for a NULL value where this fill() rendered the string "null". +const t = useOptionalT('connectivity', { fallback: enLookup }) // NUMERALS ARE HELD for the Driver (dx-shepherd, 2026-09-26): a locale-following format would switch // 'ar' to Arabic-Indic digits. Until that is decided, numbers keep the browser-default format they // had before this pass; only the TEXT is translated. diff --git a/plugin-designs/cities-internet/connectivity-map.vue b/plugin-designs/cities-internet/connectivity-map.vue index 4745f3540..fc79afaea 100644 --- a/plugin-designs/cities-internet/connectivity-map.vue +++ b/plugin-designs/cities-internet/connectivity-map.vue @@ -33,7 +33,7 @@ * carrier flag is a NAME-PATTERN HEURISTIC, not a verified classification. */ import { computed, inject, ref, shallowRef, markRaw, onMounted, onBeforeUnmount } from 'vue' -import { useI18n } from 'vue-i18n' +import { useOptionalT } from '@map/composables/useOptionalT.js' import enConnectivity from '@map/i18n/locales/en/connectivity.json' import { useShadowStyles } from '@map/composables/useShadowStyles.js' import { addMapAttribution } from '@map/composables/useMapAttribution' @@ -208,12 +208,14 @@ function paintGroups(map) { // Carrier and platform names are data and are not translated. const enLookup = (key) => String(key).replace(/^connectivity\./, '').split('.').reduce((o, k) => (o == null ? o : o[k]), enConnectivity) -const fill = (str, params) => (params ? String(str).replace(/\{(\w+)\}/g, (m, k) => (k in params ? params[k] : m)) : str) -let t = (key, params) => fill(enLookup(key) ?? String(key), params) -try { - const i18n = useI18n() - t = (key, params) => { const v = i18n.t(key, params || {}); return v === key ? fill(enLookup(key) ?? key, params) : v } -} catch (_) { /* no i18n on this host: the English catalog above is the fallback */ } +// C4 wave 3b: 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. gospel-plugin chose the +// locale-honouring catalogue default for their bundle instead ("a partner host that never installed +// vue-i18n serves its readers English while the bundle already carries all 11 catalogues"); the same +// choice is open to cities-internet's owner and is one word to make — it is not a migration's to take. +// The local fill() is gone with the guard: fillParams applies the same interpolation, except it leaves +// a placeholder for a NULL value where this fill() rendered the string "null". +const t = useOptionalT('connectivity', { fallback: enLookup }) // Bands, not a continuous ramp: a reader can hold four categories, not a gradient. const BANDS = [ diff --git a/plugin-designs/cities-internet/data-tables.vue b/plugin-designs/cities-internet/data-tables.vue index fd1265b3d..74bd0be27 100644 --- a/plugin-designs/cities-internet/data-tables.vue +++ b/plugin-designs/cities-internet/data-tables.vue @@ -25,7 +25,7 @@ import { computed, inject, ref, watch } from 'vue' import DataTable from '@map/components/tables/DataTable.vue' import { useShadowStyles } from '@map/composables/useShadowStyles.js' import { resolveAssetsBase } from '@map/utils/embedBase.js' -import { useI18n } from 'vue-i18n' +import { useOptionalT } from '@map/composables/useOptionalT.js' import enConnectivity from '@map/i18n/locales/en/connectivity.json' @@ -38,12 +38,14 @@ const assetsBase = computed(() => resolveAssetsBase(profileConfig.value)) // shipped catalog is the fallback, never a blank or a key. Region and country names are data. const enLookup = (key) => String(key).replace(/^connectivity\./, '').split('.').reduce((o, k) => (o == null ? o : o[k]), enConnectivity) -const fill = (str, params) => (params ? String(str).replace(/\{(\w+)\}/g, (m, k) => (k in params ? params[k] : m)) : str) -let t = (key, params) => fill(enLookup(key) ?? String(key), params) -try { - const i18n = useI18n() - t = (key, params) => { const v = i18n.t(key, params || {}); return v === key ? fill(enLookup(key) ?? key, params) : v } -} catch (_) { /* no i18n on this host: the English catalog above is the fallback */ } +// C4 wave 3b: 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. gospel-plugin chose the +// locale-honouring catalogue default for their bundle instead ("a partner host that never installed +// vue-i18n serves its readers English while the bundle already carries all 11 catalogues"); the same +// choice is open to cities-internet's owner and is one word to make — it is not a migration's to take. +// The local fill() is gone with the guard: fillParams applies the same interpolation, except it leaves +// a placeholder for a NULL value where this fill() rendered the string "null". +const t = useOptionalT('connectivity', { fallback: enLookup }) // NUMERALS ARE HELD for the Driver (dx-shepherd, 2026-09-26): a locale-following format would switch // 'ar' to Arabic-Indic digits. Until that is decided, numbers keep the browser-default format they // had before this pass; only the TEXT is translated. diff --git a/plugin-designs/regions-dashboard/scripts/strip-code.mjs b/plugin-designs/regions-dashboard/scripts/strip-code.mjs index a3da12eb3..50456fbb3 100644 --- a/plugin-designs/regions-dashboard/scripts/strip-code.mjs +++ b/plugin-designs/regions-dashboard/scripts/strip-code.mjs @@ -21,6 +21,16 @@ // // Comments are replaced by SPACES, preserving length and line structure, so line numbers reported // against the result still point at the real file. +// ★ WHAT THIS DOES NOT STRIP, and both gaps have already cost a real measurement: +// - CSS comments inside a JS TEMPLATE LITERAL. A string is copied through verbatim, correctly for +// JS, but useShadowStyles(`...`) contains CSS whose /* */ the BROWSER honours. Reading specificity +// off such a file once gave (0,5,53) because a comment landed inside the selector. +// - HTML comments in a .vue TEMPLATE. `` is invisible here, so any identifier quoted in +// one is counted as code. That made a bare-useI18n census read 3 where the code had 2. +// gospel-plugin's generalisation, worth keeping: "any in a template that quotes an identifier +// will keep doing that to you". THE RULE: the stripper must match the LANGUAGE of the text being read. +// For a .vue file, strip HTML comments first, then call this for the script block. + export function codeOnly(src) { let out = '' let i = 0