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

Commit 2b57676

Browse files
committed
wip(feat): add language selection on settings page
1 parent 28b48ae commit 2b57676

File tree

11 files changed

+197
-4
lines changed

11 files changed

+197
-4
lines changed

src/components/ChooseLanguage.tsx

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

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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,18 @@ export default {
425425
"Request support for more currencies",
426426
error_unsupported_currency: "Please Select a supported currency."
427427
},
428+
// TODO: (@leonardo) check which language fields would be really needed
429+
language: {
430+
title: "Language",
431+
caption: "Choose your preferred language",
432+
select_language: "Select Language",
433+
select_language_label: "Language",
434+
select_language_caption:
435+
"Choosing a new currency will change the wallet language, ignoring current browser language",
436+
request_language_support_link:
437+
"Request support for more languages",
438+
error_unsupported_language: "Please Select a supported language."
439+
},
428440
lnurl_auth: {
429441
title: "LNURL Auth",
430442
auth: "Auth",

src/router.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
Channels,
2424
Connections,
2525
Currency,
26+
Language,
2627
EmergencyKit,
2728
Encrypt,
2829
Gift,
@@ -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: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
BTC_OPTION,
3131
Currency,
3232
eify,
33+
Language,
3334
subscriptionValid,
3435
USD_OPTION
3536
} from "~/utils";
@@ -54,6 +55,7 @@ type MegaStore = [
5455
price_sync_backoff_multiple?: number;
5556
price: number;
5657
fiat: Currency;
58+
lang?: string;
5759
has_backed_up: boolean;
5860
wallet_loading: boolean;
5961
setup_error?: Error;
@@ -83,6 +85,7 @@ type MegaStore = [
8385
checkForSubscription(justPaid?: boolean): Promise<void>;
8486
fetchPrice(fiat: Currency): Promise<number>;
8587
saveFiat(fiat: Currency): void;
88+
saveLanguage(lang: string): void;
8689
saveNpub(npub: string): void;
8790
setPreferredInvoiceType(
8891
type: "unified" | "lightning" | "onchain"
@@ -132,6 +135,7 @@ export const Provider: ParentComponent = (props) => {
132135
load_stage: "fresh" as LoadStage,
133136
settings: undefined as MutinyWalletSettingStrings | undefined,
134137
safe_mode: searchParams.safe_mode === "true",
138+
lang: localStorage.getItem("lang") || undefined,
135139
npub: localStorage.getItem("npub") || undefined,
136140
preferredInvoiceType: "unified" as "unified" | "lightning" | "onchain",
137141
betaWarned: localStorage.getItem("betaWarned") === "true",
@@ -332,6 +336,11 @@ export const Provider: ParentComponent = (props) => {
332336
fiat: fiat
333337
});
334338
},
339+
saveLanguage(lang: string) {
340+
localStorage.setItem("i18nextLng", lang);
341+
setState({ lang });
342+
window.location.reload();
343+
},
335344
saveNpub(npub: string) {
336345
localStorage.setItem("npub", npub);
337346
setState({ npub });
@@ -386,7 +395,7 @@ export const Provider: ParentComponent = (props) => {
386395
if (result.value?.fedimint_invite) {
387396
navigate(
388397
"/settings/federations?fedimint_invite=" +
389-
encodeURIComponent(result.value?.fedimint_invite)
398+
encodeURIComponent(result.value?.fedimint_invite)
390399
);
391400
actions.setScanResult(undefined);
392401
}
@@ -397,7 +406,7 @@ export const Provider: ParentComponent = (props) => {
397406
);
398407
navigate(
399408
"/settings/connections/?nwa=" +
400-
encodeURIComponent(result.value?.nostr_wallet_auth)
409+
encodeURIComponent(result.value?.nostr_wallet_auth)
401410
);
402411
}
403412
}

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";

src/utils/languages.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export interface Language {
2+
value: string;
3+
label: string;
4+
hasSymbol?: string;
5+
}
6+
7+
export const EN_OPTION: Language = {
8+
value: "English",
9+
label: "en",
10+
hasSymbol: "EN"
11+
};
12+
13+
14+
export const LANGUAGE_OPTIONS: Language[] = [
15+
{
16+
value: "Português",
17+
label: "pt",
18+
hasSymbol: "PT",
19+
}, {
20+
value: "Korean",
21+
label: "ko",
22+
hasSymbol: "KO",
23+
}
24+
];

0 commit comments

Comments
 (0)