From cd5e5f559099f3f4d0df5061d23b2f1b920caf48 Mon Sep 17 00:00:00 2001 From: doxa-maps-88655c70 <88627970+doxa-maps-88655c70@users.noreply.github.com> Date: Thu, 24 Sep 2026 03:36:52 +0000 Subject: [PATCH] test(packet): verify the packet in ITS OWN language, not in English MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gate asserted a hardcoded English word list for the prayer rhythms — which tests the opposite of what i18n means. It passed a packet whose translation silently did nothing, and FAILED every correctly translated one: the Spanish packet reported "only 0/11 prayer rhythms present" while rendering all twelve correctly as ALABANZA / ESPERAR / CONFESAR. Expected words now come from the strings catalog for the packet's OWN language, resolved per key with English fallback — the same contract the renderer honours. So the gate asserts the PROPERTY (all rhythms present, in this language) rather than a vocabulary. The catalog path is derived from the packet's own location instead of a machine-specific constant, with PACKET_STRINGS to override. Adds the assertion the tick exposed: a non-English packet must ACTUALLY DIFFER from English. That failure is invisible to every other check, because an all-English packet is a perfectly valid packet — a mis-keyed block, a block under the wrong ISO code, or a renderer ignoring its lang argument all produce one. Confirmed to fire before being accepted: a packet relabelled lang="de" with no de block reports "lang=de but no string differs from English". --- internal/check-packet-render.mjs | 48 +++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/internal/check-packet-render.mjs b/internal/check-packet-render.mjs index 60a3df5db..081ff9824 100644 --- a/internal/check-packet-render.mjs +++ b/internal/check-packet-render.mjs @@ -27,8 +27,8 @@ // Default path is overridable with PACKET_HTML so this is not machine-specific. import { chromium } from 'playwright' -import { existsSync } from 'node:fs' -import { resolve } from 'node:path' +import { existsSync, readFileSync } from 'node:fs' +import { resolve, dirname } from 'node:path' import { pathToFileURL } from 'node:url' const target = process.argv[2] || process.env.PACKET_HTML @@ -50,11 +50,45 @@ const errs = [] page.on('pageerror', (e) => errs.push(String(e).slice(0, 120))) await page.goto(pathToFileURL(file).href, { waitUntil: 'networkidle' }) +// Resolve the rhythm words for THIS packet's language from the shared catalog. +// Per-key fallback to English, the same contract the renderer honours. +const lang = await page.evaluate(() => document.documentElement.lang || 'en') +// The catalog sits beside the generator that produced this packet, so derive it +// from the packet's own path rather than hardcoding a machine-specific location. +const catalogPath = process.env.PACKET_STRINGS + || resolve(dirname(file), '..', '..', 'packet-strings.json') +if (!existsSync(catalogPath)) { + console.error(`PACKET-RENDER: FAIL — no strings catalog at ${catalogPath}`) + console.error(' Set PACKET_STRINGS if the packet lives outside the generator tree.') + process.exit(1) +} +const catalog = JSON.parse(readFileSync(catalogPath, 'utf8')) +const enR = catalog.en.rhythms +const langR = (catalog[lang] || {}).rhythms || {} +const expected = [...new Set(Object.keys(enR).map((k) => langR[k] ?? enR[k]))] +// ★ A NON-ENGLISH PACKET MUST ACTUALLY DIFFER FROM ENGLISH. The failure this +// catches is a language block that exists but does nothing — a typo'd key, a +// block under the wrong ISO code, or a renderer that ignored the lang argument. +// Every assertion below would still pass in that case, because an all-English +// packet is a perfectly valid packet; only the comparison reveals it. +const translated = lang !== 'en' + ? Object.keys(catalog.en).filter((k) => typeof catalog.en[k] === 'string' + && (catalog[lang] || {})[k] && catalog[lang][k] !== catalog.en[k]).length + : null +await page.evaluate((words) => { window.__EXPECTED_RHYTHMS = words }, expected) + const r = await page.evaluate(() => { const body = document.body.innerText const imgs = [...document.querySelectorAll('.card img')] - const RHYTHMS = ['PRAISE', 'WAIT', 'CONFESS', 'READ THE WORD', 'ASK', 'INTERCESSION', - 'PRAY THE WORD', 'THANK', 'SING', 'MEDITATE', 'LISTEN'] + // ⚠ The expected rhythm words come from the STRINGS CATALOG FOR THIS PACKET'S + // LANGUAGE, never a hardcoded English list. A fixed list tests the opposite of + // what i18n means: it passed a packet whose translation silently did nothing and + // FAILED every correctly-translated one (measured — the Spanish packet reported + // 0/11 rhythms while rendering all twelve correctly as ALABANZA/ESPERAR/...). + // Read the packet's own and resolve per-key, exactly as the renderer + // does, so this asserts the PROPERTY (all rhythms present, in this language) + // rather than a vocabulary. + const RHYTHMS = window.__EXPECTED_RHYTHMS || [] // Whole-map property, NOT one sampled element — see header note 2. const mapFaults = (() => { @@ -106,6 +140,10 @@ if (r.rhythms !== r.rhythmsTotal) fails.push(`only ${r.rhythms}/${r.rhythmsTotal if (!r.hasMark5) fails.push('Mark 5:1-20 is NOT printed in full (Driver requirement for point 4)') if (r.metacamp !== 2) fails.push(`${r.metacamp} metacamp.org attributions, expected exactly 2`) if (r.mapFaults.length) fails.push(`map: ${r.mapFaults.join('; ')}`) +if (translated === 0) { + fails.push(`lang="${lang}" but no string differs from English — ` + + 'the language block is missing, mis-keyed, or the renderer ignored it') +} if (errs.length) fails.push(`${errs.length} page error(s): ${errs[0]}`) if (landscape) fails.push(`${landscape} landscape page(s) — the packet must be portrait`) if (pages !== r.sheets) fails.push(`${pages} PDF pages for ${r.sheets} sheets — one sheet must be exactly one page`) @@ -117,6 +155,8 @@ console.log(` Mark 5 in full : ${r.hasMark5}`) console.log(` metacamp.org : ${r.metacamp}`) console.log(` map : ${r.mapFaults.length ? r.mapFaults.join('; ') : 'palette resolves, not black'}`) console.log(` page size : ${boxes[0] ? boxes[0].join(' x ') : '?'} pt`) +console.log(` language : ${lang} (${r.rhythms}/${r.rhythmsTotal} rhythms in ${lang}` + + `${translated === null ? '' : `, ${translated} strings translated`})`) if (fails.length) { console.error(`\nPACKET-RENDER: FAIL — ${fails.length} problem(s)`)