Name the Deaf packet in its reader's language, not by its internal label
The Deaf zone printed "Deaf (affinity bloc A017)" as its title - on page 1, at the head of every profile page and in the document's own title. That is the legend's internal label: the unified Deaf row is an affinity bloc spanning every region, so there is no country to name it and zone-title.js declined it, leaving the label to print. An affinity-bloc zone has a name of its own even though it has no country. zone-title.js now reads it from the packet strings, keyed by bloc (zone_affinity_A017: "Deaf people groups", in all eleven languages), so another affinity bloc a list adds is named with one string and no code. With no strings it still declines and the label stays - it never invents a name. Both print paths now pass the language's strings, English per key, the same fallback the renderer applies. check-zone-title-tier-a covers the unified Deaf zone, which it never saw (it enumerates clusters.json entries, where Deaf is two older lists): the translated name in en/es/ar, never internal text, and a decline without strings. It now tests with the real packet strings, which carry the multi-country joiner, so a broken one-country guard would generate a title and be caught.
This commit is contained in:
parent
bc422bb678
commit
6af71bce45
5 changed files with 56 additions and 5 deletions
|
|
@ -68,8 +68,12 @@ if (probe !== 'Sudan') {
|
||||||
process.exit(75)
|
process.exit(75)
|
||||||
}
|
}
|
||||||
|
|
||||||
const strings = { zone_join: '{a} + {b}', zone_join_more: '{a} + {n} more' }
|
// ★ THE REAL PACKET STRINGS, per language with English per key — what the print paths pass. They carry
|
||||||
const titleFor = (zone, locale) => zoneTitle(zone, strings, { countryName, locale })
|
// the multi-country joiner, so a broken one-country guard would GENERATE "A + B + n more" here and the
|
||||||
|
// negative side below would catch it; a hand-made stub is weaker than the product's own catalogue.
|
||||||
|
const PS = JSON.parse(readFileSync(join(REPO, 'library/scripts/posters/packet-strings.json'), 'utf8'))
|
||||||
|
const block = (lc) => ({ ...(PS.en || {}), ...(PS[lc] || {}) })
|
||||||
|
const titleFor = (zone, locale) => zoneTitle(zone, block(locale), { countryName, locale })
|
||||||
|
|
||||||
const fails = []
|
const fails = []
|
||||||
const notes = []
|
const notes = []
|
||||||
|
|
@ -116,6 +120,28 @@ for (const z of multi) {
|
||||||
notes.push(` ok ${String(multi.length).padEnd(22)} cluster entries decline in all ${LOCALES.length} locales, keeping their labels byte-identical`)
|
notes.push(` ok ${String(multi.length).padEnd(22)} cluster entries decline in all ${LOCALES.length} locales, keeping their labels byte-identical`)
|
||||||
notes.push(` ${''.padEnd(22)} (= ${multi.length - 1} leaf zones; the 2 deaf entries collapse to 1 leaf and both decline)`)
|
notes.push(` ${''.padEnd(22)} (= ${multi.length - 1} leaf zones; the 2 deaf entries collapse to 1 leaf and both decline)`)
|
||||||
|
|
||||||
|
// ── (3) THE UNIFIED DEAF ZONE: named from the strings, never by its internal label ─────────────────
|
||||||
|
// It is not in clusters.json's entries (the legend builds it from the two deaf lists), so it gets its
|
||||||
|
// own check. Two-sided: with the strings it takes its translated name; with none it declines and keeps
|
||||||
|
// its label, rather than inventing one.
|
||||||
|
{
|
||||||
|
const { DEAF_SELECTOR } = await import(join(PLUGIN, 'data/legend-tree.js'))
|
||||||
|
const deafZone = { id: 'deaf', label: 'Deaf (affinity bloc A017)', selector: DEAF_SELECTOR }
|
||||||
|
const got = {}
|
||||||
|
for (const lc of LOCALES) {
|
||||||
|
const t = titleFor(deafZone, lc)
|
||||||
|
const want = block(lc).zone_affinity_A017
|
||||||
|
got[lc] = t
|
||||||
|
if (!want) fails.push(`the packet strings for ${lc} carry no zone_affinity_A017, so the Deaf zone cannot be named`)
|
||||||
|
else if (t !== want) fails.push(`the Deaf zone is titled ${JSON.stringify(t)} in ${lc}; its translated name is ${JSON.stringify(want)}`)
|
||||||
|
if (t && /affinity|A017/i.test(t)) fails.push(`the Deaf zone's ${lc} title prints internal text: "${t}"`)
|
||||||
|
}
|
||||||
|
if (zoneTitle(deafZone, null, { countryName, locale: 'en' }) !== null) {
|
||||||
|
fails.push('the Deaf zone generated a title with NO strings to name it — it must decline and keep its label')
|
||||||
|
}
|
||||||
|
notes.push(` ok ${'deaf'.padEnd(22)} ${LOCALES.map((lc) => `${lc}=${got[lc]}`).join(' ')} (and declines with no strings)`)
|
||||||
|
}
|
||||||
|
|
||||||
// ── report ───────────────────────────────────────────────────────────────────────────────────────
|
// ── report ───────────────────────────────────────────────────────────────────────────────────────
|
||||||
for (const n of notes) console.log(n)
|
for (const n of notes) console.log(n)
|
||||||
if (fails.length) {
|
if (fails.length) {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"map_page_sub": "A prayer list of {count} unreached people groups",
|
"map_page_sub": "A prayer list of {count} unreached people groups",
|
||||||
"map_unavailable": "The map for this prayer list is not included in this printing.",
|
"map_unavailable": "The map for this prayer list is not included in this printing.",
|
||||||
"map_page_series": "One of {k} UPG 100 prayer lists",
|
"map_page_series": "One of {k} UPG 100 prayer lists",
|
||||||
|
"zone_affinity_A017": "Deaf people groups",
|
||||||
"list_header": "{from}–{to} of {count} · pray for them by name",
|
"list_header": "{from}–{to} of {count} · pray for them by name",
|
||||||
"people_suffix": "people",
|
"people_suffix": "people",
|
||||||
"no_photo": "no photo available",
|
"no_photo": "no photo available",
|
||||||
|
|
@ -239,6 +240,7 @@
|
||||||
"map_page_sub": "Una lista de oración de {count} grupos étnicos no alcanzados",
|
"map_page_sub": "Una lista de oración de {count} grupos étnicos no alcanzados",
|
||||||
"map_unavailable": "El mapa de esta lista de oración no se incluye en esta impresión.",
|
"map_unavailable": "El mapa de esta lista de oración no se incluye en esta impresión.",
|
||||||
"map_page_series": "Una de las {k} listas de oración UPG 100",
|
"map_page_series": "Una de las {k} listas de oración UPG 100",
|
||||||
|
"zone_affinity_A017": "Grupos de personas sordas",
|
||||||
"list_header": "{from}–{to} de {count} · ora por ellos por nombre",
|
"list_header": "{from}–{to} de {count} · ora por ellos por nombre",
|
||||||
"people_suffix": "personas",
|
"people_suffix": "personas",
|
||||||
"no_photo": "sin fotografía disponible",
|
"no_photo": "sin fotografía disponible",
|
||||||
|
|
@ -466,6 +468,7 @@
|
||||||
"map_page_sub": "Une liste de prière de {count} groupes ethniques non atteints",
|
"map_page_sub": "Une liste de prière de {count} groupes ethniques non atteints",
|
||||||
"map_unavailable": "La carte de cette liste de prière n’est pas incluse dans cette impression.",
|
"map_unavailable": "La carte de cette liste de prière n’est pas incluse dans cette impression.",
|
||||||
"map_page_series": "Une des {k} listes de prière UPG 100",
|
"map_page_series": "Une des {k} listes de prière UPG 100",
|
||||||
|
"zone_affinity_A017": "Groupes de personnes sourdes",
|
||||||
"list_header": "{from}–{to} sur {count} · priez pour eux par leur nom",
|
"list_header": "{from}–{to} sur {count} · priez pour eux par leur nom",
|
||||||
"people_suffix": "personnes",
|
"people_suffix": "personnes",
|
||||||
"no_photo": "aucune photo disponible",
|
"no_photo": "aucune photo disponible",
|
||||||
|
|
@ -693,6 +696,7 @@
|
||||||
"map_page_sub": "Uma lista de oração de {count} grupos étnicos não alcançados",
|
"map_page_sub": "Uma lista de oração de {count} grupos étnicos não alcançados",
|
||||||
"map_unavailable": "O mapa desta lista de oração não está incluído nesta impressão.",
|
"map_unavailable": "O mapa desta lista de oração não está incluído nesta impressão.",
|
||||||
"map_page_series": "Uma das {k} listas de oração UPG 100",
|
"map_page_series": "Uma das {k} listas de oração UPG 100",
|
||||||
|
"zone_affinity_A017": "Grupos de pessoas surdas",
|
||||||
"list_header": "{from}–{to} de {count} · ore por eles pelo nome",
|
"list_header": "{from}–{to} de {count} · ore por eles pelo nome",
|
||||||
"people_suffix": "pessoas",
|
"people_suffix": "pessoas",
|
||||||
"no_photo": "sem fotografia disponível",
|
"no_photo": "sem fotografia disponível",
|
||||||
|
|
@ -918,6 +922,7 @@
|
||||||
"map_page_sub": "Eine Gebetsliste von {count} unerreichten Volksgruppen",
|
"map_page_sub": "Eine Gebetsliste von {count} unerreichten Volksgruppen",
|
||||||
"map_unavailable": "Die Karte zu dieser Gebetsliste ist in diesem Ausdruck nicht enthalten.",
|
"map_unavailable": "Die Karte zu dieser Gebetsliste ist in diesem Ausdruck nicht enthalten.",
|
||||||
"map_page_series": "Eine von {k} Gebetslisten von UPG 100",
|
"map_page_series": "Eine von {k} Gebetslisten von UPG 100",
|
||||||
|
"zone_affinity_A017": "Gruppen gehörloser Menschen",
|
||||||
"list_header": "{from}–{to} von {count} · betet namentlich für sie",
|
"list_header": "{from}–{to} von {count} · betet namentlich für sie",
|
||||||
"people_suffix": "Menschen",
|
"people_suffix": "Menschen",
|
||||||
"no_photo": "kein Foto verfügbar",
|
"no_photo": "kein Foto verfügbar",
|
||||||
|
|
@ -1145,6 +1150,7 @@
|
||||||
"map_page_sub": "Una lista di preghiera di {count} gruppi etnici non raggiunti",
|
"map_page_sub": "Una lista di preghiera di {count} gruppi etnici non raggiunti",
|
||||||
"map_unavailable": "La mappa di questa lista di preghiera non è inclusa in questa stampa.",
|
"map_unavailable": "La mappa di questa lista di preghiera non è inclusa in questa stampa.",
|
||||||
"map_page_series": "Una delle {k} liste di preghiera UPG 100",
|
"map_page_series": "Una delle {k} liste di preghiera UPG 100",
|
||||||
|
"zone_affinity_A017": "Gruppi di persone sorde",
|
||||||
"list_header": "{from}–{to} di {count} · pregate per loro per nome",
|
"list_header": "{from}–{to} di {count} · pregate per loro per nome",
|
||||||
"people_suffix": "persone",
|
"people_suffix": "persone",
|
||||||
"no_photo": "nessuna foto disponibile",
|
"no_photo": "nessuna foto disponibile",
|
||||||
|
|
@ -1370,6 +1376,7 @@
|
||||||
"map_page_sub": "O listă de rugăciune pentru {count} grupuri etnice neatinse",
|
"map_page_sub": "O listă de rugăciune pentru {count} grupuri etnice neatinse",
|
||||||
"map_unavailable": "Harta acestei liste de rugăciune nu este inclusă în această tipărire.",
|
"map_unavailable": "Harta acestei liste de rugăciune nu este inclusă în această tipărire.",
|
||||||
"map_page_series": "Una dintre cele {k} liste de rugăciune UPG 100",
|
"map_page_series": "Una dintre cele {k} liste de rugăciune UPG 100",
|
||||||
|
"zone_affinity_A017": "Grupuri de persoane surde",
|
||||||
"list_header": "{from}–{to} din {count} · rugați-vă pentru ei pe nume",
|
"list_header": "{from}–{to} din {count} · rugați-vă pentru ei pe nume",
|
||||||
"people_suffix": "persoane",
|
"people_suffix": "persoane",
|
||||||
"no_photo": "nicio fotografie disponibilă",
|
"no_photo": "nicio fotografie disponibilă",
|
||||||
|
|
@ -1595,6 +1602,7 @@
|
||||||
"map_page_sub": "Молитвенный список из {count} неохваченных народов",
|
"map_page_sub": "Молитвенный список из {count} неохваченных народов",
|
||||||
"map_unavailable": "Карта этого молитвенного списка не включена в эту распечатку.",
|
"map_unavailable": "Карта этого молитвенного списка не включена в эту распечатку.",
|
||||||
"map_page_series": "Один из {k} молитвенных списков UPG 100",
|
"map_page_series": "Один из {k} молитвенных списков UPG 100",
|
||||||
|
"zone_affinity_A017": "Группы глухих людей",
|
||||||
"list_header": "{from}–{to} из {count} · молитесь за них поимённо",
|
"list_header": "{from}–{to} из {count} · молитесь за них поимённо",
|
||||||
"people_suffix": "человек",
|
"people_suffix": "человек",
|
||||||
"no_photo": "фотография недоступна",
|
"no_photo": "фотография недоступна",
|
||||||
|
|
@ -1822,6 +1830,7 @@
|
||||||
"map_page_sub": "قائمة صلاة من أجل {count} شعبًا غير مبلَّغ",
|
"map_page_sub": "قائمة صلاة من أجل {count} شعبًا غير مبلَّغ",
|
||||||
"map_unavailable": "خريطة قائمة الصلاة هذه غير مضمَّنة في هذه النسخة المطبوعة.",
|
"map_unavailable": "خريطة قائمة الصلاة هذه غير مضمَّنة في هذه النسخة المطبوعة.",
|
||||||
"map_page_series": "واحدة من {k} قائمة صلاة في UPG 100",
|
"map_page_series": "واحدة من {k} قائمة صلاة في UPG 100",
|
||||||
|
"zone_affinity_A017": "مجموعات الصمّ",
|
||||||
"list_header": "{from}–{to} من {count} · صلّوا لأجلهم بالاسم",
|
"list_header": "{from}–{to} من {count} · صلّوا لأجلهم بالاسم",
|
||||||
"people_suffix": "نسمة",
|
"people_suffix": "نسمة",
|
||||||
"no_photo": "لا توجد صورة متاحة",
|
"no_photo": "لا توجد صورة متاحة",
|
||||||
|
|
@ -2049,6 +2058,7 @@
|
||||||
"map_page_sub": "{count} अनछुए जनसमूहों की प्रार्थना सूची",
|
"map_page_sub": "{count} अनछुए जनसमूहों की प्रार्थना सूची",
|
||||||
"map_unavailable": "इस प्रार्थना सूची का नक्शा इस प्रिंट में शामिल नहीं है।",
|
"map_unavailable": "इस प्रार्थना सूची का नक्शा इस प्रिंट में शामिल नहीं है।",
|
||||||
"map_page_series": "UPG 100 की {k} प्रार्थना सूचियों में से एक",
|
"map_page_series": "UPG 100 की {k} प्रार्थना सूचियों में से एक",
|
||||||
|
"zone_affinity_A017": "बधिर लोगों के समूह",
|
||||||
"list_header": "{count} में से {from}–{to} · उनके नाम लेकर प्रार्थना करें",
|
"list_header": "{count} में से {from}–{to} · उनके नाम लेकर प्रार्थना करें",
|
||||||
"people_suffix": "लोग",
|
"people_suffix": "लोग",
|
||||||
"no_photo": "कोई चित्र उपलब्ध नहीं",
|
"no_photo": "कोई चित्र उपलब्ध नहीं",
|
||||||
|
|
@ -2274,6 +2284,7 @@
|
||||||
"map_page_sub": "为 {count} 个未得之民的祷告名单",
|
"map_page_sub": "为 {count} 个未得之民的祷告名单",
|
||||||
"map_unavailable": "此祷告名单的地图未包含在本次打印中。",
|
"map_unavailable": "此祷告名单的地图未包含在本次打印中。",
|
||||||
"map_page_series": "UPG 100 的 {k} 份祷告名单之一",
|
"map_page_series": "UPG 100 的 {k} 份祷告名单之一",
|
||||||
|
"zone_affinity_A017": "聋人群体",
|
||||||
"list_header": "{count} 之中的 {from}–{to} · 按名为他们祷告",
|
"list_header": "{count} 之中的 {from}–{to} · 按名为他们祷告",
|
||||||
"people_suffix": "人",
|
"people_suffix": "人",
|
||||||
"no_photo": "没有照片",
|
"no_photo": "没有照片",
|
||||||
|
|
|
||||||
|
|
@ -557,7 +557,7 @@ for (const z of zones) {
|
||||||
|
|
||||||
// The title in the packet's language where it can be generated (one-country zones), else the
|
// The title in the packet's language where it can be generated (one-country zones), else the
|
||||||
// hand-written label — the same rule the browser print applies.
|
// hand-written label — the same rule the browser print applies.
|
||||||
const title = ZONE_TITLE ? ZONE_TITLE(z, null, { alpha2: (c) => ALPHA2[c], locale: LANG }) : null;
|
const title = ZONE_TITLE ? ZONE_TITLE(z, { ...STRINGS.en, ...(STRINGS[LANG] || {}) }, { alpha2: (c) => ALPHA2[c], locale: LANG }) : null;
|
||||||
const { html, pageCount, withPhoto } = await buildPacket(title ? { ...z, label: title } : z, groups, mapSvg);
|
const { html, pageCount, withPhoto } = await buildPacket(title ? { ...z, label: title } : z, groups, mapSvg);
|
||||||
// ⚠ THE LIST IS PART OF THE FILENAME, and leaving it out was a real hazard I created while arguing
|
// ⚠ THE LIST IS PART OF THE FILENAME, and leaving it out was a real hazard I created while arguing
|
||||||
// against exactly this. PACKET_LIST fails closed because "a packet that showed the wrong list is
|
// against exactly this. PACKET_LIST fails closed because "a packet that showed the wrong list is
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,16 @@ export function countryLabel(iso3, opts = {}) {
|
||||||
* @returns {string|null}
|
* @returns {string|null}
|
||||||
*/
|
*/
|
||||||
export function zoneTitle(zone, strings, opts = {}) {
|
export function zoneTitle(zone, strings, opts = {}) {
|
||||||
|
// ★ AN AFFINITY-BLOC ZONE HAS NO COUNTRY TO NAME IT, BUT IT HAS A NAME OF ITS OWN. The Deaf row
|
||||||
|
// (affinity bloc A017) spans every region, so the country path below declines it — and the packet
|
||||||
|
// then printed the legend's internal label, "Deaf (affinity bloc A017)", as its title. Its name is
|
||||||
|
// translated once per language in the packet strings, keyed by bloc: zone_affinity_<rop1>. Any other
|
||||||
|
// affinity bloc a list adds is named the same way, with one string and no code.
|
||||||
|
const sel = zone?.selector
|
||||||
|
if (sel && sel.type === 'affinity_bloc' && sel.rop1) {
|
||||||
|
const name = strings && strings[`zone_affinity_${sel.rop1}`]
|
||||||
|
return typeof name === 'string' && name.trim() ? name : null
|
||||||
|
}
|
||||||
const codes = zoneCountryCodes(zone?.selector)
|
const codes = zoneCountryCodes(zone?.selector)
|
||||||
if (!codes.length) return null // tier C — declined, caller keeps its label
|
if (!codes.length) return null // tier C — declined, caller keeps its label
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1486,11 +1486,15 @@ async function printPrayerPacket(zoneId, variant = 'full') {
|
||||||
// the zone unchanged. Loaded lazily, like the packet renderer, so the map pays nothing until a print.
|
// the zone unchanged. Loaded lazily, like the packet renderer, so the map pays nothing until a print.
|
||||||
async function localizedZone(zone) {
|
async function localizedZone(zone) {
|
||||||
try {
|
try {
|
||||||
const [{ zoneTitle }, { countryName }] = await Promise.all([
|
const [{ zoneTitle }, { countryName }, strings] = await Promise.all([
|
||||||
import('./data/zone-title.js'),
|
import('./data/zone-title.js'),
|
||||||
import('@map/utils/countryName.js'),
|
import('@map/utils/countryName.js'),
|
||||||
|
import('@map/scripts/posters/packet-strings.json').then((m) => m.default || m),
|
||||||
]);
|
]);
|
||||||
const title = zoneTitle(zone, null, { countryName, locale: packetLang() });
|
// the packet's own strings for this language, English per key where a block lacks one — the same
|
||||||
|
// fallback the renderer applies (an affinity-bloc zone such as Deaf is named from them)
|
||||||
|
const block = { ...(strings.en || {}), ...(strings[packetLang()] || {}) };
|
||||||
|
const title = zoneTitle(zone, block, { countryName, locale: packetLang() });
|
||||||
return title ? { ...zone, label: title } : zone;
|
return title ? { ...zone, label: title } : zone;
|
||||||
} catch { return zone; } // a title that cannot be generated keeps the hand-written one
|
} catch { return zone; } // a title that cannot be generated keeps the hand-written one
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue