323 lines
12 KiB
TypeScript
323 lines
12 KiB
TypeScript
import { getSubdivisionName, getSubdivisionWikipediaTitle } from './subdivisions';
|
|
|
|
/** ISO 3166-1 alpha-2 country code to country name and flag emoji mapping. */
|
|
export const COUNTRIES: Record<string, { name: string; flag: string }> = {
|
|
AF: { name: 'Afghanistan', flag: '🇦🇫' },
|
|
AL: { name: 'Albania', flag: '🇦🇱' },
|
|
DZ: { name: 'Algeria', flag: '🇩🇿' },
|
|
AD: { name: 'Andorra', flag: '🇦🇩' },
|
|
AO: { name: 'Angola', flag: '🇦🇴' },
|
|
AG: { name: 'Antigua and Barbuda', flag: '🇦🇬' },
|
|
AR: { name: 'Argentina', flag: '🇦🇷' },
|
|
AM: { name: 'Armenia', flag: '🇦🇲' },
|
|
AU: { name: 'Australia', flag: '🇦🇺' },
|
|
AT: { name: 'Austria', flag: '🇦🇹' },
|
|
AZ: { name: 'Azerbaijan', flag: '🇦🇿' },
|
|
BS: { name: 'Bahamas', flag: '🇧🇸' },
|
|
BH: { name: 'Bahrain', flag: '🇧🇭' },
|
|
BD: { name: 'Bangladesh', flag: '🇧🇩' },
|
|
BB: { name: 'Barbados', flag: '🇧🇧' },
|
|
BY: { name: 'Belarus', flag: '🇧🇾' },
|
|
BE: { name: 'Belgium', flag: '🇧🇪' },
|
|
BZ: { name: 'Belize', flag: '🇧🇿' },
|
|
BJ: { name: 'Benin', flag: '🇧🇯' },
|
|
BT: { name: 'Bhutan', flag: '🇧🇹' },
|
|
BO: { name: 'Bolivia', flag: '🇧🇴' },
|
|
BA: { name: 'Bosnia and Herzegovina', flag: '🇧🇦' },
|
|
BW: { name: 'Botswana', flag: '🇧🇼' },
|
|
BR: { name: 'Brazil', flag: '🇧🇷' },
|
|
BN: { name: 'Brunei', flag: '🇧🇳' },
|
|
BG: { name: 'Bulgaria', flag: '🇧🇬' },
|
|
BF: { name: 'Burkina Faso', flag: '🇧🇫' },
|
|
BI: { name: 'Burundi', flag: '🇧🇮' },
|
|
CV: { name: 'Cabo Verde', flag: '🇨🇻' },
|
|
KH: { name: 'Cambodia', flag: '🇰🇭' },
|
|
CM: { name: 'Cameroon', flag: '🇨🇲' },
|
|
CA: { name: 'Canada', flag: '🇨🇦' },
|
|
CF: { name: 'Central African Republic', flag: '🇨🇫' },
|
|
TD: { name: 'Chad', flag: '🇹🇩' },
|
|
CL: { name: 'Chile', flag: '🇨🇱' },
|
|
CN: { name: 'China', flag: '🇨🇳' },
|
|
CO: { name: 'Colombia', flag: '🇨🇴' },
|
|
KM: { name: 'Comoros', flag: '🇰🇲' },
|
|
CG: { name: 'Congo', flag: '🇨🇬' },
|
|
CD: { name: 'Congo (DRC)', flag: '🇨🇩' },
|
|
CR: { name: 'Costa Rica', flag: '🇨🇷' },
|
|
CI: { name: "Cote d'Ivoire", flag: '🇨🇮' },
|
|
HR: { name: 'Croatia', flag: '🇭🇷' },
|
|
CU: { name: 'Cuba', flag: '🇨🇺' },
|
|
CY: { name: 'Cyprus', flag: '🇨🇾' },
|
|
CZ: { name: 'Czechia', flag: '🇨🇿' },
|
|
DK: { name: 'Denmark', flag: '🇩🇰' },
|
|
DJ: { name: 'Djibouti', flag: '🇩🇯' },
|
|
DM: { name: 'Dominica', flag: '🇩🇲' },
|
|
DO: { name: 'Dominican Republic', flag: '🇩🇴' },
|
|
EC: { name: 'Ecuador', flag: '🇪🇨' },
|
|
EG: { name: 'Egypt', flag: '🇪🇬' },
|
|
SV: { name: 'El Salvador', flag: '🇸🇻' },
|
|
GQ: { name: 'Equatorial Guinea', flag: '🇬🇶' },
|
|
ER: { name: 'Eritrea', flag: '🇪🇷' },
|
|
EE: { name: 'Estonia', flag: '🇪🇪' },
|
|
SZ: { name: 'Eswatini', flag: '🇸🇿' },
|
|
ET: { name: 'Ethiopia', flag: '🇪🇹' },
|
|
FJ: { name: 'Fiji', flag: '🇫🇯' },
|
|
FI: { name: 'Finland', flag: '🇫🇮' },
|
|
FR: { name: 'France', flag: '🇫🇷' },
|
|
GA: { name: 'Gabon', flag: '🇬🇦' },
|
|
GM: { name: 'Gambia', flag: '🇬🇲' },
|
|
GE: { name: 'Georgia', flag: '🇬🇪' },
|
|
DE: { name: 'Germany', flag: '🇩🇪' },
|
|
GH: { name: 'Ghana', flag: '🇬🇭' },
|
|
GR: { name: 'Greece', flag: '🇬🇷' },
|
|
GD: { name: 'Grenada', flag: '🇬🇩' },
|
|
GT: { name: 'Guatemala', flag: '🇬🇹' },
|
|
GN: { name: 'Guinea', flag: '🇬🇳' },
|
|
GW: { name: 'Guinea-Bissau', flag: '🇬🇼' },
|
|
GY: { name: 'Guyana', flag: '🇬🇾' },
|
|
HT: { name: 'Haiti', flag: '🇭🇹' },
|
|
HN: { name: 'Honduras', flag: '🇭🇳' },
|
|
HU: { name: 'Hungary', flag: '🇭🇺' },
|
|
IS: { name: 'Iceland', flag: '🇮🇸' },
|
|
IN: { name: 'India', flag: '🇮🇳' },
|
|
ID: { name: 'Indonesia', flag: '🇮🇩' },
|
|
IR: { name: 'Iran', flag: '🇮🇷' },
|
|
IQ: { name: 'Iraq', flag: '🇮🇶' },
|
|
IE: { name: 'Ireland', flag: '🇮🇪' },
|
|
IL: { name: 'Israel', flag: '🇮🇱' },
|
|
IT: { name: 'Italy', flag: '🇮🇹' },
|
|
JM: { name: 'Jamaica', flag: '🇯🇲' },
|
|
JP: { name: 'Japan', flag: '🇯🇵' },
|
|
JO: { name: 'Jordan', flag: '🇯🇴' },
|
|
KZ: { name: 'Kazakhstan', flag: '🇰🇿' },
|
|
KE: { name: 'Kenya', flag: '🇰🇪' },
|
|
KI: { name: 'Kiribati', flag: '🇰🇮' },
|
|
KP: { name: 'North Korea', flag: '🇰🇵' },
|
|
KR: { name: 'South Korea', flag: '🇰🇷' },
|
|
KW: { name: 'Kuwait', flag: '🇰🇼' },
|
|
KG: { name: 'Kyrgyzstan', flag: '🇰🇬' },
|
|
LA: { name: 'Laos', flag: '🇱🇦' },
|
|
LV: { name: 'Latvia', flag: '🇱🇻' },
|
|
LB: { name: 'Lebanon', flag: '🇱🇧' },
|
|
LS: { name: 'Lesotho', flag: '🇱🇸' },
|
|
LR: { name: 'Liberia', flag: '🇱🇷' },
|
|
LY: { name: 'Libya', flag: '🇱🇾' },
|
|
LI: { name: 'Liechtenstein', flag: '🇱🇮' },
|
|
LT: { name: 'Lithuania', flag: '🇱🇹' },
|
|
LU: { name: 'Luxembourg', flag: '🇱🇺' },
|
|
MG: { name: 'Madagascar', flag: '🇲🇬' },
|
|
MW: { name: 'Malawi', flag: '🇲🇼' },
|
|
MY: { name: 'Malaysia', flag: '🇲🇾' },
|
|
MV: { name: 'Maldives', flag: '🇲🇻' },
|
|
ML: { name: 'Mali', flag: '🇲🇱' },
|
|
MT: { name: 'Malta', flag: '🇲🇹' },
|
|
MH: { name: 'Marshall Islands', flag: '🇲🇭' },
|
|
MR: { name: 'Mauritania', flag: '🇲🇷' },
|
|
MU: { name: 'Mauritius', flag: '🇲🇺' },
|
|
MX: { name: 'Mexico', flag: '🇲🇽' },
|
|
FM: { name: 'Micronesia', flag: '🇫🇲' },
|
|
MD: { name: 'Moldova', flag: '🇲🇩' },
|
|
MC: { name: 'Monaco', flag: '🇲🇨' },
|
|
MN: { name: 'Mongolia', flag: '🇲🇳' },
|
|
ME: { name: 'Montenegro', flag: '🇲🇪' },
|
|
MA: { name: 'Morocco', flag: '🇲🇦' },
|
|
MZ: { name: 'Mozambique', flag: '🇲🇿' },
|
|
MM: { name: 'Myanmar', flag: '🇲🇲' },
|
|
NA: { name: 'Namibia', flag: '🇳🇦' },
|
|
NR: { name: 'Nauru', flag: '🇳🇷' },
|
|
NP: { name: 'Nepal', flag: '🇳🇵' },
|
|
NL: { name: 'Netherlands', flag: '🇳🇱' },
|
|
NZ: { name: 'New Zealand', flag: '🇳🇿' },
|
|
NI: { name: 'Nicaragua', flag: '🇳🇮' },
|
|
NE: { name: 'Niger', flag: '🇳🇪' },
|
|
NG: { name: 'Nigeria', flag: '🇳🇬' },
|
|
MK: { name: 'North Macedonia', flag: '🇲🇰' },
|
|
NO: { name: 'Norway', flag: '🇳🇴' },
|
|
OM: { name: 'Oman', flag: '🇴🇲' },
|
|
PK: { name: 'Pakistan', flag: '🇵🇰' },
|
|
PW: { name: 'Palau', flag: '🇵🇼' },
|
|
PA: { name: 'Panama', flag: '🇵🇦' },
|
|
PG: { name: 'Papua New Guinea', flag: '🇵🇬' },
|
|
PY: { name: 'Paraguay', flag: '🇵🇾' },
|
|
PE: { name: 'Peru', flag: '🇵🇪' },
|
|
PH: { name: 'Philippines', flag: '🇵🇭' },
|
|
PL: { name: 'Poland', flag: '🇵🇱' },
|
|
PT: { name: 'Portugal', flag: '🇵🇹' },
|
|
QA: { name: 'Qatar', flag: '🇶🇦' },
|
|
RO: { name: 'Romania', flag: '🇷🇴' },
|
|
RU: { name: 'Russia', flag: '🇷🇺' },
|
|
RW: { name: 'Rwanda', flag: '🇷🇼' },
|
|
KN: { name: 'Saint Kitts and Nevis', flag: '🇰🇳' },
|
|
LC: { name: 'Saint Lucia', flag: '🇱🇨' },
|
|
VC: { name: 'Saint Vincent and the Grenadines', flag: '🇻🇨' },
|
|
WS: { name: 'Samoa', flag: '🇼🇸' },
|
|
SM: { name: 'San Marino', flag: '🇸🇲' },
|
|
ST: { name: 'Sao Tome and Principe', flag: '🇸🇹' },
|
|
SA: { name: 'Saudi Arabia', flag: '🇸🇦' },
|
|
SN: { name: 'Senegal', flag: '🇸🇳' },
|
|
RS: { name: 'Serbia', flag: '🇷🇸' },
|
|
SC: { name: 'Seychelles', flag: '🇸🇨' },
|
|
SL: { name: 'Sierra Leone', flag: '🇸🇱' },
|
|
SG: { name: 'Singapore', flag: '🇸🇬' },
|
|
SK: { name: 'Slovakia', flag: '🇸🇰' },
|
|
SI: { name: 'Slovenia', flag: '🇸🇮' },
|
|
SB: { name: 'Solomon Islands', flag: '🇸🇧' },
|
|
SO: { name: 'Somalia', flag: '🇸🇴' },
|
|
ZA: { name: 'South Africa', flag: '🇿🇦' },
|
|
SS: { name: 'South Sudan', flag: '🇸🇸' },
|
|
ES: { name: 'Spain', flag: '🇪🇸' },
|
|
LK: { name: 'Sri Lanka', flag: '🇱🇰' },
|
|
SD: { name: 'Sudan', flag: '🇸🇩' },
|
|
SR: { name: 'Suriname', flag: '🇸🇷' },
|
|
SE: { name: 'Sweden', flag: '🇸🇪' },
|
|
CH: { name: 'Switzerland', flag: '🇨🇭' },
|
|
SY: { name: 'Syria', flag: '🇸🇾' },
|
|
TW: { name: 'Taiwan', flag: '🇹🇼' },
|
|
TJ: { name: 'Tajikistan', flag: '🇹🇯' },
|
|
TZ: { name: 'Tanzania', flag: '🇹🇿' },
|
|
TH: { name: 'Thailand', flag: '🇹🇭' },
|
|
TL: { name: 'Timor-Leste', flag: '🇹🇱' },
|
|
TG: { name: 'Togo', flag: '🇹🇬' },
|
|
TO: { name: 'Tonga', flag: '🇹🇴' },
|
|
TT: { name: 'Trinidad and Tobago', flag: '🇹🇹' },
|
|
TN: { name: 'Tunisia', flag: '🇹🇳' },
|
|
TR: { name: 'Turkey', flag: '🇹🇷' },
|
|
TM: { name: 'Turkmenistan', flag: '🇹🇲' },
|
|
TV: { name: 'Tuvalu', flag: '🇹🇻' },
|
|
UG: { name: 'Uganda', flag: '🇺🇬' },
|
|
UA: { name: 'Ukraine', flag: '🇺🇦' },
|
|
AE: { name: 'United Arab Emirates', flag: '🇦🇪' },
|
|
GB: { name: 'United Kingdom', flag: '🇬🇧' },
|
|
US: { name: 'United States', flag: '🇺🇸' },
|
|
UY: { name: 'Uruguay', flag: '🇺🇾' },
|
|
UZ: { name: 'Uzbekistan', flag: '🇺🇿' },
|
|
VU: { name: 'Vanuatu', flag: '🇻🇺' },
|
|
VA: { name: 'Vatican City', flag: '🇻🇦' },
|
|
VE: { name: 'Venezuela', flag: '🇻🇪' },
|
|
VN: { name: 'Vietnam', flag: '🇻🇳' },
|
|
YE: { name: 'Yemen', flag: '🇾🇪' },
|
|
ZM: { name: 'Zambia', flag: '🇿🇲' },
|
|
ZW: { name: 'Zimbabwe', flag: '🇿🇼' },
|
|
};
|
|
|
|
/** Pre-sorted array of country entries for searching. */
|
|
const COUNTRY_LIST = Object.entries(COUNTRIES)
|
|
.map(([code, { name, flag }]) => ({ code, name, flag }))
|
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
export type CountryEntry = typeof COUNTRY_LIST[number];
|
|
|
|
export interface CountryMatch {
|
|
country: CountryEntry;
|
|
exact: boolean;
|
|
}
|
|
|
|
/**
|
|
* Find a single country matching the query (case-insensitive).
|
|
* Matches exact code/name or name prefix (e.g. "angol" -> Angola).
|
|
* Returns the match with an `exact` flag indicating whether it was a full match.
|
|
*/
|
|
export function searchCountry(query: string): CountryMatch | null {
|
|
const q = query.trim().toLowerCase();
|
|
if (q.length < 2) return null;
|
|
|
|
// Exact code match
|
|
for (const entry of COUNTRY_LIST) {
|
|
if (entry.code.toLowerCase() === q) {
|
|
return { country: entry, exact: true };
|
|
}
|
|
}
|
|
|
|
// Exact name match
|
|
for (const entry of COUNTRY_LIST) {
|
|
if (entry.name.toLowerCase() === q) {
|
|
return { country: entry, exact: true };
|
|
}
|
|
}
|
|
|
|
// Prefix match (shortest name = most specific)
|
|
let best: CountryEntry | null = null;
|
|
for (const entry of COUNTRY_LIST) {
|
|
if (entry.name.toLowerCase().startsWith(q)) {
|
|
if (!best || entry.name.length < best.name.length) {
|
|
best = entry;
|
|
}
|
|
}
|
|
}
|
|
return best ? { country: best, exact: false } : null;
|
|
}
|
|
|
|
/**
|
|
* Map of ISO 3166-1 alpha-2 codes to their Wikipedia article titles,
|
|
* only for countries whose common name differs from the Wikipedia title.
|
|
*/
|
|
const WIKIPEDIA_TITLES: Record<string, string> = {
|
|
BS: 'The Bahamas',
|
|
BO: 'Bolivia',
|
|
CG: 'Republic of the Congo',
|
|
CD: 'Democratic Republic of the Congo',
|
|
CI: 'Ivory Coast',
|
|
CZ: 'Czech Republic',
|
|
SZ: 'Eswatini',
|
|
GM: 'The Gambia',
|
|
GE: 'Georgia (country)',
|
|
IR: 'Iran',
|
|
KP: 'North Korea',
|
|
KR: 'South Korea',
|
|
LA: 'Laos',
|
|
FM: 'Federated States of Micronesia',
|
|
MD: 'Moldova',
|
|
MM: 'Myanmar',
|
|
MK: 'North Macedonia',
|
|
RU: 'Russia',
|
|
ST: 'São Tomé and Príncipe',
|
|
SY: 'Syria',
|
|
TW: 'Taiwan',
|
|
TZ: 'Tanzania',
|
|
GB: 'United Kingdom',
|
|
US: 'United States',
|
|
VE: 'Venezuela',
|
|
VN: 'Vietnam',
|
|
};
|
|
|
|
/** Get the Wikipedia article title for a country or subdivision. */
|
|
export function getWikipediaTitle(code: string): string | null {
|
|
const upper = code.toUpperCase();
|
|
|
|
if (upper.includes('-')) {
|
|
// Subdivision — try subdivision-specific Wikipedia title
|
|
const subTitle = getSubdivisionWikipediaTitle(upper);
|
|
if (subTitle) return subTitle;
|
|
// Fall back to parent country
|
|
const countryCode = upper.split('-')[0];
|
|
const country = COUNTRIES[countryCode];
|
|
if (!country) return null;
|
|
return WIKIPEDIA_TITLES[countryCode] ?? country.name;
|
|
}
|
|
|
|
const country = COUNTRIES[upper];
|
|
if (!country) return null;
|
|
return WIKIPEDIA_TITLES[upper] ?? country.name;
|
|
}
|
|
|
|
/** Get country info from an ISO 3166 code (country or subdivision). */
|
|
export function getCountryInfo(code: string): { name: string; flag: string; subdivision?: string; subdivisionName?: string } | null {
|
|
const upper = code.toUpperCase();
|
|
|
|
// Handle subdivision codes like "US-CA"
|
|
if (upper.includes('-')) {
|
|
const [countryCode] = upper.split('-');
|
|
const country = COUNTRIES[countryCode];
|
|
if (!country) return null;
|
|
return {
|
|
name: country.name,
|
|
flag: country.flag,
|
|
subdivision: upper,
|
|
subdivisionName: getSubdivisionName(upper) ?? undefined,
|
|
};
|
|
}
|
|
|
|
const country = COUNTRIES[upper];
|
|
if (!country) return null;
|
|
return { name: country.name, flag: country.flag };
|
|
}
|