Skip to content
This repository was archived by the owner on Apr 21, 2025. It is now read-only.

Commit 605a19a

Browse files
committed
feat: add language selection on settings page
1 parent 28b48ae commit 605a19a

File tree

11 files changed

+189
-2
lines changed

11 files changed

+189
-2
lines changed

src/components/ChooseLanguage.tsx

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { createForm } from "@modular-forms/solid";
2+
import { useNavigate } from "@solidjs/router";
3+
import { createSignal, For, Show } from "solid-js";
4+
5+
import { Button, ExternalLink, InfoBox, NiceP, VStack } from "~/components";
6+
import { useI18n } from "~/i18n/context";
7+
import { useMegaStore } from "~/state/megaStore";
8+
import { eify, EN_OPTION, Language, LANGUAGE_OPTIONS, timeout } from "~/utils";
9+
10+
type ChooseLanguageForm = {
11+
selectedLanguage: string;
12+
};
13+
14+
const COMBINED_OPTIONS: Language[] = [EN_OPTION, ...LANGUAGE_OPTIONS];
15+
16+
export function ChooseLanguage() {
17+
const i18n = useI18n();
18+
const [error, setError] = createSignal<Error>();
19+
const [state, actions] = useMegaStore();
20+
const [loading, setLoading] = createSignal(false);
21+
const navigate = useNavigate();
22+
23+
function findLanguageByValue(value: string) {
24+
return (
25+
COMBINED_OPTIONS.find((language) => language.value === value) ??
26+
EN_OPTION
27+
);
28+
}
29+
30+
const [_chooseLanguageForm, { Form, Field }] =
31+
createForm<ChooseLanguageForm>({
32+
initialValues: {
33+
selectedLanguage: findLanguageByValue(state.lang ?? "").value
34+
},
35+
validate: (values) => {
36+
const errors: Record<string, string> = {};
37+
if (values.selectedLanguage === undefined) {
38+
errors.selectedLanguage = i18n.t(
39+
"settings.language.error_unsupported_language"
40+
);
41+
}
42+
return errors;
43+
}
44+
});
45+
46+
const handleFormSubmit = async (f: ChooseLanguageForm) => {
47+
setLoading(true);
48+
const lang = findLanguageByValue(f.selectedLanguage);
49+
try {
50+
actions.saveLanguage(lang.value);
51+
52+
await i18n.changeLanguage(lang.shortName);
53+
54+
await timeout(1000);
55+
navigate("/");
56+
} catch (e) {
57+
console.error(e);
58+
setError(eify(e));
59+
setLoading(false);
60+
}
61+
};
62+
63+
return (
64+
<VStack>
65+
<Form onSubmit={handleFormSubmit} class="flex flex-col gap-4">
66+
<NiceP>{i18n.t("settings.language.caption")}</NiceP>
67+
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/issues/new">
68+
{i18n.t("settings.language.request_language_support_link")}
69+
</ExternalLink>
70+
<div />
71+
<VStack>
72+
<Field name="selectedLanguage">
73+
{(field, props) => (
74+
<select
75+
{...props}
76+
value={field.value}
77+
class="w-full rounded-lg bg-m-grey-750 py-2 pl-4 pr-12 text-base font-normal text-white"
78+
>
79+
<For each={COMBINED_OPTIONS}>
80+
{({ value, shortName }) => (
81+
<option
82+
selected={field.value === shortName}
83+
value={value}
84+
>
85+
{value}
86+
</option>
87+
)}
88+
</For>
89+
</select>
90+
)}
91+
</Field>
92+
<Show when={error()}>
93+
<InfoBox accent="red">{error()?.message}</InfoBox>
94+
</Show>
95+
<div />
96+
<Button intent="blue" loading={loading()}>
97+
{i18n.t("settings.language.select_language")}
98+
</Button>
99+
</VStack>
100+
</Form>
101+
</VStack>
102+
);
103+
}

src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export * from "./AmountEditable";
99
export * from "./BalanceBox";
1010
export * from "./BetaWarningModal";
1111
export * from "./ChooseCurrency";
12+
export * from "./ChooseLanguage";
1213
export * from "./ContactEditor";
1314
export * from "./ContactForm";
1415
export * from "./ContactViewer";

src/i18n/config.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ const i18n = use(LanguageDetector).init(
3232
fallbackNS: false,
3333
debug: true,
3434
detection: {
35-
order: ["querystring", "navigator", "htmlTag"],
36-
lookupQuerystring: "lang"
35+
order: ["localStorage", "querystring", "navigator", "htmlTag"],
36+
lookupQuerystring: "lang",
37+
lookupLocalStorage: "i18nextLng",
38+
caches: ["localStorage"]
3739
},
3840
resources: resources
3941
// FIXME: this doesn't work when deployed

src/i18n/en/translations.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,16 @@ export default {
425425
"Request support for more currencies",
426426
error_unsupported_currency: "Please Select a supported currency."
427427
},
428+
language: {
429+
title: "Language",
430+
caption: "Choose your preferred language",
431+
select_language: "Select Language",
432+
select_language_label: "Language",
433+
select_language_caption:
434+
"Choosing a new currency will change the wallet language, ignoring current browser language",
435+
request_language_support_link: "Request support for more languages",
436+
error_unsupported_language: "Please Select a supported language."
437+
},
428438
lnurl_auth: {
429439
title: "LNURL Auth",
430440
auth: "Auth",

src/router.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
EmergencyKit,
2727
Encrypt,
2828
Gift,
29+
Language,
2930
ManageFederations,
3031
Plus,
3132
Restore,
@@ -109,6 +110,7 @@ export function Router() {
109110
<Route path="/channels" component={Channels} />
110111
<Route path="/connections" component={Connections} />
111112
<Route path="/currency" component={Currency} />
113+
<Route path="/language" component={Language} />
112114
<Route path="/emergencykit" component={EmergencyKit} />
113115
<Route path="/encrypt" component={Encrypt} />
114116
<Route path="/gift" component={Gift} />

src/routes/settings/Language.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {
2+
BackLink,
3+
Card,
4+
ChooseLanguage,
5+
DefaultMain,
6+
LargeHeader,
7+
MutinyWalletGuard,
8+
NavBar,
9+
SafeArea
10+
} from "~/components";
11+
import { useI18n } from "~/i18n/context";
12+
13+
export function Language() {
14+
const i18n = useI18n();
15+
return (
16+
<MutinyWalletGuard>
17+
<SafeArea>
18+
<DefaultMain>
19+
<BackLink
20+
href="/settings"
21+
title={i18n.t("settings.header")}
22+
/>
23+
<LargeHeader>
24+
{i18n.t("settings.language.title")}
25+
</LargeHeader>
26+
<Card title={i18n.t("settings.language.select_language")}>
27+
<ChooseLanguage />
28+
</Card>
29+
<div class="h-full" />
30+
</DefaultMain>
31+
<NavBar activeTab="settings" />
32+
</SafeArea>
33+
</MutinyWalletGuard>
34+
);
35+
}

src/routes/settings/Root.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ export function Settings() {
118118
text: i18n.t("settings.currency.title"),
119119
caption: i18n.t("settings.currency.caption")
120120
},
121+
{
122+
href: "/settings/language",
123+
text: i18n.t("settings.language.title"),
124+
caption: i18n.t("settings.language.caption")
125+
},
121126
{
122127
href: "/settings/servers",
123128
text: i18n.t("settings.servers.title"),

src/routes/settings/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from "./Backup";
44
export * from "./Channels";
55
export * from "./Connections";
66
export * from "./Currency";
7+
export * from "./Language";
78
export * from "./EmergencyKit";
89
export * from "./Encrypt";
910
export * from "./Gift";

src/state/megaStore.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ type MegaStore = [
5454
price_sync_backoff_multiple?: number;
5555
price: number;
5656
fiat: Currency;
57+
lang?: string;
5758
has_backed_up: boolean;
5859
wallet_loading: boolean;
5960
setup_error?: Error;
@@ -83,6 +84,7 @@ type MegaStore = [
8384
checkForSubscription(justPaid?: boolean): Promise<void>;
8485
fetchPrice(fiat: Currency): Promise<number>;
8586
saveFiat(fiat: Currency): void;
87+
saveLanguage(lang: string): void;
8688
saveNpub(npub: string): void;
8789
setPreferredInvoiceType(
8890
type: "unified" | "lightning" | "onchain"
@@ -132,6 +134,7 @@ export const Provider: ParentComponent = (props) => {
132134
load_stage: "fresh" as LoadStage,
133135
settings: undefined as MutinyWalletSettingStrings | undefined,
134136
safe_mode: searchParams.safe_mode === "true",
137+
lang: localStorage.getItem("i18nexLng") || undefined,
135138
npub: localStorage.getItem("npub") || undefined,
136139
preferredInvoiceType: "unified" as "unified" | "lightning" | "onchain",
137140
betaWarned: localStorage.getItem("betaWarned") === "true",
@@ -332,6 +335,10 @@ export const Provider: ParentComponent = (props) => {
332335
fiat: fiat
333336
});
334337
},
338+
saveLanguage(lang: string) {
339+
localStorage.setItem("i18nextLng", lang);
340+
setState({ lang });
341+
},
335342
saveNpub(npub: string) {
336343
localStorage.setItem("npub", npub);
337344
setState({ npub });

src/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export * from "./vibrate";
1616
export * from "./openLinkProgrammatically";
1717
export * from "./nostr";
1818
export * from "./currencies";
19+
export * from "./languages";
1920
export * from "./bech32";
2021
export * from "./keypad";
2122
export * from "./debounce";

0 commit comments

Comments
 (0)