The legend gives each tab a small "i" button explaining what that tier means — what an Affinity Block is, how a people group differs from a people group in a country. SemanticTreeLegend already read that text from legend.tabInfo.<id> behind a te() guard, and not one of the ten keys the research map needs had ever been written. The tab chips were translated in all eleven locales and the explanations beside them were not, so the reader least able to guess what "PGIC" means was the one reader who got it in English. The keys are written for all eleven locales, following the terminology each catalogue already uses for these same tiers — People Group as Volksgruppe, Peuple, Pueblo, 民族, Народ, जनसमूह — with WAGF and UPG left untransliterated as the corpus leaves them. The reader-facing text drops the source field names and row counts that the English carried; those are notes for whoever maintains the tables, so they stay in the source as a comment. With the keys in place the English in the tab tables can never render, and a second copy of a string drifts from the catalogue without anyone noticing, so it is removed. `info: true` stays because the legend gates the "i" button on that field being set — dropping it would have removed the affordance instead of translating it — and tabInfo now treats only a string as a fallback, so the flag cannot reach the popover as the word "true". Verified every changed tab renders a real label and a real explanation in all eleven locales. check:i18n-parity, check:i18n-never-translated and check:i18n-treeshake pass; hardcoded-english 205 -> 190. |
||
|---|---|---|
| .. | ||
| 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.