The wall intro composes its own markup, so the catalogue holds plain text
studio landed upg100WallIntroA / Emph / B in all eleven locales (landing 10, f4871401) and kept the legacy
upg100WallIntro, so this consumer can land without a flag day. dx-shepherd UPGHTML / studio END-STU-WALLINTRO.
★ THE CATALOGUE NO LONGER CARRIES MARKUP. vue-i18n warned "[intlify] Detected HTML in 'Hand away the prayer
task…'" twice per tab pass, and it was right: I measured every en catalogue area and upg100WallIntro was 1 of
1 — the only key in the whole tree with a tag in it. Not a convention I was following; my outlier.
⚠ THE OBVIOUS FIXES DID NOT APPLY, which is why this took a design rather than a one-liner. "Markup in the
template" and "<i18n-t> interpolation" both assume this profile renders the sentence. It does not: `intro` is
a STRING PROP on the library PosterWall, rendered there with v-html (PosterWall.vue:955), and that component
has no slots. So the markup must arrive inside the string; the only question was who builds it. Building it
here keeps the catalogue clean with no library change.
★ AND IT SHRINKS WHAT v-html TRUSTS, which matters more than the warning. v-html on a whole catalogue string
trusts the CATALOGUE: any tag a translator or a bad merge puts in any of the eleven files renders as markup.
Composing from parts escapes each part and emits exactly one tag I control, so the trusted surface goes from
"every translation of this key" to this line. Verified: a tag planted in a part comes out as <img …>,
not as an element.
THREE PARTS, NOT A HEAD+EMPHASIS WRAPPER, because the emphasis is not last in every language. All eleven have
exactly one <strong> and no other tags, but de, hi and zh carry real text AFTER it (" beten.",
" प्रार्थना करते हुए।", "為他們禱告。") where the others end on a full stop. A two-key split would have
silently dropped those tails.
RENDERS IDENTICALLY BEFORE AND AFTER: while the three keys were absent it fell through to the legacy single
key, unescaped, exactly as before — escaping THAT would have printed a literal "<strong>" to the reader, a
visible regression traded for a console warning. Verified against main's catalogue: all eleven locales
recompose A + <strong>Emph</strong> + B to the current upg100WallIntro BYTE-FOR-BYTE, which is the same check
studio said they would run before committing, run independently here rather than taken on trust.
check:sfc-parse PASS. check:i18n-keys-exist still reports the three keys missing IN THIS WORKTREE, which is
correct and expected — they are on main and this branch has not merged it yet; the merge in my landing window
resolves it.
This commit is contained in:
parent
4d2b8509ab
commit
3a04a941bc
1 changed files with 40 additions and 1 deletions
|
|
@ -192,7 +192,46 @@ async function fetchListRecords(list) {
|
|||
// Product call taken by dx-shepherd (UPGHK, 2026-09-27) under the Driver's standing goal that the
|
||||
// production maps be localized.
|
||||
const t = useOptionalT('')
|
||||
const intro = computed(() => t('messages.upg100WallIntro'))
|
||||
|
||||
// ★ THE MARKUP IS COMPOSED HERE, so the CATALOGUE holds plain text (dx-shepherd UPGHTML, 2026-09-27).
|
||||
// vue-i18n warns "[intlify] Detected HTML in '…'" twice per tab pass, and it is right to: a translated
|
||||
// string carrying <strong> is the only one in this tree that does — I measured every en catalogue area
|
||||
// and upg100WallIntro is 1 of 1, so this was not a convention, it was my outlier.
|
||||
//
|
||||
// ⚠ WHY THE OBVIOUS FIXES DO NOT APPLY HERE. The shepherd's suggestion — markup in the template, or
|
||||
// <i18n-t> interpolation — assumes this profile renders the sentence. It does not: `intro` is a STRING
|
||||
// PROP on the library PosterWall, rendered there with v-html (PosterWall.vue:955), and that component
|
||||
// has no slots at all. So the markup has to arrive inside the string, and the only question is who
|
||||
// builds the string. Building it HERE keeps the catalogue clean without a library change.
|
||||
//
|
||||
// ★ AND IT IS STRICTLY SAFER THAN WHAT IT REPLACES, which is the part worth keeping. v-html on a whole
|
||||
// catalogue string TRUSTS THE CATALOGUE: any tag a translator (or a bad merge) puts in any of the 11
|
||||
// locale files renders as markup. Composing here escapes each part and emits exactly one tag I control,
|
||||
// so the trusted surface shrinks from "every translation of this key" to "this line".
|
||||
//
|
||||
// THREE PARTS, NOT A WRAPPER AROUND ONE, because the emphasis is not in the same place in every
|
||||
// language. Measured across all 11 locales: each has exactly one <strong> and no other tags, and de, hi
|
||||
// and zh carry real text AFTER the emphasised run (" beten.", " प्रार्थना करते हुए।", "為他們禱告。")
|
||||
// where the others end on a full stop. A head+emphasis wrapper would have silently dropped those tails.
|
||||
const escHtml = (s) => String(s)
|
||||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||
const intro = computed(() => {
|
||||
// The three plain-text keys studio adds (rule 5B — the catalogue is theirs). Until they land, the
|
||||
// single legacy key is still the source of truth, so this renders identically before AND after: no
|
||||
// flag day, and my landing does not have to be ordered against theirs.
|
||||
const a = t('messages.upg100WallIntroA')
|
||||
const b = t('messages.upg100WallIntroEmph')
|
||||
const c = t('messages.upg100WallIntroB')
|
||||
const missing = (v, key) => !v || v === key
|
||||
if (missing(a, 'messages.upg100WallIntroA') || missing(b, 'messages.upg100WallIntroEmph')) {
|
||||
// ⚠ THE LEGACY KEY STILL CONTAINS THE MARKUP, so it is passed through unescaped exactly as before.
|
||||
// Escaping it here would print a literal "<strong>" to the reader — a visible regression traded for
|
||||
// a console warning, which is the wrong way round.
|
||||
return t('messages.upg100WallIntro')
|
||||
}
|
||||
const tail = missing(c, 'messages.upg100WallIntroB') ? '' : escHtml(c)
|
||||
return `${escHtml(a)}<strong>${escHtml(b)}</strong>${tail}`
|
||||
})
|
||||
const heading = computed(() => t('messages.upg100WallHeading'))
|
||||
const wallTag = computed(() => t('messages.upg100WallTag'))
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue