UNPKG

react-native-localize

Version:

A toolbox for your React Native app localization.

237 lines (188 loc) 6.47 kB
import { createContext, createElement, useContext, useState } from "react"; import { CURRENCIES, USES_FAHRENHEIT, USES_IMPERIAL, USES_RTL_LAYOUT, } from "./constants"; import type { Calendar, Locale, LocalizeApi, NumberFormatSettings, ServerLanguagesProviderProps, TemperatureUnit, } from "./types"; import { getFindBestLanguageTag } from "./utils"; const dateTimeFormatters = new Map<string, Intl.DateTimeFormat>(); const numberFormatters = new Map<string, Intl.NumberFormat>(); const extractLanguageTag = ( languageTag: string, ): { languageCode: string; countryCode?: string } => { const [language = "en", country] = languageTag.split("-"); return { languageCode: language.toLowerCase(), countryCode: country ? (country === "419" ? "UN" : country).toUpperCase() : undefined, }; }; const convertLanguageTagToLocale = ( languageTag: string, countryFallback: string, ): Locale => { const { languageCode, countryCode = countryFallback } = extractLanguageTag(languageTag); return { languageCode, countryCode, languageTag: `${languageCode}-${countryCode}`, isRTL: USES_RTL_LAYOUT.has(languageCode), }; }; const getCountryImpl = (languages: readonly string[]): string => { for (const language of languages) { const { countryCode } = extractLanguageTag(language); if (countryCode) { return countryCode; } } return "US"; }; const getCurrenciesImpl = (languages: readonly string[]): string[] => { const currencies: string[] = []; for (const language of languages) { const { countryCode } = extractLanguageTag(language); if (countryCode) { const currency = CURRENCIES[countryCode]; if (currency && !currencies.includes(currency)) { currencies.push(currency); } } } if (currencies.length === 0) { currencies.push("USD"); } return currencies; }; const getLocalesImpl = ( languages: readonly string[], country: string, ): Locale[] => { const seen = new Set<string>(); const locales: Locale[] = []; for (const language of languages) { const locale = convertLanguageTagToLocale(language, country); if (!seen.has(locale.languageTag)) { locales.push(locale); seen.add(locale.languageTag); } } return locales; }; const getNumberFormatSettingsImpl = ( language: string, ): NumberFormatSettings => { let formatter = numberFormatters.get(language); if (formatter == null) { formatter = new Intl.NumberFormat(language); numberFormatters.set(language, formatter); } const separators = formatter.format(1000000.1).replace(/\d/g, ""); return { decimalSeparator: separators[separators.length - 1] || ".", groupingSeparator: separators[0] || ",", }; }; const getTemperatureUnitImpl = (country: string): TemperatureUnit => USES_FAHRENHEIT.has(country) ? "fahrenheit" : "celsius"; const getTimeZoneImpl = (language: string): string => { let formatter = dateTimeFormatters.get(language); if (formatter == null) { formatter = new Intl.DateTimeFormat(language, { hour: "numeric" }); dateTimeFormatters.set(language, formatter); } return formatter.resolvedOptions().timeZone || "Etc/UTC"; }; const uses24HourClockImpl = (language: string): boolean => { let formatter = dateTimeFormatters.get(language); if (formatter == null) { formatter = new Intl.DateTimeFormat(language, { hour: "numeric" }); dateTimeFormatters.set(language, formatter); } return !formatter.format(new Date(2000, 0, 1, 20)).match(/am|pm/i); }; const usesMetricSystemImpl = (country: string): boolean => !USES_IMPERIAL.has(country); export const getCalendar = (): Calendar => "gregorian"; export const getCountry = (): string => getCountryImpl(navigator.languages); export const getCurrencies = (): string[] => getCurrenciesImpl(navigator.languages); export const getLocales = (): Locale[] => { const { languages } = navigator; return getLocalesImpl(languages, getCountryImpl(languages)); }; export const getNumberFormatSettings = (): NumberFormatSettings => getNumberFormatSettingsImpl(navigator.language); export const getTemperatureUnit = (): TemperatureUnit => getTemperatureUnitImpl(getCountryImpl(navigator.languages)); export const getTimeZone = (): string => getTimeZoneImpl(navigator.language); export const uses24HourClock = (): boolean => uses24HourClockImpl(navigator.language); export const usesMetricSystem = (): boolean => usesMetricSystemImpl(getCountryImpl(navigator.languages)); export const usesAutoDateAndTime = (): boolean | undefined => undefined; export const usesAutoTimeZone = (): boolean | undefined => undefined; export const findBestLanguageTag = getFindBestLanguageTag(getLocales()); export const openAppLanguageSettings = async (): Promise<void> => { throw new Error("openAppLanguageSettings is supported only on Android 13+"); }; const ServerLanguagesContext = createContext<string[] | null>(null); export const ServerLanguagesProvider = ({ children, value, }: ServerLanguagesProviderProps) => typeof window === "undefined" ? createElement(ServerLanguagesContext.Provider, { children, value }) : children; const api: LocalizeApi = { getCalendar, getCountry, getCurrencies, getLocales, getNumberFormatSettings, getTemperatureUnit, getTimeZone, uses24HourClock, usesMetricSystem, usesAutoDateAndTime, usesAutoTimeZone, findBestLanguageTag, openAppLanguageSettings, }; export const useLocalize = (): LocalizeApi => { const languages = useContext(ServerLanguagesContext); return useState<LocalizeApi>(() => { if (languages == null) { return api; } const [language = "en"] = languages; const country = getCountryImpl(languages); const locales = getLocalesImpl(languages, country); return { getCalendar, getCountry: () => country, getCurrencies: () => getCurrenciesImpl(languages), getLocales: () => locales, getNumberFormatSettings: () => getNumberFormatSettingsImpl(language), getTemperatureUnit: () => getTemperatureUnitImpl(country), getTimeZone: () => getTimeZoneImpl(language), uses24HourClock: () => uses24HourClockImpl(language), usesMetricSystem: () => usesMetricSystemImpl(country), usesAutoDateAndTime, usesAutoTimeZone, findBestLanguageTag: getFindBestLanguageTag(locales), openAppLanguageSettings, }; })[0]; };