Title each packet in its reader's language where the title can be generated
Every packet was titled with its zone's hand-written English label, so a Spanish Sudan packet said "Sudan" and an Arabic one printed Latin script on page 1, on every profile page's header and in the document's own title. A one-country zone's title is that country's name, and Intl supplies it in every language: the packets now carry it - Sudán, السودان, सूडान, 苏丹 - through zone-title.js's tier A, on both print paths. Every other zone keeps its hand-written label, since a region's name is not a list of its countries (tier B and C are a pending product decision). The selection packet names its cover the same way. In the browser the title uses the library's country-name resolver; the offline generator cannot load that module under Node, so it reads the ISO-3 to ISO-2 table and injects it, the path zone-title.js was built for. A data set without zone-title.js keeps its labels. check:zone-title-tier-a was committed unregistered and so never ran; it is now in gates:pre. Its comments, and zone-title.js's, no longer cite people or dates.
This commit is contained in:
parent
a4330484a4
commit
b2ad4607ee
5 changed files with 40 additions and 10 deletions
|
|
@ -7,7 +7,7 @@
|
|||
* · a ONE-country zone generates a correct title in every locale, free (Sudan / Sudán / السودان)
|
||||
* · a MULTI-country zone generates a title WORSE than the hand-written label it replaces, in
|
||||
* ENGLISH TOO ("Middle East Core" → "Iran + Yemen + 11 more")
|
||||
* so the ruling (dx-shepherd, 2026-09-28) is tier A only. This gate holds that line.
|
||||
* so only tier A generates. This gate holds that line.
|
||||
*
|
||||
* ★ IT IS TWO-SIDED, AND THAT IS THE ENTIRE POINT. A gate that only checked "the 3 zones generate"
|
||||
* would pass a build that ALSO regressed the other 25 — the exact failure being guarded against. So
|
||||
|
|
|
|||
|
|
@ -61,6 +61,11 @@ const SERIES = SERIES_ALL[SERIES_ID];
|
|||
// The data folder is a SERIES property, not a constant. It was hard-wired to my-upg-100-list,
|
||||
// which is the other half of why a country run could only ever be a UPG-100 run.
|
||||
const DATA = join(REPO, SERIES.dataDir);
|
||||
// Generated zone titles (zone-title.js) and the ISO-3 to ISO-2 table Intl needs for them. countryName.js
|
||||
// cannot load under Node (it imports JSON without an attribute), so the table is read here and INJECTED,
|
||||
// which is the path zone-title.js was built for. A data set without zone-title.js keeps its labels.
|
||||
const ALPHA2 = JSON.parse(readFileSync(join(REPO, 'library/data/iso3-to-alpha2.json'), 'utf8')).map;
|
||||
const ZONE_TITLE = await import(join(DATA, 'zone-title.js')).then((m) => m.zoneTitle).catch(() => null);
|
||||
const OUT = join(__dirname, 'out', 'packets');
|
||||
mkdirSync(OUT, { recursive: true });
|
||||
|
||||
|
|
@ -548,7 +553,10 @@ for (const z of zones) {
|
|||
if (!mapSvg) mapMiss = `out/posters/${sheetFile} has no map-sheet <svg>`;
|
||||
}
|
||||
|
||||
const { html, pageCount, withPhoto } = await buildPacket(z, groups, mapSvg);
|
||||
// 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.
|
||||
const title = ZONE_TITLE ? ZONE_TITLE(z, null, { alpha2: (c) => ALPHA2[c], locale: LANG }) : null;
|
||||
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
|
||||
// against exactly this. PACKET_LIST fails closed because "a packet that showed the wrong list is
|
||||
// printed and handed to someone" — and then wrote the frontier packet over the default-list packet of
|
||||
|
|
|
|||
|
|
@ -212,6 +212,7 @@
|
|||
"check:packet-strings-parity": "node internal/check-packet-strings-parity.mjs",
|
||||
"check:packet-variants": "node internal/check-packet-variants.mjs",
|
||||
"check:packet-a1-structure": "node internal/check-packet-a1-structure.mjs",
|
||||
"check:zone-title-tier-a": "node internal/check-zone-title-tier-a.mjs",
|
||||
"check:packets-all": "node internal/check-packets-all.mjs",
|
||||
"check:parse": "node internal/inline-parse-check.mjs",
|
||||
"check:pinned-umd": "node internal/check-pinned-umd.mjs",
|
||||
|
|
@ -309,7 +310,7 @@
|
|||
"fix:sri": "node internal/fix-sri.mjs",
|
||||
"gallery": "node internal/generate-gallery.mjs",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:poster-print-fallback check:country-names check:reduced-motion check:no-dead-library",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:zone-title-tier-a",
|
||||
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
|
||||
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
|
||||
"library-index": "node internal/generate-library-index.mjs",
|
||||
|
|
@ -483,6 +484,7 @@
|
|||
"check:packet-strings-parity": "the PACKET catalog (packet-strings.json) must be whole in every language: all string keys, all 12 prayer points with every field, all 12 wheel rhythms. check:i18n-parity covers library/i18n/locales only — six gates READ this file and none checked it. Asserts PRESENCE, not translatedness, with point4_passage declared-absent on record.",
|
||||
"check:packet-variants": "the poster wall's Prayer List prints the PACKET (not the columnar list), and every variant it offers exists in packet-render's VARIANTS. A variant may drop fixed pages but never people: lightweight keeps every group. By the UPG-100 owner; mutation-tested. Headless; in gates:pre.",
|
||||
"check:packet-a1-structure": "the prayer packet is built to its specified structure, read page by page from each sheet's CONTENT: full detail = map, triangle, life calendar, % evangelical, profiles, bounded set, centered set, wheel, points; lightweight = map, triangle, profiles, wheel, points. Page 1 places a map-only vertical sheet on the packet's own margin and says it is one of the UPG 100 prayer lists only when a count is supplied; page 2 is one inverted triangle whose packets tier and \"- N\" steps appear only where true; the wheel carries the Metacamp Module 1 PDF's colours and opens at nine o'clock; all 28 legend zones resolve, Deaf included.",
|
||||
"check:zone-title-tier-a": "generated zone titles ship for ONE-country zones only: the 3 such zones produce a localized, non-code title in en/es/ar that differs across languages, AND every other zone declines so its hand-written label survives unchanged (a multi-country zone has a name that is not a list of its members). Two-sided, and NOT MEASURED when the country-name supplier cannot resolve a known code.",
|
||||
"check:packets-all": "runs check:packet-render over EVERY generated packet, not one hand-named file. The single-file gate SKIPs with no argument, so it was in package.json, passing in all 11 locales, and in neither gates:pre nor gates:post — it only ran when someone typed a filename. SKIPs when no packets are generated.",
|
||||
"check:pin-dim-slider": "the side menu's pin-dim slider and the legend's dimming must COMPOSE, not compete. Both want the pin layer's circle-opacity, so there is ONE writer and the value is the PRODUCT: slider x (1 matched, 0 dimmed). Asserts the LIVE paint property, not the store: flat 1 with clustering off, flat 0.5 with clustering on, ['case', expr, 0.5, 0] once something is dimmed, and ['case', expr, 1, 0] when the slider returns to 1. Dims by RELIGION deliberately - a region selection sets a layer FILTER rather than an opacity expression, so testing that path would report a failure about code that never writes opacity.",
|
||||
"check:pinned-umd": "library/utils/loadPinnedUmd.js (d3 for PPLR and the Wall) loads a pinned UMD with SRI and never touches the host page's global of the same name: a host's own d3 gains no keys, a failed load leaves it in place. Headless.",
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@
|
|||
* zone-title — compose a zone's DISPLAY TITLE from parts that are ALREADY translated, so adding a
|
||||
* project list costs no new translation and adding a locale costs no code change.
|
||||
*
|
||||
* THE PROBLEM (Driver, 2026-09-27). Every zone label is a hand-written English string — "Middle East
|
||||
* THE PROBLEM. Every zone label is a hand-written English string — "Middle East
|
||||
* Core", "Malabar Coast" — so the Arabic map reads English. Hand-translating them does not scale,
|
||||
* because UPG-100 is becoming a PATTERN: Project 7000 / 4700 / 3000 / 2000 / 700 each invent their own
|
||||
* ~100 clusters, and a per-title translation is a cost paid again for every list, forever. The Driver's
|
||||
* question was precisely "how do you generate a title without a new translation each time".
|
||||
* ~100 clusters, and a per-title translation is a cost paid again for every list, forever.
|
||||
* The question is how to generate a title without a new translation each time.
|
||||
*
|
||||
* ★ WHAT THE DATA ACTUALLY ALLOWS, measured before any of this was written (2026-09-27):
|
||||
*
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
* `pg_slugs` whose every record is ONE country (measured: asia-india-kerala is 78/78 IND), so countries
|
||||
* would render "India" twelve times and could not tell "Malabar Coast" from "Northwest India" — those
|
||||
* labels are SUB-NATIONAL. Naming them needs a province/subdivision source, which Intl has none of; the
|
||||
* curated-table-vs-keep-English choice is with the Driver (repo's digest item D). Until it is decided
|
||||
* curated-table-vs-keep-English choice is a pending product decision. Until it is decided
|
||||
* this returns null for tier C and the caller keeps the existing label, so nothing regresses.
|
||||
*
|
||||
* THE ONLY TRANSLATED STRINGS ARE THE TWO JOINERS — `zone_join` ("{a} + {b}") and `zone_join_more`
|
||||
|
|
@ -137,7 +137,7 @@ export function zoneTitle(zone, strings, opts = {}) {
|
|||
const codes = zoneCountryCodes(zone?.selector)
|
||||
if (!codes.length) return null // tier C — declined, caller keeps its label
|
||||
|
||||
// ★ ONE COUNTRY ONLY (dx-shepherd, 2026-09-28). A multi-country zone DECLINES, exactly as tier C
|
||||
// ★ ONE COUNTRY ONLY. A multi-country zone DECLINES, exactly as tier C
|
||||
// does, and keeps its hand-written label.
|
||||
//
|
||||
// This is not a limitation of the joiner below — the joiner works. It is that A MULTI-COUNTRY ZONE
|
||||
|
|
|
|||
|
|
@ -1470,13 +1470,31 @@ async function printPrayerPacket(zoneId, variant = 'full') {
|
|||
import('@map/scripts/posters/packet-strings.json').then((m) => m.default || m),
|
||||
]);
|
||||
const R = createPacketRenderer({ strings, lang: packetLang() });
|
||||
await packetPrints.printPacket(zone, {
|
||||
// ★ THE TITLE IN THE READER'S LANGUAGE, WHERE IT CAN BE GENERATED. A one-country zone's title is that
|
||||
// country's name, which Intl supplies in every language (zone-title.js tier A); every other zone keeps
|
||||
// its hand-written label, because a region's name is not a list of its countries. The title prints on
|
||||
// page 1, heads every profile page and names the document.
|
||||
const titled = await localizedZone(zone);
|
||||
await packetPrints.printPacket(titled, {
|
||||
variant,
|
||||
mapSvg: await packetMapSvg(zoneId, R),
|
||||
funnel: await packetFunnel(),
|
||||
});
|
||||
}
|
||||
|
||||
// The zone as the packet should print it: a tier-A zone's generated title in the packet's language, or
|
||||
// the zone unchanged. Loaded lazily, like the packet renderer, so the map pays nothing until a print.
|
||||
async function localizedZone(zone) {
|
||||
try {
|
||||
const [{ zoneTitle }, { countryName }] = await Promise.all([
|
||||
import('./data/zone-title.js'),
|
||||
import('@map/utils/countryName.js'),
|
||||
]);
|
||||
const title = zoneTitle(zone, null, { countryName, locale: packetLang() });
|
||||
return title ? { ...zone, label: title } : zone;
|
||||
} catch { return zone; } // a title that cannot be generated keeps the hand-written one
|
||||
}
|
||||
|
||||
// Every list across all three cluster buckets, flattened.
|
||||
//
|
||||
// ★ THE ONE SEAM THE `list` PROP WILL FEED. Driver 2026-09-26 via the shepherd: UPG-100 is the mass
|
||||
|
|
@ -1748,7 +1766,9 @@ async function selectionAsList() {
|
|||
// and names.reduce(join2) on an empty array THROWS ("Reduce of empty array with no initial value")
|
||||
// — so even after the selector fix the packet would have died on its cover. The names come from
|
||||
// selectedTerritories in the reader's own selection order, which is the order the cover promises.
|
||||
const zoneNames = lists.map((l) => l.label || l.id);
|
||||
// Each chosen zone named as its own packet would title it — generated in the reader's language where
|
||||
// it can be (one-country zones), the hand-written label otherwise.
|
||||
const zoneNames = (await Promise.all(lists.map(localizedZone))).map((l) => l.label || l.id);
|
||||
const countryNames = selectedTerritories.value.filter((t) => t.kind === 'adm0').map((t) => t.name);
|
||||
const names = [...zoneNames, ...countryNames];
|
||||
const S = await packetStrings();
|
||||
|
|
|
|||
Loading…
Reference in a new issue