/** * Country utilities for geographic visualization. * Maps ISO 3166-1 alpha-2 codes to display names and centroid coordinates. */ type CountryInfo = { name: string lat: number lng: number } /** * Country data: ISO alpha-2 code → { name, lat, lng } * Centroid coordinates for pin placement on world maps. */ export const COUNTRIES: Record = { AF: { name: 'Afghanistan', lat: 33.94, lng: 67.71 }, AL: { name: 'Albania', lat: 41.15, lng: 20.17 }, DZ: { name: 'Algeria', lat: 28.03, lng: 1.66 }, AD: { name: 'Andorra', lat: 42.55, lng: 1.6 }, AO: { name: 'Angola', lat: -11.2, lng: 17.87 }, AG: { name: 'Antigua and Barbuda', lat: 17.06, lng: -61.8 }, AR: { name: 'Argentina', lat: -38.42, lng: -63.62 }, AM: { name: 'Armenia', lat: 40.07, lng: 45.04 }, AU: { name: 'Australia', lat: -25.27, lng: 133.78 }, AT: { name: 'Austria', lat: 47.52, lng: 14.55 }, AZ: { name: 'Azerbaijan', lat: 40.14, lng: 47.58 }, BS: { name: 'Bahamas', lat: 25.03, lng: -77.4 }, BH: { name: 'Bahrain', lat: 26.07, lng: 50.56 }, BD: { name: 'Bangladesh', lat: 23.68, lng: 90.36 }, BB: { name: 'Barbados', lat: 13.19, lng: -59.54 }, BY: { name: 'Belarus', lat: 53.71, lng: 27.95 }, BE: { name: 'Belgium', lat: 50.5, lng: 4.47 }, BZ: { name: 'Belize', lat: 17.19, lng: -88.5 }, BJ: { name: 'Benin', lat: 9.31, lng: 2.32 }, BT: { name: 'Bhutan', lat: 27.51, lng: 90.43 }, BO: { name: 'Bolivia', lat: -16.29, lng: -63.59 }, BA: { name: 'Bosnia and Herzegovina', lat: 43.92, lng: 17.68 }, BW: { name: 'Botswana', lat: -22.33, lng: 24.68 }, BR: { name: 'Brazil', lat: -14.24, lng: -51.93 }, BN: { name: 'Brunei', lat: 4.54, lng: 114.73 }, BG: { name: 'Bulgaria', lat: 42.73, lng: 25.49 }, BF: { name: 'Burkina Faso', lat: 12.24, lng: -1.56 }, BI: { name: 'Burundi', lat: -3.37, lng: 29.92 }, CV: { name: 'Cabo Verde', lat: 16.0, lng: -24.01 }, KH: { name: 'Cambodia', lat: 12.57, lng: 104.99 }, CM: { name: 'Cameroon', lat: 7.37, lng: 12.35 }, CA: { name: 'Canada', lat: 56.13, lng: -106.35 }, CF: { name: 'Central African Republic', lat: 6.61, lng: 20.94 }, TD: { name: 'Chad', lat: 15.45, lng: 18.73 }, CL: { name: 'Chile', lat: -35.68, lng: -71.54 }, CN: { name: 'China', lat: 35.86, lng: 104.2 }, CO: { name: 'Colombia', lat: 4.57, lng: -74.3 }, KM: { name: 'Comoros', lat: -11.88, lng: 43.87 }, CG: { name: 'Congo', lat: -0.23, lng: 15.83 }, CD: { name: 'Congo (DRC)', lat: -4.04, lng: 21.76 }, CR: { name: 'Costa Rica', lat: 9.75, lng: -83.75 }, CI: { name: "Cote d'Ivoire", lat: 7.54, lng: -5.55 }, HR: { name: 'Croatia', lat: 45.1, lng: 15.2 }, CU: { name: 'Cuba', lat: 21.52, lng: -77.78 }, CY: { name: 'Cyprus', lat: 35.13, lng: 33.43 }, CZ: { name: 'Czechia', lat: 49.82, lng: 15.47 }, DK: { name: 'Denmark', lat: 56.26, lng: 9.5 }, DJ: { name: 'Djibouti', lat: 11.83, lng: 42.59 }, DM: { name: 'Dominica', lat: 15.41, lng: -61.37 }, DO: { name: 'Dominican Republic', lat: 18.74, lng: -70.16 }, EC: { name: 'Ecuador', lat: -1.83, lng: -78.18 }, EG: { name: 'Egypt', lat: 26.82, lng: 30.8 }, SV: { name: 'El Salvador', lat: 13.79, lng: -88.9 }, GQ: { name: 'Equatorial Guinea', lat: 1.65, lng: 10.27 }, ER: { name: 'Eritrea', lat: 15.18, lng: 39.78 }, EE: { name: 'Estonia', lat: 58.6, lng: 25.01 }, SZ: { name: 'Eswatini', lat: -26.52, lng: 31.47 }, ET: { name: 'Ethiopia', lat: 9.15, lng: 40.49 }, FJ: { name: 'Fiji', lat: -17.71, lng: 178.07 }, FI: { name: 'Finland', lat: 61.92, lng: 25.75 }, FR: { name: 'France', lat: 46.23, lng: 2.21 }, GA: { name: 'Gabon', lat: -0.8, lng: 11.61 }, GM: { name: 'Gambia', lat: 13.44, lng: -15.31 }, GE: { name: 'Georgia', lat: 42.32, lng: 43.36 }, DE: { name: 'Germany', lat: 51.17, lng: 10.45 }, GH: { name: 'Ghana', lat: 7.95, lng: -1.02 }, GR: { name: 'Greece', lat: 39.07, lng: 21.82 }, GD: { name: 'Grenada', lat: 12.12, lng: -61.68 }, GT: { name: 'Guatemala', lat: 15.78, lng: -90.23 }, GN: { name: 'Guinea', lat: 9.95, lng: -11.08 }, GW: { name: 'Guinea-Bissau', lat: 11.8, lng: -15.18 }, GY: { name: 'Guyana', lat: 4.86, lng: -58.93 }, HT: { name: 'Haiti', lat: 18.97, lng: -72.29 }, HN: { name: 'Honduras', lat: 15.2, lng: -86.24 }, HU: { name: 'Hungary', lat: 47.16, lng: 19.5 }, IS: { name: 'Iceland', lat: 64.96, lng: -19.02 }, IN: { name: 'India', lat: 20.59, lng: 78.96 }, ID: { name: 'Indonesia', lat: -0.79, lng: 113.92 }, IR: { name: 'Iran', lat: 32.43, lng: 53.69 }, IQ: { name: 'Iraq', lat: 33.22, lng: 43.68 }, IE: { name: 'Ireland', lat: 53.14, lng: -7.69 }, IL: { name: 'Israel', lat: 31.05, lng: 34.85 }, IT: { name: 'Italy', lat: 41.87, lng: 12.57 }, JM: { name: 'Jamaica', lat: 18.11, lng: -77.3 }, JP: { name: 'Japan', lat: 36.2, lng: 138.25 }, JO: { name: 'Jordan', lat: 30.59, lng: 36.24 }, KZ: { name: 'Kazakhstan', lat: 48.02, lng: 66.92 }, KE: { name: 'Kenya', lat: -0.02, lng: 37.91 }, KI: { name: 'Kiribati', lat: -3.37, lng: -168.73 }, KP: { name: 'North Korea', lat: 40.34, lng: 127.51 }, KR: { name: 'South Korea', lat: 35.91, lng: 127.77 }, KW: { name: 'Kuwait', lat: 29.31, lng: 47.48 }, KG: { name: 'Kyrgyzstan', lat: 41.2, lng: 74.77 }, LA: { name: 'Laos', lat: 19.86, lng: 102.5 }, LV: { name: 'Latvia', lat: 56.88, lng: 24.6 }, LB: { name: 'Lebanon', lat: 33.85, lng: 35.86 }, LS: { name: 'Lesotho', lat: -29.61, lng: 28.23 }, LR: { name: 'Liberia', lat: 6.43, lng: -9.43 }, LY: { name: 'Libya', lat: 26.34, lng: 17.23 }, LI: { name: 'Liechtenstein', lat: 47.17, lng: 9.56 }, LT: { name: 'Lithuania', lat: 55.17, lng: 23.88 }, LU: { name: 'Luxembourg', lat: 49.82, lng: 6.13 }, MG: { name: 'Madagascar', lat: -18.77, lng: 46.87 }, MW: { name: 'Malawi', lat: -13.25, lng: 34.3 }, MY: { name: 'Malaysia', lat: 4.21, lng: 101.98 }, MV: { name: 'Maldives', lat: 3.2, lng: 73.22 }, ML: { name: 'Mali', lat: 17.57, lng: -4.0 }, MT: { name: 'Malta', lat: 35.94, lng: 14.38 }, MH: { name: 'Marshall Islands', lat: 7.13, lng: 171.18 }, MR: { name: 'Mauritania', lat: 21.01, lng: -10.94 }, MU: { name: 'Mauritius', lat: -20.35, lng: 57.55 }, MX: { name: 'Mexico', lat: 23.63, lng: -102.55 }, FM: { name: 'Micronesia', lat: 7.43, lng: 150.55 }, MD: { name: 'Moldova', lat: 47.41, lng: 28.37 }, MC: { name: 'Monaco', lat: 43.75, lng: 7.42 }, MN: { name: 'Mongolia', lat: 46.86, lng: 103.85 }, ME: { name: 'Montenegro', lat: 42.71, lng: 19.37 }, MA: { name: 'Morocco', lat: 31.79, lng: -7.09 }, MZ: { name: 'Mozambique', lat: -18.67, lng: 35.53 }, MM: { name: 'Myanmar', lat: 21.91, lng: 95.96 }, NA: { name: 'Namibia', lat: -22.96, lng: 18.49 }, NR: { name: 'Nauru', lat: -0.52, lng: 166.93 }, NP: { name: 'Nepal', lat: 28.39, lng: 84.12 }, NL: { name: 'Netherlands', lat: 52.13, lng: 5.29 }, NZ: { name: 'New Zealand', lat: -40.9, lng: 174.89 }, NI: { name: 'Nicaragua', lat: 12.87, lng: -85.21 }, NE: { name: 'Niger', lat: 17.61, lng: 8.08 }, NG: { name: 'Nigeria', lat: 9.08, lng: 8.68 }, MK: { name: 'North Macedonia', lat: 41.51, lng: 21.75 }, NO: { name: 'Norway', lat: 60.47, lng: 8.47 }, OM: { name: 'Oman', lat: 21.47, lng: 55.98 }, PK: { name: 'Pakistan', lat: 30.38, lng: 69.35 }, PW: { name: 'Palau', lat: 7.51, lng: 134.58 }, PS: { name: 'Palestine', lat: 31.95, lng: 35.23 }, PA: { name: 'Panama', lat: 8.54, lng: -80.78 }, PG: { name: 'Papua New Guinea', lat: -6.31, lng: 143.96 }, PY: { name: 'Paraguay', lat: -23.44, lng: -58.44 }, PE: { name: 'Peru', lat: -9.19, lng: -75.02 }, PH: { name: 'Philippines', lat: 12.88, lng: 121.77 }, PL: { name: 'Poland', lat: 51.92, lng: 19.15 }, PT: { name: 'Portugal', lat: 39.4, lng: -8.22 }, QA: { name: 'Qatar', lat: 25.35, lng: 51.18 }, RO: { name: 'Romania', lat: 45.94, lng: 24.97 }, RU: { name: 'Russia', lat: 61.52, lng: 105.32 }, RW: { name: 'Rwanda', lat: -1.94, lng: 29.87 }, KN: { name: 'Saint Kitts and Nevis', lat: 17.36, lng: -62.78 }, LC: { name: 'Saint Lucia', lat: 13.91, lng: -60.98 }, VC: { name: 'Saint Vincent and the Grenadines', lat: 12.98, lng: -61.29 }, WS: { name: 'Samoa', lat: -13.76, lng: -172.1 }, SM: { name: 'San Marino', lat: 43.94, lng: 12.46 }, ST: { name: 'Sao Tome and Principe', lat: 0.19, lng: 6.61 }, SA: { name: 'Saudi Arabia', lat: 23.89, lng: 45.08 }, SN: { name: 'Senegal', lat: 14.5, lng: -14.45 }, RS: { name: 'Serbia', lat: 44.02, lng: 21.01 }, SC: { name: 'Seychelles', lat: -4.68, lng: 55.49 }, SL: { name: 'Sierra Leone', lat: 8.46, lng: -11.78 }, SG: { name: 'Singapore', lat: 1.35, lng: 103.82 }, SK: { name: 'Slovakia', lat: 48.67, lng: 19.7 }, SI: { name: 'Slovenia', lat: 46.15, lng: 14.99 }, SB: { name: 'Solomon Islands', lat: -9.65, lng: 160.16 }, SO: { name: 'Somalia', lat: 5.15, lng: 46.2 }, ZA: { name: 'South Africa', lat: -30.56, lng: 22.94 }, SS: { name: 'South Sudan', lat: 6.88, lng: 31.31 }, ES: { name: 'Spain', lat: 40.46, lng: -3.75 }, LK: { name: 'Sri Lanka', lat: 7.87, lng: 80.77 }, SD: { name: 'Sudan', lat: 12.86, lng: 30.22 }, SR: { name: 'Suriname', lat: 3.92, lng: -56.03 }, SE: { name: 'Sweden', lat: 60.13, lng: 18.64 }, CH: { name: 'Switzerland', lat: 46.82, lng: 8.23 }, SY: { name: 'Syria', lat: 34.8, lng: 39.0 }, TW: { name: 'Taiwan', lat: 23.7, lng: 120.96 }, TJ: { name: 'Tajikistan', lat: 38.86, lng: 71.28 }, TZ: { name: 'Tanzania', lat: -6.37, lng: 34.89 }, TH: { name: 'Thailand', lat: 15.87, lng: 100.99 }, TL: { name: 'Timor-Leste', lat: -8.87, lng: 125.73 }, TG: { name: 'Togo', lat: 8.62, lng: 0.82 }, TO: { name: 'Tonga', lat: -21.18, lng: -175.2 }, TT: { name: 'Trinidad and Tobago', lat: 10.69, lng: -61.22 }, TN: { name: 'Tunisia', lat: 33.89, lng: 9.54 }, TR: { name: 'Turkey', lat: 38.96, lng: 35.24 }, TM: { name: 'Turkmenistan', lat: 38.97, lng: 59.56 }, TV: { name: 'Tuvalu', lat: -7.11, lng: 177.65 }, UG: { name: 'Uganda', lat: 1.37, lng: 32.29 }, UA: { name: 'Ukraine', lat: 48.38, lng: 31.17 }, AE: { name: 'United Arab Emirates', lat: 23.42, lng: 53.85 }, GB: { name: 'United Kingdom', lat: 55.38, lng: -3.44 }, US: { name: 'United States', lat: 37.09, lng: -95.71 }, UY: { name: 'Uruguay', lat: -32.52, lng: -55.77 }, UZ: { name: 'Uzbekistan', lat: 41.38, lng: 64.59 }, VU: { name: 'Vanuatu', lat: -15.38, lng: 166.96 }, VA: { name: 'Vatican City', lat: 41.9, lng: 12.45 }, VE: { name: 'Venezuela', lat: 6.42, lng: -66.59 }, VN: { name: 'Vietnam', lat: 14.06, lng: 108.28 }, YE: { name: 'Yemen', lat: 15.55, lng: 48.52 }, ZM: { name: 'Zambia', lat: -13.13, lng: 27.85 }, ZW: { name: 'Zimbabwe', lat: -19.02, lng: 29.15 }, } export function getCountryName(code: string): string { return COUNTRIES[code]?.name || code } export function getCountryCoordinates(code: string): [number, number] | null { const country = COUNTRIES[code] if (!country) return null return [country.lat, country.lng] } /** * Country name to ISO-2 code mappings. * Includes English, French, and common alternate spellings. */ const COUNTRY_NAME_TO_CODE: Record = { // Build reverse lookup from COUNTRIES ...Object.fromEntries( Object.entries(COUNTRIES).flatMap(([code, info]) => [ [info.name.toLowerCase(), code], ]) ), // French names and alternate spellings 'tunisie': 'TN', 'royaume-uni': 'GB', 'uk': 'GB', 'angleterre': 'GB', 'england': 'GB', 'espagne': 'ES', 'inde': 'IN', 'états-unis': 'US', 'etats-unis': 'US', 'usa': 'US', 'allemagne': 'DE', 'italie': 'IT', 'suisse': 'CH', 'belgique': 'BE', 'pays-bas': 'NL', 'australie': 'AU', 'japon': 'JP', 'chine': 'CN', 'brésil': 'BR', 'bresil': 'BR', 'mexique': 'MX', 'maroc': 'MA', 'egypte': 'EG', 'afrique du sud': 'ZA', 'sénégal': 'SN', 'senegal': 'SN', "côte d'ivoire": 'CI', 'cote d\'ivoire': 'CI', 'indonésie': 'ID', 'indonesie': 'ID', 'thaïlande': 'TH', 'thailande': 'TH', 'malaisie': 'MY', 'singapour': 'SG', 'grèce': 'GR', 'grece': 'GR', 'turquie': 'TR', 'pologne': 'PL', 'norvège': 'NO', 'norvege': 'NO', 'suède': 'SE', 'suede': 'SE', 'danemark': 'DK', 'finlande': 'FI', 'irlande': 'IE', 'autriche': 'AT', 'nigéria': 'NG', 'nigeria': 'NG', 'tanzanie': 'TZ', 'ouganda': 'UG', 'zambie': 'ZM', 'somalie': 'SO', 'jordanie': 'JO', 'algérie': 'DZ', 'algerie': 'DZ', 'cameroun': 'CM', 'maurice': 'MU', 'malte': 'MT', 'croatie': 'HR', 'roumanie': 'RO', 'hongrie': 'HU', 'tchéquie': 'CZ', 'tcheque': 'CZ', 'slovaquie': 'SK', 'slovénie': 'SI', 'estonie': 'EE', 'lettonie': 'LV', 'lituanie': 'LT', 'chypre': 'CY', 'malawi': 'MW', 'mozambique': 'MZ', 'namibie': 'NA', 'botswana': 'BW', 'zimbabwe': 'ZW', 'éthiopie': 'ET', 'ethiopie': 'ET', 'soudan': 'SD', 'libye': 'LY', 'arabie saoudite': 'SA', 'émirats arabes unis': 'AE', 'emirats arabes unis': 'AE', 'uae': 'AE', 'qatar': 'QA', 'koweït': 'KW', 'koweit': 'KW', 'bahreïn': 'BH', 'bahrein': 'BH', 'oman': 'OM', 'yémen': 'YE', 'yemen': 'YE', 'irak': 'IQ', 'iran': 'IR', 'afghanistan': 'AF', 'pakistan': 'PK', 'bangladesh': 'BD', 'sri lanka': 'LK', 'népal': 'NP', 'nepal': 'NP', 'birmanie': 'MM', 'myanmar': 'MM', 'cambodge': 'KH', 'laos': 'LA', 'corée du sud': 'KR', 'coree du sud': 'KR', 'south korea': 'KR', 'corée du nord': 'KP', 'coree du nord': 'KP', 'north korea': 'KP', 'nouvelle-zélande': 'NZ', 'nouvelle zelande': 'NZ', 'fidji': 'FJ', 'fiji': 'FJ', 'papouasie-nouvelle-guinée': 'PG', 'argentine': 'AR', 'chili': 'CL', 'colombie': 'CO', 'pérou': 'PE', 'perou': 'PE', 'venezuela': 'VE', 'équateur': 'EC', 'equateur': 'EC', 'bolivie': 'BO', 'paraguay': 'PY', 'uruguay': 'UY', 'costa rica': 'CR', 'panama': 'PA', 'guatemala': 'GT', 'honduras': 'HN', 'salvador': 'SV', 'nicaragua': 'NI', 'cuba': 'CU', 'haïti': 'HT', 'haiti': 'HT', 'jamaïque': 'JM', 'jamaique': 'JM', 'trinidad': 'TT', 'trinité-et-tobago': 'TT', 'république dominicaine': 'DO', 'republique dominicaine': 'DO', 'dominican republic': 'DO', 'puerto rico': 'PR', 'porto rico': 'PR', } /** * Convert a country name or code to ISO-2 code. * Handles: * - Already valid ISO-2 codes (returns as-is) * - Full country names (English or French) * - Common alternate spellings * * @param input Country name or code * @returns ISO-2 code or null if not recognized */ export function normalizeCountryToCode(input: string | null | undefined): string | null { if (!input) return null const trimmed = input.trim() if (!trimmed) return null // If already a valid 2-letter ISO code if (/^[A-Z]{2}$/.test(trimmed) && COUNTRIES[trimmed]) { return trimmed } // Check uppercase version const upper = trimmed.toUpperCase() if (/^[A-Z]{2}$/.test(upper) && COUNTRIES[upper]) { return upper } // Try to find in name mappings const lower = trimmed.toLowerCase() const code = COUNTRY_NAME_TO_CODE[lower] if (code) return code // Try partial matching for country names for (const [name, countryCode] of Object.entries(COUNTRY_NAME_TO_CODE)) { if (lower.includes(name) || name.includes(lower)) { return countryCode } } return null }