[READ-ONLY] Mirror of https://github.com/mrgnw/ananas. learn multiple languages at once ananas.xcc.es
0

Configure Feed

Select the types of activity you want to include in your feed.

ananas / scripts / fetch-wikidata-country-data.js
5.0 kB 152 lines
1/** 2 * Fetches country data from Wikidata, including: 3 * - ISO 3166-1 alpha-2 codes 4 * - Official/primary languages 5 * - Language speaker counts 6 * - Flag emoji 7 * 8 * Run with: bun run fetch:countries 9 */ 10 11const WIKIDATA_ENDPOINT = 'https://query.wikidata.org/sparql'; 12 13const QUERY = ` 14SELECT DISTINCT ?country ?countryLabel ?isoCode ?iso3Code ?flagImage 15(GROUP_CONCAT(DISTINCT ?nativeName; separator="|") as ?nativeNames) 16(GROUP_CONCAT(DISTINCT ?lang; separator="|") as ?languages) 17(GROUP_CONCAT(DISTINCT ?langLabel; separator="|") as ?languageLabels) 18(GROUP_CONCAT(DISTINCT ?langIso; separator="|") as ?languageIsos) 19(GROUP_CONCAT(DISTINCT ?langIso1; separator="|") as ?languageIso1s) 20(GROUP_CONCAT(DISTINCT ?speakers; separator="|") as ?speakerCounts) 21WHERE { 22 ?country wdt:P31 wd:Q3624078 . # Instance of: sovereign state 23 ?country wdt:P297 ?isoCode . # ISO 3166-1 alpha-2 code 24 ?country wdt:P298 ?iso3Code . # ISO 3166-1 alpha-3 code 25 26 # Get official native name (P1448) 27 OPTIONAL { 28 ?country wdt:P1448 ?nativeName . 29 } 30 31 # Get flag image 32 OPTIONAL { ?country wdt:P41 ?flagImage . } 33 34 # Get official languages 35 OPTIONAL { 36 ?country wdt:P37 ?lang . # Official language 37 ?lang rdfs:label ?langLabel . 38 FILTER(LANG(?langLabel) = "en") 39 40 # Get ISO codes for languages 41 OPTIONAL { ?lang wdt:P220 ?langIso } # ISO 639-3 code 42 OPTIONAL { ?lang wdt:P218 ?langIso1 } # ISO 639-1 code 43 44 # Get speaker counts where available 45 OPTIONAL { 46 ?lang wdt:P1098 ?speakers . # Number of speakers 47 } 48 } 49 50 # Get English labels 51 SERVICE wikibase:label { 52 bd:serviceParam wikibase:language "en" . 53 } 54} 55GROUP BY ?country ?countryLabel ?isoCode ?iso3Code ?flagImage 56ORDER BY ?isoCode 57`; 58 59async function fetchWikidataCountries() { 60 const response = await fetch(WIKIDATA_ENDPOINT, { 61 method: 'POST', 62 headers: { 63 'Content-Type': 'application/x-www-form-urlencoded', 64 'Accept': 'application/json', 65 'User-Agent': 'Ananas Language Learning App/1.0' 66 }, 67 body: 'query=' + encodeURIComponent(QUERY) 68 }); 69 70 if (!response.ok) { 71 throw new Error(`HTTP error! status: ${response.status}`); 72 } 73 74 const data = await response.json(); 75 76 // Process the results into a more usable format 77 let countries = data.results.bindings.map(country => { 78 const languages = country.languages?.value.split('|').filter(Boolean) || []; 79 const languageLabels = country.languageLabels?.value.split('|').filter(Boolean) || []; 80 const languageIsos = country.languageIsos?.value.split('|').filter(Boolean) || []; 81 const languageIso1s = country.languageIso1s?.value.split('|').filter(Boolean) || []; 82 const speakerCounts = country.speakerCounts?.value.split('|').map(Number).filter(Boolean) || []; 83 84 // Create language objects with labels, ISO codes and speaker counts 85 let languagesWithData = languages.map((lang, index) => ({ 86 id: lang.split('/').pop(), 87 name: languageLabels[index], 88 iso: languageIsos[index], 89 iso1: languageIso1s[index] || null, 90 speakers: speakerCounts[index] || null, 91 speakers_m: speakerCounts[index] ? +(speakerCounts[index] / 1000000).toFixed(1) : null 92 })); 93 94 // Sort languages by id for idempotency 95 languagesWithData.sort((a, b) => { 96 if (!a.id && !b.id) return 0; 97 if (!a.id) return 1; 98 if (!b.id) return -1; 99 return a.id.localeCompare(b.id); 100 }); 101 102 return { 103 wikidata_id: country.country.value.split('/').pop(), 104 name: country.countryLabel.value, 105 native_name: country.nativeNames?.value.split('|')[0] || null, 106 iso: country.isoCode.value.toLowerCase(), 107 iso3: country.iso3Code.value.toLowerCase(), 108 flag: String.fromCodePoint( 109 ...country.isoCode.value 110 .toUpperCase() 111 .split('') 112 .map(char => char.charCodeAt(0) + 127397) 113 ), 114 languages: languagesWithData 115 }; 116 }); 117 118 // Remove duplicate countries by wikidata_id 119 const seen = new Set(); 120 countries = countries.filter(country => { 121 if (seen.has(country.wikidata_id)) return false; 122 seen.add(country.wikidata_id); 123 return true; 124 }); 125 126 // Ensure languages are sorted by id, then name for stability 127 countries.forEach(country => { 128 country.languages.sort((a, b) => { 129 if (a.id && b.id) { 130 const cmp = a.id.localeCompare(b.id); 131 if (cmp !== 0) return cmp; 132 } 133 if (a.name && b.name) return a.name.localeCompare(b.name); 134 if (a.name) return -1; 135 if (b.name) return 1; 136 return 0; 137 }); 138 }); 139 140 // Sort countries by name for idempotency and readability 141 countries.sort((a, b) => a.name.localeCompare(b.name)); 142 143 // Write the results to a JSON file 144 await Bun.write( 145 './src/lib/data/wikidata-countries.json', 146 JSON.stringify(countries, null, 2) 147 ); 148 149 console.log(`Wrote ${countries.length} countries to wikidata-countries.json`); 150} 151 152fetchWikidataCountries().catch(console.error);