diff --git a/android/app/build.gradle b/android/app/build.gradle
index 8a8948f1..718777d3 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -7,8 +7,8 @@ android {
applicationId "com.mutinywallet.mutinywallet"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 49
- versionName "0.5.8"
+ versionCode 50
+ versionName "0.5.9"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj
index 0337b0fb..d9398078 100644
--- a/ios/App/App.xcodeproj/project.pbxproj
+++ b/ios/App/App.xcodeproj/project.pbxproj
@@ -360,7 +360,7 @@
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- MARKETING_VERSION = 1.5.8;
+ MARKETING_VERSION = 1.5.9;
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = com.mutinywallet.mutiny;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -387,7 +387,7 @@
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
- MARKETING_VERSION = 1.5.8;
+ MARKETING_VERSION = 1.5.9;
PRODUCT_BUNDLE_IDENTIFIER = com.mutinywallet.mutiny;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
diff --git a/package.json b/package.json
index 52fbf3fe..3f9510c8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "mutiny-wallet",
- "version": "0.5.8",
+ "version": "0.5.9",
"license": "MIT",
"packageManager": "pnpm@8.6.6",
"scripts": {
@@ -55,7 +55,7 @@
"@kobalte/core": "^0.9.8",
"@kobalte/tailwindcss": "^0.5.0",
"@modular-forms/solid": "^0.18.1",
- "@mutinywallet/mutiny-wasm": "0.5.8",
+ "@mutinywallet/mutiny-wasm": "0.5.9",
"@mutinywallet/waila-wasm": "^0.2.6",
"@solid-primitives/upload": "^0.0.111",
"@solidjs/meta": "^0.29.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1093e522..14598a6b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -54,8 +54,8 @@ importers:
specifier: ^0.18.1
version: 0.18.1(solid-js@1.8.5)
'@mutinywallet/mutiny-wasm':
- specifier: 0.5.8
- version: 0.5.8
+ specifier: 0.5.9
+ version: 0.5.9
'@mutinywallet/waila-wasm':
specifier: ^0.2.6
version: 0.2.6
@@ -2570,8 +2570,8 @@ packages:
solid-js: 1.8.5
dev: false
- /@mutinywallet/mutiny-wasm@0.5.8:
- resolution: {integrity: sha512-Bwx3sMXuMiE876Kk1FI9uZoJMPkHc5RGFmUHsFeSMMGDxC2YLO5N5ilaIIig89ZbAYcBi7Y1N4Nq/2nZFcM+9A==}
+ /@mutinywallet/mutiny-wasm@0.5.9:
+ resolution: {integrity: sha512-skSSpMprn/iA6Zsk092S1UVCkgjaCfXZXdvzVahFLDDS/89GtxyHtSsY64Oy3KFCULB6X+UfFp9nRFHtWA7sIw==}
dev: false
/@mutinywallet/waila-wasm@0.2.6:
diff --git a/src/components/ActivityDetailsModal.tsx b/src/components/ActivityDetailsModal.tsx
index f9fffc10..3c29bdd3 100644
--- a/src/components/ActivityDetailsModal.tsx
+++ b/src/components/ActivityDetailsModal.tsx
@@ -571,8 +571,8 @@ export function ActivityDetailsModal(props: {
-
-
+
+
-
-
+
+
diff --git a/src/components/ChooseCurrency.tsx b/src/components/ChooseCurrency.tsx
index 3134282e..024e31ad 100644
--- a/src/components/ChooseCurrency.tsx
+++ b/src/components/ChooseCurrency.tsx
@@ -23,7 +23,7 @@ const COMBINED_OPTIONS: Currency[] = [USD_OPTION, BTC_OPTION, ...FIAT_OPTIONS];
export function ChooseCurrency() {
const i18n = useI18n();
const [error, setError] = createSignal();
- const [_state, actions] = useMegaStore();
+ const [state, actions] = useMegaStore();
const [loading, setLoading] = createSignal(false);
const navigate = useNavigate();
@@ -37,7 +37,7 @@ export function ChooseCurrency() {
const [_chooseCurrencyForm, { Form, Field }] =
createForm({
initialValues: {
- fiatCurrency: ""
+ fiatCurrency: state.fiat.value
},
validate: (values) => {
const errors: Record = {};
@@ -82,7 +82,12 @@ export function ChooseCurrency() {
>
{({ value, label }) => (
-
+
)}
diff --git a/src/components/ChooseLanguage.tsx b/src/components/ChooseLanguage.tsx
new file mode 100644
index 00000000..360529da
--- /dev/null
+++ b/src/components/ChooseLanguage.tsx
@@ -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();
+ const [state, actions] = useMegaStore();
+ const [loading, setLoading] = createSignal(false);
+ const navigate = useNavigate();
+
+ const [_chooseLanguageForm, { Form, Field }] =
+ createForm({
+ initialValues: {
+ selectedLanguage: state.lang ?? ""
+ },
+ validate: (values) => {
+ const errors: Record = {};
+ 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 (
+
+
+
+ );
+}
diff --git a/src/components/Logs.tsx b/src/components/Logs.tsx
index ec31ab96..bd53cee9 100644
--- a/src/components/Logs.tsx
+++ b/src/components/Logs.tsx
@@ -1,15 +1,7 @@
import { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { createSignal, Show } from "solid-js";
-import {
- Button,
- InfoBox,
- InnerCard,
- NiceP,
- SimpleDialog,
- TextField,
- VStack
-} from "~/components";
+import { Button, InfoBox, InnerCard, NiceP, VStack } from "~/components";
import { useI18n } from "~/i18n/context";
import { downloadTextFile, eify } from "~/utils";
@@ -18,13 +10,11 @@ export function Logs() {
// Create state for errors, password and dialog visibility
const [error, setError] = createSignal();
- const [exportDecrypt, setExportDecrypt] = createSignal(false);
- const [password, setPassword] = createSignal();
async function handleSave() {
try {
setError(undefined);
- const logs = await MutinyWallet.get_logs(password()); // Use password if required
+ const logs = await MutinyWallet.get_logs();
await downloadTextFile(
logs.join("") || "",
"mutiny-logs.txt",
@@ -32,26 +22,10 @@ export function Logs() {
);
} catch (e) {
console.error(e);
- const err = eify(e);
- if (err.message === "Incorrect password entered.") {
- setExportDecrypt(true);
- } else {
- setError(err);
- }
+ setError(eify(e));
}
}
- function savePassword(e: Event) {
- e.preventDefault();
- handleSave();
- setPassword(undefined); // clear password after use
- setExportDecrypt(false); // close the dialog
- }
-
- function noop() {
- // noop
- }
-
return (
@@ -65,33 +39,6 @@ export function Logs() {
{error()?.message}
-
-
-
);
}
diff --git a/src/components/index.ts b/src/components/index.ts
index 3b6a779f..287c4e7d 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 41859469..1897837f 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 44463015..4349a78b 100644
--- a/src/i18n/en/translations.ts
+++ b/src/i18n/en/translations.ts
@@ -432,6 +432,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",
diff --git a/src/router.tsx b/src/router.tsx
index 557331ee..bb8407c6 100644
--- a/src/router.tsx
+++ b/src/router.tsx
@@ -27,6 +27,7 @@ import {
EmergencyKit,
Encrypt,
Gift,
+ Language,
ManageFederations,
Plus,
Restore,
@@ -111,6 +112,7 @@ export function Router() {
+
diff --git a/src/routes/settings/Language.tsx b/src/routes/settings/Language.tsx
new file mode 100644
index 00000000..136ff736
--- /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/ManageFederations.tsx b/src/routes/settings/ManageFederations.tsx
index e3860dea..9a2a5b67 100644
--- a/src/routes/settings/ManageFederations.tsx
+++ b/src/routes/settings/ManageFederations.tsx
@@ -5,6 +5,7 @@ import {
setValue,
SubmitHandler
} from "@modular-forms/solid";
+import { FederationBalance } from "@mutinywallet/mutiny-wasm";
import { useSearchParams } from "@solidjs/router";
import {
createResource,
@@ -51,7 +52,15 @@ export type MutinyFederationIdentity = {
federation_expiry_timestamp: number;
};
-function AddFederationForm() {
+type RefetchType = (
+ info?: unknown
+) =>
+ | FederationBalance[]
+ | Promise
+ | null
+ | undefined;
+
+function AddFederationForm(props: { refetch?: RefetchType }) {
const i18n = useI18n();
const [state, actions] = useMegaStore();
const [error, setError] = createSignal();
@@ -88,6 +97,9 @@ function AddFederationForm() {
i18n.t("settings.manage_federations.federation_added_success")
);
await actions.refreshFederations();
+ if (props.refetch) {
+ await props.refetch();
+ }
reset(feedbackForm);
} catch (e) {
console.error("Error submitting federation:", e);
@@ -225,7 +237,7 @@ export function ManageFederations() {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
- const [balances] = createResource(async () => {
+ const [balances, { refetch }] = createResource(async () => {
try {
const balances =
await state.mutiny_wallet?.get_federation_balances();
@@ -253,7 +265,7 @@ export function ManageFederations() {
{i18n.t("settings.manage_federations.learn_more")}
-
+
diff --git a/src/routes/settings/Root.tsx b/src/routes/settings/Root.tsx
index ee141774..d7e5e74c 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 0cf20685..3de23b8b 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 68c90417..e4e8b9e7 100644
--- a/src/state/megaStore.tsx
+++ b/src/state/megaStore.tsx
@@ -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;
@@ -83,6 +84,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 +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",
@@ -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 });
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 754f0922..cf118bb3 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 00000000..1a7c6978
--- /dev/null
+++ b/src/utils/languages.ts
@@ -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"
+ }
+];