From d24d5de426b2102896639e6d5de1f63943bb8c38 Mon Sep 17 00:00:00 2001 From: Leonardo Lima Date: Mon, 22 Jan 2024 12:45:20 +0000 Subject: [PATCH] feat: add language selection on settings page --- src/components/ChooseLanguage.tsx | 103 ++++++++++++++++++++++++++++++ src/components/index.ts | 1 + src/i18n/config.ts | 6 +- src/i18n/en/translations.ts | 12 ++++ src/router.tsx | 2 + src/routes/settings/Language.tsx | 35 ++++++++++ src/routes/settings/Root.tsx | 5 ++ src/routes/settings/index.ts | 1 + src/state/megaStore.tsx | 12 +++- src/utils/index.ts | 1 + src/utils/languages.ts | 24 +++++++ 11 files changed, 198 insertions(+), 4 deletions(-) create mode 100644 src/components/ChooseLanguage.tsx create mode 100644 src/routes/settings/Language.tsx create mode 100644 src/utils/languages.ts diff --git a/src/components/ChooseLanguage.tsx b/src/components/ChooseLanguage.tsx new file mode 100644 index 000000000..c1a03fdf5 --- /dev/null +++ b/src/components/ChooseLanguage.tsx @@ -0,0 +1,103 @@ +import { createForm } from "@modular-forms/solid"; +import { useNavigate } from "@solidjs/router"; +import { createSignal, For, Show } from "solid-js"; + +import { Button, ExternalLink, InfoBox, NiceP, VStack } from "~/components"; +import { useI18n } from "~/i18n/context"; +import { useMegaStore } from "~/state/megaStore"; +import { + Language, + eify, + timeout, + LANGUAGE_OPTIONS, + EN_OPTION +} from "~/utils"; + +type ChooseLanguageForm = { + selectedLanguage: string; +}; + +const COMBINED_OPTIONS: Language[] = [...LANGUAGE_OPTIONS]; + +export function ChooseLanguage() { + const i18n = useI18n(); + const [error, setError] = createSignal(); + const [_state, actions] = useMegaStore(); + const [loading, setLoading] = createSignal(false); + const navigate = useNavigate(); + + function findLanguageByValue(value: string) { + return ( + COMBINED_OPTIONS.find((language) => language.value === value) ?? + EN_OPTION + ); + } + + const [_chooseLanguageForm, { Form, Field }] = + createForm({ + initialValues: { + selectedLanguage: "" + }, + validate: (values) => { + const errors: Record = {}; + if (values.selectedLanguage === undefined) { + errors.selectedLanguage = i18n.t( + "settings.currency.error_unsupported_currency" + ); + } + return errors; + } + }); + + const handleFormSubmit = async (f: ChooseLanguageForm) => { + setLoading(true); + try { + actions.saveLanguage(findLanguageByValue(f.selectedLanguage).label); + + await i18n.changeLanguage(findLanguageByValue(f.selectedLanguage).label); + + await timeout(1000); + navigate("/"); + } catch (e) { + console.error(e); + setError(eify(e)); + setLoading(false); + } + }; + + return ( + +
+ {i18n.t("settings.language.caption")} + + {i18n.t("settings.language.request_language_support_link")} + +
+ + + {(field, props) => ( + + )} + + + {error()?.message} + +
+ + + + + ); +} diff --git a/src/components/index.ts b/src/components/index.ts index 241728ba5..ec1046430 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -9,6 +9,7 @@ export * from "./AmountEditable"; export * from "./BalanceBox"; export * from "./BetaWarningModal"; export * from "./ChooseCurrency"; +export * from "./ChooseLanguage"; export * from "./ContactEditor"; export * from "./ContactForm"; export * from "./ContactViewer"; diff --git a/src/i18n/config.ts b/src/i18n/config.ts index 418594695..c69f1923a 100644 --- a/src/i18n/config.ts +++ b/src/i18n/config.ts @@ -32,8 +32,10 @@ const i18n = use(LanguageDetector).init( fallbackNS: false, debug: true, detection: { - order: ["querystring", "navigator", "htmlTag"], - lookupQuerystring: "lang" + order: ["localStorage", "querystring", "navigator", "htmlTag"], + lookupQuerystring: "lang", + lookupLocalStorage: "i18nextLng", + caches: ['localStorage'], }, resources: resources // FIXME: this doesn't work when deployed diff --git a/src/i18n/en/translations.ts b/src/i18n/en/translations.ts index 617863790..e6437eaf3 100644 --- a/src/i18n/en/translations.ts +++ b/src/i18n/en/translations.ts @@ -425,6 +425,18 @@ export default { "Request support for more currencies", error_unsupported_currency: "Please Select a supported currency." }, + // TODO: (@leonardo) check which language fields would be really needed + language: { + title: "Language", + caption: "Choose your preferred language", + select_language: "Select Language", + select_language_label: "Language", + select_language_caption: + "Choosing a new currency will change the wallet language, ignoring current browser language", + request_language_support_link: + "Request support for more languages", + error_unsupported_language: "Please Select a supported language." + }, lnurl_auth: { title: "LNURL Auth", auth: "Auth", diff --git a/src/router.tsx b/src/router.tsx index 8eea78a7a..3bc9801ba 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -23,6 +23,7 @@ import { Channels, Connections, Currency, + Language, EmergencyKit, Encrypt, Gift, @@ -109,6 +110,7 @@ export function Router() { + diff --git a/src/routes/settings/Language.tsx b/src/routes/settings/Language.tsx new file mode 100644 index 000000000..136ff7367 --- /dev/null +++ b/src/routes/settings/Language.tsx @@ -0,0 +1,35 @@ +import { + BackLink, + Card, + ChooseLanguage, + DefaultMain, + LargeHeader, + MutinyWalletGuard, + NavBar, + SafeArea +} from "~/components"; +import { useI18n } from "~/i18n/context"; + +export function Language() { + const i18n = useI18n(); + return ( + + + + + + {i18n.t("settings.language.title")} + + + + +
+ + + + + ); +} diff --git a/src/routes/settings/Root.tsx b/src/routes/settings/Root.tsx index ee1417747..d7e5e74c8 100644 --- a/src/routes/settings/Root.tsx +++ b/src/routes/settings/Root.tsx @@ -118,6 +118,11 @@ export function Settings() { text: i18n.t("settings.currency.title"), caption: i18n.t("settings.currency.caption") }, + { + href: "/settings/language", + text: i18n.t("settings.language.title"), + caption: i18n.t("settings.language.caption") + }, { href: "/settings/servers", text: i18n.t("settings.servers.title"), diff --git a/src/routes/settings/index.ts b/src/routes/settings/index.ts index 0cf20685f..3de23b8b7 100644 --- a/src/routes/settings/index.ts +++ b/src/routes/settings/index.ts @@ -4,6 +4,7 @@ export * from "./Backup"; export * from "./Channels"; export * from "./Connections"; export * from "./Currency"; +export * from "./Language"; export * from "./EmergencyKit"; export * from "./Encrypt"; export * from "./Gift"; diff --git a/src/state/megaStore.tsx b/src/state/megaStore.tsx index f4f11e7a6..f70cc5a90 100644 --- a/src/state/megaStore.tsx +++ b/src/state/megaStore.tsx @@ -30,6 +30,7 @@ import { BTC_OPTION, Currency, eify, + Language, subscriptionValid, USD_OPTION } from "~/utils"; @@ -54,6 +55,7 @@ type MegaStore = [ price_sync_backoff_multiple?: number; price: number; fiat: Currency; + lang?: string; has_backed_up: boolean; wallet_loading: boolean; setup_error?: Error; @@ -83,6 +85,7 @@ type MegaStore = [ checkForSubscription(justPaid?: boolean): Promise; fetchPrice(fiat: Currency): Promise; saveFiat(fiat: Currency): void; + saveLanguage(lang: string): void; saveNpub(npub: string): void; setPreferredInvoiceType( type: "unified" | "lightning" | "onchain" @@ -132,6 +135,7 @@ export const Provider: ParentComponent = (props) => { load_stage: "fresh" as LoadStage, settings: undefined as MutinyWalletSettingStrings | undefined, safe_mode: searchParams.safe_mode === "true", + lang: localStorage.getItem("lang") || undefined, npub: localStorage.getItem("npub") || undefined, preferredInvoiceType: "unified" as "unified" | "lightning" | "onchain", betaWarned: localStorage.getItem("betaWarned") === "true", @@ -332,6 +336,10 @@ export const Provider: ParentComponent = (props) => { fiat: fiat }); }, + saveLanguage(lang: string) { + localStorage.setItem("i18nextLng", lang); + setState({ lang }); + }, saveNpub(npub: string) { localStorage.setItem("npub", npub); setState({ npub }); @@ -386,7 +394,7 @@ export const Provider: ParentComponent = (props) => { if (result.value?.fedimint_invite) { navigate( "/settings/federations?fedimint_invite=" + - encodeURIComponent(result.value?.fedimint_invite) + encodeURIComponent(result.value?.fedimint_invite) ); actions.setScanResult(undefined); } @@ -397,7 +405,7 @@ export const Provider: ParentComponent = (props) => { ); navigate( "/settings/connections/?nwa=" + - encodeURIComponent(result.value?.nostr_wallet_auth) + encodeURIComponent(result.value?.nostr_wallet_auth) ); } } diff --git a/src/utils/index.ts b/src/utils/index.ts index 754f09220..cf118bb35 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -16,6 +16,7 @@ export * from "./vibrate"; export * from "./openLinkProgrammatically"; export * from "./nostr"; export * from "./currencies"; +export * from "./languages"; export * from "./bech32"; export * from "./keypad"; export * from "./debounce"; diff --git a/src/utils/languages.ts b/src/utils/languages.ts new file mode 100644 index 000000000..012274b95 --- /dev/null +++ b/src/utils/languages.ts @@ -0,0 +1,24 @@ +export interface Language { + value: string; + label: string; + hasSymbol?: string; +} + +export const EN_OPTION: Language = { + value: "English", + label: "en", + hasSymbol: "EN" +}; + + +export const LANGUAGE_OPTIONS: Language[] = [ + { + value: "Português", + label: "pt", + hasSymbol: "PT", + }, { + value: "Korean", + label: "ko", + hasSymbol: "KO", + } +];