mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-19 23:34:22 +01:00
feat: add i18n
This commit is contained in:
committed by
Paul Miller
parent
8fbda8856f
commit
a5bbbf0c46
@@ -45,6 +45,9 @@
|
|||||||
"@solidjs/router": "^0.8.2",
|
"@solidjs/router": "^0.8.2",
|
||||||
"@thisbeyond/solid-select": "^0.14.0",
|
"@thisbeyond/solid-select": "^0.14.0",
|
||||||
"class-variance-authority": "^0.4.0",
|
"class-variance-authority": "^0.4.0",
|
||||||
|
"i18next": "^22.5.1",
|
||||||
|
"i18next-browser-languagedetector": "^7.0.2",
|
||||||
|
"i18next-http-backend": "^2.2.1",
|
||||||
"nostr-tools": "^1.11.1",
|
"nostr-tools": "^1.11.1",
|
||||||
"qr-scanner": "^1.4.2",
|
"qr-scanner": "^1.4.2",
|
||||||
"solid-js": "^1.7.7",
|
"solid-js": "^1.7.7",
|
||||||
|
|||||||
64
pnpm-lock.yaml
generated
64
pnpm-lock.yaml
generated
@@ -35,6 +35,15 @@ dependencies:
|
|||||||
class-variance-authority:
|
class-variance-authority:
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.0
|
||||||
version: 0.4.0(typescript@4.9.5)
|
version: 0.4.0(typescript@4.9.5)
|
||||||
|
i18next:
|
||||||
|
specifier: ^22.5.1
|
||||||
|
version: 22.5.1
|
||||||
|
i18next-browser-languagedetector:
|
||||||
|
specifier: ^7.0.2
|
||||||
|
version: 7.0.2
|
||||||
|
i18next-http-backend:
|
||||||
|
specifier: ^2.2.1
|
||||||
|
version: 2.2.1
|
||||||
nostr-tools:
|
nostr-tools:
|
||||||
specifier: ^1.11.1
|
specifier: ^1.11.1
|
||||||
version: 1.12.1
|
version: 1.12.1
|
||||||
@@ -2628,6 +2637,14 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.21.9
|
browserslist: 4.21.9
|
||||||
|
|
||||||
|
/cross-fetch@3.1.6:
|
||||||
|
resolution: {integrity: sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==}
|
||||||
|
dependencies:
|
||||||
|
node-fetch: 2.6.11
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- encoding
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cross-spawn@7.0.3:
|
/cross-spawn@7.0.3:
|
||||||
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
|
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -3709,6 +3726,26 @@ packages:
|
|||||||
resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==}
|
resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==}
|
||||||
engines: {node: '>=14.18.0'}
|
engines: {node: '>=14.18.0'}
|
||||||
|
|
||||||
|
/i18next-browser-languagedetector@7.0.2:
|
||||||
|
resolution: {integrity: sha512-5ViaK+gikxfqZ9M3jJ7gJkUzzu/p3HwiqfLoL1bdiL7CUb0IylcTyVLdPaTU3pH5VFWFCiGFuJDg3VkLUikWgg==}
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.22.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/i18next-http-backend@2.2.1:
|
||||||
|
resolution: {integrity: sha512-ZXIdn/8NJIBJ0X4hzXfc3STYxKrCKh1fYjji9HPyIpEJfvTvy8/ZlTl8RuTizzCPj2ZcWrfaecyOMKs6bQ7u5A==}
|
||||||
|
dependencies:
|
||||||
|
cross-fetch: 3.1.6
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- encoding
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/i18next@22.5.1:
|
||||||
|
resolution: {integrity: sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==}
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.22.5
|
||||||
|
dev: false
|
||||||
|
|
||||||
/idb@7.1.1:
|
/idb@7.1.1:
|
||||||
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
|
resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -4225,6 +4262,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
/node-fetch@2.6.11:
|
||||||
|
resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==}
|
||||||
|
engines: {node: 4.x || >=6.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
encoding: ^0.1.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
encoding:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
whatwg-url: 5.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/node-releases@2.0.12:
|
/node-releases@2.0.12:
|
||||||
resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==}
|
resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==}
|
||||||
|
|
||||||
@@ -5159,6 +5208,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
/tr46@0.0.3:
|
||||||
|
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/tr46@1.0.1:
|
/tr46@1.0.1:
|
||||||
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
|
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -5436,10 +5489,21 @@ packages:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
|
|
||||||
|
/webidl-conversions@3.0.1:
|
||||||
|
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/webidl-conversions@4.0.2:
|
/webidl-conversions@4.0.2:
|
||||||
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
|
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/whatwg-url@5.0.0:
|
||||||
|
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||||
|
dependencies:
|
||||||
|
tr46: 0.0.3
|
||||||
|
webidl-conversions: 3.0.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/whatwg-url@7.1.0:
|
/whatwg-url@7.1.0:
|
||||||
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
|
resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { NiceP } from "./layout";
|
import { NiceP } from "./layout";
|
||||||
import { For, Match, Show, Switch, createEffect, createSignal } from "solid-js";
|
import { For, Match, Show, Switch, createEffect, createSignal } from "solid-js";
|
||||||
import { useMegaStore } from "~/state/megaStore";
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
import { Contact } from "@mutinywallet/mutiny-wasm";
|
import { Contact } from "@mutinywallet/mutiny-wasm";
|
||||||
import { ActivityItem, HackActivityType } from "./ActivityItem";
|
import { ActivityItem, HackActivityType } from "./ActivityItem";
|
||||||
import { DetailsIdModal } from "./DetailsModal";
|
import { DetailsIdModal } from "./DetailsModal";
|
||||||
@@ -77,6 +78,7 @@ function UnifiedActivityItem(props: {
|
|||||||
|
|
||||||
export function CombinedActivity(props: { limit?: number }) {
|
export function CombinedActivity(props: { limit?: number }) {
|
||||||
const [state, actions] = useMegaStore();
|
const [state, actions] = useMegaStore();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
const [detailsOpen, setDetailsOpen] = createSignal(false);
|
const [detailsOpen, setDetailsOpen] = createSignal(false);
|
||||||
const [detailsKind, setDetailsKind] = createSignal<HackActivityType>();
|
const [detailsKind, setDetailsKind] = createSignal<HackActivityType>();
|
||||||
@@ -115,7 +117,7 @@ export function CombinedActivity(props: { limit?: number }) {
|
|||||||
<Switch>
|
<Switch>
|
||||||
<Match when={state.activity.length === 0}>
|
<Match when={state.activity.length === 0}>
|
||||||
<div class="w-full text-center pb-4">
|
<div class="w-full text-center pb-4">
|
||||||
<NiceP>Receive some sats to get started</NiceP>
|
<NiceP>{i18n.t("receive_some_sats_to_get_started")}</NiceP>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
<Match
|
<Match
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import { InfoBox } from "./InfoBox";
|
|||||||
import { Network } from "~/logic/mutinyWalletSetup";
|
import { Network } from "~/logic/mutinyWalletSetup";
|
||||||
import { FeesModal } from "./MoreInfoModal";
|
import { FeesModal } from "./MoreInfoModal";
|
||||||
import { useNavigate } from "@solidjs/router";
|
import { useNavigate } from "@solidjs/router";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
const CHARACTERS = [
|
const CHARACTERS = [
|
||||||
"1",
|
"1",
|
||||||
@@ -193,6 +194,7 @@ export const AmountEditable: ParentComponent<{
|
|||||||
maxAmountSats?: bigint;
|
maxAmountSats?: bigint;
|
||||||
fee?: string;
|
fee?: string;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
|
const i18n = useI18n();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isOpen, setIsOpen] = createSignal(props.initialOpen);
|
const [isOpen, setIsOpen] = createSignal(props.initialOpen);
|
||||||
const [state, _actions] = useMegaStore();
|
const [state, _actions] = useMegaStore();
|
||||||
@@ -236,7 +238,7 @@ export const AmountEditable: ParentComponent<{
|
|||||||
if (network === "bitcoin") {
|
if (network === "bitcoin") {
|
||||||
return "Your first lightning receive needs to be 50,000 sats or greater. A setup fee will be deducted from the requested amount.";
|
return "Your first lightning receive needs to be 50,000 sats or greater. A setup fee will be deducted from the requested amount.";
|
||||||
} else {
|
} else {
|
||||||
return "Your first lightning receive needs to be 10,000 sats or greater. A setup fee will be deducted from the requested amount.";
|
return i18n.t("amount_editable_first_payment_10k_or_greater");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,7 +401,7 @@ export const AmountEditable: ParentComponent<{
|
|||||||
<Show
|
<Show
|
||||||
when={localSats() !== "0"}
|
when={localSats() !== "0"}
|
||||||
fallback={
|
fallback={
|
||||||
<div class="inline-block font-semibold">Set amount</div>
|
<div class="inline-block font-semibold">{i18n.t("set_amount")}</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<InlineAmount amount={maxOrLocalSats()} />
|
<InlineAmount amount={maxOrLocalSats()} />
|
||||||
@@ -526,7 +528,7 @@ export const AmountEditable: ParentComponent<{
|
|||||||
class="w-full flex-none"
|
class="w-full flex-none"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
>
|
>
|
||||||
Set Amount
|
{i18n.t("set_amount")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Content>
|
</Dialog.Content>
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ import { BetaWarningModal } from "~/components/BetaWarningModal";
|
|||||||
import settings from "~/assets/icons/settings.svg";
|
import settings from "~/assets/icons/settings.svg";
|
||||||
import pixelLogo from "~/assets/mutiny-pixel-logo.png";
|
import pixelLogo from "~/assets/mutiny-pixel-logo.png";
|
||||||
import { PendingNwc } from "./PendingNwc";
|
import { PendingNwc } from "./PendingNwc";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [state, _actions] = useMegaStore();
|
const [state, _actions] = useMegaStore();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
@@ -68,7 +70,7 @@ export default function App() {
|
|||||||
href="/activity"
|
href="/activity"
|
||||||
class="text-m-red active:text-m-red/80 font-semibold no-underline self-center"
|
class="text-m-red active:text-m-red/80 font-semibold no-underline self-center"
|
||||||
>
|
>
|
||||||
View All
|
{i18n.t("view_all")}
|
||||||
</A>
|
</A>
|
||||||
</Show>
|
</Show>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -76,7 +78,7 @@ export default function App() {
|
|||||||
Bugs? Feedback?{" "}
|
Bugs? Feedback?{" "}
|
||||||
<span class="text-neutral-400">
|
<span class="text-neutral-400">
|
||||||
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/issues">
|
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/issues">
|
||||||
Create an issue
|
{i18n.t("create_an_issue")}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
19
src/components/I18nProvider.tsx
Normal file
19
src/components/I18nProvider.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { ParentComponent, Show, createResource } from "solid-js";
|
||||||
|
import { I18nContext } from "../i18n/context";
|
||||||
|
import i18next from "i18next";
|
||||||
|
import i18nConfig from "~/i18n/config";
|
||||||
|
|
||||||
|
export const I18nProvider: ParentComponent = (props) => {
|
||||||
|
const [i18nConfigured] = createResource(async () => {
|
||||||
|
await i18nConfig;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show when={i18nConfigured()}>
|
||||||
|
<I18nContext.Provider value={i18next}>
|
||||||
|
{props.children}
|
||||||
|
</I18nContext.Provider>
|
||||||
|
</Show>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -3,23 +3,21 @@ import { ParentComponent, createSignal } from "solid-js";
|
|||||||
import { DIALOG_CONTENT, DIALOG_POSITIONER, OVERLAY } from "./DetailsModal";
|
import { DIALOG_CONTENT, DIALOG_POSITIONER, OVERLAY } from "./DetailsModal";
|
||||||
import { ModalCloseButton, SmallHeader } from "./layout";
|
import { ModalCloseButton, SmallHeader } from "./layout";
|
||||||
import { ExternalLink } from "./layout/ExternalLink";
|
import { ExternalLink } from "./layout/ExternalLink";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
export function FeesModal() {
|
export function FeesModal() {
|
||||||
|
const i18n = useI18n();
|
||||||
return (
|
return (
|
||||||
<MoreInfoModal title="What's with the fees?" linkText="Why?">
|
<MoreInfoModal title={i18n.t("whats_with_the_fees")} linkText={i18n.t("why?")}>
|
||||||
<p>
|
<p>
|
||||||
Mutiny is a self-custodial wallet. To initiate a lightning
|
{i18n.t("more_info_modal_p1")}
|
||||||
payment we must open a lightning channel, which requires a
|
|
||||||
minimum amount and a setup fee.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Future payments, both send and recieve, will only incur normal
|
{i18n.t("more_info_modal_p2")}
|
||||||
network fees and a nominal service fee unless your channel runs
|
|
||||||
out of inbound capacity.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/wiki/Understanding-liquidity">
|
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/wiki/Understanding-liquidity">
|
||||||
Learn more about liquidity
|
{i18n.t("learn_more_about_liquidity")}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</p>
|
</p>
|
||||||
</MoreInfoModal>
|
</MoreInfoModal>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export const SmallHeader: ParentComponent<{ class?: string }> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Card: ParentComponent<{
|
export const Card: ParentComponent<{
|
||||||
title?: string;
|
title?: string | null;
|
||||||
titleElement?: JSX.Element;
|
titleElement?: JSX.Element;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
26
src/i18n/config.ts
Normal file
26
src/i18n/config.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import i18next from 'i18next';
|
||||||
|
import HttpApi from 'i18next-http-backend';
|
||||||
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||||
|
const i18n = i18next
|
||||||
|
.use(HttpApi)
|
||||||
|
.use(LanguageDetector)
|
||||||
|
.init({
|
||||||
|
fallbackLng: 'en',
|
||||||
|
preload: ['en'],
|
||||||
|
load: 'languageOnly',
|
||||||
|
ns: ['translations'],
|
||||||
|
defaultNS: 'translations',
|
||||||
|
fallbackNS: false,
|
||||||
|
debug: true,
|
||||||
|
detection: {
|
||||||
|
order: ['querystring', 'navigator', 'htmlTag'],
|
||||||
|
lookupQuerystring: 'lang',
|
||||||
|
},
|
||||||
|
backend: {
|
||||||
|
loadPath: 'src/i18n/{{lng}}/{{ns}}.json',
|
||||||
|
}
|
||||||
|
}, (err, t) => {
|
||||||
|
// Do we actually wanna log something in case of an unsupported language?
|
||||||
|
if (err) return console.error(err)
|
||||||
|
});
|
||||||
|
export default i18n;
|
||||||
12
src/i18n/context.ts
Normal file
12
src/i18n/context.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { createContext, useContext } from 'solid-js';
|
||||||
|
import { i18n } from "i18next";
|
||||||
|
|
||||||
|
export const I18nContext = createContext<i18n>();
|
||||||
|
|
||||||
|
export function useI18n() {
|
||||||
|
const context = useContext(I18nContext);
|
||||||
|
|
||||||
|
if (!context) throw new ReferenceError('I18nContext');
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
19
src/i18n/en/translations.json
Normal file
19
src/i18n/en/translations.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"create_an_issue": "Create an issue",
|
||||||
|
"view_all": "View all",
|
||||||
|
"receive_some_sats_to_get_started": "Receive some sats to get started",
|
||||||
|
"send_bitcoin": "Send Bitcoin",
|
||||||
|
"view_transaction": "View Transaction",
|
||||||
|
"amount_editable_first_payment_10k_or_greater": "Your first lightning receive needs to be 10,000 sats or greater. A setup fee will be deducted from the requested amount.",
|
||||||
|
"why?": "Why?",
|
||||||
|
"more_info_modal_p1": "Mutiny is a self-custodial wallet. To initiate a lightning payment we must open a lightning channel, which requires a minimum amount and a setup fee.",
|
||||||
|
"more_info_modal_p2": "Future payments, both send and recieve, will only incur normal network fees and a nominal service fee unless your channel runs out of inbound capacity.",
|
||||||
|
"learn_more_about_liquidity": "Learn more about liquidity",
|
||||||
|
"set_amount": "Set amount",
|
||||||
|
"whats_with_the_fees": "What's with the fees?",
|
||||||
|
"private_tags": "Private tags",
|
||||||
|
"receive_add_the_sender": "Add the sender for your records",
|
||||||
|
"continue": "Continue",
|
||||||
|
"receive_bitcoin": "Receive Bitcoin",
|
||||||
|
"keep_mutiny_open": "Keep Mutiny open to complete the payment."
|
||||||
|
}
|
||||||
18
src/i18n/pt/translations.json
Normal file
18
src/i18n/pt/translations.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"create_an_issue": "Crie uma issue",
|
||||||
|
"view_all": "Ver todas",
|
||||||
|
"receive_some_sats_to_get_started": "Receba alguns satoshis para começar",
|
||||||
|
"send_bitcoin": "Enviar Bitcoin",
|
||||||
|
"view_transaction": "Ver transação",
|
||||||
|
"amount_editable_first_payment_10k_or_greater": "Seu primeiro recebimento na lightning precisa ser de pelo menos 10.000 sats. Uma taxa de configuração será deduzida da quantidade requisitada.",
|
||||||
|
"why?": "Por que?",
|
||||||
|
"more_info_modal_p1": "Mutiny é uma carteira de auto custódia. Para iniciar um pagamento na lightning, nós precisamos abrir um canal que requer uma quantidade mínima e uma taxa de configuração.",
|
||||||
|
"more_info_modal_p2": "Transaçoēs futuras, como envios e recebimentos, terão somente taxas normais da rede e uma taxa de serviço nominal a não ser que seu canal fique sem capacidade de entrada.",
|
||||||
|
"learn_more_about_liquidity": "Aprenda mais sobre liquidez",
|
||||||
|
"set_amount": "Definir quantidade",
|
||||||
|
"whats_with_the_fees": "O que há com as taxas?",
|
||||||
|
"private_tags": "Tags privadas",
|
||||||
|
"receive_add_the_sender": "Marque quem o enviou para registro próprio",
|
||||||
|
"continue": "Continuar",
|
||||||
|
"receive_bitcoin": "Receber Bitcoin"
|
||||||
|
}
|
||||||
15
src/root.tsx
15
src/root.tsx
@@ -16,6 +16,7 @@ import "./root.css";
|
|||||||
import { Provider as MegaStoreProvider } from "~/state/megaStore";
|
import { Provider as MegaStoreProvider } from "~/state/megaStore";
|
||||||
import { Toaster } from "~/components/Toaster";
|
import { Toaster } from "~/components/Toaster";
|
||||||
import ErrorDisplay from "./components/ErrorDisplay";
|
import ErrorDisplay from "./components/ErrorDisplay";
|
||||||
|
import { I18nProvider } from "./components/I18nProvider";
|
||||||
|
|
||||||
export default function Root() {
|
export default function Root() {
|
||||||
return (
|
return (
|
||||||
@@ -76,12 +77,14 @@ export default function Root() {
|
|||||||
<Body>
|
<Body>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<ErrorBoundary fallback={(e) => <ErrorDisplay error={e} />}>
|
<ErrorBoundary fallback={(e) => <ErrorDisplay error={e} />}>
|
||||||
<MegaStoreProvider>
|
<I18nProvider>
|
||||||
<Routes>
|
<MegaStoreProvider>
|
||||||
<FileRoutes />
|
<Routes>
|
||||||
</Routes>
|
<FileRoutes />
|
||||||
<Toaster />
|
</Routes>
|
||||||
</MegaStoreProvider>
|
<Toaster />
|
||||||
|
</MegaStoreProvider>
|
||||||
|
</I18nProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<Scripts />
|
<Scripts />
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import { InfoBox } from "~/components/InfoBox";
|
|||||||
import { FeesModal } from "~/components/MoreInfoModal";
|
import { FeesModal } from "~/components/MoreInfoModal";
|
||||||
import { IntegratedQr } from "~/components/IntegratedQR";
|
import { IntegratedQr } from "~/components/IntegratedQR";
|
||||||
import side2side from "~/assets/icons/side-to-side.svg";
|
import side2side from "~/assets/icons/side-to-side.svg";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
type OnChainTx = {
|
type OnChainTx = {
|
||||||
transaction: {
|
transaction: {
|
||||||
@@ -143,6 +144,7 @@ function FeeExplanation(props: { fee: bigint }) {
|
|||||||
export default function Receive() {
|
export default function Receive() {
|
||||||
const [state, _actions] = useMegaStore();
|
const [state, _actions] = useMegaStore();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
const [amount, setAmount] = createSignal("");
|
const [amount, setAmount] = createSignal("");
|
||||||
const [receiveState, setReceiveState] = createSignal<ReceiveState>("edit");
|
const [receiveState, setReceiveState] = createSignal<ReceiveState>("edit");
|
||||||
@@ -331,7 +333,7 @@ export default function Receive() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Receive Bitcoin
|
{i18n.t("receive_bitcoin")}
|
||||||
</LargeHeader>
|
</LargeHeader>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={!unified() || receiveState() === "edit"}>
|
<Match when={!unified() || receiveState() === "edit"}>
|
||||||
@@ -344,11 +346,11 @@ export default function Receive() {
|
|||||||
exitRoute={amount() ? "/receive" : "/"}
|
exitRoute={amount() ? "/receive" : "/"}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Card title="Private tags">
|
<Card title={i18n.t("private_tags")}>
|
||||||
<TagEditor
|
<TagEditor
|
||||||
selectedValues={selectedValues()}
|
selectedValues={selectedValues()}
|
||||||
setSelectedValues={setSelectedValues}
|
setSelectedValues={setSelectedValues}
|
||||||
placeholder="Add the sender for your records"
|
placeholder={i18n.t("receive_add_the_sender")}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -359,7 +361,7 @@ export default function Receive() {
|
|||||||
intent="green"
|
intent="green"
|
||||||
onClick={onSubmit}
|
onClick={onSubmit}
|
||||||
>
|
>
|
||||||
Continue
|
{i18n.t("continue")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
@@ -371,7 +373,7 @@ export default function Receive() {
|
|||||||
kind={flavor()}
|
kind={flavor()}
|
||||||
/>
|
/>
|
||||||
<p class="text-neutral-400 text-center">
|
<p class="text-neutral-400 text-center">
|
||||||
Keep Mutiny open to receive the payment.
|
{i18n.t("keep_mutiny_open")}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
class="font-bold text-m-grey-400 flex gap-2 p-2 items-center mx-auto"
|
class="font-bold text-m-grey-400 flex gap-2 p-2 items-center mx-auto"
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import { Network } from "~/logic/mutinyWalletSetup";
|
|||||||
import { SuccessModal } from "~/components/successfail/SuccessModal";
|
import { SuccessModal } from "~/components/successfail/SuccessModal";
|
||||||
import { ExternalLink } from "~/components/layout/ExternalLink";
|
import { ExternalLink } from "~/components/layout/ExternalLink";
|
||||||
import { InfoBox } from "~/components/InfoBox";
|
import { InfoBox } from "~/components/InfoBox";
|
||||||
|
import { useI18n } from "~/i18n/context";
|
||||||
|
|
||||||
export type SendSource = "lightning" | "onchain";
|
export type SendSource = "lightning" | "onchain";
|
||||||
|
|
||||||
@@ -193,6 +194,7 @@ function DestinationShower(props: {
|
|||||||
export default function Send() {
|
export default function Send() {
|
||||||
const [state, actions] = useMegaStore();
|
const [state, actions] = useMegaStore();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
// These can only be set by the user
|
// These can only be set by the user
|
||||||
const [fieldDestination, setFieldDestination] = createSignal("");
|
const [fieldDestination, setFieldDestination] = createSignal("");
|
||||||
@@ -558,7 +560,7 @@ export default function Send() {
|
|||||||
title="Start Over"
|
title="Start Over"
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<LargeHeader>Send Bitcoin</LargeHeader>
|
<LargeHeader>{i18n.t("send_bitcoin")}</LargeHeader>
|
||||||
<SuccessModal
|
<SuccessModal
|
||||||
title={
|
title={
|
||||||
sentDetails()?.amount ? "Sent" : "Payment Failed"
|
sentDetails()?.amount ? "Sent" : "Payment Failed"
|
||||||
@@ -602,7 +604,7 @@ export default function Send() {
|
|||||||
network
|
network
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
View Transaction
|
{i18n.t("view_transaction")}
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</Show>
|
</Show>
|
||||||
</Match>
|
</Match>
|
||||||
@@ -634,7 +636,7 @@ export default function Send() {
|
|||||||
lnurl={lnurlp()}
|
lnurl={lnurlp()}
|
||||||
clearAll={clearAll}
|
clearAll={clearAll}
|
||||||
/>
|
/>
|
||||||
<SmallHeader>Private tags</SmallHeader>
|
<SmallHeader>{i18n.t("private_tags")}</SmallHeader>
|
||||||
<TagEditor
|
<TagEditor
|
||||||
selectedValues={selectedContacts()}
|
selectedValues={selectedContacts()}
|
||||||
setSelectedValues={
|
setSelectedValues={
|
||||||
|
|||||||
Reference in New Issue
Block a user