The map sheets are generated once, in English, so in an Arabic or Chinese packet page 1's map labelled every country in Latin capitals, and its privacy line was English. The labels are now replaced as the sheet is lifted into the packet. They are recognised by the generator's own rule rather than a list: generate.mjs draws each label as esc(ISO3_NAME[iso3].toUpperCase()) from the zone data's ISO3_NAME table, so inverting that table under that transform names the country behind every label it can draw. The inverse is unambiguous (162 labels, no two countries sharing one), so a label is known exactly or left as drawn. The map's privacy line is word for word the packet's privacy_note, so it takes this language's. A width guard: the generator placed every label and then dropped any that would collide, measuring the English name, so a translation is fitted into that same box - estimated wider, it shrinks in proportion and cannot reach a neighbour the English name did not. On the Sudan poster, six Arabic names shrink (the longest, Saudi Arabia, from 15px to 10px); none of the narrower Chinese ones do. Capitals' tracking is dropped for scripts without capitals, where it breaks Arabic joining. English is untouched: its relabeller is null, and the lifted sheet is byte-identical with and without it. No sheet or poster is regenerated - the change is in the lift, so the tracked 3x4 posters page 1 now uses are relabelled as they are. Both print paths supply the relabeller: the UPG 100 profile from its own ISO3_NAME and the library's country-name resolver, the offline generator from the same table and the injected ISO-3 to ISO-2 table. check-map-labels (gates:pre), on the tracked Sudan poster: English byte-identical; in ar and zh no known English name left, halo matching fill, privacy localized; the guard fires for Arabic, not Chinese, and grows nothing; Arabic tracking off, Spanish kept; an unknown label left alone. Mutation-tested five ways, each caught. |
||
|---|---|---|
| .. | ||
| api | ||
| charts/whiteboard | ||
| colors | ||
| components | ||
| composables | ||
| constants | ||
| dashboards | ||
| data | ||
| elements | ||
| i18n | ||
| layouts | ||
| lists | ||
| maps | ||
| packet | ||
| packets | ||
| scripts | ||
| stores | ||
| styles | ||
| utils | ||
| vendor/cldr | ||
| index.js | ||
| ProfileLoader.vue | ||
| README.md | ||
library/ (@map) — the shared map template
The other half of the architecture (see ../plugin-designs/README.md):
every map in plugin-designs/<your-map>/ composes the reusable, parameterized pieces that live here.
Fix something here and every map benefits on the next build; break something here and every map
breaks — read components/WARNING.md before touching shared pieces.
| Folder | What lives there |
|---|---|
ProfileLoader.vue |
The root of every bundle: parses profile-config, loads the profile, provides the stores + config |
components/ |
Shared UI — map, legends, controls, popup (PeopleGroupDetail), tables, charts, hints (README) |
composables/ |
Shared logic — map engine (useMapInstance), provider switch, shadow-DOM styles, theming |
stores/ |
Per-instance Pinia stores (ui / map / data) — provided by ProfileLoader, injected by children |
api/ |
Data-source registry (sources.json) + fetch layer — the JAMstack runtime wiring |
colors/ |
Color sets maps opt into via profile-config.colorSet |
i18n/ |
The shared string catalogs (all maps speak the same languages) |
data/ |
Small shared datasets baked at build time (big served data lives in ../data/) |
constants/ |
Shared constants |
scripts/ |
Maintainer pipelines (posters, research) — not part of any bundle |
The rule of thumb (same as WARNING.md): custom code in your app-profile, reusable code here — and things only move from the first to the second once they've earned it.