Compare commits
4 commits
main
...
country-po
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f487ed17d2 | ||
|
|
6fe29c8ad0 | ||
|
|
3be2ea856b | ||
|
|
940c65e85c |
21 changed files with 929 additions and 24 deletions
|
|
@ -63,8 +63,19 @@ const HAND_VOCAB = {
|
|||
'Project 700',
|
||||
'How this list was found',
|
||||
],
|
||||
// The phrases that NAME a series. Derived vocabulary drops any sentence two series share, so a verbatim
|
||||
// copy of one series' framing into another would go unseen without these (the copy becomes "shared").
|
||||
country: [
|
||||
'PRAY FOR THIS COUNTRY',
|
||||
'Country Prayer Map',
|
||||
'one sheet for each country',
|
||||
'which country God has put',
|
||||
],
|
||||
indiaState: [
|
||||
'PRAY FOR THIS STATE',
|
||||
'State Prayer Map',
|
||||
'one sheet for each state',
|
||||
'which state God has put',
|
||||
],
|
||||
}
|
||||
|
||||
|
|
@ -100,13 +111,13 @@ function deriveVocab(series) {
|
|||
...strings(e.poster || {}),
|
||||
].map((s) => s.trim()).filter((s) => s.length >= 12))
|
||||
}
|
||||
// Drop anything two series share — it cannot identify either of them.
|
||||
for (const a of ids) {
|
||||
for (const b of ids) {
|
||||
if (a === b) continue
|
||||
for (const s of own[b]) own[a].delete(s)
|
||||
}
|
||||
}
|
||||
// Drop anything two series share — it cannot identify either of them. The shared set is found on the
|
||||
// ORIGINAL sets first: deleting while still comparing pairs removed a shared string from the earlier
|
||||
// series only, so the later one kept it as a "fingerprint" and the earlier one failed for carrying it.
|
||||
// Invisible with two series; a third (indiaState, sharing country's networks) exposed it.
|
||||
const shared = new Set()
|
||||
for (const a of ids) for (const b of ids) if (a !== b) for (const s of own[a]) if (own[b].has(s)) shared.add(s)
|
||||
for (const id of ids) for (const s of shared) own[id].delete(s)
|
||||
const out = {}
|
||||
for (const id of ids) out[id] = [...new Set([...(HAND_VOCAB[id] || []), ...own[id]])]
|
||||
return out
|
||||
|
|
@ -120,8 +131,10 @@ function* strings(v) {
|
|||
}
|
||||
|
||||
/** The text a series OWNS: its overrides and its poster furniture. Not its prose documentation. */
|
||||
// The poster header block too: that is where the country poster once printed the UPG-100 side box, and a scan
|
||||
// of strings + networks alone could never see a series' framing copied into another's poster text.
|
||||
function seriesText(entry) {
|
||||
return [...strings(entry.strings || {}), ...strings(entry.networks || [])]
|
||||
return [...strings(entry.strings || {}), ...strings(entry.networks || []), ...strings(entry.poster || {})]
|
||||
}
|
||||
|
||||
function checkDeclarations(series) {
|
||||
|
|
@ -208,6 +221,9 @@ function selfTest() {
|
|||
['upg100 loses its funnel', (s) => { s.upg100.funnel = false }],
|
||||
['a series loses a networks block', (s) => { s.country.networks.pop() }],
|
||||
['dataDir points nowhere', (s) => { s.country.dataDir = 'no/such/folder' }],
|
||||
['indiaState keeps the country side box', (s) => { s.indiaState.poster.sideBoxHeading = 'What is a Country Prayer Map?' }],
|
||||
['country keeps the state network line', (s) => { s.country.networks[0][1] = s.indiaState.networks[0][1] }],
|
||||
['indiaState keeps PRAY FOR THIS COUNTRY', (s) => { s.indiaState.strings.map_page_kicker = 'PRAY FOR THIS COUNTRY' }],
|
||||
]
|
||||
let pass = 0
|
||||
for (const [name, mutate] of cases) {
|
||||
|
|
@ -224,6 +240,19 @@ function selfTest() {
|
|||
fails.length = 0
|
||||
if (pass !== cases.length) fail(`self-test: caught ${pass}/${cases.length} known-bad inputs`)
|
||||
else ok(`self-test: caught all ${cases.length} known-bad inputs`)
|
||||
// Shared wording is nobody's fingerprint, in EITHER order: three series where the first and last share a
|
||||
// sentence must neither flag it nor lose their own phrases.
|
||||
const SHARED = 'A sentence two series both print, word for word.'
|
||||
const tri = {
|
||||
a: { strings: { k: 'Only series a says this phrase.' }, networks: [['h', SHARED]] },
|
||||
b: { strings: { k: 'Only series b says this phrase.' }, networks: [] },
|
||||
c: { strings: { k: 'Only series c says this phrase.' }, networks: [['h', SHARED]] },
|
||||
}
|
||||
const v = deriveVocab(tri)
|
||||
const leaked = Object.entries(v).filter(([, p]) => p.includes(SHARED)).map(([id]) => id)
|
||||
const lost = ['a', 'b', 'c'].filter((id) => !v[id].includes(`Only series ${id} says this phrase.`))
|
||||
if (leaked.length || lost.length) fail(`self-test: shared wording kept as vocabulary of [${leaked}] / own phrases lost for [${lost}]`)
|
||||
else ok('self-test: shared wording is dropped from every series, whatever their order')
|
||||
}
|
||||
|
||||
// ── run ──────────────────────────────────────────────────────────────────────
|
||||
|
|
|
|||
42
library/i18n/locales/ar/countryPosters.json
Normal file
42
library/i18n/locales/ar/countryPosters.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"indiaStatesHeading": "الهند — الولايات والأقاليم الاتحادية",
|
||||
"indiaStateNoGroups": "لا توجد هنا شعوب غير مُبشَّرة مُدرَجة.",
|
||||
"stateNames": {
|
||||
"IN-AN": "جزر أندمان وَ نِكُبَار",
|
||||
"IN-AP": "أندرا برديش",
|
||||
"IN-AR": "أروناجل برديش",
|
||||
"IN-AS": "آسام",
|
||||
"IN-BR": "بهار",
|
||||
"IN-CH": "شَنديغار",
|
||||
"IN-CT": "تشاتيسغار",
|
||||
"IN-DH": "دادرا وناغار هافيلي ودامان وديو",
|
||||
"IN-DL": "دلهي",
|
||||
"IN-GA": "غوا",
|
||||
"IN-GJ": "كجرات",
|
||||
"IN-HP": "هماجل برديش",
|
||||
"IN-HR": "هريانة",
|
||||
"IN-JH": "جاركند",
|
||||
"IN-JK": "إقليم جمون وكشمير",
|
||||
"IN-KA": "كرناتك",
|
||||
"IN-KL": "كيرلا",
|
||||
"IN-LA": "لداخ",
|
||||
"IN-LD": "لكشديب",
|
||||
"IN-MH": "مهاراشترة",
|
||||
"IN-ML": "ميكالية",
|
||||
"IN-MN": "منيبور",
|
||||
"IN-MP": "مدية برديش",
|
||||
"IN-MZ": "مِزُورَم",
|
||||
"IN-NL": "ناغالند",
|
||||
"IN-OR": "أديشة",
|
||||
"IN-PB": "بنجاب",
|
||||
"IN-PY": "بُدُجيري",
|
||||
"IN-RJ": "راجستان",
|
||||
"IN-SK": "سِكِم",
|
||||
"IN-TG": "تلنكانة",
|
||||
"IN-TN": "تاميل نادو",
|
||||
"IN-TR": "تريبورة",
|
||||
"IN-UP": "أتر برديش",
|
||||
"IN-UT": "أوتاراخند",
|
||||
"IN-WB": "بنغال الغربية"
|
||||
}
|
||||
}
|
||||
42
library/i18n/locales/de/countryPosters.json
Normal file
42
library/i18n/locales/de/countryPosters.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"indiaStatesHeading": "Indien – Bundesstaaten und Unionsterritorien",
|
||||
"indiaStateNoGroups": "Hier sind keine unerreichten Volksgruppen verzeichnet.",
|
||||
"stateNames": {
|
||||
"IN-AN": "Andamanen und Nikobaren",
|
||||
"IN-AP": "Andhra Pradesh",
|
||||
"IN-AR": "Arunachal Pradesh",
|
||||
"IN-AS": "Assam",
|
||||
"IN-BR": "Bihar",
|
||||
"IN-CH": "Chandigarh",
|
||||
"IN-CT": "Chhattisgarh",
|
||||
"IN-DH": "Dadra und Nagar Haveli und Daman und Diu",
|
||||
"IN-DL": "Delhi",
|
||||
"IN-GA": "Goa",
|
||||
"IN-GJ": "Gujarat",
|
||||
"IN-HP": "Himachal Pradesh",
|
||||
"IN-HR": "Haryana",
|
||||
"IN-JH": "Jharkhand",
|
||||
"IN-JK": "Jammu und Kashmir",
|
||||
"IN-KA": "Karnataka",
|
||||
"IN-KL": "Kerala",
|
||||
"IN-LA": "Ladakh",
|
||||
"IN-LD": "Lakshadweep",
|
||||
"IN-MH": "Maharashtra",
|
||||
"IN-ML": "Meghalaya",
|
||||
"IN-MN": "Manipur",
|
||||
"IN-MP": "Madhya Pradesh",
|
||||
"IN-MZ": "Mizoram",
|
||||
"IN-NL": "Nagaland",
|
||||
"IN-OR": "Odisha",
|
||||
"IN-PB": "Punjab",
|
||||
"IN-PY": "Puducherry",
|
||||
"IN-RJ": "Rajasthan",
|
||||
"IN-SK": "Sikkim",
|
||||
"IN-TG": "Telangana",
|
||||
"IN-TN": "Tamil Nadu",
|
||||
"IN-TR": "Tripura",
|
||||
"IN-UP": "Uttar Pradesh",
|
||||
"IN-UT": "Uttarakhand",
|
||||
"IN-WB": "Westbengalen"
|
||||
}
|
||||
}
|
||||
42
library/i18n/locales/en/countryPosters.json
Normal file
42
library/i18n/locales/en/countryPosters.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"indiaStatesHeading": "India — states and union territories",
|
||||
"indiaStateNoGroups": "No unreached people groups are listed here.",
|
||||
"stateNames": {
|
||||
"IN-AN": "Andaman and Nicobar Islands",
|
||||
"IN-AP": "Andhra Pradesh",
|
||||
"IN-AR": "Arunachal Pradesh",
|
||||
"IN-AS": "Assam",
|
||||
"IN-BR": "Bihar",
|
||||
"IN-CH": "Chandigarh",
|
||||
"IN-CT": "Chhattisgarh",
|
||||
"IN-DH": "Dadra and Nagar Haveli and Daman and Diu",
|
||||
"IN-DL": "Delhi",
|
||||
"IN-GA": "Goa",
|
||||
"IN-GJ": "Gujarat",
|
||||
"IN-HP": "Himachal Pradesh",
|
||||
"IN-HR": "Haryana",
|
||||
"IN-JH": "Jharkhand",
|
||||
"IN-JK": "Jammu and Kashmir",
|
||||
"IN-KA": "Karnataka",
|
||||
"IN-KL": "Kerala",
|
||||
"IN-LA": "Ladakh",
|
||||
"IN-LD": "Lakshadweep",
|
||||
"IN-MH": "Maharashtra",
|
||||
"IN-ML": "Meghalaya",
|
||||
"IN-MN": "Manipur",
|
||||
"IN-MP": "Madhya Pradesh",
|
||||
"IN-MZ": "Mizoram",
|
||||
"IN-NL": "Nagaland",
|
||||
"IN-OR": "Odisha",
|
||||
"IN-PB": "Punjab",
|
||||
"IN-PY": "Puducherry",
|
||||
"IN-RJ": "Rajasthan",
|
||||
"IN-SK": "Sikkim",
|
||||
"IN-TG": "Telangana",
|
||||
"IN-TN": "Tamil Nadu",
|
||||
"IN-TR": "Tripura",
|
||||
"IN-UP": "Uttar Pradesh",
|
||||
"IN-UT": "Uttarakhand",
|
||||
"IN-WB": "West Bengal"
|
||||
}
|
||||
}
|
||||
42
library/i18n/locales/es/countryPosters.json
Normal file
42
library/i18n/locales/es/countryPosters.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"indiaStatesHeading": "India: estados y territorios de la unión",
|
||||
"indiaStateNoGroups": "Aquí no figura ningún pueblo no alcanzado.",
|
||||
"stateNames": {
|
||||
"IN-AN": "Islas Andamán y Nicobar",
|
||||
"IN-AP": "Andhra Pradesh",
|
||||
"IN-AR": "Arunachal Pradesh",
|
||||
"IN-AS": "Assam",
|
||||
"IN-BR": "Bihar",
|
||||
"IN-CH": "Chandigarh",
|
||||
"IN-CT": "Chhattisgarh",
|
||||
"IN-DH": "Dadra y Nagar Haveli y Damán y Diu",
|
||||
"IN-DL": "Delhi",
|
||||
"IN-GA": "Goa",
|
||||
"IN-GJ": "Guyarat",
|
||||
"IN-HP": "Himachal Pradesh",
|
||||
"IN-HR": "Haryana",
|
||||
"IN-JH": "Jharkhand",
|
||||
"IN-JK": "Jammu y Cachemira",
|
||||
"IN-KA": "Karnataka",
|
||||
"IN-KL": "Kerala",
|
||||
"IN-LA": "Ladakh",
|
||||
"IN-LD": "Laquedivas",
|
||||
"IN-MH": "Maharashtra",
|
||||
"IN-ML": "Meghalaya",
|
||||
"IN-MN": "Manipur",
|
||||
"IN-MP": "Madhya Pradesh",
|
||||
"IN-MZ": "Mizoram",
|
||||
"IN-NL": "Nagaland",
|
||||
"IN-OR": "Orissa",
|
||||
"IN-PB": "Punyab",
|
||||
"IN-PY": "Puducherry",
|
||||
"IN-RJ": "Rajastán",
|
||||
"IN-SK": "Sikkim",
|
||||
"IN-TG": "Telangana",
|
||||
"IN-TN": "Tamil Nadu",
|
||||
"IN-TR": "Tripura",
|
||||
"IN-UP": "Uttar Pradesh",
|
||||
"IN-UT": "Uttarakhand",
|
||||
"IN-WB": "Bengala Occidental"
|
||||
}
|
||||
}
|
||||
42
library/i18n/locales/fr/countryPosters.json
Normal file
42
library/i18n/locales/fr/countryPosters.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"indiaStatesHeading": "Inde — États et territoires de l’Union",
|
||||
"indiaStateNoGroups": "Aucun peuple non atteint n’est répertorié ici.",
|
||||
"stateNames": {
|
||||
"IN-AN": "Îles Andaman-et-Nicobar",
|
||||
"IN-AP": "Andhra Pradesh",
|
||||
"IN-AR": "Arunachal Pradesh",
|
||||
"IN-AS": "Assam",
|
||||
"IN-BR": "Bihar",
|
||||
"IN-CH": "Chandigarh",
|
||||
"IN-CT": "Chhattisgarh",
|
||||
"IN-DH": "Dadra et Nagar Haveli et Daman et Diu",
|
||||
"IN-DL": "Delhi",
|
||||
"IN-GA": "Goa",
|
||||
"IN-GJ": "Gujarat",
|
||||
"IN-HP": "Himachal Pradesh",
|
||||
"IN-HR": "Haryana",
|
||||
"IN-JH": "Jharkhand",
|
||||
"IN-JK": "Jammu-et-Cachemire",
|
||||
"IN-KA": "Karnataka",
|
||||
"IN-KL": "Kerala",
|
||||
"IN-LA": "Ladakh",
|
||||
"IN-LD": "Lakshadweep",
|
||||
"IN-MH": "Maharashtra",
|
||||
"IN-ML": "Meghalaya",
|
||||
"IN-MN": "Manipur",
|
||||
"IN-MP": "Madhya Pradesh",
|
||||
"IN-MZ": "Mizoram",
|
||||
"IN-NL": "Nagaland",
|
||||
"IN-OR": "Odisha",
|
||||
"IN-PB": "Pendjab",
|
||||
"IN-PY": "Territoire de Pondichéry",
|
||||
"IN-RJ": "Rajasthan",
|
||||
"IN-SK": "Sikkim",
|
||||
"IN-TG": "Telangana",
|
||||
"IN-TN": "Tamil Nadu",
|
||||
"IN-TR": "Tripura",
|
||||
"IN-UP": "Uttar Pradesh",
|
||||
"IN-UT": "Uttarakhand",
|
||||
"IN-WB": "Bengale-Occidental"
|
||||
}
|
||||
}
|
||||
42
library/i18n/locales/hi/countryPosters.json
Normal file
42
library/i18n/locales/hi/countryPosters.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"indiaStatesHeading": "भारत — राज्य और केंद्र शासित प्रदेश",
|
||||
"indiaStateNoGroups": "यहाँ कोई अपहुँचा जनसमूह सूचीबद्ध नहीं है।",
|
||||
"stateNames": {
|
||||
"IN-AN": "अण्डमान और निकोबार द्वीपसमूह",
|
||||
"IN-AP": "आन्ध्र प्रदेश",
|
||||
"IN-AR": "अरुणाचल प्रदेश",
|
||||
"IN-AS": "असम",
|
||||
"IN-BR": "बिहार",
|
||||
"IN-CH": "चण्डीगढ़",
|
||||
"IN-CT": "छत्तीसगढ़",
|
||||
"IN-DH": "दादरा और नगर हवेली और दमन और दीव",
|
||||
"IN-DL": "दिल्ली",
|
||||
"IN-GA": "गोवा",
|
||||
"IN-GJ": "गुजरात",
|
||||
"IN-HP": "हिमाचल प्रदेश",
|
||||
"IN-HR": "हरियाणा",
|
||||
"IN-JH": "झारखण्ड",
|
||||
"IN-JK": "जम्मू और कश्मीर",
|
||||
"IN-KA": "कर्नाटक",
|
||||
"IN-KL": "केरल",
|
||||
"IN-LA": "लद्दाख़",
|
||||
"IN-LD": "लक्षद्वीप",
|
||||
"IN-MH": "महाराष्ट्र",
|
||||
"IN-ML": "मेघालय",
|
||||
"IN-MN": "मणिपुर",
|
||||
"IN-MP": "मध्य प्रदेश",
|
||||
"IN-MZ": "मिज़ोरम",
|
||||
"IN-NL": "नागालैण्ड",
|
||||
"IN-OR": "ओडिशा",
|
||||
"IN-PB": "पंजाब",
|
||||
"IN-PY": "पुदुचेरी",
|
||||
"IN-RJ": "राजस्थान",
|
||||
"IN-SK": "सिक्किम",
|
||||
"IN-TG": "तेलंगाना",
|
||||
"IN-TN": "तमिल नाडु",
|
||||
"IN-TR": "त्रिपुरा",
|
||||
"IN-UP": "उत्तर प्रदेश",
|
||||
"IN-UT": "उत्तराखण्ड",
|
||||
"IN-WB": "पश्चिम बंगाल"
|
||||
}
|
||||
}
|
||||
42
library/i18n/locales/it/countryPosters.json
Normal file
42
library/i18n/locales/it/countryPosters.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"indiaStatesHeading": "India — stati e territori dell’Unione",
|
||||
"indiaStateNoGroups": "Qui non è elencato alcun popolo non raggiunto.",
|
||||
"stateNames": {
|
||||
"IN-AN": "isole Andamane e Nicobare",
|
||||
"IN-AP": "Andhra Pradesh",
|
||||
"IN-AR": "Arunachal Pradesh",
|
||||
"IN-AS": "Assam",
|
||||
"IN-BR": "Bihar",
|
||||
"IN-CH": "Chandigarh",
|
||||
"IN-CT": "Chhattisgarh",
|
||||
"IN-DH": "Dadra e Nagar Haveli e Daman e Diu",
|
||||
"IN-DL": "Delhi",
|
||||
"IN-GA": "Goa",
|
||||
"IN-GJ": "Gujarat",
|
||||
"IN-HP": "Himachal Pradesh",
|
||||
"IN-HR": "Haryana",
|
||||
"IN-JH": "Jharkhand",
|
||||
"IN-JK": "Jammu e Kashmir",
|
||||
"IN-KA": "Karnataka",
|
||||
"IN-KL": "Kerala",
|
||||
"IN-LA": "Ladakh",
|
||||
"IN-LD": "Laccadive",
|
||||
"IN-MH": "Maharashtra",
|
||||
"IN-ML": "Meghalaya",
|
||||
"IN-MN": "Manipur",
|
||||
"IN-MP": "Madhya Pradesh",
|
||||
"IN-MZ": "Mizoram",
|
||||
"IN-NL": "Nagaland",
|
||||
"IN-OR": "Orissa",
|
||||
"IN-PB": "Punjab",
|
||||
"IN-PY": "Pondicherry",
|
||||
"IN-RJ": "Rajasthan",
|
||||
"IN-SK": "Sikkim",
|
||||
"IN-TG": "Telangana",
|
||||
"IN-TN": "Tamil Nadu",
|
||||
"IN-TR": "Tripura",
|
||||
"IN-UP": "Uttar Pradesh",
|
||||
"IN-UT": "Uttarakhand",
|
||||
"IN-WB": "Bengala Occidentale"
|
||||
}
|
||||
}
|
||||
42
library/i18n/locales/pt/countryPosters.json
Normal file
42
library/i18n/locales/pt/countryPosters.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"indiaStatesHeading": "Índia — estados e territórios da união",
|
||||
"indiaStateNoGroups": "Nenhum povo não alcançado está listado aqui.",
|
||||
"stateNames": {
|
||||
"IN-AN": "Andamão e Nicobar",
|
||||
"IN-AP": "Andra Pradexe",
|
||||
"IN-AR": "Arunachal Pradesh",
|
||||
"IN-AS": "Assam",
|
||||
"IN-BR": "Bihar",
|
||||
"IN-CH": "Chandigarh",
|
||||
"IN-CT": "Chhattisgarh",
|
||||
"IN-DH": "Dadrá e Nagar Aveli e Damão e Diu",
|
||||
"IN-DL": "Delhi",
|
||||
"IN-GA": "Goa",
|
||||
"IN-GJ": "Gujarate",
|
||||
"IN-HP": "Himachal Pradesh",
|
||||
"IN-HR": "Haryana",
|
||||
"IN-JH": "Jharkhand",
|
||||
"IN-JK": "Jammu e Caxemira",
|
||||
"IN-KA": "Carnataca",
|
||||
"IN-KL": "Kerala",
|
||||
"IN-LA": "Ladaque",
|
||||
"IN-LD": "Laquedivas",
|
||||
"IN-MH": "Maarastra",
|
||||
"IN-ML": "Meghalaya",
|
||||
"IN-MN": "Manipur",
|
||||
"IN-MP": "Madhya Pradesh",
|
||||
"IN-MZ": "Mizoram",
|
||||
"IN-NL": "Nagaland",
|
||||
"IN-OR": "Orissa",
|
||||
"IN-PB": "Punjab",
|
||||
"IN-PY": "Puducherry",
|
||||
"IN-RJ": "Rajastão",
|
||||
"IN-SK": "Siquim",
|
||||
"IN-TG": "Telangana",
|
||||
"IN-TN": "Tamil Nadu",
|
||||
"IN-TR": "Tripura",
|
||||
"IN-UP": "Utar Pradexe",
|
||||
"IN-UT": "Utaracanda",
|
||||
"IN-WB": "Bengala Ocidental"
|
||||
}
|
||||
}
|
||||
42
library/i18n/locales/ro/countryPosters.json
Normal file
42
library/i18n/locales/ro/countryPosters.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"indiaStatesHeading": "India — state și teritorii ale Uniunii",
|
||||
"indiaStateNoGroups": "Aici nu este listat niciun popor neatins.",
|
||||
"stateNames": {
|
||||
"IN-AN": "Insulele Andaman și Nicobar",
|
||||
"IN-AP": "Andhra Pradesh",
|
||||
"IN-AR": "Arunachal Pradesh",
|
||||
"IN-AS": "Assam",
|
||||
"IN-BR": "Bihar",
|
||||
"IN-CH": "Teritoriul Unional Chandigarh",
|
||||
"IN-CT": "Chhattisgarh",
|
||||
"IN-DH": "Dadra și Nagar Haveli și Daman și Diu",
|
||||
"IN-DL": "Delhi",
|
||||
"IN-GA": "Goa",
|
||||
"IN-GJ": "Gujarat",
|
||||
"IN-HP": "Himachal Pradesh",
|
||||
"IN-HR": "Haryana",
|
||||
"IN-JH": "Jharkhand",
|
||||
"IN-JK": "Jammu și Cașmir",
|
||||
"IN-KA": "Karnataka",
|
||||
"IN-KL": "Kerala",
|
||||
"IN-LA": "Ladakh",
|
||||
"IN-LD": "Lakshadweep",
|
||||
"IN-MH": "Maharashtra",
|
||||
"IN-ML": "Meghalaya",
|
||||
"IN-MN": "Manipur",
|
||||
"IN-MP": "Madhya Pradesh",
|
||||
"IN-MZ": "Mizoram",
|
||||
"IN-NL": "Nagaland",
|
||||
"IN-OR": "Odisha",
|
||||
"IN-PB": "Punjab",
|
||||
"IN-PY": "Puducherry",
|
||||
"IN-RJ": "Rajasthan",
|
||||
"IN-SK": "Sikkim",
|
||||
"IN-TG": "Telangana",
|
||||
"IN-TN": "Tamil Nadu",
|
||||
"IN-TR": "Tripura",
|
||||
"IN-UP": "Uttar Pradesh",
|
||||
"IN-UT": "Uttarakhand",
|
||||
"IN-WB": "Bengalul de Vest"
|
||||
}
|
||||
}
|
||||
42
library/i18n/locales/ru/countryPosters.json
Normal file
42
library/i18n/locales/ru/countryPosters.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"indiaStatesHeading": "Индия — штаты и союзные территории",
|
||||
"indiaStateNoGroups": "Здесь не указано ни одного недостигнутого народа.",
|
||||
"stateNames": {
|
||||
"IN-AN": "Андаманские и Никобарские острова",
|
||||
"IN-AP": "Андхра-Прадеш",
|
||||
"IN-AR": "Аруначал-Прадеш",
|
||||
"IN-AS": "Ассам",
|
||||
"IN-BR": "Бихар",
|
||||
"IN-CH": "Чандигарх",
|
||||
"IN-CT": "Чхаттисгарх",
|
||||
"IN-DH": "Дадра и Нагар-Хавели и Даман и Диу",
|
||||
"IN-DL": "Дели",
|
||||
"IN-GA": "Гоа",
|
||||
"IN-GJ": "Гуджарат",
|
||||
"IN-HP": "Химачал-Прадеш",
|
||||
"IN-HR": "Харьяна",
|
||||
"IN-JH": "Джаркханд",
|
||||
"IN-JK": "Джамму и Кашмир",
|
||||
"IN-KA": "Карнатака",
|
||||
"IN-KL": "Керала",
|
||||
"IN-LA": "Ладакх",
|
||||
"IN-LD": "Лакшадвип",
|
||||
"IN-MH": "Махараштра",
|
||||
"IN-ML": "Мегхалая",
|
||||
"IN-MN": "Манипур",
|
||||
"IN-MP": "Мадхья-Прадеш",
|
||||
"IN-MZ": "Мизорам",
|
||||
"IN-NL": "Нагаленд",
|
||||
"IN-OR": "Одиша",
|
||||
"IN-PB": "Пенджаб",
|
||||
"IN-PY": "Пондичерри",
|
||||
"IN-RJ": "Раджастхан",
|
||||
"IN-SK": "Сикким",
|
||||
"IN-TG": "Телингана",
|
||||
"IN-TN": "Тамилнад",
|
||||
"IN-TR": "Трипура",
|
||||
"IN-UP": "Уттар-Прадеш",
|
||||
"IN-UT": "Уттаракханд",
|
||||
"IN-WB": "Западная Бенгалия"
|
||||
}
|
||||
}
|
||||
42
library/i18n/locales/zh/countryPosters.json
Normal file
42
library/i18n/locales/zh/countryPosters.json
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"indiaStatesHeading": "印度——邦与中央直辖区",
|
||||
"indiaStateNoGroups": "此处未列出任何未得之民族群。",
|
||||
"stateNames": {
|
||||
"IN-AN": "安达曼-尼科巴群岛",
|
||||
"IN-AP": "安得拉邦",
|
||||
"IN-AR": "阿鲁纳恰尔邦",
|
||||
"IN-AS": "阿萨姆邦",
|
||||
"IN-BR": "比哈尔邦",
|
||||
"IN-CH": "昌迪加尔",
|
||||
"IN-CT": "切蒂斯格尔邦",
|
||||
"IN-DH": "达德拉-纳加尔哈维利和达曼-第乌",
|
||||
"IN-DL": "德里",
|
||||
"IN-GA": "果阿邦",
|
||||
"IN-GJ": "古吉拉特邦",
|
||||
"IN-HP": "喜马偕尔邦",
|
||||
"IN-HR": "哈里亚纳邦",
|
||||
"IN-JH": "贾坎德邦",
|
||||
"IN-JK": "查谟和克什米尔",
|
||||
"IN-KA": "卡纳塔克邦",
|
||||
"IN-KL": "喀拉拉邦",
|
||||
"IN-LA": "拉达克",
|
||||
"IN-LD": "拉克沙群岛",
|
||||
"IN-MH": "马哈拉施特拉邦",
|
||||
"IN-ML": "梅加拉亚邦",
|
||||
"IN-MN": "曼尼普尔邦",
|
||||
"IN-MP": "中央邦",
|
||||
"IN-MZ": "米佐拉姆邦",
|
||||
"IN-NL": "那加兰邦",
|
||||
"IN-OR": "奥迪沙邦",
|
||||
"IN-PB": "旁遮普邦",
|
||||
"IN-PY": "本地治里",
|
||||
"IN-RJ": "拉贾斯坦邦",
|
||||
"IN-SK": "锡金邦",
|
||||
"IN-TG": "特伦甘纳邦",
|
||||
"IN-TN": "泰米尔纳德邦",
|
||||
"IN-TR": "特里普拉邦",
|
||||
"IN-UP": "北方邦",
|
||||
"IN-UT": "北阿坎德邦",
|
||||
"IN-WB": "西孟加拉邦"
|
||||
}
|
||||
}
|
||||
|
|
@ -58,8 +58,9 @@
|
|||
* keeps the renderer's own default rather than a fabricated position.
|
||||
*
|
||||
* A null sheet is a tier too: India (sheet null, too many groups for any paper size) is its own
|
||||
* 1-of-1 series. Skipping it printed "map 1 of 0" on India's packet page 1 from the Regions tab
|
||||
* while the wall's own copy printed "map 1 of 1" -- the two paths disagreeing again.
|
||||
* 1-of-1 series. Skipping it gave India's sheet "map 1 of 0" on the Regions path and "map 1 of 1" on the
|
||||
* wall's old copy. Neither was visible then (the map-only packet page prints no series line, and India's
|
||||
* poster is gated), but any India sheet with a header would have shown it.
|
||||
* Every null-sheet country shares that ONE oversized series: a second would make India "map 1 of 2".
|
||||
* check:country-sheet-one-path fails when that happens, so it is decided, not drifted into.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -143,5 +143,49 @@
|
|||
"emit-countries.mjs), so the SHAPE was replaced: not 'split the set' but 'ask which one,",
|
||||
"then give them that poster'. The numbers are gone from the headings for the same reason."
|
||||
]
|
||||
},
|
||||
"indiaState": {
|
||||
"label": "India states and union territories — one poster for each of the 34 with people groups listed",
|
||||
"dataDir": "plugin-designs/my-upg-100-list/data",
|
||||
"countryMode": true,
|
||||
"funnel": false,
|
||||
"poster": {
|
||||
"kicker": "Unreached People Groups",
|
||||
"titleQualifier": "Pray for",
|
||||
"seriesName": "India State Maps",
|
||||
"sideBoxHeading": "What is a State Prayer Map?",
|
||||
"sideBoxBody": "This map is one of a {approxLists}-map set called the {seriesName} — one sheet for each state and union territory of India, naming the unreached people groups who live there so a reader can pray for them by name."
|
||||
},
|
||||
"_poster_why": [
|
||||
"A state poster is a country poster one level down: the Country Posters wall prints one for each of India's",
|
||||
"states and union territories that has people groups listed (34 of 36; data/people-groups/india-states.json).",
|
||||
"The country series' text cannot be reused: it would call Gujarāt's poster one of the ~170 'Country Maps',",
|
||||
"one sheet for each country. The term is 'state and union territory', never 'province'. Approved as the",
|
||||
"default wording 2026-09-30 and shown as editable; a change is a strings-only follow-up."
|
||||
],
|
||||
"strings": {
|
||||
"map_page_kicker": "PRAY FOR THIS STATE",
|
||||
"map_page_sub": "A prayer list of {count} unreached people groups who live here",
|
||||
"points_intro": "One hour with God, five minutes each. Pray these twelve for the peoples of this state.",
|
||||
"christian_lede": "This is the same grid — one human life in weeks. Now each box is a share of the people in this state. The shaded boxes are the evangelical share of their population; the rest are waiting."
|
||||
},
|
||||
"_strings_why": [
|
||||
"The country series' four keys with 'country' read as 'state'. Used by the Node generator; the browser",
|
||||
"packet reads the localized packet-strings.json, which is series-invariant."
|
||||
],
|
||||
"networks": [
|
||||
[
|
||||
"1 · Disciples.",
|
||||
"Ask a disciple which state God has put on their heart, then give them that state's poster to print and prepare."
|
||||
],
|
||||
[
|
||||
"2 · Churches.",
|
||||
"List the churches you know and ask each the same question, so every church has a poster to pray over together."
|
||||
],
|
||||
[
|
||||
"3 · Indigenous leaders.",
|
||||
"Ask which leaders you know who live in one of these states, and give them their own state's map to pray for it specifically."
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -449,7 +449,7 @@
|
|||
"check:composite-token-url": "a composite embedded BARE (no outer ProfileLoader) must honour a tokenUrl-only config: it reads tokenUrl, forwards it, and asks the token service only when no token is given. The expectation is derived from ProfileLoader, not restated. Headless; in gates:pre.",
|
||||
"check:composites-mount": "every capability-4/5 composite's OWN setup() and mounted hooks run without throwing, in en and ar, headless (no browser): vite loads each composite as its bundle entry defines it (configureApp, and ProfileLoader's provides where the entry mounts ProfileLoader), and @vue/runtime-core mounts it on a no-op renderer. Exists because 08f89f55 shipped two composites calling getRuntimeLocale/detectLocale/RTL_LOCALES with no import: the build was green, setup threw a ReferenceError in every locale, and Big Picture and Simple Research rendered nothing. Proves setup runs; cannot prove the children render.",
|
||||
"check:country-names": "every ISO 3166-1 alpha-3 our data uses must resolve to a localized country name. Guards the JOIN, not Intl: library/data/iso3-to-alpha2.json drifting from library/data/countries.json makes a country render as a bare code, which still looks like a value. Checks en/ar/zh (ar is our only RTL locale; zh makes an untranslated Latin code unmistakable) and asserts BOTH directions of drift. Carries two negative arms — an unassigned code and a malformed one — and FAILS if either resolves, because a coverage count cannot otherwise tell a hit from a lookup that returns its input.",
|
||||
"check:country-sheet-one-path": "a country sheet is drawn through ONE path, @map/packet/countrySheet.js, so one country cannot print two posters. Parses (comments cannot count) every plugin source naming generateCountrySheet/makeCountrySheet: a direct generateCountrySheet in code FAILS, every makeCountrySheet call must pass `countries` (the series list), and both known callers (the Country Posters wall, regions-list) must be found. Also runs seriesFor over the real country-posters.json with named answers: India 1 of 1, every 3x3 country N of the 3x3 tier with no repeat, a code absent from the data no series.",
|
||||
"check:country-sheet-one-path": "a country sheet, and an Indian state or union territory sheet, each draw through ONE path (@map/packet/countrySheet.js; country-posters/src/indiaStateSheet.js), so one place cannot print two posters. Parses (comments cannot count) plugin sources: a direct generateCountrySheet FAILS anywhere, a direct generateSheet FAILS inside country-posters outside the state helper, every makeCountrySheet call must pass `countries`, and the known callers must be found. Named data answers: seriesFor over country-posters.json (India 1 of 1, each 3x3 country N of 173, an absent code no series, exactly one no-sheet country); over the served india-states.json + gb-IND-ADM1.geojson: 36 divisions, the 34 with groups numbered 1..34 each with an outline, the rest named, the shared ADM1 never mutated.",
|
||||
"check:country-sheet-one-path:self-test": "plants each defect check:country-sheet-one-path guards (a direct generateCountrySheet call, makeCountrySheet without countries, the old guard that skipped India, a position given to an absent code) and requires each to FAIL; a comment naming generateCountrySheet must NOT.",
|
||||
"check:coverage-consistent": "the gospel-resources surfaces must agree: no resource on an ISO RESERVED code (und/mul/mis/zxx), every resourced code NAMED, table reach recounted off the pins, no invented URL, tiers reconcile. Static, sub-second. Self-test: `bun run check:coverage-consistent:poison`, all 12 clauses (A-L) must fail. Clause L is the one that is not about consistency: it fails when the link-verification record goes stale, because 'link checked' is a claim about the present tense. Written after the same drift defect was found by hand three times.",
|
||||
"check:csv-shape": "the export must OPEN as a table: header on line 0, every row matching. Run the `check:csv-shape:self-test` script for the NEGATIVE test (forces the provenance comment back to line 0 IN MEMORY and requires this gate to REJECT it) — the header used to claim 'NEGATIVE-TESTED' with no way to re-run it.",
|
||||
|
|
|
|||
|
|
@ -35,20 +35,23 @@
|
|||
* The shared wall always sorts sections AND cards by descending count, so the
|
||||
* dense countries lead and the long tail sinks. There is no floor.
|
||||
*/
|
||||
import { computed, inject, ref } from 'vue'
|
||||
import { computed, inject, onBeforeUnmount, ref, watch } from 'vue'
|
||||
import PosterWall from '@map/components/poster/PosterWall.vue'
|
||||
import { useOptionalT, useOptionalI18n } from '@map/composables/useOptionalT.js'
|
||||
// A country's name in the reader's language (Intl, joined from our ISO-3 codes).
|
||||
import { countryName } from '@map/utils/countryName.js'
|
||||
import { createPeopleGroupRecords } from '@map/lists/peopleGroupRecords.js'
|
||||
import { getApiBaseUrl } from '@map/api/apiBaseUrl.js'
|
||||
import { getRuntimeLocale, detectLocale } from '@map/i18n/index.js'
|
||||
import { getRuntimeLocale, detectLocale, tStatic } from '@map/i18n/index.js'
|
||||
// MOVED to the shared library 2026-09-24: the regions-dashboard poster dropdown is a second
|
||||
// consumer of this same dataset, and RULE 2 puts a dataset with a named second consumer in
|
||||
// library/. Generated by library/scripts/posters/emit-countries.mjs, which now writes there.
|
||||
import data from '@map/data/country-posters.json'
|
||||
import { resolveAssetsBase } from '@map/utils/embedBase.js'
|
||||
import { makeCountrySheet } from '@map/packet/countrySheet.js'
|
||||
// India's states and union territories: the shared table (fetched when the wall shows them) and their sheet.
|
||||
import { loadIndiaStates } from '@map/data/indiaStates.js'
|
||||
import { makeIndiaStateSheet, statesWithPosters } from './src/indiaStateSheet.js'
|
||||
// The regions catalogue holds the not-yet sentence a host's country gets when no poster exists (all eleven locales);
|
||||
// its keys are English sentences with dots, so it is read by literal key, the way regions-list.vue reads it.
|
||||
import { useRegionsI18n } from '@map/i18n/useRegionsI18n.js'
|
||||
|
|
@ -105,15 +108,48 @@ const hostColumns = computed(() => Number(profileConfig.value?.columns) || 2)
|
|||
let tRegions = (key) => key
|
||||
try { tRegions = useRegionsI18n().t } catch (_) { /* a host with no i18n reads the English sentence, like every string here */ }
|
||||
|
||||
// ★ INDIA'S STATES AND UNION TERRITORIES, one poster each, like a country one level down. The table is
|
||||
// fetched (and sha-checked) only when the unfiltered wall is shown; a host's filtered wall never asks for it.
|
||||
// A failure hides the section rather than showing cards that cannot print, and is retried a bounded number of
|
||||
// times (the loader drops a failed fetch from its cache, so a retry really fetches again). States with no people
|
||||
// groups listed (Nagaland, Mizoram) are NAMED with a card that says so, never silently left out.
|
||||
const indiaStates = ref(null)
|
||||
const STATE_RETRY_MS = [5000, 20000, 60000]
|
||||
let stateRetry = null
|
||||
function loadStates(attempt = 0) {
|
||||
loadIndiaStates(assetsBase.value).then((table) => { indiaStates.value = table.states }).catch(() => {
|
||||
if (attempt < STATE_RETRY_MS.length && !filtered.value) stateRetry = setTimeout(() => loadStates(attempt + 1), STATE_RETRY_MS[attempt])
|
||||
})
|
||||
}
|
||||
watch(filtered, (f) => {
|
||||
if (f || indiaStates.value || stateRetry) return
|
||||
loadStates()
|
||||
}, { immediate: true })
|
||||
onBeforeUnmount(() => { clearTimeout(stateRetry) })
|
||||
const INDIA_REGION = (byIso3.get('IND') || {}).region || 'Asia'
|
||||
const stateItems = computed(() => {
|
||||
const all = indiaStates.value
|
||||
if (filtered.value || !all) return []
|
||||
const withGroups = statesWithPosters(all).map((st) => ({
|
||||
id: st.id, iso: st.iso, label: st.name, count: st.count, sheet: '3x3', region: INDIA_REGION, indiaState: true, slugs: st.slugs,
|
||||
}))
|
||||
const none = all.filter((st) => !st.count).map((st) => ({ id: st.id, iso: st.iso, label: st.name, count: 0, indiaState: true, noGroups: true, slugs: [] }))
|
||||
return [...withGroups, ...none]
|
||||
})
|
||||
|
||||
const items = computed(() => {
|
||||
const l = wallLocale && (wallLocale.value ?? wallLocale)
|
||||
const locale = (typeof l === 'string' && l) || getRuntimeLocale() || detectLocale() || 'en'
|
||||
const countries = selected.value
|
||||
if (/^en\b/i.test(locale)) return countries
|
||||
return countries.map((c) => {
|
||||
// A state's card is named in the reader's language from the catalogue (countryPosters.stateNames, generated from
|
||||
// Wikidata by scripts/emit-india-state-names.mjs); its printed sheet stays English, like a country's.
|
||||
const states = /^en\b/i.test(locale) ? stateItems.value
|
||||
: stateItems.value.map((st) => ({ ...st, label: tStatic(`countryPosters.stateNames.${st.iso}`, locale) || st.label }))
|
||||
if (/^en\b/i.test(locale)) return [...countries, ...states]
|
||||
return [...countries.map((c) => {
|
||||
const name = countryName(c.iso3, locale)
|
||||
return name && name !== c.iso3 ? { ...c, label: name } : c
|
||||
})
|
||||
}), ...states]
|
||||
})
|
||||
|
||||
// ★ EVERY COUNTRY PRINTS ITS PRAYER RESOURCES — the list, the card deck and the packet — through the
|
||||
|
|
@ -125,7 +161,9 @@ const recordsFor = createPeopleGroupRecords({
|
|||
lang: () => getRuntimeLocale() || detectLocale(),
|
||||
profileConfig,
|
||||
})
|
||||
const countryRecords = (c) => recordsFor({ type: 'country', values: [c.iso3] })
|
||||
const countryRecords = (c) => (c.indiaState
|
||||
? (c.slugs.length ? recordsFor({ type: 'pg_slugs', values: c.slugs }) : Promise.resolve([]))
|
||||
: recordsFor({ type: 'country', values: [c.iso3] }))
|
||||
// ★ GENERATED AT THE CLICK, through the ONE shared path (@map/packet/countrySheet.js, which the Regions
|
||||
// tab uses too): the country's sheet is drawn in the browser from its people groups and the world
|
||||
// boundaries, for the poster print (letter, vector) and the packet's page 1. No stored sheet exists for a
|
||||
|
|
@ -139,7 +177,12 @@ const sheetForRecord = makeCountrySheet({
|
|||
assetsBase: () => assetsBase.value,
|
||||
countries: data.countries || [],
|
||||
})
|
||||
const countrySheet = (card, format) => sheetForRecord(byId.get(card.id) || card, format)
|
||||
const countryOnlySheet = (card, format) => sheetForRecord(byId.get(card.id) || card, format)
|
||||
// A state's sheet through its own ONE path (src/indiaStateSheet.js), in India's hue and the indiaState series.
|
||||
const stateSheet = makeIndiaStateSheet({
|
||||
records: recordsFor, assetsBase: () => assetsBase.value, states: () => indiaStates.value, region: INDIA_REGION,
|
||||
})
|
||||
const countrySheet = (card, format) => (card.indiaState ? stateSheet(card, format) : countryOnlySheet(card, format))
|
||||
// The name table the generated sheet was labelled from, so a packet in another language relabels page 1's
|
||||
// country names (the same seam the UPG-100 wall passes).
|
||||
const iso3Names = () => import('@map/data/iso3-name.js').then((m) => m.ISO3_NAME)
|
||||
|
|
@ -161,6 +204,7 @@ async function countryMap(c) {
|
|||
const t = useOptionalT('')
|
||||
const regionSlug = (label) => String(label).toLowerCase().replace(/\s+/g, '_')
|
||||
const groupBy = (c) => {
|
||||
if (c.indiaState) return t('countryPosters.indiaStatesHeading')
|
||||
if (!c.region || c.region === 'Other') return t('dashboard.other')
|
||||
const key = `dashboard.wagfRegion.${regionSlug(c.region)}`
|
||||
const v = t(key)
|
||||
|
|
@ -178,7 +222,7 @@ const collapsedBy = (sec) => sec.total < 60
|
|||
// unsupportedReason is an English note (and a flag), not display text. The card already states the
|
||||
// count above it, so the sentence needs no number and no plural agreement.
|
||||
// A requested country with no poster at all says so in regions-list.vue's words (the regions catalogue).
|
||||
const unsupported = (c) => (c.noPoster ? tRegions('No poster has been generated for this country yet.') : c.unsupportedReason ? t('messages.countryPosterTooMany') : null)
|
||||
const unsupported = (c) => (c.noGroups ? t('countryPosters.indiaStateNoGroups') : c.noPoster ? tRegions('No poster has been generated for this country yet.') : c.unsupportedReason ? t('messages.countryPosterTooMany') : null)
|
||||
|
||||
// ★ THROUGH THE CATALOGUE, like the UPG-100 wall: the heading, the tag, the intro and the counts read
|
||||
// in the page's language. The intro keeps its one emphasis as three plain keys (A, the emphasis, B)
|
||||
|
|
|
|||
|
|
@ -183,7 +183,10 @@ function onShowOnMap(e) {
|
|||
// The attribute carries the country ID, not its geometry: countries-map resolves the id against
|
||||
// the same library/data/country-posters.json the wall renders from, so the bbox has exactly one
|
||||
// source of truth rather than being copied through an attribute.
|
||||
const countryId = d.listId ?? null
|
||||
// An Indian state's card (the state table's ids are 'india-<code>') shows India: this map draws countries,
|
||||
// not states, so framing the country is the honest answer rather than a world view with nothing selected.
|
||||
const listId = d.listId ?? null
|
||||
const countryId = typeof listId === 'string' && listId.startsWith('india-') ? 'country-ind' : listId
|
||||
const el = paneEls.map
|
||||
if (!el) return
|
||||
if (countryId) el.setAttribute('select-country', String(countryId))
|
||||
|
|
|
|||
44
plugin-designs/country-posters/data/india-state-names.meta.json
vendored
Normal file
44
plugin-designs/country-posters/data/india-state-names.meta.json
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"what": "India's 36 states and union territories, named in the 11 UI locales, for the Country Posters wall's card labels.",
|
||||
"source": "Wikidata (https://www.wikidata.org): labels of the items whose ISO 3166-2 code (P300) matches data/people-groups/india-states.json; dissolved items (P576) excluded.",
|
||||
"licence": "CC0 1.0: Wikidata structured data is released under CC0 (https://creativecommons.org/publicdomain/zero/1.0/).",
|
||||
"english": "the state table's own name (the English wall is unchanged); Wikidata's English is only a cross-check",
|
||||
"retrieved": "2026-09-30",
|
||||
"regenerate": "node plugin-designs/country-posters/scripts/emit-india-state-names.mjs",
|
||||
"query": "SELECT ?code ?lang ?label WHERE {\n VALUES ?code { \"IN-AN\" \"IN-AP\" \"IN-AR\" \"IN-AS\" \"IN-BR\" \"IN-CG\" \"IN-CH\" \"IN-DH\" \"IN-DL\" \"IN-GA\" \"IN-GJ\" \"IN-HP\" \"IN-HR\" \"IN-JH\" \"IN-JK\" \"IN-KA\" \"IN-KL\" \"IN-LA\" \"IN-LD\" \"IN-MH\" \"IN-ML\" \"IN-MN\" \"IN-MP\" \"IN-MZ\" \"IN-NL\" \"IN-OD\" \"IN-PB\" \"IN-PY\" \"IN-RJ\" \"IN-SK\" \"IN-TN\" \"IN-TR\" \"IN-TS\" \"IN-UK\" \"IN-UP\" \"IN-WB\" }\n ?item wdt:P300 ?code .\n FILTER NOT EXISTS { ?item wdt:P576 ?dissolved }\n ?item rdfs:label ?label . BIND(LANG(?label) AS ?lang)\n FILTER(?lang IN (\"en\", \"es\", \"fr\", \"pt\", \"de\", \"it\", \"ro\", \"ru\", \"ar\", \"hi\", \"zh-hans\", \"zh-cn\", \"zh\"))\n}",
|
||||
"codeAliases": {
|
||||
"IN-CT": "IN-CG",
|
||||
"IN-OR": "IN-OD",
|
||||
"IN-TG": "IN-TS",
|
||||
"IN-UT": "IN-UK"
|
||||
},
|
||||
"reviewedOverrides": [
|
||||
"IN-DH zh: \"达德拉-纳加尔哈维利和达曼-第乌\" (Wikidata has only a Traditional-script zh label; converted character by character to the Simplified catalogue script)",
|
||||
"IN-OR ar: \"أديشة\" (the label is the pre-2011 name Orissa (أريسة); ar.wikipedia's title for the item is أديشة (Odisha))"
|
||||
],
|
||||
"gaps": [],
|
||||
"ambiguous": [],
|
||||
"fallbacks": [
|
||||
"IN-CH es: \"Chandigarh\" from the same-named item (1 of 1)",
|
||||
"IN-CH fr: \"Chandigarh\" from the same-named item (1 of 1)",
|
||||
"IN-CH pt: \"Chandigarh\" from the same-named item (1 of 1)",
|
||||
"IN-CH de: \"Chandigarh\" from the same-named item (1 of 1)",
|
||||
"IN-CH it: \"Chandigarh\" from the same-named item (1 of 1)",
|
||||
"IN-CH ar: \"شَنديغار\" from the same-named item (1 of 1)",
|
||||
"IN-CH hi: \"चण्डीगढ़\" from the same-named item (1 of 1)",
|
||||
"IN-CH zh: \"昌迪加尔\" from the same-named item (1 of 1)",
|
||||
"IN-DL es: \"Delhi\" from the same-named item (preferred)",
|
||||
"IN-DL fr: \"Delhi\" from the same-named item (preferred)",
|
||||
"IN-DL pt: \"Delhi\" from the same-named item (preferred)",
|
||||
"IN-DL de: \"Delhi\" from the same-named item (preferred)",
|
||||
"IN-DL it: \"Delhi\" from the same-named item (preferred)",
|
||||
"IN-DL ro: \"Delhi\" from the same-named item (preferred)",
|
||||
"IN-DL ru: \"Дели\" from the same-named item (preferred)",
|
||||
"IN-DL ar: \"دلهي\" from the same-named item (preferred)",
|
||||
"IN-DL hi: \"दिल्ली\" from the same-named item (preferred)",
|
||||
"IN-DL zh: \"德里\" from the same-named item (preferred)"
|
||||
],
|
||||
"englishMismatches": [
|
||||
"IN-DL: table \"Delhi\" vs Wikidata \"National Capital Territory of Delhi\"; same-named labels preferred"
|
||||
]
|
||||
}
|
||||
|
|
@ -25,6 +25,10 @@ import { parse as parseSfc, babelParse } from '@vue/compiler-sfc'
|
|||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
|
||||
const PD = join(ROOT, 'plugin-designs')
|
||||
const KNOWN_CALLERS = ['plugin-designs/country-posters/country-poster-wall.vue', 'plugin-designs/regions-dashboard/regions-list.vue']
|
||||
// India's states and union territories draw through their own ONE path; inside this plugin only that helper may
|
||||
// call the generator directly.
|
||||
const STATE_HELPER = 'plugin-designs/country-posters/src/indiaStateSheet.js'
|
||||
const STATE_CALLER = 'plugin-designs/country-posters/country-poster-wall.vue'
|
||||
|
||||
function scriptsOf(file, src) {
|
||||
if (!file.endsWith('.vue')) return [src]
|
||||
|
|
@ -78,9 +82,11 @@ function listSources(dir, out = []) {
|
|||
export function armA(files, callers = []) {
|
||||
const fails = []
|
||||
for (const [rel, src] of Object.entries(files)) {
|
||||
if (!/generateCountrySheet|makeCountrySheet/.test(src)) continue // a text prefilter; the parse decides
|
||||
if (!/generateCountrySheet|makeCountrySheet|generateSheet|makeIndiaStateSheet/.test(src)) continue // a text prefilter; the parse decides
|
||||
const { names, makeCalls } = codeFacts(rel, src)
|
||||
if (names.has('generateCountrySheet')) fails.push(`${rel}: draws a country sheet directly (generateCountrySheet in code); use makeCountrySheet from @map/packet/countrySheet.js`)
|
||||
if (rel.startsWith('plugin-designs/country-posters/') && rel !== STATE_HELPER && names.has('generateSheet')) fails.push(`${rel}: calls generateSheet directly; a state sheet goes through makeIndiaStateSheet (${STATE_HELPER})`)
|
||||
if (rel === STATE_CALLER && names.has('makeIndiaStateSheet')) callers.push(`${rel}#state`)
|
||||
if (makeCalls.length) callers.push(rel)
|
||||
makeCalls.forEach((c, i) => { if (!c.hasCountries) fails.push(`${rel}: makeCountrySheet call #${i + 1} passes no \`countries\`, so its "map N of M" is not the wall's`) })
|
||||
}
|
||||
|
|
@ -111,6 +117,44 @@ export function armB(seriesFor, countries) {
|
|||
return fails
|
||||
}
|
||||
|
||||
// ARM C: the state posters over the SERVED table and the renderer's own ADM1 file (named expectations).
|
||||
export function armC(H, states, adm1Ind) {
|
||||
const fails = []
|
||||
const before = JSON.stringify((adm1Ind.features || []).map((f) => f.properties && f.properties.listId)) // BEFORE any call
|
||||
const withGroups = H.statesWithPosters(states)
|
||||
const zero = (states || []).filter((st) => !st.count).map((st) => st.name)
|
||||
if ((states || []).length !== 36) fails.push(`the state table has ${(states || []).length} divisions, expected 36 (28 states + 8 union territories)`)
|
||||
if (withGroups.length + zero.length !== (states || []).length) fails.push('every division is either postered or named as having none')
|
||||
const idx = withGroups.map((st) => (H.stateSeries(states, st) || {}).index)
|
||||
if (new Set(idx).size !== withGroups.length || Math.min(...idx) !== 1 || Math.max(...idx) !== withGroups.length) fails.push(`state series indexes are not 1..${withGroups.length}, unique: ${idx.join(',')}`)
|
||||
const noShape = withGroups.filter((st) => !H.stateGeometry(H.stateZone(st, '#000'), adm1Ind, st.shapeName)).map((st) => st.name)
|
||||
if (noShape.length) fails.push(`states with people groups but no outline in gb-IND-ADM1.geojson (their poster would not draw): ${noShape.join(', ')}`)
|
||||
withGroups.forEach((st) => H.stateGeometry(H.stateZone(st, '#000'), adm1Ind, st.shapeName))
|
||||
if (JSON.stringify((adm1Ind.features || []).map((f) => f.properties && f.properties.listId)) !== before) fails.push('stateGeometry mutated the shared ADM1 collection')
|
||||
return fails
|
||||
}
|
||||
|
||||
// ARM D: the state cards' localized names cover EXACTLY the table's divisions in every locale (a runtime-built key,
|
||||
// `countryPosters.stateNames.${iso}`, that the i18n call-site gates cannot see), English is the table's own name, and
|
||||
// the generated names carry their source licence.
|
||||
const NAME_LOCALES = ['en', 'es', 'fr', 'pt', 'de', 'it', 'ro', 'ru', 'ar', 'hi', 'zh']
|
||||
export function armD(states, areas, meta) {
|
||||
const fails = []
|
||||
const want = new Set((states || []).map((st) => st.iso))
|
||||
for (const l of NAME_LOCALES) {
|
||||
const names = (areas[l] && areas[l].stateNames) || {}
|
||||
const have = new Set(Object.keys(names))
|
||||
const missing = [...want].filter((x) => !have.has(x)), extra = [...have].filter((x) => !want.has(x))
|
||||
const empty = Object.entries(names).filter(([, v]) => typeof v !== 'string' || !v.trim()).map(([k]) => k)
|
||||
if (missing.length || extra.length || empty.length) fails.push(`${l} countryPosters.stateNames: missing [${missing}] extra [${extra}] empty [${empty}]`)
|
||||
}
|
||||
const en = (areas.en && areas.en.stateNames) || {}
|
||||
const drift = (states || []).filter((st) => en[st.iso] !== st.name).map((st) => st.iso)
|
||||
if (drift.length) fails.push(`English state names differ from the table's: ${drift.join(', ')} (re-run emit-india-state-names.mjs)`)
|
||||
if (!meta || !/CC0/.test(String(meta.licence || ''))) fails.push('india-state-names.meta.json must record the source licence (CC0)')
|
||||
return fails
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const selfTest = process.argv.includes('--self-test')
|
||||
const { seriesFor } = await import(join(ROOT, 'library/packet/countrySheet.js'))
|
||||
|
|
@ -118,6 +162,14 @@ async function main() {
|
|||
const countries = data.countries || []
|
||||
if (countries.length < 170) { console.log(`NOT MEASURED: country-posters.json parsed ${countries.length} countries (floor 170)`); process.exitCode = 75; return }
|
||||
|
||||
const H = await import(join(ROOT, STATE_HELPER))
|
||||
const states = JSON.parse(readFileSync(join(ROOT, 'public/dx-maps/assets/people-groups/india-states.json'), 'utf8')).states || []
|
||||
const adm1Ind = JSON.parse(readFileSync(join(ROOT, 'public/dx-maps/assets/boundaries/gb-IND-ADM1.geojson'), 'utf8'))
|
||||
if (states.length < 30 || (adm1Ind.features || []).length < 30) { console.log(`NOT MEASURED: state table ${states.length} / ADM1 ${(adm1Ind.features || []).length} features (floor 30)`); process.exitCode = 75; return }
|
||||
|
||||
const areas = Object.fromEntries(NAME_LOCALES.map((l) => [l, JSON.parse(readFileSync(join(ROOT, `library/i18n/locales/${l}/countryPosters.json`), 'utf8'))]))
|
||||
const meta = JSON.parse(readFileSync(join(ROOT, 'plugin-designs/country-posters/data/india-state-names.meta.json'), 'utf8'))
|
||||
|
||||
const files = {}
|
||||
for (const p of listSources(PD)) files[relative(ROOT, p)] = readFileSync(p, 'utf8')
|
||||
|
||||
|
|
@ -129,6 +181,12 @@ async function main() {
|
|||
['India skipped (the old guard)', () => armB((l, it) => (!it || !it.sheet ? undefined : seriesFor(l, it)), countries)],
|
||||
['absent code given a position', () => armB((l, it) => seriesFor(l, it) || { index: 1, totalLists: 1, totalGroups: 0 }, countries)],
|
||||
['a second no-sheet country joins India', () => armB(seriesFor, countries.map((c) => (c.iso3 === 'CHN' ? { ...c, sheet: null } : c)))],
|
||||
['the wall calls generateSheet directly', () => armA({ ...files, [wall]: files[wall].replace('<script setup>', "<script setup>\nimport { generateSheet } from '@map/packet/posterSheets.js'\ngenerateSheet({})") })],
|
||||
['a state with groups loses its outline', () => armC(H, states.map((st) => (st.id === 'india-gj' ? { ...st, shapeName: 'Nowhere' } : st)), adm1Ind)],
|
||||
['a division silently dropped', () => armC(H, states.filter((st) => st.id !== 'india-mz'), adm1Ind)],
|
||||
['a locale loses a state name', () => armD(states, { ...areas, ro: { ...areas.ro, stateNames: Object.fromEntries(Object.entries(areas.ro.stateNames).filter(([k]) => k !== 'IN-GJ')) } }, meta)],
|
||||
['the names lose their licence line', () => armD(states, areas, { ...meta, licence: '' })],
|
||||
['a helper that mutates the shared ADM1', () => armC({ ...H, stateGeometry: (z, a, n) => { (a.features || []).forEach((f) => { if (f.properties.shapeName === n) f.properties.listId = z.id }); return H.stateGeometry(z, a, n) } }, states, JSON.parse(JSON.stringify(adm1Ind)))],
|
||||
]
|
||||
let bad = 0
|
||||
for (const [name, run] of cases) {
|
||||
|
|
@ -145,12 +203,12 @@ async function main() {
|
|||
}
|
||||
|
||||
const callers = []
|
||||
const fails = [...armA(files, callers), ...armB(seriesFor, countries)]
|
||||
const missing = KNOWN_CALLERS.filter((k) => !callers.includes(k))
|
||||
const fails = [...armA(files, callers), ...armB(seriesFor, countries), ...armC(H, states, adm1Ind), ...armD(states, areas, meta)]
|
||||
const missing = [...KNOWN_CALLERS, `${STATE_CALLER}#state`].filter((k) => !callers.includes(k))
|
||||
if (missing.length) fails.push(`known callers not found calling makeCountrySheet: ${missing.join(', ')}`)
|
||||
if (fails.length) { fails.forEach((f) => console.log(`FAIL ${f}`)); process.exitCode = 1; return }
|
||||
const ind = seriesFor(countries, countries.find((c) => c.iso3 === 'IND'))
|
||||
console.log(`PASS check:country-sheet-one-path — ${Object.keys(files).length} plugin sources parsed-or-skipped, makeCountrySheet callers: ${callers.join(', ')}; seriesFor over ${countries.length} countries (India ${ind.index} of ${ind.totalLists})`)
|
||||
console.log(`PASS check:country-sheet-one-path — ${Object.keys(files).length} plugin sources parsed-or-skipped, makeCountrySheet callers: ${callers.join(', ')}; seriesFor over ${countries.length} countries (India ${ind.index} of ${ind.totalLists}); ${H.statesWithPosters(states).length} state posters, series 1..${H.statesWithPosters(states).length}, every outline found; state names in ${NAME_LOCALES.length} locales x ${states.length}`)
|
||||
}
|
||||
|
||||
main().catch((e) => { console.error(`FAIL ${e.stack || e}`); process.exitCode = 1 })
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
#!/usr/bin/env node
|
||||
// emit-india-state-names.mjs — India's 36 states and union territories named in the 11 UI locales, for the Country
|
||||
// Posters wall's India section: the CARD labels, the print HUD and the packet/list titles. The printed sheet stays
|
||||
// English by recorded decision (series.json english_only_note), like every country poster.
|
||||
//
|
||||
// SOURCE: Wikidata labels, matched on ISO 3166-2 (P300) against data/people-groups/india-states.json. Wikidata's
|
||||
// structured data is CC0. The labels are human-edited, not machine translation. Dissolved items (P576) are excluded,
|
||||
// so a retired code such as the pre-2020 Dadra and Nagar Haveli cannot win.
|
||||
// English is the TABLE's own name, so the English wall does not change; Wikidata's English is only a cross-check.
|
||||
// A locale Wikidata has no label for falls back to the English name AND is recorded in the meta file, never silently.
|
||||
//
|
||||
// Writes: library/i18n/locales/<locale>/countryPosters.json -> "stateNames": { "IN-GJ": "…", … } (other keys kept)
|
||||
// plugin-designs/country-posters/data/india-state-names.meta.json (source, licence, query, date, gaps)
|
||||
// Regenerate: node plugin-designs/country-posters/scripts/emit-india-state-names.mjs
|
||||
import { readFileSync, writeFileSync } from 'node:fs'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
|
||||
const LOCALES = ['en', 'es', 'fr', 'pt', 'de', 'it', 'ro', 'ru', 'ar', 'hi', 'zh']
|
||||
const WD_LANG = { zh: ['zh-hans', 'zh-cn', 'zh'] } // the zh catalogue is Simplified
|
||||
const wdLangs = (l) => WD_LANG[l] || [l]
|
||||
|
||||
// The state table keeps geoBoundaries' ISO codes; ISO 3166-2:IN has since renamed four, and Wikidata's P300 holds
|
||||
// the CURRENT code. Queried under the current code, keyed back to the table's.
|
||||
const CODE_ALIAS = { 'IN-CT': 'IN-CG', 'IN-OR': 'IN-OD', 'IN-TG': 'IN-TS', 'IN-UT': 'IN-UK' }
|
||||
const wdCode = (iso) => CODE_ALIAS[iso] || iso
|
||||
|
||||
// REVIEWED OVERRIDES (hi, ar, zh read by eye, 2026-09-30), each with its evidence. Kept small and explicit: neither
|
||||
// Wikidata's labels nor its Wikipedia titles are uniformly current (zh's Odisha TITLE is the old Orissa while its
|
||||
// label is current; ar's Odisha LABEL is the old Orissa while its title is current), so no blanket rule is safe.
|
||||
const OVERRIDES = {
|
||||
'IN-OR': { ar: ['أديشة', "the label is the pre-2011 name Orissa (أريسة); ar.wikipedia's title for the item is أديشة (Odisha)"] },
|
||||
'IN-DH': { zh: ['达德拉-纳加尔哈维利和达曼-第乌', 'Wikidata has only a Traditional-script zh label; converted character by character to the Simplified catalogue script'] },
|
||||
}
|
||||
|
||||
const states = JSON.parse(readFileSync(join(ROOT, 'data/people-groups/india-states.json'), 'utf8')).states
|
||||
const isos = states.map((s) => wdCode(s.iso)).sort()
|
||||
const langs = [...new Set(LOCALES.flatMap(wdLangs))]
|
||||
const query = `SELECT ?code ?lang ?label WHERE {
|
||||
VALUES ?code { ${isos.map((c) => `"${c}"`).join(' ')} }
|
||||
?item wdt:P300 ?code .
|
||||
FILTER NOT EXISTS { ?item wdt:P576 ?dissolved }
|
||||
?item rdfs:label ?label . BIND(LANG(?label) AS ?lang)
|
||||
FILTER(?lang IN (${langs.map((l) => `"${l}"`).join(', ')}))
|
||||
}`
|
||||
const res = await fetch('https://query.wikidata.org/sparql?format=json&query=' + encodeURIComponent(query), {
|
||||
headers: { accept: 'application/sparql-results+json', 'user-agent': 'dx-maps country-posters emit-india-state-names' },
|
||||
signal: AbortSignal.timeout(60000),
|
||||
})
|
||||
if (!res.ok) { console.error(`Wikidata ${res.status}`); process.exit(1) }
|
||||
const rows = (await res.json()).results.bindings
|
||||
const got = {} // iso -> Wikidata language -> Set(labels)
|
||||
for (const r of rows) ((got[r.code.value] ||= {})[r.lang.value] ||= new Set()).add(r.label.value)
|
||||
|
||||
// FALLBACK for a language the matched item has no label in (e.g. the "National Capital Territory of Delhi" item):
|
||||
// the labels of items in India (P17 = Q668) whose English label IS the table's name (the city of Delhi,
|
||||
// Chandigarh). Used only for the missing languages, and recorded.
|
||||
async function sameNameLabels(name) {
|
||||
const q = `SELECT ?lang ?label WHERE { ?item rdfs:label ${JSON.stringify(name)}@en ; wdt:P17 wd:Q668 .
|
||||
?item rdfs:label ?label . BIND(LANG(?label) AS ?lang) FILTER(?lang IN (${langs.map((l) => `"${l}"`).join(', ')})) }`
|
||||
const r = await fetch('https://query.wikidata.org/sparql?format=json&query=' + encodeURIComponent(q), {
|
||||
headers: { accept: 'application/sparql-results+json', 'user-agent': 'dx-maps country-posters emit-india-state-names' },
|
||||
signal: AbortSignal.timeout(60000),
|
||||
})
|
||||
if (!r.ok) throw new Error(`Wikidata ${r.status}`)
|
||||
const by = {}
|
||||
for (const b of (await r.json()).results.bindings) {
|
||||
const m = (by[b.lang.value] ||= new Map()); m.set(b.label.value, (m.get(b.label.value) || 0) + 1)
|
||||
}
|
||||
return by // lang -> Map(label -> how many same-named items carry it)
|
||||
}
|
||||
const fallbacks = [], overridden = [], unreviewedZh = []
|
||||
|
||||
const gaps = [], ambiguous = [], englishMismatches = []
|
||||
const names = Object.fromEntries(LOCALES.map((l) => [l, {}]))
|
||||
for (const st of [...states].sort((a, b) => a.iso.localeCompare(b.iso))) {
|
||||
const wd = got[wdCode(st.iso)] || {}
|
||||
if (!Object.keys(wd).length) gaps.push(`${st.iso}: no Wikidata item (queried as ${wdCode(st.iso)})`)
|
||||
let same = null
|
||||
const wdEn = wd.en && [...wd.en][0]
|
||||
// The table names the division more briefly than its item ("Delhi" vs "National Capital Territory of Delhi"): then
|
||||
// the SAME-NAMED item's labels (the city of Delhi) are preferred in every language, so hi and ru match the English.
|
||||
const preferSame = Boolean(wdEn && wdEn !== st.name)
|
||||
if (preferSame) englishMismatches.push(`${st.iso}: table "${st.name}" vs Wikidata "${wdEn}"; same-named labels preferred`)
|
||||
for (const l of LOCALES) {
|
||||
if (l === 'en') { names.en[st.iso] = st.name; continue }
|
||||
const ov = OVERRIDES[st.iso] && OVERRIDES[st.iso][l]
|
||||
if (ov) { names[l][st.iso] = ov[0]; overridden.push(`${st.iso} ${l}: "${ov[0]}" (${ov[1]})`); continue }
|
||||
if (preferSame) {
|
||||
same ||= await sameNameLabels(st.name)
|
||||
const m = wdLangs(l).map((w) => same[w]).find((x) => x && x.size)
|
||||
if (m) { const [label] = [...m].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0]; names[l][st.iso] = label; fallbacks.push(`${st.iso} ${l}: "${label}" from the same-named item (preferred)`); continue }
|
||||
}
|
||||
const src = wdLangs(l).find((w) => wd[w] && wd[w].size)
|
||||
if (l === 'zh' && src === 'zh') unreviewedZh.push(`${st.iso}: only a plain "zh" label (script unknown: may be Traditional); add a reviewed OVERRIDE`)
|
||||
const hit = src && wd[src]
|
||||
if (!hit) {
|
||||
same ||= await sameNameLabels(st.name)
|
||||
const m = wdLangs(l).map((w) => same[w]).find((x) => x && x.size)
|
||||
if (m) {
|
||||
const [label, n] = [...m].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))[0]
|
||||
names[l][st.iso] = label; fallbacks.push(`${st.iso} ${l}: "${label}" from the same-named item (${n} of ${[...m.values()].reduce((a, x) => a + x, 0)})`)
|
||||
continue
|
||||
}
|
||||
names[l][st.iso] = st.name; gaps.push(`${st.iso} ${l}: no label, English used`); continue
|
||||
}
|
||||
if (hit.size > 1) ambiguous.push(`${st.iso} ${l}: ${[...hit].join(' | ')}`)
|
||||
names[l][st.iso] = [...hit].sort()[0]
|
||||
}
|
||||
}
|
||||
for (const l of LOCALES) {
|
||||
const p = join(ROOT, `library/i18n/locales/${l}/countryPosters.json`)
|
||||
const area = JSON.parse(readFileSync(p, 'utf8'))
|
||||
area.stateNames = names[l]
|
||||
writeFileSync(p, JSON.stringify(area, null, 2) + '\n')
|
||||
}
|
||||
const meta = {
|
||||
what: "India's 36 states and union territories, named in the 11 UI locales, for the Country Posters wall's card labels.",
|
||||
source: 'Wikidata (https://www.wikidata.org): labels of the items whose ISO 3166-2 code (P300) matches data/people-groups/india-states.json; dissolved items (P576) excluded.',
|
||||
licence: 'CC0 1.0: Wikidata structured data is released under CC0 (https://creativecommons.org/publicdomain/zero/1.0/).',
|
||||
english: "the state table's own name (the English wall is unchanged); Wikidata's English is only a cross-check",
|
||||
retrieved: new Date().toISOString().slice(0, 10),
|
||||
regenerate: 'node plugin-designs/country-posters/scripts/emit-india-state-names.mjs',
|
||||
query,
|
||||
codeAliases: CODE_ALIAS,
|
||||
reviewedOverrides: overridden,
|
||||
gaps, ambiguous, fallbacks, englishMismatches,
|
||||
}
|
||||
writeFileSync(join(ROOT, 'plugin-designs/country-posters/data/india-state-names.meta.json'), JSON.stringify(meta, null, 2) + '\n')
|
||||
console.log(`states ${states.length}, Wikidata rows ${rows.length}; gaps ${gaps.length}, ambiguous ${ambiguous.length}, fallbacks ${fallbacks.length}, English mismatches ${englishMismatches.length}`)
|
||||
for (const x of [...gaps, ...ambiguous, ...fallbacks, ...overridden, ...englishMismatches]) console.log(' ' + x)
|
||||
// A zh name from a plain "zh" label may be Traditional script in a Simplified catalogue: refuse rather than ship it.
|
||||
if (unreviewedZh.length) { unreviewedZh.forEach((x) => console.error('FAIL ' + x)); process.exitCode = 1 }
|
||||
86
plugin-designs/country-posters/src/indiaStateSheet.js
Normal file
86
plugin-designs/country-posters/src/indiaStateSheet.js
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* indiaStateSheet.js — one Indian state's or union territory's map sheet, GENERATED in the browser at the click,
|
||||
* the same print standard as a country's (@map/packet/countrySheet.js): no sheet is stored, only previews.
|
||||
*
|
||||
* A state poster is a country poster one level down. Its people groups come from the shared state table
|
||||
* (library/data/indiaStates.js: each state's slugs), its outline from the ADM1 boundaries the renderer
|
||||
* already loads (boundaries/gb-IND-ADM1.geojson, matched on shapeName), and its text from the `indiaState`
|
||||
* series (series.json), so it never calls itself one of the Country Maps.
|
||||
*
|
||||
* const sheet = makeIndiaStateSheet({ records, assetsBase: () => base, states })
|
||||
* <PosterWall :sheet="sheet" /> // (item, format) -> html, '' when it cannot be drawn
|
||||
*
|
||||
* ONE PATH: the wall's state cards draw through this and nothing else (check:country-sheet-one-path).
|
||||
*/
|
||||
|
||||
// The states that get a poster: those with people groups listed, in the wall's own order (most groups first,
|
||||
// then by name), so a sheet's "map N of M" matches the card's place on the wall.
|
||||
export function statesWithPosters(states) {
|
||||
return (states || []).filter((s) => s && s.count > 0)
|
||||
.slice().sort((a, b) => b.count - a.count || String(a.name).localeCompare(String(b.name), 'en'))
|
||||
}
|
||||
|
||||
export function stateSeries(states, state) {
|
||||
const tier = statesWithPosters(states)
|
||||
const index = tier.findIndex((s) => s.id === state?.id) + 1
|
||||
if (!index) return undefined
|
||||
return { totalGroups: tier.reduce((a, s) => a + s.count, 0), totalLists: tier.length, index }
|
||||
}
|
||||
|
||||
// The zone the renderer draws. `provinceLabel` makes the title the state's own name, and the header's
|
||||
// "Administrative Areas" row lists it (the renderer never says "province" for India).
|
||||
export function stateZone(state, color) {
|
||||
return {
|
||||
id: state.id, zoneKey: state.id, label: state.name, provinceLabel: state.name, provinces: [state.name],
|
||||
count: state.count, color,
|
||||
}
|
||||
}
|
||||
|
||||
// The renderer knows a zone's OWN ADM1 shapes by `listId === zone.id`; the served file carries UPG-100's zone
|
||||
// ids there. So it gets a COPY with this state's feature re-tagged (the cached collection is shared, never
|
||||
// mutated), plus the stamped zone geometry. null when the state's outline is not in the file.
|
||||
export function stateGeometry(zone, adm1Ind, shapeName) {
|
||||
const feats = (adm1Ind && adm1Ind.features) || []
|
||||
const own = feats.filter((f) => f.properties && f.properties.shapeName === shapeName)
|
||||
if (!own.length) return null
|
||||
const adm1 = { ...adm1Ind, features: feats.map((f) => (own.includes(f) ? { ...f, properties: { ...f.properties, listId: zone.id } } : f)) }
|
||||
const zoneFC = {
|
||||
type: 'FeatureCollection',
|
||||
features: own.map((f) => ({
|
||||
type: 'Feature',
|
||||
properties: { ...f.properties, listId: zone.id, zoneKey: zone.zoneKey, zoneColor: zone.color, zoneLabel: zone.label },
|
||||
geometry: f.geometry,
|
||||
})),
|
||||
}
|
||||
return { adm1, zoneFC }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {object} o
|
||||
* records (selector) => Promise<rows[]> the shared provider; asked for the state's slugs
|
||||
* assetsBase () => string where boundaries/ is served from
|
||||
* states () => states[] | null the state table's entries (library/data/indiaStates.js)
|
||||
* region the WAGF region the hue comes from (India's: 'Asia'), so a state prints in India's hue
|
||||
* @returns {(item, format) => Promise<string>}
|
||||
*/
|
||||
export function makeIndiaStateSheet({ records, assetsBase, states, region = 'Asia' }) {
|
||||
return async function indiaStateSheet(item, format) {
|
||||
const all = states() || []
|
||||
const state = all.find((s) => s.id === (item && item.id))
|
||||
if (!state || !state.count) return ''
|
||||
const [{ generateSheet, loadBoundaries, pinsFrom, countryRegionColor }, { ISO3_NAME }, rows] = await Promise.all([
|
||||
import('@map/packet/posterSheets.js'), import('@map/data/iso3-name.js'),
|
||||
records({ type: 'pg_slugs', values: state.slugs }),
|
||||
])
|
||||
if (!rows || !rows.length) return ''
|
||||
const bounds = await loadBoundaries(assetsBase())
|
||||
const zone = stateZone(state, countryRegionColor(region))
|
||||
const geo = stateGeometry(zone, bounds.adm1Ind, state.shapeName)
|
||||
if (!geo) return '' // no outline to draw: fall through to the stored preview, never a guessed map
|
||||
return generateSheet({
|
||||
zone, pins: pinsFrom(rows), zoneFC: geo.zoneFC, format, seriesId: 'indiaState', ISO3_NAME,
|
||||
adm0: bounds.adm0, adm1Ind: geo.adm1, adm1Chn: bounds.adm1Chn,
|
||||
series: stateSeries(all, state), assetsBase: assetsBase(),
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue