Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add language selection on settings page #842

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/components/ChooseLanguage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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 { eify, EN_OPTION, Language, LANGUAGE_OPTIONS, timeout } from "~/utils";

type ChooseLanguageForm = {
selectedLanguage: string;
};

const COMBINED_OPTIONS: Language[] = [EN_OPTION, ...LANGUAGE_OPTIONS];

export function ChooseLanguage() {
const i18n = useI18n();
const [error, setError] = createSignal<Error>();
const [state, actions] = useMegaStore();
const [loading, setLoading] = createSignal(false);
const navigate = useNavigate();

const [_chooseLanguageForm, { Form, Field }] =
createForm<ChooseLanguageForm>({
initialValues: {
selectedLanguage: state.lang ?? ""
},
validate: (values) => {
const errors: Record<string, string> = {};
if (values.selectedLanguage === undefined) {
errors.selectedLanguage = i18n.t(
"settings.language.error_unsupported_language"
);
}
return errors;
}
});

const handleFormSubmit = async (f: ChooseLanguageForm) => {
setLoading(true);
try {
actions.saveLanguage(f.selectedLanguage);

await i18n.changeLanguage(f.selectedLanguage);

await timeout(1000);
navigate("/");
} catch (e) {
console.error(e);
setError(eify(e));
setLoading(false);
}
};

return (
<VStack>
<Form onSubmit={handleFormSubmit} class="flex flex-col gap-4">
<NiceP>{i18n.t("settings.language.caption")}</NiceP>
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/issues/new">
{i18n.t("settings.language.request_language_support_link")}
</ExternalLink>
<div />
<VStack>
<Field name="selectedLanguage">
{(field, props) => (
<select
{...props}
value={field.value}
class="w-full rounded-lg bg-m-grey-750 py-2 pl-4 pr-12 text-base font-normal text-white"
>
<For each={COMBINED_OPTIONS}>
{({ value, shortName }) => (
<option
selected={field.value === shortName}
value={shortName}
>
{value}
</option>
)}
</For>
</select>
)}
</Field>
<Show when={error()}>
<InfoBox accent="red">{error()?.message}</InfoBox>
</Show>
<div />
<Button intent="blue" loading={loading()}>
{i18n.t("settings.language.select_language")}
</Button>
</VStack>
</Form>
</VStack>
);
}
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
6 changes: 4 additions & 2 deletions src/i18n/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions src/i18n/en/translations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,16 @@ export default {
"Request support for more currencies",
error_unsupported_currency: "Please Select a supported currency."
},
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",
Expand Down
2 changes: 2 additions & 0 deletions src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
EmergencyKit,
Encrypt,
Gift,
Language,
ManageFederations,
Plus,
Restore,
Expand Down Expand Up @@ -109,6 +110,7 @@ export function Router() {
<Route path="/channels" component={Channels} />
<Route path="/connections" component={Connections} />
<Route path="/currency" component={Currency} />
<Route path="/language" component={Language} />
<Route path="/emergencykit" component={EmergencyKit} />
<Route path="/encrypt" component={Encrypt} />
<Route path="/gift" component={Gift} />
Expand Down
35 changes: 35 additions & 0 deletions src/routes/settings/Language.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<MutinyWalletGuard>
<SafeArea>
<DefaultMain>
<BackLink
href="/settings"
title={i18n.t("settings.header")}
/>
<LargeHeader>
{i18n.t("settings.language.title")}
</LargeHeader>
<Card title={i18n.t("settings.language.select_language")}>
<ChooseLanguage />
</Card>
<div class="h-full" />
</DefaultMain>
<NavBar activeTab="settings" />
</SafeArea>
</MutinyWalletGuard>
);
}
5 changes: 5 additions & 0 deletions src/routes/settings/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
1 change: 1 addition & 0 deletions src/routes/settings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
7 changes: 7 additions & 0 deletions src/state/megaStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type MegaStore = [
price_sync_backoff_multiple?: number;
price: number;
fiat: Currency;
lang?: string;
has_backed_up: boolean;
wallet_loading: boolean;
setup_error?: Error;
Expand Down Expand Up @@ -83,6 +84,7 @@ type MegaStore = [
checkForSubscription(justPaid?: boolean): Promise<void>;
fetchPrice(fiat: Currency): Promise<number>;
saveFiat(fiat: Currency): void;
saveLanguage(lang: string): void;
saveNpub(npub: string): void;
setPreferredInvoiceType(
type: "unified" | "lightning" | "onchain"
Expand Down Expand Up @@ -132,6 +134,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("i18nexLng") || undefined,
npub: localStorage.getItem("npub") || undefined,
preferredInvoiceType: "unified" as "unified" | "lightning" | "onchain",
betaWarned: localStorage.getItem("betaWarned") === "true",
Expand Down Expand Up @@ -332,6 +335,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 });
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
20 changes: 20 additions & 0 deletions src/utils/languages.ts
Copy link
Collaborator

@benalleng benalleng Feb 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from the element chat we came to some consensus that we might want to change the key names for thie Language interface as it might be a little confusing with the value being the "label" in the ui and the label being the "value" sent to i18next. @futurepaul suggested we replace label with shortName

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export interface Language {
value: string;
shortName: string;
}

export const EN_OPTION: Language = {
value: "English",
shortName: "en"
};

export const LANGUAGE_OPTIONS: Language[] = [
{
value: "Português",
shortName: "pt"
},
{
value: "Korean",
shortName: "ko"
}
];
Loading