Big Picture speaks Arabic where it was still speaking English: chrome and data labels
rega's ?lang=ar pass (KA3, 3d64bcad) found English on a localized map in four places, and the census
behind it explains why no gate saw them. Catalogue coverage is COMPLETE: of the 760 keys my bundles
name across the 21 areas they reach, ZERO are missing from any of the ten non-English locales, and the
per-locale English-identical values are numerals, acronyms and cognates rather than debt. Everything
rega actually read was a string no catalogue ever held.
· THE PROJECTION PILL said "🌐 Globe" / "▭ Flat" in all eleven locales. The icon is now the label.
Neither word has a catalogue entry and authoring ten new strings for a two-state toggle to say what
a glyph already says is the wrong trade — but mapui.enableGlobe / mapui.disableGlobe DO ship in all
eleven, as the ACTION, which is what a button should announce. They become the accessible name and
the tooltip. min-width joins min-height on the pill: the 24px used to come free from the text.
· THE TABLE FOOTER said "Σ 2,107 groups". dashboard.groups has shipped in eleven locales all along
(ar "مجموعة") and this one call site never read it. Written with the same singular/plural test as
the header count so the two cannot disagree at one row.
· THE REGION CHART labelled its bars with the prayer API's English region names. Seven regions is a
closed set, so they are now CATALOGUED — five generated from vendored CLDR by
internal/generate-wagf-region-names.mjs, which is the same data countryName.js already trusts.
Generating is what keeps seventy translations out of my hands. middle_east and
north_america_&_non-spanish_caribbean stay English: M49 145 is "Western Asia" and 021 is "Northern
America", and neither means the region we draw. An English label a reader can match to the map beats
a localized one that names the wrong territory. --check verifies the catalogue against CLDR.
· TOP COUNTRIES labelled its bars with the API's English country names. By aggregation time the
alpha-3 is gone, so countryName.js gains iso3ForEnglishName() — reversing its OWN table, exact where
it matches and silent where it does not — which is precisely the ✓ case its header describes.
One thing found while wiring those: the BAR label was the only chart surface still printing the raw
facet key. The donut, its legend and both tooltips already went through legendLabel(); the bar list did
not, which is why these labels stayed English after the donut was fixed for rega's earlier survey. Both
new facets are projected there, so the two charts cannot disagree about a name again.
English is left EXACTLY as authored. Both projections return the key untouched at locale 'en', which
also keeps the 42 countries whose CLDR name differs from ours reading as they do today. Nothing
localizes a VALUE: data-key, isActive() and every click and filter path still carry the canonical
English, the rule the religion donut already followed.
Verified: gates:pre 12/12, check:i18n-keys-exist / parity / never-translated PASS, and the generator's
own --check PASS. Rendering on ?lang=ar goes to rega.
This commit is contained in:
parent
a43415dcae
commit
ed87323592
16 changed files with 263 additions and 18 deletions
80
internal/generate-wagf-region-names.mjs
Normal file
80
internal/generate-wagf-region-names.mjs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* generate-wagf-region-names.mjs — the WAGF region names, in the reader's language, from vendored CLDR.
|
||||
*
|
||||
* ★ THE DEFECT (rega KA3, 3d64bcad, ?lang=ar). Big Picture's region chart labels its bars with the
|
||||
* prayer API's ENGLISH region names — "Africa", "Asia", "Latin America & Caribbean" — on an Arabic page.
|
||||
* They are not chrome and no catalogue held them: they are DATA labels, so every i18n gate was green
|
||||
* while the chart read English to every reader. The regions are a closed set of seven
|
||||
* (library/colors/wagf-region.js WAGF_REGIONS), which is what makes cataloguing them possible at all.
|
||||
*
|
||||
* ★ WHY GENERATED AND NOT HAND-AUTHORED. Ten locales x seven names is seventy strings I cannot write.
|
||||
* Five of the seven ARE standard UN M49 macro-regions, so CLDR already names them in every locale we
|
||||
* ship, and Node's own ICU is the same data library/utils/countryName.js already trusts for countries.
|
||||
* Generating is what keeps a translation out of my hands: the strings are copied from CLDR, committed,
|
||||
* and reviewable, and re-running this reproduces them byte for byte.
|
||||
*
|
||||
* africa -> 002 · asia -> 142 · europe -> 150 · latin_america_&_caribbean -> 419 · oceania -> 009
|
||||
*
|
||||
* ★ TWO REGIONS ARE DELIBERATELY NOT WRITTEN, because CLDR has no code that means them:
|
||||
* · middle_east — M49 145 is "Western Asia", a different set (it includes the Caucasus and excludes
|
||||
* Egypt). Naming it "Western Asia" in ten locales would silently redraw the region.
|
||||
* · north_america_&_non-spanish_caribbean — M49 021 is "Northern America", which excludes the
|
||||
* Caribbean this bucket exists to include.
|
||||
* Both stay English until the Driver rules on a translation source. An English label a reader can
|
||||
* still match to the map beats a localized label that names the wrong territory.
|
||||
*
|
||||
* ★ AND ONE SCOPE NOTE ON 419: CLDR's long name is "Latin America" while our label says
|
||||
* "Latin America & Caribbean". The ENGLISH entry keeps our wording (it is the authored, authoritative
|
||||
* one); the other ten take CLDR's, which omits the Caribbean from the NAME while the bucket still holds
|
||||
* it. Recorded here rather than silently accepted — the alternative was English in ten locales.
|
||||
*
|
||||
* node internal/generate-wagf-region-names.mjs # write
|
||||
* node internal/generate-wagf-region-names.mjs --check # verify the catalogue matches CLDR (CI)
|
||||
*/
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const CHECK = process.argv.includes('--check')
|
||||
const M49 = { africa: '002', asia: '142', europe: '150', 'latin_america_&_caribbean': '419', oceania: '009' }
|
||||
const HELD = ['middle_east', 'north_america_&_non-spanish_caribbean']
|
||||
const LOCALES = ['ar', 'de', 'en', 'es', 'fr', 'hi', 'it', 'pt', 'ro', 'ru', 'zh']
|
||||
// The English entries stay as the product authored them (see the 419 note above); every other locale
|
||||
// is CLDR's. Read from the one file that already owns the region set, so a seventh region added there
|
||||
// shows up here as a missing name rather than as a silent English label.
|
||||
const { WAGF_REGIONS } = await import(join(ROOT, 'library/colors/wagf-region.js'))
|
||||
|
||||
const names = (lc) => {
|
||||
const dn = new Intl.DisplayNames([lc], { type: 'region' })
|
||||
const out = {}
|
||||
for (const [slug, code] of Object.entries(M49)) out[slug] = lc === 'en' ? WAGF_REGIONS[slug] : dn.of(code)
|
||||
return out
|
||||
}
|
||||
let wrote = 0
|
||||
const drift = []
|
||||
for (const lc of LOCALES) {
|
||||
const p = join(ROOT, 'library/i18n/locales', lc, 'dashboard.json')
|
||||
const cat = JSON.parse(readFileSync(p, 'utf8'))
|
||||
const want = names(lc)
|
||||
if (CHECK) {
|
||||
const have = cat.wagfRegion || {}
|
||||
for (const [k, v] of Object.entries(want)) if (have[k] !== v) drift.push(`${lc}.wagfRegion.${k}: catalogue ${JSON.stringify(have[k])} vs CLDR ${JSON.stringify(v)}`)
|
||||
continue
|
||||
}
|
||||
cat.wagfRegion = want
|
||||
writeFileSync(p, JSON.stringify(cat, null, 2) + '\n')
|
||||
wrote++
|
||||
}
|
||||
const unaccounted = Object.keys(WAGF_REGIONS).filter((s) => !M49[s] && !HELD.includes(s))
|
||||
if (unaccounted.length) {
|
||||
console.error(`WAGF-REGION-NAMES: FAIL — ${unaccounted.length} region(s) in WAGF_REGIONS have neither a CLDR code nor a recorded reason: ${unaccounted.join(', ')}`)
|
||||
process.exit(1)
|
||||
}
|
||||
if (CHECK) {
|
||||
if (drift.length) { console.error(`WAGF-REGION-NAMES: FAIL — ${drift.length} catalogue entry(ies) no longer match CLDR:\n ${drift.join('\n ')}`); process.exit(1) }
|
||||
console.log(`WAGF-REGION-NAMES: PASS — ${Object.keys(M49).length} name(s) x ${LOCALES.length} locale(s) match vendored CLDR; ${HELD.length} held in English on purpose (${HELD.join(', ')}).`)
|
||||
} else {
|
||||
console.log(`WAGF-REGION-NAMES: wrote ${Object.keys(M49).length} name(s) into ${wrote} locale catalogue(s); ${HELD.length} held in English (${HELD.join(', ')}).`)
|
||||
}
|
||||
|
|
@ -78,5 +78,12 @@
|
|||
"selected_first": "المحدَّدة أولاً",
|
||||
"selected_first_hint": "رفع الشعوب المحدَّدة إلى الأعلى مع الإبقاء على ترتيب الأعمدة",
|
||||
"n_shown": "{n} معروض",
|
||||
"other": "أخرى"
|
||||
"other": "أخرى",
|
||||
"wagfRegion": {
|
||||
"africa": "أفريقيا",
|
||||
"asia": "آسيا",
|
||||
"europe": "أوروبا",
|
||||
"latin_america_&_caribbean": "أمريكا اللاتينية",
|
||||
"oceania": "أوقيانوسيا"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,5 +78,12 @@
|
|||
"selected_first": "Ausgewählte zuerst",
|
||||
"selected_first_hint": "Die ausgewählten Volksgruppen nach oben holen, die Spaltensortierung bleibt erhalten",
|
||||
"n_shown": "{n} angezeigt",
|
||||
"other": "Andere"
|
||||
"other": "Andere",
|
||||
"wagfRegion": {
|
||||
"africa": "Afrika",
|
||||
"asia": "Asien",
|
||||
"europe": "Europa",
|
||||
"latin_america_&_caribbean": "Lateinamerika",
|
||||
"oceania": "Ozeanien"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,5 +78,12 @@
|
|||
"selected_first": "Selected first",
|
||||
"selected_first_hint": "Float the selected people groups to the top, keeping your column sort",
|
||||
"n_shown": "{n} shown",
|
||||
"other": "Other"
|
||||
"other": "Other",
|
||||
"wagfRegion": {
|
||||
"africa": "Africa",
|
||||
"asia": "Asia",
|
||||
"europe": "Europe",
|
||||
"latin_america_&_caribbean": "Latin America & Caribbean",
|
||||
"oceania": "Oceania"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,5 +78,12 @@
|
|||
"selected_first": "Los seleccionados primero",
|
||||
"selected_first_hint": "Llevar arriba los grupos de personas seleccionados, manteniendo el orden de las columnas",
|
||||
"n_shown": "{n} mostrados",
|
||||
"other": "Otros"
|
||||
"other": "Otros",
|
||||
"wagfRegion": {
|
||||
"africa": "África",
|
||||
"asia": "Asia",
|
||||
"europe": "Europa",
|
||||
"latin_america_&_caribbean": "Latinoamérica",
|
||||
"oceania": "Oceanía"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,5 +78,12 @@
|
|||
"selected_first": "La sélection en premier",
|
||||
"selected_first_hint": "Faire remonter les groupes de personnes sélectionnés, en conservant le tri des colonnes",
|
||||
"n_shown": "{n} affichés",
|
||||
"other": "Autres"
|
||||
"other": "Autres",
|
||||
"wagfRegion": {
|
||||
"africa": "Afrique",
|
||||
"asia": "Asie",
|
||||
"europe": "Europe",
|
||||
"latin_america_&_caribbean": "Amérique latine",
|
||||
"oceania": "Océanie"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,5 +78,12 @@
|
|||
"selected_first": "चयनित पहले",
|
||||
"selected_first_hint": "चयनित जनसमूहों को ऊपर लाएँ, आपका कॉलम क्रम बनाए रखते हुए",
|
||||
"n_shown": "{n} दिखाए गए",
|
||||
"other": "अन्य"
|
||||
"other": "अन्य",
|
||||
"wagfRegion": {
|
||||
"africa": "अफ़्रीका",
|
||||
"asia": "एशिया",
|
||||
"europe": "यूरोप",
|
||||
"latin_america_&_caribbean": "लैटिन अमेरिका",
|
||||
"oceania": "ओशिआनिया"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,5 +78,12 @@
|
|||
"selected_first": "Prima i selezionati",
|
||||
"selected_first_hint": "Porta in alto i gruppi di persone selezionati, mantenendo l’ordinamento delle colonne",
|
||||
"n_shown": "{n} mostrati",
|
||||
"other": "Altri"
|
||||
"other": "Altri",
|
||||
"wagfRegion": {
|
||||
"africa": "Africa",
|
||||
"asia": "Asia",
|
||||
"europe": "Europa",
|
||||
"latin_america_&_caribbean": "America Latina",
|
||||
"oceania": "Oceania"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,5 +78,12 @@
|
|||
"selected_first": "Os selecionados primeiro",
|
||||
"selected_first_hint": "Trazer para cima os grupos de pessoas selecionados, mantendo a ordenação das colunas",
|
||||
"n_shown": "{n} apresentados",
|
||||
"other": "Outros"
|
||||
"other": "Outros",
|
||||
"wagfRegion": {
|
||||
"africa": "África",
|
||||
"asia": "Ásia",
|
||||
"europe": "Europa",
|
||||
"latin_america_&_caribbean": "América Latina",
|
||||
"oceania": "Oceania"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,5 +78,12 @@
|
|||
"selected_first": "Cele selectate primele",
|
||||
"selected_first_hint": "Ridică grupurile de oameni selectate în partea de sus, păstrând sortarea coloanelor",
|
||||
"n_shown": "{n} afișate",
|
||||
"other": "Altele"
|
||||
"other": "Altele",
|
||||
"wagfRegion": {
|
||||
"africa": "Africa",
|
||||
"asia": "Asia",
|
||||
"europe": "Europa",
|
||||
"latin_america_&_caribbean": "America Latină",
|
||||
"oceania": "Oceania"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,5 +78,12 @@
|
|||
"selected_first": "Выбранные сначала",
|
||||
"selected_first_hint": "Поднять выбранные народы наверх, сохранив сортировку столбцов",
|
||||
"n_shown": "{n} показано",
|
||||
"other": "Другое"
|
||||
"other": "Другое",
|
||||
"wagfRegion": {
|
||||
"africa": "Африка",
|
||||
"asia": "Азия",
|
||||
"europe": "Европа",
|
||||
"latin_america_&_caribbean": "Латинская Америка",
|
||||
"oceania": "Океания"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,5 +78,12 @@
|
|||
"selected_first": "所选优先",
|
||||
"selected_first_hint": "将所选民族置顶,同时保留您的列排序",
|
||||
"n_shown": "显示 {n} 个",
|
||||
"other": "其他"
|
||||
"other": "其他",
|
||||
"wagfRegion": {
|
||||
"africa": "非洲",
|
||||
"asia": "亚洲",
|
||||
"europe": "欧洲",
|
||||
"latin_america_&_caribbean": "拉丁美洲",
|
||||
"oceania": "大洋洲"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import countries from '../data/countries.json'
|
|||
|
||||
const ALPHA2 = table.map
|
||||
const ENGLISH = new Map(countries.map((c) => [c.value, c.label]))
|
||||
const BY_ENGLISH = new Map(countries.map((c) => [String(c.label).trim().toLowerCase(), c.value]))
|
||||
|
||||
/** The alpha-2 for an alpha-3 code, or undefined. Exported because callers building URLs and flag
|
||||
* assets need the same lookup, and a second copy of it would drift. */
|
||||
|
|
@ -49,6 +50,24 @@ export function alpha2Of(iso3) {
|
|||
return iso3 ? ALPHA2[String(iso3).toUpperCase()] : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* The alpha-3 for a canonical ENGLISH country name, or undefined.
|
||||
*
|
||||
* ★ FOR LABELS THAT ARRIVE IN ENGLISH WITH NO CODE ATTACHED — the case the header's ✓ names. Big
|
||||
* Picture's top-countries chart aggregates on the prayer API's English country LABEL, so by the time a
|
||||
* bar is drawn the alpha-3 is gone and only "India" is in hand. Reversing our own countries.json is
|
||||
* exact where it matches and silent where it does not, which is the only safe shape here: an unmatched
|
||||
* name keeps rendering as itself rather than becoming a code or a blank.
|
||||
*
|
||||
* Built from the SAME table englishName() reads, so the two can never disagree about which name is
|
||||
* canonical. Names are compared case-insensitively and trimmed; nothing else is normalized, because a
|
||||
* looser match is how "Congo" would quietly become the wrong country.
|
||||
*/
|
||||
export function iso3ForEnglishName(name) {
|
||||
if (!name) return undefined
|
||||
return BY_ENGLISH.get(String(name).trim().toLowerCase())
|
||||
}
|
||||
|
||||
/** The canonical English name, or undefined. */
|
||||
export function englishName(iso3) {
|
||||
return iso3 ? ENGLISH.get(String(iso3).toUpperCase()) : undefined
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<script setup>
|
||||
import { religionFamilyLabel } from '@map/colors/religion.js'
|
||||
import { tStatic } from '@map/i18n/index.js'
|
||||
import { WAGF_REGIONS } from '@map/colors/wagf-region.js'
|
||||
import countryName, { iso3ForEnglishName } from '@map/utils/countryName.js'
|
||||
import { tStatic, getRuntimeLocale } from '@map/i18n/index.js'
|
||||
/**
|
||||
* ChartsRail — RDASH phase 5: cross-filtered charts rail. Religion donut + Region donut (doxa-region
|
||||
* palette) + Top-country bar, all re-aggregating on filterSet and brushable back into it (click a
|
||||
|
|
@ -98,9 +100,45 @@ const isActive = (facet, key) => bus.isFilterActive(facet, key)
|
|||
// Two projections, one function, and both follow the same rule: the key crosses, the label is read.
|
||||
// OTHER_KEY is the rollup sentinel aggregate() produces; it must stay English on the wire (this
|
||||
// component refuses to filter on it by testing `key === 'Other'`) and be localized for a reader.
|
||||
// ★ TWO MORE FACETS CARRY ENGLISH DATA LABELS, and rega read both on ?lang=ar (KA3, 3d64bcad).
|
||||
// They are not chrome: no catalogue held them, so every i18n gate passed while the chart spoke English.
|
||||
// · wagf_region — the prayer API's seven region names. A closed set, so they are CATALOGUED, five of
|
||||
// them from vendored CLDR (internal/generate-wagf-region-names.mjs); middle_east and
|
||||
// north_america_&_non-spanish_caribbean have no CLDR code that means them and stay English.
|
||||
// · country_code — the API's English country name. The alpha-3 is gone by aggregation time, so
|
||||
// library/utils/countryName.js reverses its own table and then asks CLDR, per the ✓ case in that
|
||||
// file's header: a label that arrives in English regardless of the reader.
|
||||
// English is left EXACTLY as authored: at locale 'en' both projections return the key untouched, which
|
||||
// also keeps the 42 countries whose CLDR name differs from ours (see countryName.js) reading as before.
|
||||
// The five keys are written out rather than assembled from the slug: a template-literal key is
|
||||
// invisible to check:i18n-keys-exist (it counts such expressions as dynamic and will not guess at
|
||||
// them), and these entries are generated, so the gate is the only thing standing between a regenerated
|
||||
// catalogue and a chart silently back in English. Written out, deleting one fails the build.
|
||||
const REGION_SLUG = new Map(Object.entries(WAGF_REGIONS).map(([slug, label]) => [label, slug]))
|
||||
const REGION_KEY = {
|
||||
africa: 'dashboard.wagfRegion.africa',
|
||||
asia: 'dashboard.wagfRegion.asia',
|
||||
europe: 'dashboard.wagfRegion.europe',
|
||||
'latin_america_&_caribbean': 'dashboard.wagfRegion.latin_america_&_caribbean',
|
||||
oceania: 'dashboard.wagfRegion.oceania',
|
||||
}
|
||||
const regionLabel = (key) => {
|
||||
const slug = REGION_SLUG.get(key)
|
||||
const k = slug && REGION_KEY[slug]
|
||||
return (k && tStatic(k)) || key
|
||||
}
|
||||
const countryLabel = (key) => {
|
||||
const lc = getRuntimeLocale()
|
||||
if (!lc || lc === 'en') return key
|
||||
const iso3 = iso3ForEnglishName(key)
|
||||
return iso3 ? countryName(iso3, lc) : key
|
||||
}
|
||||
const legendLabel = (facet, key) => {
|
||||
if (key === OTHER_KEY) return otherLabel()
|
||||
return facet === 'religion_family' ? religionFamilyLabel(key) : key
|
||||
if (facet === 'religion_family') return religionFamilyLabel(key)
|
||||
if (facet === 'wagf_region') return regionLabel(key)
|
||||
if (facet === 'country_code') return countryLabel(key)
|
||||
return key
|
||||
}
|
||||
// true when this chart's own facet has an active selection → drives the pop-out / dim-others state.
|
||||
const hasSelection = (facet) => filterSet[facet] != null // any selection, scalar or array
|
||||
|
|
@ -372,7 +410,11 @@ useShadowStyles(`
|
|||
<div class="rd-bars">
|
||||
<div v-for="b in chart.geometry" :key="b.key" class="rd-bar" :class="{ on: isActive(chart.facet, b.key) }"
|
||||
:data-facet="chart.facet" :data-key="b.key">
|
||||
<span class="rd-bl">{{ b.key }}</span>
|
||||
<!-- ★ THE BAR LABEL WAS THE ONE CHART SURFACE STILL PRINTING THE RAW KEY. The donut, its
|
||||
legend and both tooltips already read legendLabel(); the bar list did not, which is why
|
||||
the top-countries and region charts stayed English after the donut was fixed. Same
|
||||
projector, so the two charts cannot disagree about a name again. -->
|
||||
<span class="rd-bl">{{ legendLabel(chart.facet, b.key) }}</span>
|
||||
<span class="rd-bt"><span class="rd-bf" :style="{ width: (b.frac * 100) + '%', background: b.color }"></span></span>
|
||||
<span class="rd-bv">{{ b.value }}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -919,7 +919,13 @@ useShadowStyles(`
|
|||
<div class="rd-td chk"></div>
|
||||
<div v-for="(col, i) in COLUMNS" :key="'f-' + col.key" class="rd-td"
|
||||
:class="{ num: col.type === 'num' }" :style="colFlex(col)">
|
||||
<template v-if="i === 0">Σ {{ fmt(rowCount) }} groups</template>
|
||||
<!-- ★ "groups" WAS THE LAST ENGLISH WORD IN THIS TABLE (rega KA3, ?lang=ar): the footer read
|
||||
"Σ 2,107 groups" on an Arabic page. dashboard.groups already ships in all eleven locales
|
||||
(ar "مجموعة"), so this is a call site that never read its own catalogue -- no new
|
||||
string, no new key. Singular/plural is written the same way as the header count at
|
||||
L794 -- the two say the same thing about the same number and would otherwise disagree at
|
||||
one row. Σ and the numeral stay as they are: NUMERALS ARE HELD. -->
|
||||
<template v-if="i === 0">Σ {{ fmt(rowCount) }} {{ rowCount === 1 ? T('dashboard.group', 'group') : T('dashboard.groups', 'groups') }}</template>
|
||||
<template v-else-if="col.key === 'population'">{{ fmt(sumPop) }}</template>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1384,6 +1384,12 @@ function applyProjection() {
|
|||
// the engine by trial instead of by identity. The helper instance-tests instead (C2).
|
||||
setProjectionOn(m, projection.value)
|
||||
}
|
||||
// The pill's accessible name: the action, from the catalogue, in the reader's language. Both keys
|
||||
// ship in all eleven locales, so this never falls back to English.
|
||||
const projName = computed(() => (projection.value === 'globe'
|
||||
? T('mapui.disableGlobe', 'Disable globe')
|
||||
: T('mapui.enableGlobe', 'Enable globe')))
|
||||
|
||||
function toggleProjection() { projection.value = projection.value === 'globe' ? 'mercator' : 'globe'; applyProjection() }
|
||||
|
||||
// ── RDASH-ENHANCE: fly-to on selection. Any facet brush (tree node / chart slice / region / row)
|
||||
|
|
@ -1590,7 +1596,10 @@ useShadowStyles(`
|
|||
the media query meant phones passed at 34px while desktop stayed at 73.5x23, the same one-pixel
|
||||
failure, now invisible to a phone sweep. Measured again at 1440x900 by upg100 (2a2ea36e). One
|
||||
rule, every width, no query to be right about. */
|
||||
.rd-proj{ min-height:24px; display:inline-flex; align-items:center; }
|
||||
/* ★ AND min-WIDTH now that the word is gone. The 24 used to come for free from the text
|
||||
("🌐 Globe" measured 74px wide); a glyph-only pill is about 29px with its padding, which passes,
|
||||
but only by accident of the font. Stating both axes keeps 2.5.8 AA true if the glyph changes. */
|
||||
.rd-proj{ min-height:24px; min-width:24px; display:inline-flex; align-items:center; justify-content:center; }
|
||||
/* Zoom cluster. MapToolbar defaults to top:6px on the inline-end edge, which is exactly where
|
||||
the Globe/Flat pill already sits, so drop this instance below it. 34px clears the pill
|
||||
(top:8px + roughly 22px tall) with a small breathing gap.
|
||||
|
|
@ -1691,8 +1700,20 @@ useShadowStyles(`
|
|||
<ZoomInButton :map="map" :disabled="!canZoomIn" />
|
||||
<ZoomOutButton :map="map" :disabled="!canZoomOut" />
|
||||
</MapToolbar>
|
||||
<span class="rd-proj" role="button" tabindex="0" data-proj-toggle @click="toggleProjection">
|
||||
{{ projection === 'globe' ? '🌐 Globe' : '▭ Flat' }}
|
||||
<!-- ★ THE ICON IS THE LABEL, because the word could not be translated without inventing one.
|
||||
rega saw "🌐 Globe" in Arabic on ?lang=ar (KA3, 3d64bcad): hardcoded English chrome on a
|
||||
localized map. "Globe" has no catalogue entry and "Flat" has none in any of the eleven
|
||||
locales, so translating the visible text meant either authoring ten new strings for a
|
||||
two-state toggle or leaving nine locales reading English. The catalogue DOES already ship
|
||||
mapui.enableGlobe / mapui.disableGlobe in all eleven — as the ACTION, which is what a button
|
||||
should announce anyway — so the accessible name and the tooltip come from there and the
|
||||
visible pill carries the glyph alone. A reader now sees no English, and a screen reader hears
|
||||
the action in their own language rather than the state in someone else's.
|
||||
The label names what the click DOES, not where you are: in globe mode the button turns the
|
||||
globe off. -->
|
||||
<span class="rd-proj" role="button" tabindex="0" data-proj-toggle @click="toggleProjection"
|
||||
:title="projName" :aria-label="projName">
|
||||
<span aria-hidden="true">{{ projection === 'globe' ? '🌐' : '▭' }}</span>
|
||||
</span>
|
||||
<div class="rd-legend-map" data-map-legend :aria-label="T('aria.upgCountLegend', 'UPG count legend')">
|
||||
<!-- ★ COMPOSED FROM TWO EXISTING CATALOGUE ENTRIES, not from new wording. This legend title
|
||||
|
|
|
|||
Loading…
Reference in a new issue