The gate caught what I shipped: rendering the Tibet packet at PACKET_LANG=ar failed with "the funnel page carries Latin-script prose in a ar packet: 'This packet is', 'UPG-', 'lists … How each number is reached'". Exactly right — my new strings were English-only, and two of them PRINT on a page whose siblings (funnel_title, funnel_intro, funnel_people) are fully translated. English text on an otherwise Arabic page is not a graceful fallback, it is a half-translated page. So all 17 new keys are now genuinely translated across es fr pt de it ro ru ar hi zh — 150 values — and DECLARED_ABSENT is back to its original two entries (the Mark 5 passage and its credit). The declaration mechanism was the right call while they were untranslated and the wrong place to leave them. ⚠ 'UPG-' IS ALLOWLISTED, AND THE ORDER MATTERS. It is the Latin run left by "UPG-100", the product name every locale keeps verbatim — the same reason 'Project' is already in LATIN_OK. But it is allowed only AFTER the real translations landed: an allowlist entry added when the failure first appeared would have silenced the gate on fifteen genuinely untranslated strings. The allowlist is for names, not for work not yet done. VERIFIED at the rendered layer in BOTH locales: en and ar each PASS with 9 sheets, map painted, content intact. The Arabic packet carries its life-calendar title, christian-share title, both legend labels, the three prayer columns and the "1 of 28" tip in Arabic, with 2 grids and 32 shaded cells — the same numbers as the English one, which is the point: the arithmetic is language-independent and only the words change. check:packet-strings-parity PASS. |
||
|---|---|---|
| .. | ||
| api | ||
| colors | ||
| components | ||
| composables | ||
| constants | ||
| data | ||
| i18n | ||
| layouts | ||
| lists | ||
| maps | ||
| packet | ||
| 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.