#!/usr/bin/env node
/**
* check-map-labels — the map on page 1 names its countries in the packet's language.
*
* Map sheets are generated once, in English; the packet relabels their country names (and the map's
* privacy line) at print time (library/packet/mapLabels.js + packet-render.js liftMap). Checked on a
* real tracked poster, the artwork page 1 actually lifts:
*
* (1) ENGLISH IS UNTOUCHED — lifting with and without the relabeller is byte-identical in English,
* and the relabeller for English is null.
* (2) KNOWN NAMES ARE ALL REPLACED — in ar and zh no known English label survives, every halo still
* matches its fill, and the privacy line takes the language's own sentence.
* (3) THE WIDTH GUARD FITS, NEVER GROWS — no label is ever set larger than the sheet's size, the guard
* FIRES for a translation wider than its English (ar), and does NOT for shorter ones (zh).
* (4) TRACKING OFF WHERE IT BREAKS SCRIPT — Arabic labels drop letter-spacing; Spanish capitals keep it.
* (5) UNKNOWN LABELS ARE LEFT AS DRAWN — never guessed.
*
* Exit 0 pass · 1 fail · 75 NOT MEASURED.
*/
import { readFileSync, existsSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const HERE = dirname(fileURLToPath(import.meta.url))
const REPO = resolve(HERE, '..')
const POSTER = join(REPO, 'data/posters/sudan.html')
if (!existsSync(POSTER)) {
console.error('MAP-LABELS: NOT MEASURED (exit 75) — no tracked poster at data/posters/sudan.html to lift')
process.exit(75)
}
const { createPacketRenderer } = await import(join(REPO, 'library/packet/packet-render.js'))
const { makeCountryLabelFor } = await import(join(REPO, 'library/packet/mapLabels.js'))
const { ISO3_NAME } = await import(join(REPO, 'plugin-designs/my-upg-100-list/data/territory-counts.js'))
const strings = JSON.parse(readFileSync(join(REPO, 'library/scripts/posters/packet-strings.json'), 'utf8'))
const a2 = JSON.parse(readFileSync(join(REPO, 'library/data/iso3-to-alpha2.json'), 'utf8')).map
const countryName = (iso3, lc) => { const t = a2[iso3]; try { return t ? new Intl.DisplayNames([lc], { type: 'region' }).of(t) : '' } catch { return '' } }
// A label the generator cannot draw, planted to prove unknowns are left alone.
const poster = readFileSync(POSTER, 'utf8').replace('', 'ATLANTIS')
const esc = (s) => String(s).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"')
const KNOWN = new Set(Object.values(ISO3_NAME).map((n) => esc(String(n).toUpperCase())))
const labels = (html) => [...html.matchAll(/]*class="(ctry-name(?:-halo|-own)?)"[^>]*)>([^<]*)<\/text>/g)]
.map((m) => ({ attrs: m[1], cls: m[2], text: m[3], px: Number((m[1].match(/font-size:([\d.]+)px/) || [])[1]) || null }))
const base = Number((poster.match(/\.ctry-name\{[^}]*font-size:([\d.]+)px/) || [])[1]) || 15
const fails = [], notes = []
const ok = (c, msg, note) => { if (!c) fails.push(msg); else if (note) notes.push(` ok ${note}`) }
const lift = (lc) => createPacketRenderer({ strings, lang: lc }).liftMap(poster, { labelFor: makeCountryLabelFor({ iso3Name: ISO3_NAME, countryName, lang: lc }) })
// (1)
{
const R = createPacketRenderer({ strings, lang: 'en' })
ok(makeCountryLabelFor({ iso3Name: ISO3_NAME, countryName, lang: 'en' }) === null, 'the English relabeller is not null')
ok(R.liftMap(poster) === lift('en'), 'lifting in English CHANGED the sheet', 'English: the lifted sheet is byte-identical with and without the relabeller')
}
const out = { ar: lift('ar'), zh: lift('zh'), es: lift('es') }
// (2)
for (const lc of ['ar', 'zh']) {
const L = labels(out[lc])
const left = L.filter((l) => KNOWN.has(l.text)).map((l) => l.text)
const halos = L.filter((l) => l.cls === 'ctry-name-halo'), fills = L.filter((l) => l.cls !== 'ctry-name-halo')
ok(!left.length, `${lc}: English country names survived: ${[...new Set(left)].join(', ')}`)
ok(halos.length && halos.every((h, i) => fills[i] && fills[i].text === h.text), `${lc}: a halo no longer matches its fill — the outline and the name would disagree`)
ok(out[lc].includes(esc(strings[lc].privacy_note)), `${lc}: the map's privacy line was not localized`)
if (!left.length) notes.push(` ok ${lc}: ${halos.length} country labels relabelled, halo = fill, privacy line localized`)
}
// (3)
{
const all = [...labels(out.ar), ...labels(out.zh), ...labels(out.es)]
ok(all.every((l) => l.px === null || l.px <= base + 1e-9), 'a relabelled name was set LARGER than the sheet\'s label size')
const arShrunk = labels(out.ar).filter((l) => l.px !== null && l.px < base).length
const zhShrunk = labels(out.zh).filter((l) => l.px !== null && l.px < base).length
ok(arShrunk > 0, 'the width guard never fired in Arabic, where the Saudi Arabia label is far wider than its English')
ok(zhShrunk === 0, `the width guard shrank ${zhShrunk} Chinese label(s) that are narrower than their English`,
`width guard: shrinks ${arShrunk} Arabic labels to fit their English boxes, none of the narrower Chinese, and grows nothing`)
}
// (4)
{
const arFill = labels(out.ar).filter((l) => l.cls !== 'ctry-name-halo' && !KNOWN.has(l.text) && l.text !== 'ATLANTIS')
const esFill = labels(out.es).filter((l) => l.cls !== 'ctry-name-halo' && l.text !== 'ATLANTIS')
ok(arFill.length && arFill.every((l) => /letter-spacing:0/.test(l.attrs)), 'Arabic labels keep the capitals\' tracking, which breaks letter joining')
ok(esFill.every((l) => !/letter-spacing:0/.test(l.attrs)), 'Spanish capitals lost their tracking',
'tracking is dropped for Arabic and kept for Spanish capitals')
}
// (5)
ok(labels(out.ar).filter((l) => l.text === 'ATLANTIS').length === 1, 'an unknown label was altered or removed', 'an unknown label is left exactly as drawn')
for (const n of notes) console.log(n)
if (fails.length) {
console.error('MAP-LABELS: FAIL')
for (const f of fails) console.error(` ✗ ${f}`)
process.exit(1)
}
console.log('MAP-LABELS: PASS — the map names its countries in the packet\'s language, fits each name to its English box, and leaves English untouched.')