Country names in the reader's language, for all 242
Our data speaks ISO alpha-3. Intl.DisplayNames('region') takes alpha-2 or
UN M49 and NOT alpha-3, so every country name was stuck in English for want
of the join.
Vendored CLDR cldr-core 48.2.2 (Unicode-3.0, 36KB, sha256 and source URL
recorded in library/vendor/cldr/README.md). No npm dependency: a package
would pull the whole supplemental tree and make a shipped data file depend
on an install step, where a vendored copy lets the generator reproduce the
map offline -- which is the point of vendoring rather than merely having a
copy.
generate-iso3-to-alpha2.mjs joins it against the 242 codes in
countries.json and GRADES ITS OWN OUTPUT, writing nothing on failure: 242
coverage, injectivity, two-letter well-formedness, and that each documented
exception is still needed. KOS -> XK is the only exception -- Kosovo has no
ISO 3166-1 assignment, so no refresh will ever supply it, and XK is the
user-assigned code CLDR itself uses in locale ids. It is recorded with its
reason instead of patched into the vendored bytes.
countryName(iso3, locale) falls back localized name -> canonical English
name -> the code. Intl.DisplayNames THROWS on a missing or malformed code
rather than returning null, so the guard is checked before asking; the
probe demonstrates that throw rather than asserting it in a comment. An
unassigned-but-well-formed region returns the input unchanged, so that case
falls back instead of rendering "DE" as a name.
THE RESEARCH SPINE MAP IS NOT TOUCHED. iso3-to-iso2.json is a different
file with a different job -- the 176-country spine that drives the data
fetches -- and expanding it is a Driver decision, not a side effect of
adding country names. The generator cross-checks it and REPORTS
disagreements without rewriting. There are none: CLDR independently agrees
with all 176 values, including the two corrected this session.
Also fixes a duplicate "check:religion-label-not-value" key in
package.json, left by a merge. JSON keeps the last, so one description was
silently dead and check:doc-scripts could not see it; bun's parser warned.
The surviving entry absorbs the only fact the dead one carried, the two
library sites by name.
This commit is contained in:
parent
8f79a3af0f
commit
725467a9de
8 changed files with 2485 additions and 0 deletions
|
|
@ -200,6 +200,8 @@ and a change here can break other maps.
|
|||
`import … from '@map/utils/colocation.js'`
|
||||
- **countries-meta** — countries-meta.js — a PORT of DX-life's config/countries-meta.ts (326 lines), the rollup
|
||||
`import … from '@map/utils/countries-meta.js'`
|
||||
- **countryName** — countryName.js — a country's name in the reader's language.
|
||||
`import … from '@map/utils/countryName.js'`
|
||||
- **embedBase** — embedBase.js — Where THIS bundle's built tree is served from, resolved at runtime.
|
||||
`import … from '@map/utils/embedBase.js'`
|
||||
- **geoUtils** — geoUtils.js — Geographic / GeoJSON Utility Functions
|
||||
|
|
@ -241,6 +243,8 @@ and a change here can break other maps.
|
|||
`import … from '@map/data/country-posters.json'`
|
||||
- **iso-numeric-to-alpha2** — "4":"AF","8":"AL","12":"DZ","24":"AO","32":"AR","36":"AU","40":"AT","50":"BD",
|
||||
`import … from '@map/data/iso-numeric-to-alpha2.json'`
|
||||
- **iso3-to-alpha2** — "_meta": {
|
||||
`import … from '@map/data/iso3-to-alpha2.json'`
|
||||
- **langFamilyByIso** — "aar": "Afro-Asiatic",
|
||||
`import … from '@map/data/langFamilyByIso.json'`
|
||||
- **langFamilyByLanguage** — {"Chamacoco":"Zamucoan","Ninam":"Yanomaman","Yagua":"Yaguan","Muinane":"Witotoan","Finnish":"Uralic","Hill Mar
|
||||
|
|
|
|||
256
library/data/iso3-to-alpha2.json
Normal file
256
library/data/iso3-to-alpha2.json
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
{
|
||||
"_meta": {
|
||||
"generated_by": "library/scripts/generate-iso3-to-alpha2.mjs (bun run gen:iso3-to-alpha2)",
|
||||
"purpose": "alpha-3 -> alpha-2 so Intl.DisplayNames can produce a LOCALIZED country name. Drives no data fetch.",
|
||||
"not_to_be_confused_with": "library/scripts/research/out/iso3-to-iso2.json — the 176-country research spine that DOES drive fetches",
|
||||
"source": "CLDR cldr-core 48.2.2, vendored at library/vendor/cldr/codeMappings.json (Unicode-3.0)",
|
||||
"countries": 242,
|
||||
"exceptions": {
|
||||
"KOS": "XK — Kosovo has no ISO 3166-1 assignment; XK is the user-assigned code CLDR itself uses in locale ids (sr_Cyrl_XK)."
|
||||
}
|
||||
},
|
||||
"map": {
|
||||
"AFG": "AF",
|
||||
"ALB": "AL",
|
||||
"DZA": "DZ",
|
||||
"ASM": "AS",
|
||||
"AND": "AD",
|
||||
"AGO": "AO",
|
||||
"AIA": "AI",
|
||||
"ATG": "AG",
|
||||
"ARG": "AR",
|
||||
"ARM": "AM",
|
||||
"ABW": "AW",
|
||||
"AUS": "AU",
|
||||
"AUT": "AT",
|
||||
"AZE": "AZ",
|
||||
"BHR": "BH",
|
||||
"BGD": "BD",
|
||||
"BRB": "BB",
|
||||
"BLR": "BY",
|
||||
"BEL": "BE",
|
||||
"BLZ": "BZ",
|
||||
"BEN": "BJ",
|
||||
"BMU": "BM",
|
||||
"BTN": "BT",
|
||||
"BOL": "BO",
|
||||
"BES": "BQ",
|
||||
"BIH": "BA",
|
||||
"BWA": "BW",
|
||||
"BRA": "BR",
|
||||
"BRN": "BN",
|
||||
"BGR": "BG",
|
||||
"BFA": "BF",
|
||||
"BDI": "BI",
|
||||
"CPV": "CV",
|
||||
"KHM": "KH",
|
||||
"CMR": "CM",
|
||||
"CAN": "CA",
|
||||
"CYM": "KY",
|
||||
"CAF": "CF",
|
||||
"TCD": "TD",
|
||||
"CHL": "CL",
|
||||
"CHN": "CN",
|
||||
"CXR": "CX",
|
||||
"CCK": "CC",
|
||||
"COL": "CO",
|
||||
"COM": "KM",
|
||||
"COK": "CK",
|
||||
"CRI": "CR",
|
||||
"CIV": "CI",
|
||||
"HRV": "HR",
|
||||
"CUB": "CU",
|
||||
"CUW": "CW",
|
||||
"CYP": "CY",
|
||||
"DNK": "DK",
|
||||
"DJI": "DJ",
|
||||
"DMA": "DM",
|
||||
"DOM": "DO",
|
||||
"ECU": "EC",
|
||||
"EGY": "EG",
|
||||
"SLV": "SV",
|
||||
"GNQ": "GQ",
|
||||
"ERI": "ER",
|
||||
"EST": "EE",
|
||||
"SWZ": "SZ",
|
||||
"ETH": "ET",
|
||||
"FLK": "FK",
|
||||
"FRO": "FO",
|
||||
"FJI": "FJ",
|
||||
"FIN": "FI",
|
||||
"FRA": "FR",
|
||||
"GUF": "GF",
|
||||
"PYF": "PF",
|
||||
"GAB": "GA",
|
||||
"GEO": "GE",
|
||||
"DEU": "DE",
|
||||
"GHA": "GH",
|
||||
"GIB": "GI",
|
||||
"GRC": "GR",
|
||||
"GRL": "GL",
|
||||
"GRD": "GD",
|
||||
"GLP": "GP",
|
||||
"GUM": "GU",
|
||||
"GTM": "GT",
|
||||
"GGY": "GG",
|
||||
"GIN": "GN",
|
||||
"GNB": "GW",
|
||||
"GUY": "GY",
|
||||
"HTI": "HT",
|
||||
"HND": "HN",
|
||||
"HKG": "HK",
|
||||
"HUN": "HU",
|
||||
"ISL": "IS",
|
||||
"IND": "IN",
|
||||
"IDN": "ID",
|
||||
"IRN": "IR",
|
||||
"IRQ": "IQ",
|
||||
"IRL": "IE",
|
||||
"IMN": "IM",
|
||||
"ISR": "IL",
|
||||
"ITA": "IT",
|
||||
"JAM": "JM",
|
||||
"JPN": "JP",
|
||||
"JEY": "JE",
|
||||
"JOR": "JO",
|
||||
"KAZ": "KZ",
|
||||
"KEN": "KE",
|
||||
"KIR": "KI",
|
||||
"KOS": "XK",
|
||||
"KWT": "KW",
|
||||
"KGZ": "KG",
|
||||
"LAO": "LA",
|
||||
"LVA": "LV",
|
||||
"LBN": "LB",
|
||||
"LSO": "LS",
|
||||
"LBR": "LR",
|
||||
"LBY": "LY",
|
||||
"LIE": "LI",
|
||||
"LTU": "LT",
|
||||
"LUX": "LU",
|
||||
"MAC": "MO",
|
||||
"MDG": "MG",
|
||||
"MWI": "MW",
|
||||
"MYS": "MY",
|
||||
"MLI": "ML",
|
||||
"MLT": "MT",
|
||||
"MTQ": "MQ",
|
||||
"MRT": "MR",
|
||||
"MUS": "MU",
|
||||
"MYT": "YT",
|
||||
"MEX": "MX",
|
||||
"MDA": "MD",
|
||||
"MCO": "MC",
|
||||
"MNG": "MN",
|
||||
"MNE": "ME",
|
||||
"MSR": "MS",
|
||||
"MAR": "MA",
|
||||
"MOZ": "MZ",
|
||||
"MMR": "MM",
|
||||
"NAM": "NA",
|
||||
"NRU": "NR",
|
||||
"NPL": "NP",
|
||||
"NCL": "NC",
|
||||
"NZL": "NZ",
|
||||
"NIC": "NI",
|
||||
"NER": "NE",
|
||||
"NGA": "NG",
|
||||
"NIU": "NU",
|
||||
"NFK": "NF",
|
||||
"PRK": "KP",
|
||||
"MKD": "MK",
|
||||
"NOR": "NO",
|
||||
"OMN": "OM",
|
||||
"PAK": "PK",
|
||||
"PLW": "PW",
|
||||
"PSE": "PS",
|
||||
"PAN": "PA",
|
||||
"PNG": "PG",
|
||||
"PRY": "PY",
|
||||
"PER": "PE",
|
||||
"POL": "PL",
|
||||
"PRT": "PT",
|
||||
"PRI": "PR",
|
||||
"QAT": "QA",
|
||||
"REU": "RE",
|
||||
"ROU": "RO",
|
||||
"RWA": "RW",
|
||||
"BLM": "BL",
|
||||
"SHN": "SH",
|
||||
"KNA": "KN",
|
||||
"LCA": "LC",
|
||||
"MAF": "MF",
|
||||
"SPM": "PM",
|
||||
"VCT": "VC",
|
||||
"WSM": "WS",
|
||||
"SMR": "SM",
|
||||
"STP": "ST",
|
||||
"SAU": "SA",
|
||||
"SEN": "SN",
|
||||
"SRB": "RS",
|
||||
"SYC": "SC",
|
||||
"SLE": "SL",
|
||||
"SGP": "SG",
|
||||
"SXM": "SX",
|
||||
"SVK": "SK",
|
||||
"SVN": "SI",
|
||||
"SOM": "SO",
|
||||
"ZAF": "ZA",
|
||||
"KOR": "KR",
|
||||
"SSD": "SS",
|
||||
"ESP": "ES",
|
||||
"LKA": "LK",
|
||||
"SDN": "SD",
|
||||
"SUR": "SR",
|
||||
"SJM": "SJ",
|
||||
"SWE": "SE",
|
||||
"CHE": "CH",
|
||||
"SYR": "SY",
|
||||
"TWN": "TW",
|
||||
"TJK": "TJ",
|
||||
"TZA": "TZ",
|
||||
"THA": "TH",
|
||||
"BHS": "BS",
|
||||
"VGB": "VG",
|
||||
"CZE": "CZ",
|
||||
"COD": "CD",
|
||||
"FSM": "FM",
|
||||
"GMB": "GM",
|
||||
"MDV": "MV",
|
||||
"MHL": "MH",
|
||||
"NLD": "NL",
|
||||
"MNP": "MP",
|
||||
"PHL": "PH",
|
||||
"PCN": "PN",
|
||||
"COG": "CG",
|
||||
"RUS": "RU",
|
||||
"SLB": "SB",
|
||||
"TCA": "TC",
|
||||
"ARE": "AE",
|
||||
"GBR": "GB",
|
||||
"USA": "US",
|
||||
"VIR": "VI",
|
||||
"TLS": "TL",
|
||||
"TGO": "TG",
|
||||
"TKL": "TK",
|
||||
"TON": "TO",
|
||||
"TTO": "TT",
|
||||
"TUN": "TN",
|
||||
"TUR": "TR",
|
||||
"TKM": "TM",
|
||||
"TUV": "TV",
|
||||
"UGA": "UG",
|
||||
"UKR": "UA",
|
||||
"URY": "UY",
|
||||
"UZB": "UZ",
|
||||
"VUT": "VU",
|
||||
"VAT": "VA",
|
||||
"VEN": "VE",
|
||||
"VNM": "VN",
|
||||
"WLF": "WF",
|
||||
"ESH": "EH",
|
||||
"YEM": "YE",
|
||||
"ZMB": "ZM",
|
||||
"ZWE": "ZW"
|
||||
}
|
||||
}
|
||||
122
library/scripts/generate-iso3-to-alpha2.mjs
Normal file
122
library/scripts/generate-iso3-to-alpha2.mjs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#!/usr/bin/env node
|
||||
// generate-iso3-to-alpha2.mjs — `bun run gen:iso3-to-alpha2`. Offline, no network, no dependency.
|
||||
//
|
||||
// Writes library/data/iso3-to-alpha2.json: the alpha-2 code for each of the 242 alpha-3 codes in
|
||||
// library/data/countries.json, so a country NAME can be localized. Intl.DisplayNames('region') takes
|
||||
// alpha-2 or UN M49 only -- it does NOT accept alpha-3 -- and our data speaks alpha-3 throughout, so
|
||||
// without this join every country name is stuck in English.
|
||||
//
|
||||
// ★ THIS IS NOT THE RESEARCH MAP. library/scripts/research/out/iso3-to-iso2.json is a different file
|
||||
// with a different job: the 176-country spine that drives the data FETCHES. Expanding that one changes
|
||||
// what gets fetched, which is a data-scope decision for the Driver (dx-shepherd, REG242B) and must not
|
||||
// happen as a side effect of adding country names. This map drives no fetch. The two are deliberately
|
||||
// not interchangeable, which is why they have different names, different shapes and this paragraph.
|
||||
//
|
||||
// ★ IT GRADES ITS OWN OUTPUT AND EXITS NON-ZERO. A generator that writes whatever it derived is a
|
||||
// generator that will one day write a hole. Coverage, injectivity and the single documented exception
|
||||
// are asserted here, so a bad refresh of the vendored CLDR cannot land quietly.
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { resolve, dirname, join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const REPO = process.env.DOXA_REPO || resolve(dirname(fileURLToPath(import.meta.url)), '..', '..')
|
||||
const read = (p) => JSON.parse(readFileSync(join(REPO, p), 'utf8'))
|
||||
|
||||
const VENDOR = 'library/vendor/cldr/codeMappings.json'
|
||||
const OUT = 'library/data/iso3-to-alpha2.json'
|
||||
|
||||
// ── the one exception, and why it is not a fudge ─────────────────────────────────────────────────
|
||||
// Kosovo has NO ISO 3166-1 assignment, so CLDR's codeMappings cannot carry it and no amount of
|
||||
// re-downloading will add it. XK is the USER-ASSIGNED code the EU, the IMF, Unicode's own CLDR
|
||||
// locale data (sr_Cyrl_XK) and every major platform settled on. It is recorded here as a named
|
||||
// exception with its reason rather than patched into the vendored file, so the vendored bytes stay
|
||||
// exactly upstream's and the deviation stays visible to the next reader.
|
||||
const EXCEPTIONS = {
|
||||
KOS: { alpha2: 'XK', why: 'Kosovo has no ISO 3166-1 assignment; XK is the user-assigned code CLDR itself uses in locale ids (sr_Cyrl_XK).' },
|
||||
}
|
||||
|
||||
const cldr = read(VENDOR).supplemental.codeMappings
|
||||
const countries = read('library/data/countries.json')
|
||||
|
||||
// CLDR is keyed by alpha-2 with an _alpha3 inside; we need the inverse.
|
||||
const byAlpha3 = new Map()
|
||||
const cldrCollisions = []
|
||||
for (const [alpha2, v] of Object.entries(cldr)) {
|
||||
const a3 = v && v._alpha3
|
||||
if (!a3) continue
|
||||
if (byAlpha3.has(a3)) cldrCollisions.push(`${a3} claimed by ${byAlpha3.get(a3)} and ${alpha2}`)
|
||||
else byAlpha3.set(a3, alpha2)
|
||||
}
|
||||
|
||||
const map = {}
|
||||
const missing = []
|
||||
for (const { value: iso3, label } of countries) {
|
||||
const fromCldr = byAlpha3.get(iso3)
|
||||
const exc = EXCEPTIONS[iso3]
|
||||
if (fromCldr) map[iso3] = fromCldr
|
||||
else if (exc) map[iso3] = exc.alpha2
|
||||
else missing.push(`${iso3} (${label})`)
|
||||
}
|
||||
|
||||
// ── assertions: the generator refuses to ship a map it cannot defend ────────────────────────────
|
||||
let fails = 0
|
||||
const ok = (c, m) => { console.log(` ${c ? 'ok ' : 'FAIL'} - ${m}`); if (!c) fails++ }
|
||||
|
||||
ok(countries.length === 242, `the country list holds 242 codes (found ${countries.length})`)
|
||||
ok(missing.length === 0, missing.length
|
||||
? `${missing.length} code(s) have NO alpha-2 and no documented exception: ${missing.join(', ')}`
|
||||
: 'every code resolved, via CLDR or a documented exception')
|
||||
|
||||
const seen = new Map()
|
||||
const dupes = []
|
||||
for (const [iso3, a2] of Object.entries(map)) {
|
||||
if (seen.has(a2)) dupes.push(`${a2} claimed by ${seen.get(a2)} and ${iso3}`)
|
||||
else seen.set(a2, iso3)
|
||||
}
|
||||
ok(dupes.length === 0, dupes.length
|
||||
? `alpha-2 collisions -- at least one country would take another's name: ${dupes.join('; ')}`
|
||||
: `all ${Object.keys(map).length} codes map to a DISTINCT alpha-2`)
|
||||
|
||||
ok(Object.entries(map).every(([, a2]) => /^[A-Z]{2}$/.test(a2)), 'every alpha-2 is two uppercase letters')
|
||||
|
||||
// Every exception must still BE one. If CLDR ever assigns KOS, this fires and the entry should go.
|
||||
for (const [iso3, e] of Object.entries(EXCEPTIONS)) {
|
||||
ok(!byAlpha3.has(iso3),
|
||||
`the ${iso3} exception is still needed -- CLDR ${byAlpha3.has(iso3) ? `now assigns it ${byAlpha3.get(iso3)}, so DELETE the exception` : 'still has no assignment for it'}`)
|
||||
}
|
||||
|
||||
// ── cross-check the research map rather than touching it ────────────────────────────────────────
|
||||
// CLDR is an independent source for the 176 values that map already ships. Disagreements are reported,
|
||||
// never written: that file drives fetches and is not this generator's to edit.
|
||||
let disagreements = 0
|
||||
try {
|
||||
const research = read('library/scripts/research/out/iso3-to-iso2.json')
|
||||
for (const [iso3, a2] of Object.entries(research)) {
|
||||
const c = byAlpha3.get(iso3)
|
||||
if (c && c !== a2) { console.log(` FAIL - research map says ${iso3} -> ${a2}, CLDR says ${c}`); disagreements++ }
|
||||
}
|
||||
ok(disagreements === 0,
|
||||
`CLDR agrees with all ${Object.keys(research).length} values in the research spine map (reported, never rewritten -- that file drives fetches)`)
|
||||
} catch (e) {
|
||||
console.log(` note - research map not readable here (${e.code || e.message}); cross-check skipped`)
|
||||
}
|
||||
|
||||
if (fails) {
|
||||
console.log(`\nISO3-TO-ALPHA2: ${fails} FAIL(s) — nothing written`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const cldrVersion = '48.2.2' // keep in step with library/vendor/cldr/README.md
|
||||
writeFileSync(join(REPO, OUT), JSON.stringify({
|
||||
_meta: {
|
||||
generated_by: 'library/scripts/generate-iso3-to-alpha2.mjs (bun run gen:iso3-to-alpha2)',
|
||||
purpose: 'alpha-3 -> alpha-2 so Intl.DisplayNames can produce a LOCALIZED country name. Drives no data fetch.',
|
||||
not_to_be_confused_with: 'library/scripts/research/out/iso3-to-iso2.json — the 176-country research spine that DOES drive fetches',
|
||||
source: `CLDR cldr-core ${cldrVersion}, vendored at ${VENDOR} (Unicode-3.0)`,
|
||||
countries: Object.keys(map).length,
|
||||
exceptions: Object.fromEntries(Object.entries(EXCEPTIONS).map(([k, v]) => [k, `${v.alpha2} — ${v.why}`])),
|
||||
},
|
||||
map,
|
||||
}, null, 2) + '\n')
|
||||
|
||||
console.log(`\nISO3-TO-ALPHA2: wrote ${OUT} — ${Object.keys(map).length} countries, ${Object.keys(EXCEPTIONS).length} documented exception(s)`)
|
||||
84
library/utils/countryName.js
Normal file
84
library/utils/countryName.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* countryName.js — a country's name in the reader's language.
|
||||
*
|
||||
* Our data speaks ISO 3166-1 ALPHA-3 ("DEU"). Intl.DisplayNames('region') accepts alpha-2 or UN M49
|
||||
* and NOT alpha-3, so every country name was stuck in English until the two were joined. The join is
|
||||
* library/data/iso3-to-alpha2.json, derived from vendored CLDR by generate-iso3-to-alpha2.mjs.
|
||||
*
|
||||
* ★ Intl.DisplayNames THROWS on a missing code. `dn.of(undefined)` is a RangeError, not a null return,
|
||||
* so every path here checks the alpha-2 before asking. That is the single defect this file exists to
|
||||
* not have: a country absent from the map would otherwise take down whatever rendered it.
|
||||
*
|
||||
* ★ AND IT THROWS ON A MALFORMED ONE TOO, which is why the regex guard is not redundant with the
|
||||
* lookup. dn.of('XYZ') is also a RangeError -- 'structurally invalid' is a different failure from
|
||||
* 'not found', and only the second returns the input unchanged.
|
||||
*
|
||||
* FALLBACK ORDER, each step a real answer rather than a placeholder:
|
||||
* 1. the localized name from Intl.DisplayNames
|
||||
* 2. the canonical ENGLISH name from library/data/countries.json — a real name in the wrong language
|
||||
* beats a bare code for a reader, and it is what our own tables already show
|
||||
* 3. the alpha-3 code itself, so something identifiable always renders
|
||||
*
|
||||
* No i18n framework is required: this reads a locale STRING the caller already has.
|
||||
*/
|
||||
import table from '../data/iso3-to-alpha2.json'
|
||||
import countries from '../data/countries.json'
|
||||
|
||||
const ALPHA2 = table.map
|
||||
const ENGLISH = new Map(countries.map((c) => [c.value, c.label]))
|
||||
|
||||
/** 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. */
|
||||
export function alpha2Of(iso3) {
|
||||
return iso3 ? ALPHA2[String(iso3).toUpperCase()] : undefined
|
||||
}
|
||||
|
||||
/** The canonical English name, or undefined. */
|
||||
export function englishName(iso3) {
|
||||
return iso3 ? ENGLISH.get(String(iso3).toUpperCase()) : undefined
|
||||
}
|
||||
|
||||
// One DisplayNames per locale. Constructing it is not cheap and a country list rebuilds it per row;
|
||||
// a Map keyed by locale is the whole optimisation. A locale that throws is cached as null so the
|
||||
// failure costs one construction, not one per call.
|
||||
const cache = new Map()
|
||||
function displayNames(locale) {
|
||||
const key = locale || 'en'
|
||||
if (cache.has(key)) return cache.get(key)
|
||||
let dn = null
|
||||
try {
|
||||
dn = new Intl.DisplayNames([key], { type: 'region' })
|
||||
} catch (_) {
|
||||
dn = null // an unsupported locale tag, or no Intl.DisplayNames at all
|
||||
}
|
||||
cache.set(key, dn)
|
||||
return dn
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} iso3 ISO 3166-1 alpha-3, e.g. 'DEU'. Case-insensitive.
|
||||
* @param {string} [locale] BCP-47 tag, e.g. 'ar'. Defaults to English.
|
||||
* @returns {string} the country's name in that locale, its English name, or the code.
|
||||
*/
|
||||
export function countryName(iso3, locale) {
|
||||
const code = iso3 ? String(iso3).toUpperCase() : ''
|
||||
const a2 = ALPHA2[code]
|
||||
const fallback = ENGLISH.get(code) || code
|
||||
|
||||
// Guard BEFORE asking: dn.of(undefined) and dn.of('XYZ') both throw.
|
||||
if (!a2 || !/^[A-Z]{2}$/.test(a2)) return fallback
|
||||
|
||||
const dn = displayNames(locale)
|
||||
if (!dn) return fallback
|
||||
try {
|
||||
const name = dn.of(a2)
|
||||
// An UNKNOWN-but-well-formed region comes back as the input string. That is not a name, so it
|
||||
// must not win over our English label -- this is the difference between "no translation exists"
|
||||
// and "the translation is literally 'DE'".
|
||||
return name && name !== a2 ? name : fallback
|
||||
} catch (_) {
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
export default countryName
|
||||
41
library/vendor/cldr/LICENSE
vendored
Normal file
41
library/vendor/cldr/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
UNICODE LICENSE V3
|
||||
|
||||
COPYRIGHT AND PERMISSION NOTICE
|
||||
|
||||
Copyright © 2015-2024 Unicode, Inc.
|
||||
|
||||
NOTICE TO USER: Carefully read the following legal agreement. BY
|
||||
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
|
||||
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
|
||||
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
|
||||
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of data files and any associated documentation (the "Data Files") or
|
||||
software and any associated documentation (the "Software") to deal in the
|
||||
Data Files or Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, and/or sell
|
||||
copies of the Data Files or Software, and to permit persons to whom the
|
||||
Data Files or Software are furnished to do so, provided that either (a)
|
||||
this copyright and permission notice appear with all copies of the Data
|
||||
Files or Software, or (b) this copyright and permission notice appear in
|
||||
associated Documentation.
|
||||
|
||||
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
||||
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
THIRD PARTY RIGHTS.
|
||||
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
|
||||
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
|
||||
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
|
||||
FILES OR SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of a copyright holder shall
|
||||
not be used in advertising or otherwise to promote the sale, use or other
|
||||
dealings in these Data Files or Software without prior written
|
||||
authorization of the copyright holder.
|
||||
|
||||
SPDX-License-Identifier: Unicode-3.0
|
||||
42
library/vendor/cldr/README.md
vendored
Normal file
42
library/vendor/cldr/README.md
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Vendored CLDR — `codeMappings.json`
|
||||
|
||||
The ISO 3166-1 alpha-2 / alpha-3 / numeric correspondences, used to turn the alpha-3 codes our data
|
||||
speaks into the alpha-2 codes `Intl.DisplayNames` requires to produce a LOCALIZED country name.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Package | `cldr-core` (unicode-org/cldr-json) |
|
||||
| Version | **48.2.2** |
|
||||
| Source | `https://raw.githubusercontent.com/unicode-org/cldr-json/main/cldr-json/cldr-core/supplemental/codeMappings.json` |
|
||||
| Licence | **Unicode-3.0** — full text in `LICENSE` beside this file |
|
||||
| Bytes | 35,948 |
|
||||
| sha256 | `0d1ef50b92c1140e5847d22d96faf1a9c35543b0dedf8e9a2fcb87e4c51b9ed6` |
|
||||
| Vendored | 2026-09-27 |
|
||||
|
||||
## Why vendored rather than a dependency
|
||||
|
||||
No npm package is added for this. A dependency would pull CLDR's whole supplemental tree, and it would
|
||||
make the derivation of a shipped data file depend on an install step. The file is 35KB, it is
|
||||
licensed for redistribution, and vendoring it means `bun run gen:iso3-to-alpha2` reproduces the derived
|
||||
map OFFLINE — which is the actual point of vendoring, not merely having a copy.
|
||||
|
||||
## What is derived from it
|
||||
|
||||
`library/scripts/generate-iso3-to-alpha2.mjs` joins this file against the 242 alpha-3 codes in
|
||||
`library/data/countries.json` and writes `library/data/iso3-to-alpha2.json`. The generator asserts its
|
||||
own output (242 coverage, injectivity, the one documented exception) and exits non-zero if any of it
|
||||
fails, so a bad refresh cannot land quietly.
|
||||
|
||||
## DO NOT CONFUSE THIS WITH THE RESEARCH MAP
|
||||
|
||||
`library/scripts/research/out/iso3-to-iso2.json` is a DIFFERENT map with a different job: it is the
|
||||
176-country research spine that drives the data fetches, and expanding it changes what gets fetched —
|
||||
a data-scope decision that belongs to the Driver, not a side effect of adding country names. The map
|
||||
derived here is for DISPLAY NAMES ONLY and drives no fetch. CLDR agrees with all 176 values in the
|
||||
research map, which is a useful independent check on it, but the two files are not interchangeable.
|
||||
|
||||
## Refreshing
|
||||
|
||||
Re-download the URL above, update the version, bytes and sha256 in this table, then run the generator
|
||||
and let its assertions grade the result. If CLDR ever disagrees with a value in the research map, the
|
||||
generator says so rather than silently overwriting anything.
|
||||
1934
library/vendor/cldr/codeMappings.json
vendored
Normal file
1934
library/vendor/cldr/codeMappings.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -236,6 +236,7 @@
|
|||
"gallery": "node internal/generate-gallery.mjs",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- 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: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",
|
||||
"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",
|
||||
"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",
|
||||
"lists": "node library/scripts/people-groups/build-lists.mjs",
|
||||
|
|
@ -422,6 +423,7 @@
|
|||
"check:zone-census": "every zone a USER CAN SELECT must have a poster (asks the LEGEND, not clusters.json — two collapsed deaf lists are unreachable and not a gap). CONDITIONAL: SKIPs without out/posters/, which is gitignored — run `bun run posters` first. It cannot be fixtured like check:packets-all, because it asserts COVERAGE of real generated output and a fixture would only assert the fixture is complete.",
|
||||
"deploy:dev": "destination + allowlist come from vite.doxa-maps-build-config.json (dropin.dest / dropin.include); DOXA_DROPIN_DEST=<dir> overrides the destination",
|
||||
"gallery": "writes public/__gallery/index.html — the ARCHIVED bundle-index gallery, from public/doxa-maps/manifest.json. It is NO LONGER the root page: as of 2026-09-22 `/` mounts <plugin-studio> in BOTH trees (the built root page is written by `bun run tree`), and the gallery is the opt-in index at /__gallery. Archived, not deleted — the dev server imports renderGallery()/readBuildConfig() from this module.",
|
||||
"gen:iso3-to-alpha2": "derive library/data/iso3-to-alpha2.json from vendored CLDR (48.2.2, Unicode-3.0) joined against the 242 codes in library/data/countries.json, so Intl.DisplayNames can produce a LOCALIZED country name — it takes alpha-2 and our data speaks alpha-3. Offline, no dependency. GRADES ITS OWN OUTPUT and writes nothing on failure: 242 coverage, injectivity, and KOS -> XK as the one documented exception (Kosovo has no ISO assignment). Cross-checks the 176-country research spine map and REPORTS disagreements without rewriting it — that file drives fetches and is not this generator's to edit.",
|
||||
"internet:adm1": "regenerate data/tables/internet-by-adm1.json (internet use by first-level area; India from NFHS-5 2019-21, CC BY 4.0) from data/internet/ through a strict name crosswalk to the shipped gb-IND-ADM1 boundaries. Fails on an unmatched name, never guesses. Generic rows (iso3, adm1, measure, sex, area, value, source, year) so other plugins and go-int can reuse it.",
|
||||
"library-index": "regenerate documentation/reference-library-index.md — the DERIVED menu of the 84 reusable library pieces an author can import instead of rebuilding. Was UNREACHABLE, and the index did not exist at all: its header promised 'can't drift from the code' while nothing ever ran it.",
|
||||
"lists": "regenerate data/people-groups/lists/<id>.json, the DOXA slugs in each people-group list of library/lists/lists.json, from the IMB CSV and the DOXA snapshot. Re-run after either changes.",
|
||||
|
|
|
|||
Loading…
Reference in a new issue