interface CountryInfo {
/** ISO 3166-1 alpha-2 country code (e.g., 'US', 'GB', 'NG') */
countryCode: string;
/** Full country name */
countryName?: string;
/** Timezone detected */
timezone?: string;
/** Browser language */
language?: string;
/** Detection method used */
detectionMethod: 'timezone' | 'language' | 'api' | 'unknown';
}
// Timezone to country code mapping (common timezones)
const TIMEZONE_TO_COUNTRY: Record<string, string> = {
'America/New_York': 'US',
'America/Chicago': 'US',
'America/Denver': 'US',
'America/Los_Angeles': 'US',
'America/Phoenix': 'US',
'America/Anchorage': 'US',
'America/Toronto': 'CA',
'America/Vancouver': 'CA',
'Europe/London': 'GB',
'Europe/Paris': 'FR',
'Europe/Berlin': 'DE',
'Europe/Rome': 'IT',
'Europe/Madrid': 'ES',
'Europe/Amsterdam': 'NL',
'Europe/Brussels': 'BE',
'Europe/Vienna': 'AT',
'Europe/Stockholm': 'SE',
'Europe/Copenhagen': 'DK',
'Europe/Oslo': 'NO',
'Europe/Helsinki': 'FI',
'Europe/Warsaw': 'PL',
'Europe/Prague': 'CZ',
'Europe/Budapest': 'HU',
'Europe/Bucharest': 'RO',
'Europe/Athens': 'GR',
'Europe/Lisbon': 'PT',
'Europe/Dublin': 'IE',
'Europe/Zurich': 'CH',
'Europe/Moscow': 'RU',
'Asia/Dubai': 'AE',
'Asia/Tokyo': 'JP',
'Asia/Seoul': 'KR',
'Asia/Shanghai': 'CN',
'Asia/Hong_Kong': 'HK',
'Asia/Singapore': 'SG',
'Asia/Bangkok': 'TH',
'Asia/Jakarta': 'ID',
'Asia/Manila': 'PH',
'Asia/Kuala_Lumpur': 'MY',
'Asia/Kolkata': 'IN',
'Asia/Karachi': 'PK',
'Asia/Dhaka': 'BD',
'Asia/Tehran': 'IR',
'Asia/Baghdad': 'IQ',
'Asia/Riyadh': 'SA',
'Asia/Tel_Aviv': 'IL',
'Asia/Istanbul': 'TR',
'Africa/Cairo': 'EG',
'Africa/Johannesburg': 'ZA',
'Africa/Lagos': 'NG',
'Africa/Nairobi': 'KE',
'Africa/Accra': 'GH',
'Africa/Casablanca': 'MA',
'Africa/Algiers': 'DZ',
'Australia/Sydney': 'AU',
'Australia/Melbourne': 'AU',
'Australia/Brisbane': 'AU',
'Australia/Perth': 'AU',
'Pacific/Auckland': 'NZ',
'America/Sao_Paulo': 'BR',
'America/Buenos_Aires': 'AR',
'America/Santiago': 'CL',
'America/Lima': 'PE',
'America/Bogota': 'CO',
'America/Mexico_City': 'MX',
};
// Language code to country code mapping
const LANGUAGE_TO_COUNTRY: Record<string, string> = {
'en-US': 'US',
'en-GB': 'GB',
'en-CA': 'CA',
'en-AU': 'AU',
'en-NZ': 'NZ',
'en-IE': 'IE',
'fr-FR': 'FR',
'fr-CA': 'CA',
'de-DE': 'DE',
'de-AT': 'AT',
'de-CH': 'CH',
'es-ES': 'ES',
'es-MX': 'MX',
'es-AR': 'AR',
'it-IT': 'IT',
'pt-BR': 'BR',
'pt-PT': 'PT',
'ja-JP': 'JP',
'ko-KR': 'KR',
'zh-CN': 'CN',
'zh-TW': 'TW',
'zh-HK': 'HK',
'ru-RU': 'RU',
'ar-SA': 'SA',
'ar-EG': 'EG',
'hi-IN': 'IN',
'th-TH': 'TH',
'vi-VN': 'VN',
'id-ID': 'ID',
'tr-TR': 'TR',
'pl-PL': 'PL',
'nl-NL': 'NL',
'sv-SE': 'SE',
'no-NO': 'NO',
'da-DK': 'DK',
'fi-FI': 'FI',
};
/**
* Gets the country code from browser timezone
* @returns string | null - ISO 3166-1 alpha-2 country code or null
*/
function getCountryFromTimezone(): string | null {
try {
if (typeof Intl === 'undefined' || !Intl.DateTimeFormat) {
return null;
}
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
if (!timezone) {
return null;
}
return TIMEZONE_TO_COUNTRY[timezone] || null;
} catch (error) {
console.warn('Failed to detect country from timezone:', error);
return null;
}
}
/**
* Gets the country code from browser language
* @returns string | null - ISO 3166-1 alpha-2 country code or null
*/
function getCountryFromLanguage(): string | null {
try {
if (typeof navigator === 'undefined') {
return null;
}
const language =
navigator.language || (navigator as UnknownObject).userLanguage;
if (!language) {
return null;
}
// Try exact match first
if (LANGUAGE_TO_COUNTRY[language]) {
return LANGUAGE_TO_COUNTRY[language];
}
// Try language code only (e.g., 'en' from 'en-US')
const langCode = language.split('-')[0];
// Some basic mappings for language-only codes
const langOnlyMap: Record<string, string> = {
en: 'US',
fr: 'FR',
de: 'DE',
es: 'ES',
it: 'IT',
pt: 'BR',
ja: 'JP',
ko: 'KR',
zh: 'CN',
ru: 'RU',
ar: 'SA',
hi: 'IN',
};
return langOnlyMap[langCode] || null;
} catch (error) {
console.warn('Failed to detect country from language:', error);
return null;
}
}
/**
* Gets the browser's country using multiple detection methods
* @returns string - ISO 3166-1 alpha-2 country code (e.g., 'US', 'GB', 'NG')
*/
export function getCountry(): string {
// Try timezone first (most reliable)
const countryFromTimezone = getCountryFromTimezone();
if (countryFromTimezone) {
return countryFromTimezone;
}
// Try language as fallback
const countryFromLanguage = getCountryFromLanguage();
if (countryFromLanguage) {
return countryFromLanguage;
}
// Default to 'US' if nothing works
return 'US';
}
/**
* Gets detailed country information using multiple detection methods
* @returns CountryInfo - Detailed country information
*/
export function getCountryInfo(): CountryInfo {
const timezone =
typeof Intl !== 'undefined' && Intl.DateTimeFormat
? Intl.DateTimeFormat().resolvedOptions().timeZone
: undefined;
const language =
typeof navigator !== 'undefined' ? navigator.language : undefined;
// Try timezone first
const countryFromTimezone = getCountryFromTimezone();
if (countryFromTimezone) {
return {
countryCode: countryFromTimezone,
timezone,
language,
detectionMethod: 'timezone',
};
}
// Try language as fallback
const countryFromLanguage = getCountryFromLanguage();
if (countryFromLanguage) {
return {
countryCode: countryFromLanguage,
timezone,
language,
detectionMethod: 'language',
};
}
// Unknown
return {
countryCode: 'US',
timezone,
language,
detectionMethod: 'unknown',
};
}
/**
* Checks if the user is from a specific country
* @param countryCode - ISO 3166-1 alpha-2 country code to check
* @returns boolean
*/
export function isCountry(countryCode: string): boolean {
const userCountry = getCountry();
return userCountry.toLowerCase() === countryCode.toLowerCase();
}
/**
* Checks if the user is from any of the specified countries
* @param countryCodes - Array of ISO 3166-1 alpha-2 country codes
* @returns boolean
*/
export function isFromCountries(countryCodes: string[]): boolean {
const userCountry = getCountry().toLowerCase();
return countryCodes.some((code) => code.toLowerCase() === userCountry);
}