import { IntlShape } from 'react-intl/src/types'; import { formatCurrencyCode } from '@transferwise/formatting'; import { Direction } from '../direction'; export const DEFAULT_LANG = 'en'; export const DEFAULT_LOCALE = 'en-GB'; /** * Languages written right-to-left. */ export const RTL_LANGUAGES = ['ar', 'he']; /** * @deprecated The source of truth for supported languages lives in Crab. */ export const SUPPORTED_LANGUAGES = [ DEFAULT_LANG, 'cs', 'de', 'es', 'fr', 'hu', 'id', 'it', 'ja', 'nl', 'pl', 'pt', 'ro', 'ru', 'th', 'tr', 'uk', 'zh', ]; /** * Verifies and adjusts locale, replacing `_` with `-`. * * @param locale `es`, `es_ES`, `en-GB`, `en`, `ja`, `ja-JP`, etc. * @returns `null` if locale is unrecognized by `Intl.Locale`. */ export function adjustLocale(locale: string) { const localeTrimmed = locale?.trim(); if (localeTrimmed) { try { return new Intl.Locale(localeTrimmed.replace('_', '-')).baseName; } catch (error) { // eslint-disable-next-line no-console console.error(error); } } return null; } /** * Provides corresponding lang (iso2) for provided locale. * * @deprecated The use of this function almost always breaks language variants * e.g. Simplified and Traditional Chinese. * There should be no use case for this function. * To select the correct translations from a translations object, pass the * locale directly into Crab's getLocalisedMessages. * @param locale `es`, `es-ES`, `en-GB`, `en`, `ja`, `ja-JP`, etc. * @returns Two-letter ISO 639-1 language code, falling back to `null` if locale is invalid or language is unsupported. */ export function getLangFromLocale(locale: string) { const adjustedLocale = adjustLocale(locale); if (adjustedLocale != null) { const { language } = new Intl.Locale(adjustedLocale); if (SUPPORTED_LANGUAGES.includes(language)) { return language; } } return null; } /** * Provides corresponding country code (iso2) for locales code with explicit region value. * * @param locale `es-ES`, `en-GB`, `ja-JP`, etc. * @returns `null` if the locale is invalid or the region can‘t be identified. */ export function getCountryFromLocale(locale: string) { const adjustedLocale = adjustLocale(locale); return adjustedLocale != null ? (new Intl.Locale(adjustedLocale).region ?? null) : null; } /** * Provides the layout direction for a given locale. * * @param locale `es`, `es-ES`, `en-GB`, `en`, `ja`, `ja-JP`, etc. * @returns The layout direction based on the locale, falling back to `Direction.LTR` if the locale is invalid or unsupported. */ export function getDirectionFromLocale(locale: string) { const adjustedLocale = adjustLocale(locale); return adjustedLocale != null && RTL_LANGUAGES.includes(new Intl.Locale(adjustedLocale).language) ? Direction.RTL : Direction.LTR; } /** * Provides the localized currency name for a given currency code. * @param intl The `intl` object from `react-intl` used for localization. * @param currencyCode The ISO 4217 currency code (e.g., `USD`, `EUR`, `JPY`). * @returns The localized currency name if Intl.DisplayNames is supported supported, otherwise returns the original currency code. */ export function getLocaleCurrencyName(intl: IntlShape, currencyCode: string) { if (typeof Intl.DisplayNames === 'function') { const localisedName = intl.formatDisplayName(currencyCode, { type: 'currency', fallback: 'none', }); return localisedName != null ? localisedName : formatCurrencyCode(currencyCode); } return formatCurrencyCode(currencyCode); }