diff --git a/.changeset/tall-foxes-eat.md b/.changeset/tall-foxes-eat.md new file mode 100644 index 000000000..c64297b12 --- /dev/null +++ b/.changeset/tall-foxes-eat.md @@ -0,0 +1,5 @@ +--- +"@alephium/mobile-wallet": patch +--- + +Format amounts based on user's locale diff --git a/.changeset/wild-jobs-train.md b/.changeset/wild-jobs-train.md new file mode 100644 index 000000000..5a3ed2ef2 --- /dev/null +++ b/.changeset/wild-jobs-train.md @@ -0,0 +1,5 @@ +--- +"@alephium/mobile-wallet": patch +--- + +Tap to reveal hidden amounts diff --git a/apps/desktop-wallet/src/App.tsx b/apps/desktop-wallet/src/App.tsx index 9c3aad126..e711d97b3 100644 --- a/apps/desktop-wallet/src/App.tsx +++ b/apps/desktop-wallet/src/App.tsx @@ -14,12 +14,12 @@ import useTrackUserSettings from '@/features/analytics/useTrackUserSettings' import AutoUpdateSnackbar from '@/features/autoUpdate/AutoUpdateSnackbar' import { languageOptions } from '@/features/localization/languages' import { systemLanguageMatchFailed, systemLanguageMatchSucceeded } from '@/features/localization/localizationActions' +import useRegionOptions from '@/features/settings/regionSettings/useRegionOptions' import { localStorageGeneralSettingsMigrated, systemRegionMatchFailed, systemRegionMatchSucceeded } from '@/features/settings/settingsActions' -import useRegionOptions from '@/features/settings/useRegionOptions' import { darkTheme, lightTheme } from '@/features/theme/themes' import { WalletConnectContextProvider } from '@/features/walletConnect/walletConnectContext' import { useAppDispatch, useAppSelector } from '@/hooks/redux' diff --git a/apps/desktop-wallet/src/features/analytics/useTrackUserSettings.ts b/apps/desktop-wallet/src/features/analytics/useTrackUserSettings.ts index 35d55ed77..8da0a550d 100644 --- a/apps/desktop-wallet/src/features/analytics/useTrackUserSettings.ts +++ b/apps/desktop-wallet/src/features/analytics/useTrackUserSettings.ts @@ -15,6 +15,7 @@ const useTrackUserSettings = () => { const passwordRequirement = useAppSelector(selectEffectivePasswordRequirement) const fiatCurrency = useAppSelector((s) => s.settings.fiatCurrency) const network = useAppSelector((s) => s.network.name) + const region = useAppSelector((s) => s.settings.region) useEffect(() => { if (posthog.__loaded) @@ -27,7 +28,8 @@ const useTrackUserSettings = () => { language, passwordRequirement, fiatCurrency, - network + network, + region }) }, [ devTools, @@ -38,6 +40,7 @@ const useTrackUserSettings = () => { passwordRequirement, posthog.__loaded, posthog.people, + region, theme, walletLockTimeInMinutes ]) diff --git a/apps/desktop-wallet/src/features/settings/RegionSettings.tsx b/apps/desktop-wallet/src/features/settings/RegionSettings.tsx index 882ec7915..5579f0f26 100644 --- a/apps/desktop-wallet/src/features/settings/RegionSettings.tsx +++ b/apps/desktop-wallet/src/features/settings/RegionSettings.tsx @@ -3,8 +3,8 @@ import { useTranslation } from 'react-i18next' import KeyValueInput from '@/components/Inputs/InlineLabelValueInput' import Select from '@/components/Inputs/Select' import useAnalytics from '@/features/analytics/useAnalytics' +import useRegionOptions from '@/features/settings/regionSettings/useRegionOptions' import { numberFormatRegionChanged } from '@/features/settings/settingsActions' -import useRegionOptions from '@/features/settings/useRegionOptions' import { useAppDispatch, useAppSelector } from '@/hooks/redux' const RegionSettings = () => { diff --git a/apps/desktop-wallet/src/features/settings/regions.json b/apps/desktop-wallet/src/features/settings/regionSettings/regions.json similarity index 100% rename from apps/desktop-wallet/src/features/settings/regions.json rename to apps/desktop-wallet/src/features/settings/regionSettings/regions.json diff --git a/apps/desktop-wallet/src/features/settings/useRegionOptions.ts b/apps/desktop-wallet/src/features/settings/regionSettings/useRegionOptions.ts similarity index 91% rename from apps/desktop-wallet/src/features/settings/useRegionOptions.ts rename to apps/desktop-wallet/src/features/settings/regionSettings/useRegionOptions.ts index 8732af416..51a9b80b4 100644 --- a/apps/desktop-wallet/src/features/settings/useRegionOptions.ts +++ b/apps/desktop-wallet/src/features/settings/regionSettings/useRegionOptions.ts @@ -1,7 +1,6 @@ import { upperFirst } from 'lodash' import { useMemo } from 'react' -import { Language } from '@/features/localization/languages' import { useAppSelector } from '@/hooks/redux' import regionsLocales from './regions.json' @@ -14,6 +13,11 @@ const useRegionOptions = () => { export default useRegionOptions +const getRegionsOptions = (languageLocale: string) => + regionsLocales + .map((regionLocale) => getRegionOption(regionLocale, languageLocale)) + .sort((a, b) => a.label.localeCompare(b.label)) + // Inspired by https://github.com/LedgerHQ/ledger-live/blob/065dda3/apps/ledger-live-desktop/src/renderer/screens/settings/sections/General/RegionSelect.tsx const getRegionOption = (regionLocale: string, languageLocale: string | Intl.Locale) => { const [language, region = ''] = regionLocale.split('-') @@ -32,8 +36,3 @@ const getRegionOption = (regionLocale: string, languageLocale: string | Intl.Loc label } } - -const getRegionsOptions = (languageLocale: Language) => - regionsLocales - .map((regionLocale) => getRegionOption(regionLocale, languageLocale)) - .sort((a, b) => a.label.localeCompare(b.label)) diff --git a/apps/mobile-wallet/locales/en-US/translation.json b/apps/mobile-wallet/locales/en-US/translation.json index edb19a599..143b35e05 100644 --- a/apps/mobile-wallet/locales/en-US/translation.json +++ b/apps/mobile-wallet/locales/en-US/translation.json @@ -456,5 +456,7 @@ "Splitting your funds into multiple addresses can help you stay organized and increase privacy.": "Splitting your funds into multiple addresses can help you stay organized and increase privacy.", "No dApps match your search: \"{{ searchText }}\"": "No dApps match your search: \"{{ searchText }}\"", "Visit dApp": "Visit dApp", + "Region": "Region", + "Choose your region to update formats of dates, time, and currencies.": "Choose your region to update formats of dates, time, and currencies.", "More details on Alph.land": "More details on Alph.land" } diff --git a/apps/mobile-wallet/src/App.tsx b/apps/mobile-wallet/src/App.tsx index 7048cf2c2..61daa5989 100644 --- a/apps/mobile-wallet/src/App.tsx +++ b/apps/mobile-wallet/src/App.tsx @@ -20,6 +20,7 @@ import { DefaultTheme, ThemeProvider } from 'styled-components/native' import ToastAnchor from '~/components/toasts/ToastAnchor' import LoadingManager from '~/features/loader/LoadingManager' import { useLocalization } from '~/features/localization/useLocalization' +import { useSystemRegion } from '~/features/settings/regionSettings/useSystemRegion' import useLoadStoredSettings from '~/features/settings/useLoadStoredSettings' import { useAppDispatch, useAppSelector } from '~/hooks/redux' import { useAsyncData } from '~/hooks/useAsyncData' @@ -129,6 +130,7 @@ const Main = ({ children, ...props }: ViewProps) => { useLoadStoredSettings() useInitializeClient() useLocalization() + useSystemRegion() useEffect(() => { if (walletMetadata) dispatch(appLaunchedWithLastUsedWallet(walletMetadata)) diff --git a/apps/mobile-wallet/src/components/Amount.tsx b/apps/mobile-wallet/src/components/Amount.tsx index 843f4d558..84f904299 100644 --- a/apps/mobile-wallet/src/components/Amount.tsx +++ b/apps/mobile-wallet/src/components/Amount.tsx @@ -1,4 +1,5 @@ -import { convertToPositive, formatAmountForDisplay, formatFiatAmountForDisplay } from '@alephium/shared' +import { convertToPositive, formatAmountForDisplay } from '@alephium/shared' +import { useState } from 'react' import { StyleProp, TextStyle } from 'react-native' import { useAppSelector } from '~/hooks/redux' @@ -9,7 +10,6 @@ export interface AmountProps extends AppTextProps { value?: bigint | number decimals?: number isFiat?: boolean - fadeDecimals?: boolean fullPrecision?: boolean nbOfDecimalsToShow?: number suffix?: string @@ -24,7 +24,6 @@ export interface AmountProps extends AppTextProps { const Amount = ({ value, - fadeDecimals, fullPrecision = false, suffix = '', showOnDiscreetMode = false, @@ -40,16 +39,31 @@ const Amount = ({ ...props }: AmountProps) => { const discreetMode = useAppSelector((state) => state.settings.discreetMode) + const region = useAppSelector((state) => state.settings.region) + const fiatCurrency = useAppSelector((state) => state.settings.currency) + + const [tappedToDisableDiscreetMode, setTappedToDisableDiscreetMode] = useState(false) + + const hideAmount = discreetMode && !showOnDiscreetMode && !tappedToDisableDiscreetMode + + const handleTappedToDisableDiscreetMode = () => setTappedToDisableDiscreetMode(!tappedToDisableDiscreetMode) - let quantitySymbol = '' let amount = '' + let tinyAmount = '' let isNegative = false + const color = props.color ?? (highlight && value !== undefined ? (value < 0 ? 'send' : 'receive') : 'primary') if (value !== undefined) { isNegative = value < 0 if (isFiat && typeof value === 'number') { - amount = formatFiatAmountForDisplay(isNegative ? value * -1 : value) + amount = new Intl.NumberFormat(region, { style: 'currency', currency: fiatCurrency }).format(value) + + return ( + + {hideAmount ? '•••' : amount} + + ) } else if (isUnknownToken) { amount = convertToPositive(value as bigint).toString() } else { @@ -57,31 +71,29 @@ const Amount = ({ amount: convertToPositive(value as bigint), amountDecimals: decimals, displayDecimals: nbOfDecimalsToShow, - fullPrecision + fullPrecision, + region }) - } - if (fadeDecimals && ['K', 'M', 'B', 'T'].some((char) => amount.endsWith(char))) { - quantitySymbol = amount.slice(-1) - amount = amount.slice(0, -1) - } - } - - let [integralPart, fractionalPart] = amount.split('.') + const amountIsTooSmall = formatAmountForDisplay({ + amount: convertToPositive(value as bigint), + amountDecimals: decimals, + displayDecimals: nbOfDecimalsToShow, + fullPrecision + }).startsWith('0.0000') - if (useTinyAmountShorthand && amount.startsWith('0.0000')) { - integralPart = '< 0' - fractionalPart = '0001' + tinyAmount = + useTinyAmountShorthand && amountIsTooSmall + ? formatAmountForDisplay({ amount: BigInt(1), amountDecimals: 4, region }) + : '' + } } - const color = props.color ?? (highlight && value !== undefined ? (value < 0 ? 'send' : 'receive') : 'primary') - const fadedColor = fadeDecimals ? 'secondary' : color - return ( - - {discreetMode && !showOnDiscreetMode ? ( + + {hideAmount ? ( '•••' - ) : integralPart ? ( + ) : amount ? ( <> {showPlusMinus && ( @@ -89,10 +101,8 @@ const Amount = ({ )} - {integralPart} + {tinyAmount ? `< ${tinyAmount}` : amount} - {fractionalPart && {`.${fractionalPart}`}} - {quantitySymbol && {` ${quantitySymbol} `}} {!isUnknownToken && ( {` ${suffix || 'ALPH'}`} )} diff --git a/apps/mobile-wallet/src/components/ConsolidationModal.tsx b/apps/mobile-wallet/src/components/ConsolidationModal.tsx index 0f3425953..060b04086 100644 --- a/apps/mobile-wallet/src/components/ConsolidationModal.tsx +++ b/apps/mobile-wallet/src/components/ConsolidationModal.tsx @@ -42,7 +42,7 @@ const ConsolidationModal = withModal(({ id, onConsolida {t('Fee')}: - + diff --git a/apps/mobile-wallet/src/features/modals/AppModals.tsx b/apps/mobile-wallet/src/features/modals/AppModals.tsx index b95ade612..bcb5ec1f9 100644 --- a/apps/mobile-wallet/src/features/modals/AppModals.tsx +++ b/apps/mobile-wallet/src/features/modals/AppModals.tsx @@ -31,6 +31,7 @@ import TokenAmountModal from '~/features/send/modals/TokenAmountModal' import CurrencySelectModal from '~/features/settings/CurrencySelectModal' import EditWalletNameModal from '~/features/settings/EditWalletNameModal' import MnemonicModal from '~/features/settings/MnemonicModal' +import RegionSelectModal from '~/features/settings/regionSettings/RegionSelectModal' import SafePlaceWarningModal from '~/features/settings/SafePlaceWarningModal' import WalletDeleteModal from '~/features/settings/WalletDeleteModal' import TransactionModal from '~/features/transactionsDisplay/TransactionModal' @@ -130,6 +131,8 @@ const AppModals = () => { return case 'DAppDetailsModal': return + case 'RegionSelectModal': + return default: return null } diff --git a/apps/mobile-wallet/src/features/modals/modalTypes.ts b/apps/mobile-wallet/src/features/modals/modalTypes.ts index ece9ae68b..391e2d4ae 100644 --- a/apps/mobile-wallet/src/features/modals/modalTypes.ts +++ b/apps/mobile-wallet/src/features/modals/modalTypes.ts @@ -29,6 +29,7 @@ import TokenAmountModal from '~/features/send/modals/TokenAmountModal' import CurrencySelectModal from '~/features/settings/CurrencySelectModal' import EditWalletNameModal from '~/features/settings/EditWalletNameModal' import MnemonicModal from '~/features/settings/MnemonicModal' +import RegionSelectModal from '~/features/settings/regionSettings/RegionSelectModal' import SafePlaceWarningModal from '~/features/settings/SafePlaceWarningModal' import WalletDeleteModal from '~/features/settings/WalletDeleteModal' import TransactionModal from '~/features/transactionsDisplay/TransactionModal' @@ -76,7 +77,8 @@ export const ModalComponents = { AddressQRCodeScanActionsModal, AddressPickerQuickActionsModal, DAppQuickActionsModal, - DAppDetailsModal + DAppDetailsModal, + RegionSelectModal } export type ModalName = keyof typeof ModalComponents diff --git a/apps/mobile-wallet/src/features/settings/regionSettings/RegionSelectModal.tsx b/apps/mobile-wallet/src/features/settings/regionSettings/RegionSelectModal.tsx new file mode 100644 index 000000000..b049cc1a7 --- /dev/null +++ b/apps/mobile-wallet/src/features/settings/regionSettings/RegionSelectModal.tsx @@ -0,0 +1,48 @@ +import { FlashList } from '@shopify/flash-list' +import { useTranslation } from 'react-i18next' + +import { sendAnalytics } from '~/analytics' +import RadioButtonRow from '~/components/RadioButtonRow' +import BottomModalFlashList from '~/features/modals/BottomModalFlashList' +import { closeModal } from '~/features/modals/modalActions' +import withModal from '~/features/modals/withModal' +import { numberFormatRegionChanged } from '~/features/settings/regionSettings/regionSettingsActions' +import { regionOptions } from '~/features/settings/regionSettings/regionsUtils' +import { useAppDispatch, useAppSelector } from '~/hooks/redux' + +const RegionSelectModal = withModal(({ id }) => { + const dispatch = useAppDispatch() + const { t } = useTranslation() + const currentRegion = useAppSelector((s) => s.settings.region) + + const handleRegionChange = (region: string) => { + dispatch(numberFormatRegionChanged(region)) + dispatch(closeModal({ id })) + sendAnalytics({ event: 'Region changed', props: { region } }) + } + + return ( + ( + ( + handleRegionChange(regionOption.value)} + isActive={currentRegion === regionOption.value} + isLast={index === regionOptions.length - 1} + /> + )} + {...props} + /> + )} + /> + ) +}) + +export default RegionSelectModal diff --git a/apps/mobile-wallet/src/features/settings/regionSettings/RegionSettingsRow.tsx b/apps/mobile-wallet/src/features/settings/regionSettings/RegionSettingsRow.tsx new file mode 100644 index 000000000..b90a809a6 --- /dev/null +++ b/apps/mobile-wallet/src/features/settings/regionSettings/RegionSettingsRow.tsx @@ -0,0 +1,25 @@ +import { useTranslation } from 'react-i18next' + +import AppText from '~/components/AppText' +import Row from '~/components/Row' +import { openModal } from '~/features/modals/modalActions' +import { regionOptions } from '~/features/settings/regionSettings/regionsUtils' +import { useAppDispatch, useAppSelector } from '~/hooks/redux' + +const RegionSettingsRow = () => { + const currentRegion = useAppSelector((s) => s.settings.region) + const { t } = useTranslation() + const dispatch = useAppDispatch() + + const openRegionSelectModal = () => dispatch(openModal({ name: 'RegionSelectModal' })) + + return ( + + + {regionOptions.find((region) => region.value === currentRegion)?.label} + + + ) +} + +export default RegionSettingsRow diff --git a/apps/mobile-wallet/src/features/settings/regionSettings/regionSettingsActions.ts b/apps/mobile-wallet/src/features/settings/regionSettings/regionSettingsActions.ts new file mode 100644 index 000000000..54244a7e6 --- /dev/null +++ b/apps/mobile-wallet/src/features/settings/regionSettings/regionSettingsActions.ts @@ -0,0 +1,11 @@ +import { createAction } from '@reduxjs/toolkit' + +import { Settings } from '~/types/settings' + +export const numberFormatRegionChanged = createAction( + 'settings/numberFormatRegionChanged' +) + +export const systemRegionMatchSucceeded = createAction('settings/systemRegionMatchSucceeded') + +export const systemRegionMatchFailed = createAction('settings/systemRegionMatchFailed') diff --git a/apps/mobile-wallet/src/features/settings/regionSettings/regions.json b/apps/mobile-wallet/src/features/settings/regionSettings/regions.json new file mode 100644 index 000000000..c2beb8891 --- /dev/null +++ b/apps/mobile-wallet/src/features/settings/regionSettings/regions.json @@ -0,0 +1,1578 @@ +{ + "af-NA": { + "languageDisplayName": "Afrikaans", + "regionDisplayName": "Namibia" + }, + "af-ZA": { + "languageDisplayName": "Afrikaans", + "regionDisplayName": "South Africa" + }, + "agq-CM": { + "languageDisplayName": "agq", + "regionDisplayName": "Cameroon" + }, + "ak-GH": { + "languageDisplayName": "Akan", + "regionDisplayName": "Ghana" + }, + "am-ET": { + "languageDisplayName": "አማርኛ", + "regionDisplayName": "ኢትዮጵያ" + }, + "ar-AE": { + "languageDisplayName": "العربية", + "regionDisplayName": "الإمارات العربية المتحدة" + }, + "ar-BH": { + "languageDisplayName": "العربية", + "regionDisplayName": "البحرين" + }, + "ar-DJ": { + "languageDisplayName": "العربية", + "regionDisplayName": "جيبوتي" + }, + "ar-DZ": { + "languageDisplayName": "العربية", + "regionDisplayName": "الجزائر" + }, + "ar-EG": { + "languageDisplayName": "العربية", + "regionDisplayName": "مصر" + }, + "ar-EH": { + "languageDisplayName": "العربية", + "regionDisplayName": "الصحراء الغربية" + }, + "ar-ER": { + "languageDisplayName": "العربية", + "regionDisplayName": "إريتريا" + }, + "ar-IL": { + "languageDisplayName": "العربية", + "regionDisplayName": "إسرائيل" + }, + "ar-IQ": { + "languageDisplayName": "العربية", + "regionDisplayName": "العراق" + }, + "ar-JO": { + "languageDisplayName": "العربية", + "regionDisplayName": "الأردن" + }, + "ar-KM": { + "languageDisplayName": "العربية", + "regionDisplayName": "جزر القمر" + }, + "ar-KW": { + "languageDisplayName": "العربية", + "regionDisplayName": "الكويت" + }, + "ar-LB": { + "languageDisplayName": "العربية", + "regionDisplayName": "لبنان" + }, + "ar-LY": { + "languageDisplayName": "العربية", + "regionDisplayName": "ليبيا" + }, + "ar-MA": { + "languageDisplayName": "العربية", + "regionDisplayName": "المغرب" + }, + "ar-MR": { + "languageDisplayName": "العربية", + "regionDisplayName": "موريتانيا" + }, + "ar-OM": { + "languageDisplayName": "العربية", + "regionDisplayName": "عُمان" + }, + "ar-PS": { + "languageDisplayName": "العربية", + "regionDisplayName": "فلسطين" + }, + "ar-QA": { + "languageDisplayName": "العربية", + "regionDisplayName": "قطر" + }, + "ar-SA": { + "languageDisplayName": "العربية", + "regionDisplayName": "المملكة العربية السعودية" + }, + "ar-SD": { + "languageDisplayName": "العربية", + "regionDisplayName": "السودان" + }, + "ar-SO": { + "languageDisplayName": "العربية", + "regionDisplayName": "الصومال" + }, + "ar-SY": { + "languageDisplayName": "العربية", + "regionDisplayName": "سوريا" + }, + "ar-TD": { + "languageDisplayName": "العربية", + "regionDisplayName": "تشاد" + }, + "ar-TN": { + "languageDisplayName": "العربية", + "regionDisplayName": "تونس" + }, + "ar-YE": { + "languageDisplayName": "العربية", + "regionDisplayName": "اليمن" + }, + "as-IN": { + "languageDisplayName": "as", + "regionDisplayName": "India" + }, + "asa-TZ": { + "languageDisplayName": "asa", + "regionDisplayName": "Tanzania" + }, + "az-AZ": { + "languageDisplayName": "azərbaycan", + "regionDisplayName": "Azerbaijan" + }, + "bas-CM": { + "languageDisplayName": "bas", + "regionDisplayName": "Cameroon" + }, + "be-BY": { + "languageDisplayName": "Belarusian", + "regionDisplayName": "Belarus" + }, + "bem-ZM": { + "languageDisplayName": "Bemba", + "regionDisplayName": "Zambia" + }, + "bez-TZ": { + "languageDisplayName": "bez", + "regionDisplayName": "Tanzania" + }, + "bg-BG": { + "languageDisplayName": "български", + "regionDisplayName": "България" + }, + "bm-ML": { + "languageDisplayName": "bm", + "regionDisplayName": "Mali" + }, + "bn-BD": { + "languageDisplayName": "বাংলা", + "regionDisplayName": "বাংলাদেশ" + }, + "bn-IN": { + "languageDisplayName": "বাংলা", + "regionDisplayName": "ভারত" + }, + "bo-CN": { + "languageDisplayName": "bo", + "regionDisplayName": "China" + }, + "bo-IN": { + "languageDisplayName": "bo", + "regionDisplayName": "India" + }, + "br-FR": { + "languageDisplayName": "Breton", + "regionDisplayName": "France" + }, + "brx-IN": { + "languageDisplayName": "brx", + "regionDisplayName": "India" + }, + "bs-BA": { + "languageDisplayName": "bosanski", + "regionDisplayName": "Bosnia & Herzegovina" + }, + "ca-AD": { + "languageDisplayName": "català", + "regionDisplayName": "Andorra" + }, + "ca-ES": { + "languageDisplayName": "català", + "regionDisplayName": "Espanya" + }, + "cgg-UG": { + "languageDisplayName": "cgg", + "regionDisplayName": "Uganda" + }, + "chr-US": { + "languageDisplayName": "Cherokee", + "regionDisplayName": "United States" + }, + "cs-CZ": { + "languageDisplayName": "čeština", + "regionDisplayName": "Česko" + }, + "cy-GB": { + "languageDisplayName": "Welsh", + "regionDisplayName": "United Kingdom" + }, + "da-DK": { + "languageDisplayName": "dansk", + "regionDisplayName": "Danmark" + }, + "dav-KE": { + "languageDisplayName": "dav", + "regionDisplayName": "Kenya" + }, + "de-AT": { + "languageDisplayName": "Deutsch", + "regionDisplayName": "Österreich" + }, + "de-BE": { + "languageDisplayName": "Deutsch", + "regionDisplayName": "Belgien" + }, + "de-CH": { + "languageDisplayName": "Deutsch", + "regionDisplayName": "Schweiz" + }, + "de-DE": { + "languageDisplayName": "Deutsch", + "regionDisplayName": "Deutschland" + }, + "de-LI": { + "languageDisplayName": "Deutsch", + "regionDisplayName": "Liechtenstein" + }, + "de-LU": { + "languageDisplayName": "Deutsch", + "regionDisplayName": "Luxemburg" + }, + "dje-NE": { + "languageDisplayName": "dje", + "regionDisplayName": "Niger" + }, + "dua-CM": { + "languageDisplayName": "dua", + "regionDisplayName": "Cameroon" + }, + "dyo-SN": { + "languageDisplayName": "dyo", + "regionDisplayName": "Senegal" + }, + "dz-BT": { + "languageDisplayName": "dz", + "regionDisplayName": "Bhutan" + }, + "ebu-KE": { + "languageDisplayName": "ebu", + "regionDisplayName": "Kenya" + }, + "ee-GH": { + "languageDisplayName": "Ewe", + "regionDisplayName": "Ghana" + }, + "ee-TG": { + "languageDisplayName": "Eʋegbe", + "regionDisplayName": "Togo" + }, + "el-CY": { + "languageDisplayName": "Ελληνικά", + "regionDisplayName": "Κύπρος" + }, + "el-GR": { + "languageDisplayName": "Ελληνικά", + "regionDisplayName": "Ελλάδα" + }, + "en-AG": { + "languageDisplayName": "English", + "regionDisplayName": "Antigua & Barbuda" + }, + "en-AS": { + "languageDisplayName": "English", + "regionDisplayName": "American Samoa" + }, + "en-AU": { + "languageDisplayName": "English", + "regionDisplayName": "Australia" + }, + "en-BB": { + "languageDisplayName": "English", + "regionDisplayName": "Barbados" + }, + "en-BE": { + "languageDisplayName": "English", + "regionDisplayName": "Belgium" + }, + "en-BM": { + "languageDisplayName": "English", + "regionDisplayName": "Bermuda" + }, + "en-BS": { + "languageDisplayName": "English", + "regionDisplayName": "Bahamas" + }, + "en-BW": { + "languageDisplayName": "English", + "regionDisplayName": "Botswana" + }, + "en-BZ": { + "languageDisplayName": "English", + "regionDisplayName": "Belize" + }, + "en-CA": { + "languageDisplayName": "English", + "regionDisplayName": "Canada" + }, + "en-CM": { + "languageDisplayName": "English", + "regionDisplayName": "Cameroon" + }, + "en-DM": { + "languageDisplayName": "English", + "regionDisplayName": "Dominica" + }, + "en-FJ": { + "languageDisplayName": "English", + "regionDisplayName": "Fiji" + }, + "en-FM": { + "languageDisplayName": "English", + "regionDisplayName": "Micronesia" + }, + "en-GB": { + "languageDisplayName": "English", + "regionDisplayName": "United Kingdom" + }, + "en-GD": { + "languageDisplayName": "English", + "regionDisplayName": "Grenada" + }, + "en-GG": { + "languageDisplayName": "English", + "regionDisplayName": "Guernsey" + }, + "en-GH": { + "languageDisplayName": "English", + "regionDisplayName": "Ghana" + }, + "en-GI": { + "languageDisplayName": "English", + "regionDisplayName": "Gibraltar" + }, + "en-GM": { + "languageDisplayName": "English", + "regionDisplayName": "Gambia" + }, + "en-GU": { + "languageDisplayName": "English", + "regionDisplayName": "Guam" + }, + "en-GY": { + "languageDisplayName": "English", + "regionDisplayName": "Guyana" + }, + "en-HK": { + "languageDisplayName": "English", + "regionDisplayName": "Hong Kong" + }, + "en-IE": { + "languageDisplayName": "English", + "regionDisplayName": "Ireland" + }, + "en-IM": { + "languageDisplayName": "English", + "regionDisplayName": "Isle of Man" + }, + "en-IN": { + "languageDisplayName": "English", + "regionDisplayName": "India" + }, + "en-JE": { + "languageDisplayName": "English", + "regionDisplayName": "Jersey" + }, + "en-JM": { + "languageDisplayName": "English", + "regionDisplayName": "Jamaica" + }, + "en-KE": { + "languageDisplayName": "English", + "regionDisplayName": "Kenya" + }, + "en-KI": { + "languageDisplayName": "English", + "regionDisplayName": "Kiribati" + }, + "en-KN": { + "languageDisplayName": "English", + "regionDisplayName": "St. Kitts & Nevis" + }, + "en-KY": { + "languageDisplayName": "English", + "regionDisplayName": "Cayman Islands" + }, + "en-LC": { + "languageDisplayName": "English", + "regionDisplayName": "St. Lucia" + }, + "en-LR": { + "languageDisplayName": "English", + "regionDisplayName": "Liberia" + }, + "en-LS": { + "languageDisplayName": "English", + "regionDisplayName": "Lesotho" + }, + "en-MG": { + "languageDisplayName": "English", + "regionDisplayName": "Madagascar" + }, + "en-MH": { + "languageDisplayName": "English", + "regionDisplayName": "Marshall Islands" + }, + "en-MP": { + "languageDisplayName": "English", + "regionDisplayName": "Northern Mariana Islands" + }, + "en-MT": { + "languageDisplayName": "English", + "regionDisplayName": "Malta" + }, + "en-MU": { + "languageDisplayName": "English", + "regionDisplayName": "Mauritius" + }, + "en-MW": { + "languageDisplayName": "English", + "regionDisplayName": "Malawi" + }, + "en-NA": { + "languageDisplayName": "English", + "regionDisplayName": "Namibia" + }, + "en-NG": { + "languageDisplayName": "English", + "regionDisplayName": "Nigeria" + }, + "en-NZ": { + "languageDisplayName": "English", + "regionDisplayName": "New Zealand" + }, + "en-PG": { + "languageDisplayName": "English", + "regionDisplayName": "Papua New Guinea" + }, + "en-PH": { + "languageDisplayName": "English", + "regionDisplayName": "Philippines" + }, + "en-PK": { + "languageDisplayName": "English", + "regionDisplayName": "Pakistan" + }, + "en-PR": { + "languageDisplayName": "English", + "regionDisplayName": "Puerto Rico" + }, + "en-PW": { + "languageDisplayName": "English", + "regionDisplayName": "Palau" + }, + "en-SB": { + "languageDisplayName": "English", + "regionDisplayName": "Solomon Islands" + }, + "en-SC": { + "languageDisplayName": "English", + "regionDisplayName": "Seychelles" + }, + "en-SG": { + "languageDisplayName": "English", + "regionDisplayName": "Singapore" + }, + "en-SL": { + "languageDisplayName": "English", + "regionDisplayName": "Sierra Leone" + }, + "en-SS": { + "languageDisplayName": "English", + "regionDisplayName": "South Sudan" + }, + "en-SZ": { + "languageDisplayName": "English", + "regionDisplayName": "Eswatini" + }, + "en-TC": { + "languageDisplayName": "English", + "regionDisplayName": "Turks & Caicos Islands" + }, + "en-TO": { + "languageDisplayName": "English", + "regionDisplayName": "Tonga" + }, + "en-TT": { + "languageDisplayName": "English", + "regionDisplayName": "Trinidad & Tobago" + }, + "en-TZ": { + "languageDisplayName": "English", + "regionDisplayName": "Tanzania" + }, + "en-UG": { + "languageDisplayName": "English", + "regionDisplayName": "Uganda" + }, + "en-UM": { + "languageDisplayName": "English", + "regionDisplayName": "U.S. Outlying Islands" + }, + "en-US": { + "languageDisplayName": "English", + "regionDisplayName": "United States" + }, + "en-VC": { + "languageDisplayName": "English", + "regionDisplayName": "St. Vincent & Grenadines" + }, + "en-VG": { + "languageDisplayName": "English", + "regionDisplayName": "British Virgin Islands" + }, + "en-VI": { + "languageDisplayName": "English", + "regionDisplayName": "U.S. Virgin Islands" + }, + "en-VU": { + "languageDisplayName": "English", + "regionDisplayName": "Vanuatu" + }, + "en-WS": { + "languageDisplayName": "English", + "regionDisplayName": "Samoa" + }, + "en-ZA": { + "languageDisplayName": "English", + "regionDisplayName": "South Africa" + }, + "en-ZM": { + "languageDisplayName": "English", + "regionDisplayName": "Zambia" + }, + "en-ZW": { + "languageDisplayName": "English", + "regionDisplayName": "Zimbabwe" + }, + "es-AR": { + "languageDisplayName": "español", + "regionDisplayName": "Argentina" + }, + "es-BO": { + "languageDisplayName": "español", + "regionDisplayName": "Bolivia" + }, + "es-CL": { + "languageDisplayName": "español", + "regionDisplayName": "Chile" + }, + "es-CO": { + "languageDisplayName": "español", + "regionDisplayName": "Colombia" + }, + "es-CR": { + "languageDisplayName": "español", + "regionDisplayName": "Costa Rica" + }, + "es-CU": { + "languageDisplayName": "español", + "regionDisplayName": "Cuba" + }, + "es-DO": { + "languageDisplayName": "español", + "regionDisplayName": "República Dominicana" + }, + "es-EA": { + "languageDisplayName": "español", + "regionDisplayName": "Ceuta y Melilla" + }, + "es-EC": { + "languageDisplayName": "español", + "regionDisplayName": "Ecuador" + }, + "es-ES": { + "languageDisplayName": "español", + "regionDisplayName": "España" + }, + "es-GQ": { + "languageDisplayName": "español", + "regionDisplayName": "Guinea Ecuatorial" + }, + "es-GT": { + "languageDisplayName": "español", + "regionDisplayName": "Guatemala" + }, + "es-HN": { + "languageDisplayName": "español", + "regionDisplayName": "Honduras" + }, + "es-IC": { + "languageDisplayName": "español", + "regionDisplayName": "Canarias" + }, + "es-MX": { + "languageDisplayName": "español", + "regionDisplayName": "México" + }, + "es-NI": { + "languageDisplayName": "español", + "regionDisplayName": "Nicaragua" + }, + "es-PA": { + "languageDisplayName": "español", + "regionDisplayName": "Panamá" + }, + "es-PE": { + "languageDisplayName": "español", + "regionDisplayName": "Perú" + }, + "es-PH": { + "languageDisplayName": "español", + "regionDisplayName": "Filipinas" + }, + "es-PR": { + "languageDisplayName": "español", + "regionDisplayName": "Puerto Rico" + }, + "es-PY": { + "languageDisplayName": "español", + "regionDisplayName": "Paraguay" + }, + "es-SV": { + "languageDisplayName": "español", + "regionDisplayName": "El Salvador" + }, + "es-US": { + "languageDisplayName": "español", + "regionDisplayName": "Estados Unidos" + }, + "es-UY": { + "languageDisplayName": "español", + "regionDisplayName": "Uruguay" + }, + "es-VE": { + "languageDisplayName": "español", + "regionDisplayName": "Venezuela" + }, + "et-EE": { + "languageDisplayName": "eesti", + "regionDisplayName": "Eesti" + }, + "eu-ES": { + "languageDisplayName": "Basque", + "regionDisplayName": "Spain" + }, + "ewo-CM": { + "languageDisplayName": "ewo", + "regionDisplayName": "Cameroon" + }, + "fa-AF": { + "languageDisplayName": "فارسی", + "regionDisplayName": "افغانستان" + }, + "fa-IR": { + "languageDisplayName": "فارسی", + "regionDisplayName": "ایران" + }, + "ff-SN": { + "languageDisplayName": "ff", + "regionDisplayName": "Senegal" + }, + "fi-FI": { + "languageDisplayName": "suomi", + "regionDisplayName": "Suomi" + }, + "fil-PH": { + "languageDisplayName": "Filipino", + "regionDisplayName": "Pilipinas" + }, + "fo-FO": { + "languageDisplayName": "Faroese", + "regionDisplayName": "Faroe Islands" + }, + "fr-BE": { + "languageDisplayName": "français", + "regionDisplayName": "Belgique" + }, + "fr-BF": { + "languageDisplayName": "français", + "regionDisplayName": "Burkina Faso" + }, + "fr-BI": { + "languageDisplayName": "français", + "regionDisplayName": "Burundi" + }, + "fr-BJ": { + "languageDisplayName": "français", + "regionDisplayName": "Bénin" + }, + "fr-BL": { + "languageDisplayName": "français", + "regionDisplayName": "Saint-Barthélemy" + }, + "fr-CA": { + "languageDisplayName": "français", + "regionDisplayName": "Canada" + }, + "fr-CD": { + "languageDisplayName": "français", + "regionDisplayName": "Congo-Kinshasa" + }, + "fr-CF": { + "languageDisplayName": "français", + "regionDisplayName": "République centrafricaine" + }, + "fr-CG": { + "languageDisplayName": "français", + "regionDisplayName": "Congo-Brazzaville" + }, + "fr-CH": { + "languageDisplayName": "français", + "regionDisplayName": "Suisse" + }, + "fr-CI": { + "languageDisplayName": "français", + "regionDisplayName": "Côte d’Ivoire" + }, + "fr-CM": { + "languageDisplayName": "français", + "regionDisplayName": "Cameroun" + }, + "fr-DJ": { + "languageDisplayName": "français", + "regionDisplayName": "Djibouti" + }, + "fr-DZ": { + "languageDisplayName": "français", + "regionDisplayName": "Algérie" + }, + "fr-FR": { + "languageDisplayName": "français", + "regionDisplayName": "France" + }, + "fr-GA": { + "languageDisplayName": "français", + "regionDisplayName": "Gabon" + }, + "fr-GF": { + "languageDisplayName": "français", + "regionDisplayName": "Guyane française" + }, + "fr-GN": { + "languageDisplayName": "français", + "regionDisplayName": "Guinée" + }, + "fr-GP": { + "languageDisplayName": "français", + "regionDisplayName": "Guadeloupe" + }, + "fr-GQ": { + "languageDisplayName": "français", + "regionDisplayName": "Guinée équatoriale" + }, + "fr-HT": { + "languageDisplayName": "français", + "regionDisplayName": "Haïti" + }, + "fr-KM": { + "languageDisplayName": "français", + "regionDisplayName": "Comores" + }, + "fr-LU": { + "languageDisplayName": "français", + "regionDisplayName": "Luxembourg" + }, + "fr-MA": { + "languageDisplayName": "français", + "regionDisplayName": "Maroc" + }, + "fr-MC": { + "languageDisplayName": "français", + "regionDisplayName": "Monaco" + }, + "fr-MF": { + "languageDisplayName": "français", + "regionDisplayName": "Saint-Martin" + }, + "fr-MG": { + "languageDisplayName": "français", + "regionDisplayName": "Madagascar" + }, + "fr-ML": { + "languageDisplayName": "français", + "regionDisplayName": "Mali" + }, + "fr-MQ": { + "languageDisplayName": "français", + "regionDisplayName": "Martinique" + }, + "fr-MR": { + "languageDisplayName": "français", + "regionDisplayName": "Mauritanie" + }, + "fr-MU": { + "languageDisplayName": "français", + "regionDisplayName": "Maurice" + }, + "fr-NC": { + "languageDisplayName": "français", + "regionDisplayName": "Nouvelle-Calédonie" + }, + "fr-NE": { + "languageDisplayName": "français", + "regionDisplayName": "Niger" + }, + "fr-PF": { + "languageDisplayName": "français", + "regionDisplayName": "Polynésie française" + }, + "fr-RE": { + "languageDisplayName": "français", + "regionDisplayName": "La Réunion" + }, + "fr-RW": { + "languageDisplayName": "français", + "regionDisplayName": "Rwanda" + }, + "fr-SC": { + "languageDisplayName": "français", + "regionDisplayName": "Seychelles" + }, + "fr-SN": { + "languageDisplayName": "français", + "regionDisplayName": "Sénégal" + }, + "fr-SY": { + "languageDisplayName": "français", + "regionDisplayName": "Syrie" + }, + "fr-TD": { + "languageDisplayName": "français", + "regionDisplayName": "Tchad" + }, + "fr-TG": { + "languageDisplayName": "français", + "regionDisplayName": "Togo" + }, + "fr-TN": { + "languageDisplayName": "français", + "regionDisplayName": "Tunisie" + }, + "fr-VU": { + "languageDisplayName": "français", + "regionDisplayName": "Vanuatu" + }, + "fr-YT": { + "languageDisplayName": "français", + "regionDisplayName": "Mayotte" + }, + "ga-IE": { + "languageDisplayName": "Irish", + "regionDisplayName": "Ireland" + }, + "gl-ES": { + "languageDisplayName": "Galician", + "regionDisplayName": "Spain" + }, + "gsw-CH": { + "languageDisplayName": "gsw", + "regionDisplayName": "Switzerland" + }, + "gu-IN": { + "languageDisplayName": "ગુજરાતી", + "regionDisplayName": "ભારત" + }, + "guz-KE": { + "languageDisplayName": "guz", + "regionDisplayName": "Kenya" + }, + "gv-GB": { + "languageDisplayName": "gv", + "regionDisplayName": "United Kingdom" + }, + "ha-GH": { + "languageDisplayName": "Hausa", + "regionDisplayName": "Ghana" + }, + "ha-NE": { + "languageDisplayName": "Hausa", + "regionDisplayName": "Niger" + }, + "ha-NG": { + "languageDisplayName": "Hausa", + "regionDisplayName": "Nigeria" + }, + "haw-US": { + "languageDisplayName": "Hawaiian", + "regionDisplayName": "United States" + }, + "iw-IL": { + "languageDisplayName": "עברית", + "regionDisplayName": "ישראל" + }, + "hi-IN": { + "languageDisplayName": "हिन्दी", + "regionDisplayName": "भारत" + }, + "hr-BA": { + "languageDisplayName": "hrvatski", + "regionDisplayName": "Bosna i Hercegovina" + }, + "hr-HR": { + "languageDisplayName": "hrvatski", + "regionDisplayName": "Hrvatska" + }, + "hu-HU": { + "languageDisplayName": "magyar", + "regionDisplayName": "Magyarország" + }, + "hy-AM": { + "languageDisplayName": "Armenian", + "regionDisplayName": "Armenia" + }, + "in-ID": { + "languageDisplayName": "Indonesia", + "regionDisplayName": "Indonesia" + }, + "ig-NG": { + "languageDisplayName": "Igbo", + "regionDisplayName": "Nigeria" + }, + "ii-CN": { + "languageDisplayName": "ii", + "regionDisplayName": "China" + }, + "is-IS": { + "languageDisplayName": "Icelandic", + "regionDisplayName": "Iceland" + }, + "it-CH": { + "languageDisplayName": "italiano", + "regionDisplayName": "Svizzera" + }, + "it-IT": { + "languageDisplayName": "italiano", + "regionDisplayName": "Italia" + }, + "it-SM": { + "languageDisplayName": "italiano", + "regionDisplayName": "San Marino" + }, + "ja-JP": { + "languageDisplayName": "日本語", + "regionDisplayName": "日本" + }, + "jgo-CM": { + "languageDisplayName": "jgo", + "regionDisplayName": "Cameroon" + }, + "jmc-TZ": { + "languageDisplayName": "jmc", + "regionDisplayName": "Tanzania" + }, + "ka-GE": { + "languageDisplayName": "Georgian", + "regionDisplayName": "Georgia" + }, + "kab-DZ": { + "languageDisplayName": "kab", + "regionDisplayName": "Algeria" + }, + "kam-KE": { + "languageDisplayName": "kam", + "regionDisplayName": "Kenya" + }, + "kde-TZ": { + "languageDisplayName": "kde", + "regionDisplayName": "Tanzania" + }, + "kea-CV": { + "languageDisplayName": "kea", + "regionDisplayName": "Cape Verde" + }, + "khq-ML": { + "languageDisplayName": "khq", + "regionDisplayName": "Mali" + }, + "ki-KE": { + "languageDisplayName": "ki", + "regionDisplayName": "Kenya" + }, + "kk-KZ": { + "languageDisplayName": "Kazakh", + "regionDisplayName": "Kazakhstan" + }, + "kl-GL": { + "languageDisplayName": "kl", + "regionDisplayName": "Greenland" + }, + "kln-KE": { + "languageDisplayName": "kln", + "regionDisplayName": "Kenya" + }, + "km-KH": { + "languageDisplayName": "Khmer", + "regionDisplayName": "Cambodia" + }, + "kn-IN": { + "languageDisplayName": "ಕನ್ನಡ", + "regionDisplayName": "ಭಾರತ" + }, + "ko-KP": { + "languageDisplayName": "한국어", + "regionDisplayName": "조선민주주의인민공화국" + }, + "ko-KR": { + "languageDisplayName": "한국어", + "regionDisplayName": "대한민국" + }, + "kok-IN": { + "languageDisplayName": "kok", + "regionDisplayName": "India" + }, + "ks-IN": { + "languageDisplayName": "ks", + "regionDisplayName": "India" + }, + "ksb-TZ": { + "languageDisplayName": "ksb", + "regionDisplayName": "Tanzania" + }, + "ksf-CM": { + "languageDisplayName": "ksf", + "regionDisplayName": "Cameroon" + }, + "kw-GB": { + "languageDisplayName": "kw", + "regionDisplayName": "United Kingdom" + }, + "lag-TZ": { + "languageDisplayName": "lag", + "regionDisplayName": "Tanzania" + }, + "lg-UG": { + "languageDisplayName": "Ganda", + "regionDisplayName": "Uganda" + }, + "ln-AO": { + "languageDisplayName": "Lingala", + "regionDisplayName": "Angola" + }, + "ln-CD": { + "languageDisplayName": "Lingala", + "regionDisplayName": "Congo - Kinshasa" + }, + "ln-CF": { + "languageDisplayName": "Lingala", + "regionDisplayName": "Central African Republic" + }, + "ln-CG": { + "languageDisplayName": "Lingala", + "regionDisplayName": "Congo - Brazzaville" + }, + "lo-LA": { + "languageDisplayName": "Lao", + "regionDisplayName": "Laos" + }, + "lt-LT": { + "languageDisplayName": "lietuvių", + "regionDisplayName": "Lietuva" + }, + "lu-CD": { + "languageDisplayName": "lu", + "regionDisplayName": "Congo - Kinshasa" + }, + "luo-KE": { + "languageDisplayName": "luo", + "regionDisplayName": "Kenya" + }, + "luy-KE": { + "languageDisplayName": "luy", + "regionDisplayName": "Kenya" + }, + "lv-LV": { + "languageDisplayName": "latviešu", + "regionDisplayName": "Latvija" + }, + "mas-KE": { + "languageDisplayName": "mas", + "regionDisplayName": "Kenya" + }, + "mas-TZ": { + "languageDisplayName": "mas", + "regionDisplayName": "Tanzania" + }, + "mer-KE": { + "languageDisplayName": "mer", + "regionDisplayName": "Kenya" + }, + "mfe-MU": { + "languageDisplayName": "Morisyen", + "regionDisplayName": "Mauritius" + }, + "mg-MG": { + "languageDisplayName": "Malagasy", + "regionDisplayName": "Madagascar" + }, + "mgh-MZ": { + "languageDisplayName": "mgh", + "regionDisplayName": "Mozambique" + }, + "mgo-CM": { + "languageDisplayName": "mgo", + "regionDisplayName": "Cameroon" + }, + "mk-MK": { + "languageDisplayName": "Macedonian", + "regionDisplayName": "North Macedonia" + }, + "ml-IN": { + "languageDisplayName": "മലയാളം", + "regionDisplayName": "ഇന്ത്യ" + }, + "mn-MN": { + "languageDisplayName": "Mongolian", + "regionDisplayName": "Mongolia" + }, + "mr-IN": { + "languageDisplayName": "मराठी", + "regionDisplayName": "भारत" + }, + "ms-BN": { + "languageDisplayName": "Melayu", + "regionDisplayName": "Brunei" + }, + "ms-MY": { + "languageDisplayName": "Melayu", + "regionDisplayName": "Malaysia" + }, + "ms-SG": { + "languageDisplayName": "Melayu", + "regionDisplayName": "Singapura" + }, + "mt-MT": { + "languageDisplayName": "Maltese", + "regionDisplayName": "Malta" + }, + "mua-CM": { + "languageDisplayName": "mua", + "regionDisplayName": "Cameroon" + }, + "my-MM": { + "languageDisplayName": "Burmese", + "regionDisplayName": "Myanmar (Burma)" + }, + "naq-NA": { + "languageDisplayName": "naq", + "regionDisplayName": "Namibia" + }, + "nb-NO": { + "languageDisplayName": "norsk bokmål", + "regionDisplayName": "Norge" + }, + "nd-ZW": { + "languageDisplayName": "nd", + "regionDisplayName": "Zimbabwe" + }, + "ne-IN": { + "languageDisplayName": "नेपाली", + "regionDisplayName": "India" + }, + "ne-NP": { + "languageDisplayName": "Nepali", + "regionDisplayName": "Nepal" + }, + "nl-AW": { + "languageDisplayName": "Nederlands", + "regionDisplayName": "Aruba" + }, + "nl-BE": { + "languageDisplayName": "Nederlands", + "regionDisplayName": "België" + }, + "nl-CW": { + "languageDisplayName": "Nederlands", + "regionDisplayName": "Curaçao" + }, + "nl-NL": { + "languageDisplayName": "Nederlands", + "regionDisplayName": "Nederland" + }, + "nl-SR": { + "languageDisplayName": "Nederlands", + "regionDisplayName": "Suriname" + }, + "nl-SX": { + "languageDisplayName": "Nederlands", + "regionDisplayName": "Sint-Maarten" + }, + "nmg-CM": { + "languageDisplayName": "nmg", + "regionDisplayName": "Cameroon" + }, + "nn-NO": { + "languageDisplayName": "Norwegian Nynorsk", + "regionDisplayName": "Norway" + }, + "nus-SD": { + "languageDisplayName": "nus", + "regionDisplayName": "Sudan" + }, + "nyn-UG": { + "languageDisplayName": "Nyankole", + "regionDisplayName": "Uganda" + }, + "om-ET": { + "languageDisplayName": "Oromo", + "regionDisplayName": "Ethiopia" + }, + "om-KE": { + "languageDisplayName": "Oromoo", + "regionDisplayName": "Kenya" + }, + "or-IN": { + "languageDisplayName": "Odia", + "regionDisplayName": "India" + }, + "pa-PK": { + "languageDisplayName": "پنجابی", + "regionDisplayName": "Pakistan" + }, + "pa-IN": { + "languageDisplayName": "ਪੰਜਾਬੀ", + "regionDisplayName": "India" + }, + "pl-PL": { + "languageDisplayName": "polski", + "regionDisplayName": "Polska" + }, + "ps-AF": { + "languageDisplayName": "Pashto", + "regionDisplayName": "Afghanistan" + }, + "pt-AO": { + "languageDisplayName": "português", + "regionDisplayName": "Angola" + }, + "pt-BR": { + "languageDisplayName": "português", + "regionDisplayName": "Brasil" + }, + "pt-CV": { + "languageDisplayName": "português", + "regionDisplayName": "Cabo Verde" + }, + "pt-GW": { + "languageDisplayName": "português", + "regionDisplayName": "Guiné-Bissau" + }, + "pt-MO": { + "languageDisplayName": "português", + "regionDisplayName": "Macau" + }, + "pt-MZ": { + "languageDisplayName": "português", + "regionDisplayName": "Moçambique" + }, + "pt-PT": { + "languageDisplayName": "português", + "regionDisplayName": "Portugal" + }, + "pt-ST": { + "languageDisplayName": "português", + "regionDisplayName": "São Tomé e Príncipe" + }, + "pt-TL": { + "languageDisplayName": "português", + "regionDisplayName": "Timor-Leste" + }, + "rm-CH": { + "languageDisplayName": "Romansh", + "regionDisplayName": "Switzerland" + }, + "rn-BI": { + "languageDisplayName": "Rundi", + "regionDisplayName": "Burundi" + }, + "ro-MD": { + "languageDisplayName": "română", + "regionDisplayName": "Republica Moldova" + }, + "ro-RO": { + "languageDisplayName": "română", + "regionDisplayName": "România" + }, + "rof-TZ": { + "languageDisplayName": "rof", + "regionDisplayName": "Tanzania" + }, + "ru-BY": { + "languageDisplayName": "русский", + "regionDisplayName": "Беларусь" + }, + "ru-KG": { + "languageDisplayName": "русский", + "regionDisplayName": "Киргизия" + }, + "ru-KZ": { + "languageDisplayName": "русский", + "regionDisplayName": "Казахстан" + }, + "ru-MD": { + "languageDisplayName": "русский", + "regionDisplayName": "Молдова" + }, + "ru-RU": { + "languageDisplayName": "русский", + "regionDisplayName": "Россия" + }, + "ru-UA": { + "languageDisplayName": "русский", + "regionDisplayName": "Украина" + }, + "rw-RW": { + "languageDisplayName": "Kinyarwanda", + "regionDisplayName": "Rwanda" + }, + "rwk-TZ": { + "languageDisplayName": "rwk", + "regionDisplayName": "Tanzania" + }, + "saq-KE": { + "languageDisplayName": "saq", + "regionDisplayName": "Kenya" + }, + "sbp-TZ": { + "languageDisplayName": "sbp", + "regionDisplayName": "Tanzania" + }, + "seh-MZ": { + "languageDisplayName": "seh", + "regionDisplayName": "Mozambique" + }, + "ses-ML": { + "languageDisplayName": "ses", + "regionDisplayName": "Mali" + }, + "sg-CF": { + "languageDisplayName": "sg", + "regionDisplayName": "Central African Republic" + }, + "shi-MA": { + "languageDisplayName": "shi", + "regionDisplayName": "Morocco" + }, + "si-LK": { + "languageDisplayName": "Sinhala", + "regionDisplayName": "Sri Lanka" + }, + "sk-SK": { + "languageDisplayName": "slovenčina", + "regionDisplayName": "Slovensko" + }, + "sl-SI": { + "languageDisplayName": "slovenščina", + "regionDisplayName": "Slovenija" + }, + "sn-ZW": { + "languageDisplayName": "Shona", + "regionDisplayName": "Zimbabwe" + }, + "so-DJ": { + "languageDisplayName": "Somali", + "regionDisplayName": "Djibouti" + }, + "so-ET": { + "languageDisplayName": "Somali", + "regionDisplayName": "Ethiopia" + }, + "so-KE": { + "languageDisplayName": "Soomaali", + "regionDisplayName": "Kenya" + }, + "so-SO": { + "languageDisplayName": "Somali", + "regionDisplayName": "Somalia" + }, + "sq-AL": { + "languageDisplayName": "Albanian", + "regionDisplayName": "Albania" + }, + "sq-MK": { + "languageDisplayName": "shqip", + "regionDisplayName": "North Macedonia" + }, + "sr-BA": { + "languageDisplayName": "српски", + "regionDisplayName": "Босна и Херцеговина" + }, + "sr-ME": { + "languageDisplayName": "srpski", + "regionDisplayName": "Crna Gora" + }, + "sr-RS": { + "languageDisplayName": "српски", + "regionDisplayName": "Србија" + }, + "sv-AX": { + "languageDisplayName": "svenska", + "regionDisplayName": "Åland" + }, + "sv-FI": { + "languageDisplayName": "svenska", + "regionDisplayName": "Finland" + }, + "sv-SE": { + "languageDisplayName": "svenska", + "regionDisplayName": "Sverige" + }, + "sw-KE": { + "languageDisplayName": "Kiswahili", + "regionDisplayName": "Kenya" + }, + "sw-TZ": { + "languageDisplayName": "Kiswahili", + "regionDisplayName": "Tanzania" + }, + "sw-UG": { + "languageDisplayName": "Kiswahili", + "regionDisplayName": "Uganda" + }, + "swc-CD": { + "languageDisplayName": "Kiswahili (Jamhuri ya Kidemokrasia ya Kongo)", + "regionDisplayName": "Jamhuri ya Kidemokrasia ya Kongo" + }, + "ta-IN": { + "languageDisplayName": "தமிழ்", + "regionDisplayName": "இந்தியா" + }, + "ta-LK": { + "languageDisplayName": "தமிழ்", + "regionDisplayName": "இலங்கை" + }, + "ta-MY": { + "languageDisplayName": "தமிழ்", + "regionDisplayName": "மலேசியா" + }, + "ta-SG": { + "languageDisplayName": "தமிழ்", + "regionDisplayName": "சிங்கப்பூர்" + }, + "te-IN": { + "languageDisplayName": "తెలుగు", + "regionDisplayName": "భారతదేశం" + }, + "teo-KE": { + "languageDisplayName": "teo", + "regionDisplayName": "Kenya" + }, + "teo-UG": { + "languageDisplayName": "teo", + "regionDisplayName": "Uganda" + }, + "th-TH": { + "languageDisplayName": "ไทย", + "regionDisplayName": "ไทย" + }, + "ti-ER": { + "languageDisplayName": "ትግር", + "regionDisplayName": "Eritrea" + }, + "ti-ET": { + "languageDisplayName": "Tigrinya", + "regionDisplayName": "Ethiopia" + }, + "to-TO": { + "languageDisplayName": "Tongan", + "regionDisplayName": "Tonga" + }, + "tr-CY": { + "languageDisplayName": "Türkçe", + "regionDisplayName": "Kıbrıs" + }, + "tr-TR": { + "languageDisplayName": "Türkçe", + "regionDisplayName": "Türkiye" + }, + "twq-NE": { + "languageDisplayName": "twq", + "regionDisplayName": "Niger" + }, + "tzm-MA": { + "languageDisplayName": "tzm", + "regionDisplayName": "Morocco" + }, + "uk-UA": { + "languageDisplayName": "українська", + "regionDisplayName": "Україна" + }, + "ur-IN": { + "languageDisplayName": "Urdu", + "regionDisplayName": "India" + }, + "ur-PK": { + "languageDisplayName": "Urdu", + "regionDisplayName": "Pakistan" + }, + "uz-AF": { + "languageDisplayName": "اوزبیک", + "regionDisplayName": "Afghanistan" + }, + "uz-UZ": { + "languageDisplayName": "o‘zbek", + "regionDisplayName": "Uzbekistan" + }, + "vai-LR": { + "languageDisplayName": "vai", + "regionDisplayName": "Liberia" + }, + "vi-VN": { + "languageDisplayName": "Tiếng Việt", + "regionDisplayName": "Việt Nam" + }, + "vun-TZ": { + "languageDisplayName": "vun", + "regionDisplayName": "Tanzania" + }, + "xog-UG": { + "languageDisplayName": "xog", + "regionDisplayName": "Uganda" + }, + "yav-CM": { + "languageDisplayName": "yav", + "regionDisplayName": "Cameroon" + }, + "yo-NG": { + "languageDisplayName": "Yoruba", + "regionDisplayName": "Nigeria" + }, + "zh-CN": { + "languageDisplayName": "中文", + "regionDisplayName": "中国" + }, + "zh-HK": { + "languageDisplayName": "中文", + "regionDisplayName": "香港" + }, + "zh-MO": { + "languageDisplayName": "中文", + "regionDisplayName": "澳門" + }, + "zh-SG": { + "languageDisplayName": "中文", + "regionDisplayName": "新加坡" + }, + "zh-TW": { + "languageDisplayName": "中文", + "regionDisplayName": "台灣" + }, + "zu-ZA": { + "languageDisplayName": "Zulu", + "regionDisplayName": "South Africa" + } +} diff --git a/apps/mobile-wallet/src/features/settings/regionSettings/regionsUtils.ts b/apps/mobile-wallet/src/features/settings/regionSettings/regionsUtils.ts new file mode 100644 index 000000000..58de2a2ca --- /dev/null +++ b/apps/mobile-wallet/src/features/settings/regionSettings/regionsUtils.ts @@ -0,0 +1,16 @@ +import { upperFirst } from 'lodash' + +import regionByKeys from './regions.json' + +// See https://github.com/LedgerHQ/ledger-live/blob/3308c61ab3749b293167bb7485360a66187ae9eb/apps/ledger-live-mobile/src/screens/Settings/General/Region.ts +export const regionOptions = Object.keys(regionByKeys) + .map((key) => { + const { languageDisplayName, regionDisplayName } = regionByKeys[key as keyof typeof regionByKeys] + const label = `${upperFirst(regionDisplayName)} (${upperFirst(languageDisplayName)})` + + return { + value: key, + label + } + }) + .sort((a, b) => a.label.localeCompare(b.label)) diff --git a/apps/mobile-wallet/src/features/settings/regionSettings/useSystemRegion.ts b/apps/mobile-wallet/src/features/settings/regionSettings/useSystemRegion.ts new file mode 100644 index 000000000..28089d669 --- /dev/null +++ b/apps/mobile-wallet/src/features/settings/regionSettings/useSystemRegion.ts @@ -0,0 +1,33 @@ +import { useEffect } from 'react' +import { getLocales } from 'react-native-localize' + +import { + systemRegionMatchFailed, + systemRegionMatchSucceeded +} from '~/features/settings/regionSettings/regionSettingsActions' +import { regionOptions } from '~/features/settings/regionSettings/regionsUtils' +import { useAppDispatch, useAppSelector } from '~/hooks/redux' + +export const useSystemRegion = () => { + const region = useAppSelector((s) => s.settings.region) + const settingsLoadedFromStorage = useAppSelector((s) => s.settings.loadedFromStorage) + const dispatch = useAppDispatch() + + useEffect(() => { + if (!settingsLoadedFromStorage || region !== undefined) return + + const locales = getLocales() + + if (!locales.length) { + dispatch(systemRegionMatchFailed()) + } else { + const systemLanguage = locales[0].languageTag + + if (systemLanguage && regionOptions.find((option) => option.value === systemLanguage)) { + dispatch(systemRegionMatchSucceeded(systemLanguage)) + } else { + dispatch(systemRegionMatchFailed()) + } + } + }, [dispatch, region, settingsLoadedFromStorage]) +} diff --git a/apps/mobile-wallet/src/features/settings/settingsPersistentStorage.ts b/apps/mobile-wallet/src/features/settings/settingsPersistentStorage.ts index 756778e53..9d4a940bf 100644 --- a/apps/mobile-wallet/src/features/settings/settingsPersistentStorage.ts +++ b/apps/mobile-wallet/src/features/settings/settingsPersistentStorage.ts @@ -18,6 +18,7 @@ export const defaultGeneralSettings: GeneralSettings = { walletConnect: false, usesBiometrics: false, language: undefined, + region: undefined, autoLockSeconds: 0 } diff --git a/apps/mobile-wallet/src/features/settings/settingsScreen/SettingsScreen.tsx b/apps/mobile-wallet/src/features/settings/settingsScreen/SettingsScreen.tsx index f5708c3d8..7b62c96df 100644 --- a/apps/mobile-wallet/src/features/settings/settingsScreen/SettingsScreen.tsx +++ b/apps/mobile-wallet/src/features/settings/settingsScreen/SettingsScreen.tsx @@ -17,6 +17,7 @@ import Toggle from '~/components/Toggle' import { useWalletConnectContext } from '~/contexts/walletConnect/WalletConnectContext' import { languageOptions } from '~/features/localization/languages' import { openModal } from '~/features/modals/modalActions' +import RegionSettingsRow from '~/features/settings/regionSettings/RegionSettingsRow' import SettingsAssetsSection from '~/features/settings/settingsScreen/SettingsAssetsSection' import SettingsSecuritySection from '~/features/settings/settingsScreen/SettingsSecuritySection' import { @@ -121,6 +122,9 @@ const SettingsScreen = ({ navigation, ...props }: ScreenProps) => { {languageOptions.find((l) => l.value === language)?.label} + + + {currentCurrency} diff --git a/apps/mobile-wallet/src/features/settings/settingsSlice.ts b/apps/mobile-wallet/src/features/settings/settingsSlice.ts index ace548d23..b0f02fa89 100644 --- a/apps/mobile-wallet/src/features/settings/settingsSlice.ts +++ b/apps/mobile-wallet/src/features/settings/settingsSlice.ts @@ -6,6 +6,11 @@ import { systemLanguageMatchFailed, systemLanguageMatchSucceeded } from '~/features/localization/localizationActions' +import { + numberFormatRegionChanged, + systemRegionMatchFailed, + systemRegionMatchSucceeded +} from '~/features/settings/regionSettings/regionSettingsActions' import { allBiometricsEnabled, analyticsIdGenerated } from '~/features/settings/settingsActions' import { defaultGeneralSettings, persistSettings } from '~/features/settings/settingsPersistentStorage' import { RootState } from '~/store/store' @@ -72,6 +77,12 @@ const settingsSlice = createSlice({ .addCase(languageChanged, (state, action) => { state.language = action.payload }) + .addCase(systemRegionMatchFailed, (state) => { + state.region = 'en-US' + }) + .addMatcher(isAnyOf(numberFormatRegionChanged, systemRegionMatchSucceeded), (state, action) => { + state.region = action.payload + }) } }) @@ -102,6 +113,8 @@ settingsListenerMiddleware.startListening({ autoLockSecondsChanged, allBiometricsEnabled, languageChanged, + systemRegionMatchSucceeded, + systemRegionMatchFailed, appReset ), effect: async (_, { getState }) => { diff --git a/apps/mobile-wallet/src/features/transactionsDisplay/TransactionModal.tsx b/apps/mobile-wallet/src/features/transactionsDisplay/TransactionModal.tsx index 5b2685c84..16ed0da6b 100644 --- a/apps/mobile-wallet/src/features/transactionsDisplay/TransactionModal.tsx +++ b/apps/mobile-wallet/src/features/transactionsDisplay/TransactionModal.tsx @@ -79,7 +79,7 @@ const TransactionModal = withModal(({ id, tx }) => { {status.text} - + {isMoved && ( diff --git a/apps/mobile-wallet/src/screens/AddressDiscoveryScreen.tsx b/apps/mobile-wallet/src/screens/AddressDiscoveryScreen.tsx index ae360f99a..6c0fa1442 100644 --- a/apps/mobile-wallet/src/screens/AddressDiscoveryScreen.tsx +++ b/apps/mobile-wallet/src/screens/AddressDiscoveryScreen.tsx @@ -141,7 +141,7 @@ const AddressDiscoveryScreen = ({ navigation, route: { params }, ...props }: Scr truncate isLast={index === addresses.length - 1} > - + ))} @@ -171,7 +171,7 @@ const AddressDiscoveryScreen = ({ navigation, route: { params }, ...props }: Scr isLast={index === discoveredAddresses.length - 1} > - +