mirror of
https://github.com/itflow-org/itflow
synced 2026-09-11 09:15:12 +00:00
169 lines
5.9 KiB
TypeScript
169 lines
5.9 KiB
TypeScript
import allCountries, { type Country, type Iso2 } from "../data.js";
|
|
import type { AllOptions } from "../types/public-api.js";
|
|
|
|
export interface ProcessedDialCodes {
|
|
dialCodes: Set<string>;
|
|
dialCodeMaxLength: number;
|
|
dialCodeToIso2Map: Record<string, Iso2[]>;
|
|
}
|
|
|
|
//* 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<string>();
|
|
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<string, Iso2[]> = {};
|
|
|
|
//* 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);
|
|
});
|
|
};
|