import allCountries, { type Country, type Iso2 } from "../data.js"; import type { AllOptions } from "../types/public-api.js"; export interface ProcessedDialCodes { dialCodes: Set; dialCodeMaxLength: number; dialCodeToIso2Map: Record; } //* Process onlyCountries or excludeCountries array if present. export const processAllCountries = (options: AllOptions): Country[] => { const { onlyCountries, excludeCountries } = options; if (onlyCountries?.length) { return allCountries.filter((country) => onlyCountries.includes(country.iso2), ); } else if (excludeCountries?.length) { return allCountries.filter( (country) => !excludeCountries.includes(country.iso2), ); } //* Create new array so avoid a bug where multiple instances with different countryOrder options interfere with each other (as they were sharing the same allCountries array, which is mutated by sortCountries). return [...allCountries]; }; //* Generate country names using Intl.DisplayNames export const generateCountryNames = ( countries: Country[], options: AllOptions, ): void => { const { countryNameLocale, countryNameOverrides, uiTranslations } = options; //* Some locales (e.g. bs, hy, is, mk, sq, uz) bundle their own country names //* because certain browsers' Intl.DisplayNames lacks region data for them and //* silently falls back to English (notably Chrome desktop). When the active //* uiTranslations carries these, they take precedence over Intl.DisplayNames. const bundledCountryNames = uiTranslations?.countryNames; //* Populate country names using Intl.DisplayNames (per instance) with countryNameLocale. let displayNames; try { const hasDisplayNames = typeof Intl !== "undefined" && typeof (Intl as any).DisplayNames === "function"; if (hasDisplayNames) { displayNames = new (Intl as any).DisplayNames(countryNameLocale, { type: "region", }); } else { displayNames = null; } } catch (e) { console.error(e); displayNames = null; } for (const c of countries) { c.name = countryNameOverrides[c.iso2] || bundledCountryNames?.[c.iso2] || displayNames?.of(c.iso2.toUpperCase()) || ""; } }; //* Generate dialCodes and dialCodeToIso2Map. export const processDialCodes = ( countries: Country[], ): ProcessedDialCodes => { //* Here we store just dial codes, where the key is the dial code, and the value is true //* e.g. { 1: true, 7: true, 20: true, ... }. const dialCodes = new Set(); let dialCodeMaxLength = 0; //* Here we map dialCodes (inc both dialCode and dialCode+areaCode) to iso2 codes e.g. /* * { * 1: [ 'us', 'ca', ... ], # all NANP countries (with dial code "1") * 12: [ 'us', 'ca', ... ], # subset of NANP countries (that have area codes starting with "2") * 120: [ 'us', 'ca' ], # just US and Canada (that have area codes starting "20") * 1204: [ 'ca' ], # only Canada (that has a "204" area code) * ... * } */ const dialCodeToIso2Map: Record = {}; //* Add a dial code to this.dialCodeToIso2Map. const addToDialCodeMap = (iso2: Iso2, dialCode: string) => { // Bail if no iso2 or dialCode (this can happen with onlyCountries or excludeCountries options). if (!iso2 || !dialCode) { return; } //* Update dialCodeMaxLength. if (dialCode.length > dialCodeMaxLength) { dialCodeMaxLength = dialCode.length; } //* If this entry doesn't already exist, then create it. if (!Object.hasOwn(dialCodeToIso2Map, dialCode)) { dialCodeToIso2Map[dialCode] = []; } const iso2List = dialCodeToIso2Map[dialCode]; //* Bail if we already have this country for this dialCode. if (iso2List.includes(iso2)) { return; } iso2List.push(iso2); }; // Sort countries by priority so that when we add to the dialCodeToIso2Map, higher priority countries come first const countriesSortedByPriority = [...countries].sort( (a, b) => a.priority - b.priority, ); for (const c of countriesSortedByPriority) { if (!dialCodes.has(c.dialCode)) { dialCodes.add(c.dialCode); } // add the dial code partial matches to the map for (let k = 1; k < c.dialCode.length; k++) { const partialDialCode = c.dialCode.substring(0, k); addToDialCodeMap(c.iso2, partialDialCode); } // add the full dial code to the map addToDialCodeMap(c.iso2, c.dialCode); if (c.areaCodes) { const rootIso2Code = dialCodeToIso2Map[c.dialCode][0]; //* For each area code. for (const areaCode of c.areaCodes) { //* Add partial matches: For each digit in the area code for (let k = 1; k < areaCode.length; k++) { const partialAreaCode = areaCode.substring(0, k); const partialDialCode = c.dialCode + partialAreaCode; //* Start with the root country, as that also matches this partial dial code. addToDialCodeMap(rootIso2Code, partialDialCode); addToDialCodeMap(c.iso2, partialDialCode); } //* Add the full area code. addToDialCodeMap(c.iso2, c.dialCode + areaCode); } } } return { dialCodes, dialCodeMaxLength, dialCodeToIso2Map }; }; //* Sort countries by countryOrder option (if present), then name. export const sortCountries = ( countries: Country[], options: AllOptions, ): void => { const { countryOrder } = options; countries.sort((a: Country, b: Country): number => { //* Primary sort: countryOrder option if (countryOrder) { const aIndex = countryOrder.indexOf(a.iso2); const bIndex = countryOrder.indexOf(b.iso2); const aIndexExists = aIndex > -1; const bIndexExists = bIndex > -1; if (aIndexExists || bIndexExists) { if (aIndexExists && bIndexExists) { return aIndex - bIndex; } return aIndexExists ? -1 : 1; } } //* Secondary sort: country name return a.name.localeCompare(b.name); }); };