feat: third pass at translations

This commit is contained in:
benalleng
2023-07-28 17:42:59 -04:00
committed by Paul Miller
parent 11ce48414f
commit 12a650b63e
43 changed files with 1568 additions and 448 deletions

View File

@@ -177,7 +177,9 @@ export function ActivityItem(props: {
</Switch>
<Switch>
<Match when={props.date && props.date > 2147483647}>
<time class="text-sm text-neutral-500">Pending</time>
<time class="text-sm text-neutral-500">
{i18n.t("common.pending")}
</time>
</Match>
<Match when={true}>
<time class="text-sm text-neutral-500">

View File

@@ -81,6 +81,7 @@ export function AmountCard(props: {
exitRoute?: string;
maxAmountSats?: bigint;
}) {
const i18n = useI18n();
// Normally we want to add the fee to the amount, but for max amount we just show the max
const totalOrTotalLessFee = () => {
if (
@@ -99,7 +100,7 @@ export function AmountCard(props: {
<Switch>
<Match when={props.fee}>
<div class="flex flex-col gap-1">
<KeyValue key="Amount">
<KeyValue key={i18n.t("receive.amount")}>
<Show
when={props.isAmountEditable}
fallback={
@@ -123,13 +124,13 @@ export function AmountCard(props: {
/>
</Show>
</KeyValue>
<KeyValue gray key="+ Fee">
<KeyValue gray key={i18n.t("receive.fee")}>
<InlineAmount amount={props.fee || "0"} />
</KeyValue>
</div>
<hr class="border-white/20" />
<div class="flex flex-col gap-1">
<KeyValue key="Total">
<KeyValue key={i18n.t("receive.total")}>
<InlineAmount amount={totalOrTotalLessFee()} />
</KeyValue>
<USDShower
@@ -140,7 +141,7 @@ export function AmountCard(props: {
</Match>
<Match when={props.reserve}>
<div class="flex flex-col gap-1">
<KeyValue key="Channel size">
<KeyValue key={i18n.t("receive.channel_size")}>
<InlineAmount
amount={add(
props.amountSats,
@@ -148,13 +149,16 @@ export function AmountCard(props: {
).toString()}
/>
</KeyValue>
<KeyValue gray key="- Channel Reserve">
<KeyValue
gray
key={i18n.t("receive.channel_reserve")}
>
<InlineAmount amount={props.reserve || "0"} />
</KeyValue>
</div>
<hr class="border-white/20" />
<div class="flex flex-col gap-1">
<KeyValue key="Spendable">
<KeyValue key={i18n.t("receive.spendable")}>
<InlineAmount amount={props.amountSats} />
</KeyValue>
<USDShower
@@ -165,7 +169,7 @@ export function AmountCard(props: {
</Match>
<Match when={!props.fee && !props.reserve}>
<div class="flex flex-col gap-1">
<KeyValue key="Amount">
<KeyValue key={i18n.t("receive.amount")}>
<Show
when={props.isAmountEditable}
fallback={

View File

@@ -24,18 +24,6 @@ import { FeesModal } from "./MoreInfoModal";
import { useI18n } from "~/i18n/context";
import { useNavigate } from "solid-start";
const FIXED_AMOUNTS_SATS = [
{ label: "10k", amount: "10000" },
{ label: "100k", amount: "100000" },
{ label: "1m", amount: "1000000" }
];
const FIXED_AMOUNTS_USD = [
{ label: "$1", amount: "1" },
{ label: "$10", amount: "10" },
{ label: "$100", amount: "100" }
];
function fiatInputSanitizer(input: string): string {
// Make sure only numbers and a single decimal point are allowed
const numeric = input.replace(/[^0-9.]/g, "").replace(/(\..*)\./g, "$1");
@@ -76,7 +64,7 @@ function SingleDigitButton(props: {
function onHold() {
if (
props.character === "DEL" ||
props.character === i18n.t("char.del")
props.character === i18n.t("receive.amount_editable.del")
) {
holdTimer = setTimeout(() => {
props.onClear();
@@ -148,7 +136,7 @@ function SmallSubtleAmount(props: { text: string; fiat: boolean }) {
<h2 class="flex flex-row items-end text-xl font-light text-neutral-400">
~{props.text}&nbsp;
<span class="text-base">
{props.fiat ? i18n.t("common.usd") : `${i18n.t("common.sats")}`}
{props.fiat ? i18n.t("common.usd") : i18n.t("common.sats")}
</span>
<img
class={"pl-[4px] pb-[4px] hover:cursor-pointer"}
@@ -208,6 +196,28 @@ export const AmountEditable: ParentComponent<{
false
)
);
const FIXED_AMOUNTS_SATS = [
{
label: i18n.t("receive.amount_editable.fix_amounts.ten_k"),
amount: "10000"
},
{
label: i18n.t("receive.amount_editable.fix_amounts.one_hundred_k"),
amount: "100000"
},
{
label: i18n.t("receive.amount_editable.fix_amounts.one_million"),
amount: "1000000"
}
];
const FIXED_AMOUNTS_USD = [
{ label: "$1", amount: "1" },
{ label: "$10", amount: "10" },
{ label: "$100", amount: "100" }
];
const CHARACTERS = [
"1",
"2",
@@ -220,7 +230,7 @@ export const AmountEditable: ParentComponent<{
"9",
".",
"0",
i18n.t("char.del")
i18n.t("receive.amount_editable.del")
];
const displaySats = () => toDisplayHandleNaN(localSats(), false);
@@ -295,7 +305,10 @@ export const AmountEditable: ParentComponent<{
let sane;
if (character === "DEL" || character === i18n.t("char.del")) {
if (
character === "DEL" ||
character === i18n.t("receive.amount_editable.del")
) {
if (localValue().length <= 1) {
sane = "0";
} else {

View File

@@ -15,8 +15,10 @@ import { PendingNwc } from "./PendingNwc";
import { DecryptDialog } from "./DecryptDialog";
import { LoadingIndicator } from "./LoadingIndicator";
import { FeedbackLink } from "~/routes/Feedback";
import { useI18n } from "~/i18n/context";
export default function App() {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
return (
@@ -71,7 +73,7 @@ export default function App() {
<PendingNwc />
</Show>
</Suspense>
<Card title="Activity">
<Card title={i18n.t("activity.title")}>
<div class="p-1" />
<VStack>
<Suspense>

View File

@@ -79,7 +79,9 @@ export default function BalanceBox(props: { loading?: boolean }) {
</div>
<div class="flex flex-col items-end gap-1 justify-between">
<Show when={state.balance?.unconfirmed != 0n}>
<Indicator>Pending</Indicator>
<Indicator>
{i18n.t("common.pending")}
</Indicator>
</Show>
<Show when={state.balance?.unconfirmed === 0n}>
<div />

View File

@@ -4,28 +4,26 @@ import { DIALOG_CONTENT, DIALOG_POSITIONER, OVERLAY } from "./DetailsModal";
import { ModalCloseButton, SmallHeader } from "./layout";
import { ExternalLink } from "./layout/ExternalLink";
import { getExistingSettings } from "~/logic/mutinyWalletSetup";
import { useI18n } from "~/i18n/context";
export function BetaWarningModal() {
const i18n = useI18n();
return (
<WarningModal title="Warning: beta software" linkText="Why?">
<p>
We're so glad you're here. But we do want to warn you: Mutiny
Wallet is in beta, and there are still bugs and rough edges.
</p>
<p>
Please be careful and don't put more money into Mutiny than
you're willing to lose.
</p>
<WarningModal
title={i18n.t("modals.beta_warning.title")}
linkText={i18n.t("common.why")}
>
<p>{i18n.t("translations:modals.beta_warning.beta_warning")}</p>
<p>{i18n.t("modals.beta_warning.be_careful")}</p>
<p>
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/wiki/Mutiny-Beta-Readme">
Learn more about the beta
{i18n.t("modals.beta_warning.beta_link")}
</ExternalLink>
</p>
<p class="small text-neutral-400">
If you want to use pretend money to test out Mutiny without
risk,{" "}
{i18n.t("modals.beta_warning.pretend_money")}{" "}
<ExternalLink href="https://blog.mutinywallet.com/mutiny-wallet-signet-release/">
check out our Signet version.
{i18n.t("modals.beta_warning.signet_link")}
</ExternalLink>
</p>
</WarningModal>

View File

@@ -6,11 +6,13 @@ import { SubmitHandler } from "@modular-forms/solid";
import { ContactForm } from "./ContactForm";
import { ContactFormValues } from "./ContactViewer";
import { DIALOG_CONTENT, DIALOG_POSITIONER } from "~/styles/dialogs";
import { useI18n } from "~/i18n/context";
export function ContactEditor(props: {
createContact: (contact: ContactFormValues) => void;
list?: boolean;
}) {
const i18n = useI18n();
const [isOpen, setIsOpen] = createSignal(false);
// What we're all here for in the first place: returning a value
@@ -32,12 +34,14 @@ export function ContactEditor(props: {
<div class="bg-neutral-500 flex-none h-16 w-16 rounded-full flex items-center justify-center text-4xl uppercase ">
<span class="leading-[4rem]">+</span>
</div>
<SmallHeader class="overflow-ellipsis">new</SmallHeader>
<SmallHeader class="overflow-ellipsis">
{i18n.t("contacts.new")}
</SmallHeader>
</button>
</Match>
<Match when={!props.list}>
<TinyButton onClick={() => setIsOpen(true)}>
+ Add Contact
+ {i18n.t("contacts.add_contact")}
</TinyButton>
</Match>
</Switch>
@@ -57,8 +61,8 @@ export function ContactEditor(props: {
</button>
</div>
<ContactForm
title="New contact"
cta="Create contact"
title={i18n.t("contacts.new_contact")}
cta={i18n.t("contacts.create_contact")}
handleSubmit={handleSubmit}
/>
</Dialog.Content>

View File

@@ -2,6 +2,7 @@ import { SubmitHandler, createForm, required } from "@modular-forms/solid";
import { Button, LargeHeader, VStack } from "~/components/layout";
import { TextField } from "~/components/layout/TextField";
import { ContactFormValues } from "./ContactViewer";
import { useI18n } from "~/i18n/context";
export function ContactForm(props: {
handleSubmit: SubmitHandler<ContactFormValues>;
@@ -9,6 +10,7 @@ export function ContactForm(props: {
title: string;
cta: string;
}) {
const i18n = useI18n();
const [_contactForm, { Form, Field }] = createForm<ContactFormValues>({
initialValues: props.initialValues
});
@@ -23,15 +25,15 @@ export function ContactForm(props: {
<VStack>
<Field
name="name"
validate={[required("We at least need a name")]}
validate={[required(i18n.t("contacts.error_name"))]}
>
{(field, props) => (
<TextField
{...props}
placeholder="Satoshi"
placeholder={i18n.t("contacts.placeholder")}
value={field.value}
error={field.error}
label="Name"
label={i18n.t("contacts.name")}
/>
)}
</Field>

View File

@@ -7,6 +7,7 @@ import { ContactForm } from "./ContactForm";
import { showToast } from "./Toaster";
import { Contact } from "@mutinywallet/mutiny-wasm";
import { DIALOG_CONTENT, DIALOG_POSITIONER } from "~/styles/dialogs";
import { useI18n } from "~/i18n/context";
export type ContactFormValues = {
name: string;
@@ -18,6 +19,7 @@ export function ContactViewer(props: {
gradient: string;
saveContact: (contact: Contact) => void;
}) {
const i18n = useI18n();
const [isOpen, setIsOpen] = createSignal(false);
const [isEditing, setIsEditing] = createSignal(false);
@@ -71,8 +73,8 @@ export function ContactViewer(props: {
<Switch>
<Match when={isEditing()}>
<ContactForm
title="Edit contact"
cta="Save contact"
title={i18n.t("contacts.edit_contact")}
cta={i18n.t("contacts.save_contact")}
handleSubmit={handleSubmit}
initialValues={props.contact}
/>
@@ -91,9 +93,13 @@ export function ContactViewer(props: {
<h1 class="text-2xl font-semibold uppercase mt-2 mb-4">
{props.contact.name}
</h1>
<Card title="Payment history">
<Card
title={i18n.t(
"contacts.payment_history"
)}
>
<NiceP>
No payments yet with{" "}
{i18n.t("contacts.no_payments")}{" "}
<span class="font-semibold">
{props.contact.name}
</span>
@@ -107,19 +113,22 @@ export function ContactViewer(props: {
intent="green"
onClick={() => setIsEditing(true)}
>
Edit
{i18n.t("contacts.edit")}
</Button>
<Button
intent="blue"
onClick={() => {
showToast({
title: "Unimplemented",
description:
"We don't do that yet"
title: i18n.t(
"contacts.unimplemented"
),
description: i18n.t(
"contacts.not_available"
)
});
}}
>
Pay
{i18n.t("contacts.pay")}
</Button>
</div>
</div>

View File

@@ -1,8 +1,10 @@
import { Show } from "solid-js";
import { QRCodeSVG } from "solid-qr-code";
import { useI18n } from "~/i18n/context";
import { useCopy } from "~/utils/useCopy";
export function CopyableQR(props: { value: string }) {
const i18n = useI18n();
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
return (
<div
@@ -12,7 +14,7 @@ export function CopyableQR(props: { value: string }) {
>
<Show when={copied()}>
<div class="absolute w-full h-full bg-neutral-900/60 z-50 rounded-xl flex flex-col items-center justify-center transition-all">
<p class="text-xl font-bold">Copied</p>
<p class="text-xl font-bold">{i18n.t("common.copied")}</p>
</div>
</Show>
<QRCodeSVG

View File

@@ -5,8 +5,10 @@ import { InfoBox } from "~/components/InfoBox";
import { useMegaStore } from "~/state/megaStore";
import eify from "~/utils/eify";
import { A } from "solid-start";
import { useI18n } from "~/i18n/context";
export function DecryptDialog() {
const i18n = useI18n();
const [state, actions] = useMegaStore();
const [password, setPassword] = createSignal("");
@@ -27,7 +29,7 @@ export function DecryptDialog() {
const err = eify(e);
console.error(e);
if (err.message === "wrong") {
setError("Invalid password");
setError(i18n.t("settings.decrypt.error_wrong_password"));
} else {
throw e;
}
@@ -42,7 +44,7 @@ export function DecryptDialog() {
return (
<SimpleDialog
title="Enter your password"
title={i18n.t("settings.decrypt.title")}
// Only show the dialog if we need a password and there's no setup error
open={state.needs_password && !state.setup_error}
>
@@ -62,12 +64,12 @@ export function DecryptDialog() {
<InfoBox accent="red">{error()}</InfoBox>
</Show>
<Button intent="blue" loading={loading()} onClick={decrypt}>
Decrypt Wallet
{i18n.t("settings.decrypt.decrypt_wallet")}
</Button>
</div>
</form>
<A class="self-end text-m-grey-400" href="/settings/restore">
Forgot Password?
{i18n.t("settings.decrypt.forgot_password_link")}
</A>
</SimpleDialog>
);

View File

@@ -30,6 +30,7 @@ import { Network } from "~/logic/mutinyWalletSetup";
import { AmountSmall } from "./Amount";
import { ExternalLink } from "./layout/ExternalLink";
import { InfoBox } from "./InfoBox";
import { useI18n } from "~/i18n/context";
type ChannelClosure = {
channel_id: string;
@@ -48,6 +49,7 @@ function LightningHeader(props: {
info: MutinyInvoice;
tags: MutinyTagItem[];
}) {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
return (
@@ -56,7 +58,9 @@ function LightningHeader(props: {
<img src={bolt} alt="lightning bolt" class="w-8 h-8" />
</div>
<h1 class="uppercase font-semibold">
{props.info.inbound ? "Lightning receive" : "Lightning send"}
{props.info.inbound
? i18n.t("modals.transaction_details.lightning_receive")
: i18n.t("modals.transaction_details.lightning_send")}
</h1>
<ActivityAmount
center
@@ -85,6 +89,7 @@ function OnchainHeader(props: {
tags: MutinyTagItem[];
kind?: HackActivityType;
}) {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
const isSend = () => {
@@ -118,12 +123,12 @@ function OnchainHeader(props: {
</div>
<h1 class="uppercase font-semibold">
{props.kind === "ChannelOpen"
? "Channel Open"
? i18n.t("modals.transaction_details.channel_open")
: props.kind === "ChannelClose"
? "Channel Close"
? i18n.t("modals.transaction_details.channel_close")
: isSend()
? "On-chain send"
: "On-chain receive"}
? i18n.t("modals.transaction_details.onchain_send")
: i18n.t("modals.transaction_details.onchain_receive")}
</h1>
<Show when={props.kind !== "ChannelClose"}>
<ActivityAmount
@@ -179,38 +184,45 @@ export function MiniStringShower(props: { text: string }) {
}
function LightningDetails(props: { info: MutinyInvoice }) {
const i18n = useI18n();
return (
<VStack>
<ul class="flex flex-col gap-4">
<KeyValue key="Status">
<KeyValue key={i18n.t("modals.transaction_details.status")}>
<span class="text-neutral-300">
{props.info.paid ? "Paid" : "Unpaid"}
{props.info.paid
? i18n.t("modals.transaction_details.paid")
: i18n.t("modals.transaction_details.unpaid")}
</span>
</KeyValue>
<KeyValue key="When">
<KeyValue key={i18n.t("modals.transaction_details.when")}>
<span class="text-neutral-300">
{prettyPrintTime(Number(props.info.last_updated))}
</span>
</KeyValue>
<Show when={props.info.description}>
<KeyValue key="Description">
<KeyValue
key={i18n.t("modals.transaction_details.description")}
>
<span class="text-neutral-300 truncate">
{props.info.description}
</span>
</KeyValue>
</Show>
<KeyValue key="Fees">
<KeyValue key={i18n.t("modals.transaction_details.fees")}>
<span class="text-neutral-300">
<AmountSmall amountSats={props.info.fees_paid} />
</span>
</KeyValue>
<KeyValue key="Bolt11">
<KeyValue key={i18n.t("modals.transaction_details.bolt11")}>
<MiniStringShower text={props.info.bolt11 ?? ""} />
</KeyValue>
<KeyValue key="Payment Hash">
<KeyValue
key={i18n.t("modals.transaction_details.payment_hash")}
>
<MiniStringShower text={props.info.payment_hash ?? ""} />
</KeyValue>
<KeyValue key="Preimage">
<KeyValue key={i18n.t("modals.transaction_details.preimage")}>
<MiniStringShower text={props.info.preimage ?? ""} />
</KeyValue>
</ul>
@@ -219,6 +231,7 @@ function LightningDetails(props: { info: MutinyInvoice }) {
}
function OnchainDetails(props: { info: OnChainTx; kind?: HackActivityType }) {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
const confirmationTime = () => {
@@ -251,13 +264,15 @@ function OnchainDetails(props: { info: OnChainTx; kind?: HackActivityType }) {
<VStack>
{/* <pre>{JSON.stringify(channelInfo() || "", null, 2)}</pre> */}
<ul class="flex flex-col gap-4">
<KeyValue key="Status">
<KeyValue key={i18n.t("modals.transaction_details.status")}>
<span class="text-neutral-300">
{confirmationTime() ? "Confirmed" : "Unconfirmed"}
{confirmationTime()
? i18n.t("modals.transaction_details.confirmed")
: i18n.t("modals.transaction_details.unconfirmed")}
</span>
</KeyValue>
<Show when={confirmationTime()}>
<KeyValue key="When">
<KeyValue key={i18n.t("modals.transaction_details.when")}>
<span class="text-neutral-300">
{confirmationTime()
? prettyPrintTime(Number(confirmationTime()))
@@ -266,32 +281,38 @@ function OnchainDetails(props: { info: OnChainTx; kind?: HackActivityType }) {
</KeyValue>
</Show>
<Show when={props.info.fee && props.info.fee > 0}>
<KeyValue key="Fee">
<KeyValue key={i18n.t("modals.transaction_details.fee")}>
<span class="text-neutral-300">
<AmountSmall amountSats={props.info.fee} />
</span>
</KeyValue>
</Show>
<KeyValue key="Txid">
<KeyValue key={i18n.t("modals.transaction_details.txid")}>
<MiniStringShower text={props.info.txid ?? ""} />
</KeyValue>
<Switch>
<Match when={props.kind === "ChannelOpen" && channelInfo()}>
<KeyValue key="Balance">
<KeyValue
key={i18n.t("modals.transaction_details.balance")}
>
<span class="text-neutral-300">
<AmountSmall
amountSats={channelInfo()?.balance}
/>
</span>
</KeyValue>
<KeyValue key="Reserve">
<KeyValue
key={i18n.t("modals.transaction_details.reserve")}
>
<span class="text-neutral-300">
<AmountSmall
amountSats={channelInfo()?.reserve}
/>
</span>
</KeyValue>
<KeyValue key="Peer">
<KeyValue
key={i18n.t("modals.transaction_details.peer")}
>
<span class="text-neutral-300">
<MiniStringShower
text={channelInfo()?.peer ?? ""}
@@ -301,15 +322,14 @@ function OnchainDetails(props: { info: OnChainTx; kind?: HackActivityType }) {
</Match>
<Match when={props.kind === "ChannelOpen"}>
<InfoBox accent="blue">
No channel details found, which means this channel
has likely been closed.
{i18n.t("modals.transaction_details.no_details")}
</InfoBox>
</Match>
</Switch>
</ul>
<div class="text-center">
<ExternalLink href={mempoolTxUrl(props.info.txid, network)}>
View Transaction
{i18n.t("common.view_transaction")}
</ExternalLink>
</div>
</VStack>
@@ -317,23 +337,24 @@ function OnchainDetails(props: { info: OnChainTx; kind?: HackActivityType }) {
}
function ChannelCloseDetails(props: { info: ChannelClosure }) {
const i18n = useI18n();
return (
<VStack>
{/* <pre>{JSON.stringify(props.info.value, null, 2)}</pre> */}
<ul class="flex flex-col gap-4">
<KeyValue key="Channel ID">
<KeyValue key={i18n.t("modals.transaction_details.channel_id")}>
<MiniStringShower text={props.info.channel_id ?? ""} />
</KeyValue>
<Show when={props.info.timestamp}>
<KeyValue key="When">
<KeyValue key={i18n.t("modals.transaction_details.when")}>
<span class="text-neutral-300">
{props.info.timestamp
? prettyPrintTime(Number(props.info.timestamp))
: "Pending"}
: i18n.t("common.pending")}
</span>
</KeyValue>
</Show>
<KeyValue key="Reason">
<KeyValue key={i18n.t("modals.transaction_details.reason")}>
<p class="text-neutral-300 text-right">
{props.info.reason ?? ""}
</p>
@@ -349,6 +370,7 @@ export function DetailsIdModal(props: {
id: string;
setOpen: (open: boolean) => void;
}) {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
const id = () => props.id;
@@ -473,7 +495,7 @@ export function DetailsIdModal(props: {
<Show when={props.kind !== "ChannelClose"}>
<div class="flex justify-center">
<CopyButton
title="Copy"
title={i18n.t("common.copy")}
text={json()}
/>
</div>

View File

@@ -1,6 +1,7 @@
import { Dialog } from "@kobalte/core";
import { ParentComponent } from "solid-js";
import { Button, SmallHeader } from "./layout";
import { useI18n } from "~/i18n/context";
const OVERLAY = "fixed inset-0 z-50 bg-black/50 backdrop-blur-sm";
const DIALOG_POSITIONER = "fixed inset-0 z-50 flex items-center justify-center";
@@ -14,6 +15,7 @@ export const ConfirmDialog: ParentComponent<{
onCancel: () => void;
onConfirm: () => void;
}> = (props) => {
const i18n = useI18n();
return (
<Dialog.Root open={props.open} onOpenChange={props.onCancel}>
<Dialog.Portal>
@@ -22,20 +24,26 @@ export const ConfirmDialog: ParentComponent<{
<Dialog.Content class={DIALOG_CONTENT}>
<div class="flex justify-between mb-2">
<Dialog.Title>
<SmallHeader>Are you sure?</SmallHeader>
<SmallHeader>
{i18n.t(
"modals.confirm_dialog.are_you_sure"
)}
</SmallHeader>
</Dialog.Title>
</div>
<Dialog.Description class="flex flex-col gap-4">
{props.children}
<div class="flex gap-4 w-full justify-end">
<Button onClick={props.onCancel}>Cancel</Button>
<Button onClick={props.onCancel}>
{i18n.t("modals.confirm_dialog.cancel")}
</Button>
<Button
intent="red"
onClick={props.onConfirm}
loading={props.loading}
disabled={props.loading}
>
Confirm
{i18n.t("modals.confirm_dialog.confirm")}
</Button>
</div>
</Dialog.Description>

View File

@@ -8,35 +8,38 @@ import {
SmallHeader
} from "~/components/layout";
import { ExternalLink } from "./layout/ExternalLink";
import { useI18n } from "~/i18n/context";
export default function ErrorDisplay(props: { error: Error }) {
const i18n = useI18n();
return (
<SafeArea>
<Title>Oh no!</Title>
<Title>{i18n.t("error.general.oh_no")}</Title>
<DefaultMain>
<LargeHeader>Error</LargeHeader>
<SmallHeader>This never should've happened</SmallHeader>
<LargeHeader>{i18n.t("error.title")}</LargeHeader>
<SmallHeader>
{i18n.t("error.general.never_should_happen")}
</SmallHeader>
<p class="bg-white/10 rounded-xl p-4 font-mono">
<span class="font-bold">{props.error.name}</span>:{" "}
{props.error.message}
</p>
<NiceP>
Try reloading this page or clicking the "Dangit" button. If
you keep having problems,{" "}
{i18n.t("error.general.try_reloading")}{" "}
<ExternalLink href="https://matrix.to/#/#mutiny-community:lightninghackers.com">
reach out to us for support.
{i18n.t("error.general.support_link")}
</ExternalLink>
</NiceP>
<NiceP>
Getting desperate? Try the{" "}
<A href="/emergencykit">emergency kit.</A>
{i18n.t("error.general.getting_desperate")}{" "}
<A href="/emergencykit">{i18n.t("error.emergency_link")}</A>
</NiceP>
<div class="h-full" />
<Button
onClick={() => (window.location.href = "/")}
intent="red"
>
Dangit
{i18n.t("common.dangit")}
</Button>
</DefaultMain>
</SafeArea>

View File

@@ -8,23 +8,31 @@ import copyBlack from "~/assets/icons/copy-black.svg";
import shareBlack from "~/assets/icons/share-black.svg";
import chainBlack from "~/assets/icons/chain-black.svg";
import boltBlack from "~/assets/icons/bolt-black.svg";
import { useI18n } from "~/i18n/context";
function KindIndicator(props: { kind: ReceiveFlavor }) {
const i18n = useI18n();
return (
<div class="text-black flex flex-col items-end">
<Switch>
<Match when={props.kind === "onchain"}>
<h3 class="font-semibold">On-chain</h3>
<h3 class="font-semibold">
{i18n.t("receive.integrated_qr.onchain")}
</h3>
<img src={chainBlack} alt="chain" />
</Match>
<Match when={props.kind === "lightning"}>
<h3 class="font-semibold">Lightning</h3>
<h3 class="font-semibold">
{i18n.t("receive.integrated_qr.lightning")}
</h3>
<img src={boltBlack} alt="bolt" />
</Match>
<Match when={props.kind === "unified"}>
<h3 class="font-semibold">Unified</h3>
<h3 class="font-semibold">
{i18n.t("receive.integrated_qr.unified")}
</h3>
<div class="flex gap-1">
<img src={chainBlack} alt="chain" />
<img src={boltBlack} alt="bolt" />
@@ -56,6 +64,7 @@ export function IntegratedQr(props: {
amountSats: string;
kind: ReceiveFlavor;
}) {
const i18n = useI18n();
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
return (
<div
@@ -65,7 +74,7 @@ export function IntegratedQr(props: {
>
<Show when={copied()}>
<div class="absolute w-full h-full bg-neutral-900/60 z-50 rounded-xl flex flex-col items-center justify-center transition-all">
<p class="text-xl font-bold">Copied</p>
<p class="text-xl font-bold">{i18n.t("common.copied")}</p>
</div>
</Show>
<div

View File

@@ -7,6 +7,7 @@ import {
OVERLAY
} from "~/components/DetailsModal";
import { CopyButton } from "./ShareCard";
import { useI18n } from "~/i18n/context";
export function JsonModal(props: {
title: string;
@@ -16,6 +17,7 @@ export function JsonModal(props: {
setOpen: (open: boolean) => void;
children?: JSX.Element;
}) {
const i18n = useI18n();
const json = createMemo(() =>
props.plaintext ? props.plaintext : JSON.stringify(props.data, null, 2)
);
@@ -41,7 +43,10 @@ export function JsonModal(props: {
</pre>
</div>
{props.children}
<CopyButton title="Copy" text={json()} />
<CopyButton
title={i18n.t("common.copy")}
text={json()}
/>
</Dialog.Description>
</Dialog.Content>
</div>

View File

@@ -21,6 +21,7 @@ import { Restart } from "./Restart";
import { ResyncOnchain } from "./ResyncOnchain";
import { ResetRouter } from "./ResetRouter";
import { MiniStringShower } from "./DetailsModal";
import { useI18n } from "~/i18n/context";
// TODO: hopefully I don't have to maintain this type forever but I don't know how to pass it around otherwise
type RefetchPeersType = (
@@ -28,6 +29,7 @@ type RefetchPeersType = (
) => MutinyPeer[] | Promise<MutinyPeer[] | undefined> | null | undefined;
function PeerItem(props: { peer: MutinyPeer }) {
const i18n = useI18n();
const [state, _] = useMegaStore();
const handleDisconnectPeer = async () => {
@@ -65,7 +67,7 @@ function PeerItem(props: { peer: MutinyPeer }) {
layout="xs"
onClick={handleDisconnectPeer}
>
Disconnect
{i18n.t("settings.admin.kitchen_sink.disconnect")}
</Button>
</VStack>
</Collapsible.Content>
@@ -74,6 +76,7 @@ function PeerItem(props: { peer: MutinyPeer }) {
}
function PeersList() {
const i18n = useI18n();
const [state, _] = useMegaStore();
const getPeers = async () => {
@@ -86,20 +89,26 @@ function PeersList() {
return (
<>
<InnerCard title="Peers">
<InnerCard title={i18n.t("settings.admin.kitchen_sink.peers")}>
{/* By wrapping this in a suspense I don't cause the page to jump to the top */}
<Suspense>
<VStack>
<For
each={peers.latest}
fallback={<code>No peers</code>}
fallback={
<code>
{i18n.t(
"settings.admin.kitchen_sink.no_peers"
)}
</code>
}
>
{(peer) => <PeerItem peer={peer} />}
</For>
</VStack>
</Suspense>
<Button layout="small" onClick={refetch}>
Refresh Peers
{i18n.t("settings.admin.kitchen_sink.refresh_peers")}
</Button>
</InnerCard>
<ConnectPeer refetchPeers={refetch} />
@@ -108,6 +117,7 @@ function PeersList() {
}
function ConnectPeer(props: { refetchPeers: RefetchPeersType }) {
const i18n = useI18n();
const [state, _] = useMegaStore();
const [value, setValue] = createSignal("");
@@ -139,18 +149,18 @@ function ConnectPeer(props: { refetchPeers: RefetchPeersType }) {
class="flex flex-col gap-4"
>
<TextField.Label class="text-sm font-semibold uppercase">
Connect Peer
{i18n.t("settings.admin.kitchen_sink.connect_peer")}
</TextField.Label>
<TextField.Input
class="w-full p-2 rounded-lg text-black"
placeholder="028241..."
/>
<TextField.ErrorMessage class="text-red-500">
Expecting a value...
{i18n.t("settings.admin.kitchen_sink.expect_a_value")}
</TextField.ErrorMessage>
</TextField.Root>
<Button layout="small" type="submit">
Connect
{i18n.t("settings.admin.kitchen_sink.connect")}
</Button>
</form>
</InnerCard>
@@ -164,6 +174,7 @@ type RefetchChannelsListType = (
type PendingChannelAction = "close" | "force_close" | "abandon";
function ChannelItem(props: { channel: MutinyChannel; network?: Network }) {
const i18n = useI18n();
const [state, _] = useMegaStore();
const [pendingChannelAction, setPendingChannelAction] =
@@ -206,28 +217,28 @@ function ChannelItem(props: { channel: MutinyChannel; network?: Network }) {
props.network
)}
>
View Transaction
{i18n.t("common.view_transaction")}
</ExternalLink>
<Button
intent="glowy"
layout="xs"
onClick={() => setPendingChannelAction("close")}
>
Close Channel
{i18n.t("settings.admin.kitchen_sink.close_channel")}
</Button>
<Button
intent="glowy"
layout="xs"
onClick={() => setPendingChannelAction("force_close")}
>
Force close Channel
{i18n.t("settings.admin.kitchen_sink.force_close")}
</Button>
<Button
intent="glowy"
layout="xs"
onClick={() => setPendingChannelAction("abandon")}
>
Abandon Channel
{i18n.t("settings.admin.kitchen_sink.abandon_channel")}
</Button>
</VStack>
<ConfirmDialog
@@ -238,21 +249,24 @@ function ChannelItem(props: { channel: MutinyChannel; network?: Network }) {
>
<Switch>
<Match when={pendingChannelAction() === "close"}>
<p>Are you sure you want to close this channel?</p>
<p>
{i18n.t(
"settings.admin.kitchen_sink.confirm_close_channel"
)}
</p>
</Match>
<Match when={pendingChannelAction() === "force_close"}>
<p>
Are you sure you want to force close this
channel? Your funds will take a few days to
redeem on chain.
{i18n.t(
"settings.admin.kitchen_sink.confirm_force_close"
)}
</p>
</Match>
<Match when={pendingChannelAction() === "abandon"}>
<p>
Are you sure you want to abandon this channel?
Typically only do this if the opening
transaction will never confirm. Otherwise, you
will lose funds.
{i18n.t(
"settings.admin.kitchen_sink.confirm_abandon_channel"
)}
</p>
</Match>
</Switch>
@@ -263,6 +277,7 @@ function ChannelItem(props: { channel: MutinyChannel; network?: Network }) {
}
function ChannelsList() {
const i18n = useI18n();
const [state, _] = useMegaStore();
const getChannels = async () => {
@@ -277,10 +292,19 @@ function ChannelsList() {
return (
<>
<InnerCard title="Channels">
<InnerCard title={i18n.t("settings.admin.kitchen_sink.channels")}>
{/* By wrapping this in a suspense I don't cause the page to jump to the top */}
<Suspense>
<For each={channels()} fallback={<code>No channels</code>}>
<For
each={channels()}
fallback={
<code>
{i18n.t(
"settings.admin.kitchen_sink.no_channels"
)}
</code>
}
>
{(channel) => (
<ChannelItem channel={channel} network={network} />
)}
@@ -294,7 +318,7 @@ function ChannelsList() {
refetch();
}}
>
Refresh Channels
{i18n.t("settings.admin.kitchen_sink.refresh_channels")}
</Button>
</InnerCard>
<OpenChannel refetchChannels={refetch} />
@@ -303,6 +327,7 @@ function ChannelsList() {
}
function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) {
const i18n = useI18n();
const [state, _] = useMegaStore();
const [creationError, setCreationError] = createSignal<Error>();
@@ -354,7 +379,7 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) {
class="flex flex-col gap-2"
>
<TextField.Label class="text-sm font-semibold uppercase">
Pubkey
{i18n.t("settings.admin.kitchen_sink.pubkey")}
</TextField.Label>
<TextField.Input class="w-full p-2 rounded-lg text-black" />
</TextField.Root>
@@ -364,7 +389,7 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) {
class="flex flex-col gap-2"
>
<TextField.Label class="text-sm font-semibold uppercase">
Amount
{i18n.t("settings.admin.kitchen_sink.amount")}
</TextField.Label>
<TextField.Input
type="number"
@@ -372,7 +397,7 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) {
/>
</TextField.Root>
<Button layout="small" type="submit">
Open Channel
{i18n.t("settings.admin.kitchen_sink.open_channel")}
</Button>
</form>
</InnerCard>
@@ -387,7 +412,7 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) {
network
)}
>
View Transaction
{i18n.t("common.view_transaction")}
</ExternalLink>
</Show>
<Show when={creationError()}>
@@ -398,6 +423,7 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) {
}
function ListNodes() {
const i18n = useI18n();
const [state, _] = useMegaStore();
const getNodeIds = async () => {
@@ -408,9 +434,16 @@ function ListNodes() {
const [nodeIds] = createResource(getNodeIds);
return (
<InnerCard title="Nodes">
<InnerCard title={i18n.t("settings.admin.kitchen_sink.nodes")}>
<Suspense>
<For each={nodeIds()} fallback={<code>No nodes</code>}>
<For
each={nodeIds()}
fallback={
<code>
{i18n.t("settings.admin.kitchen_sink.no_nodes")}
</code>
}
>
{(nodeId) => <MiniStringShower text={nodeId} />}
</For>
</Suspense>

View File

@@ -1,22 +1,24 @@
import { Progress } from "@kobalte/core";
import { Show } from "solid-js";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";
export function LoadingBar(props: { value: number; max: number }) {
const i18n = useI18n();
function valueToStage(value: number) {
switch (value) {
case 0:
return "Just getting started";
return i18n.t("modals.loading.default");
case 1:
return "Double checking something";
return i18n.t("modals.loading.double_checking");
case 2:
return "Downloading";
return i18n.t("modals.loading.downloading");
case 3:
return "Setup";
return i18n.t("modals.loading.setup");
case 4:
return "Done";
return i18n.t("modals.loading.done");
default:
return "Just getting started";
return i18n.t("modals.loading.default");
}
}
return (
@@ -24,7 +26,9 @@ export function LoadingBar(props: { value: number; max: number }) {
value={props.value}
minValue={0}
maxValue={props.max}
getValueLabel={({ value }) => `Loading: ${valueToStage(value)}`}
getValueLabel={({ value }) =>
i18n.t("modals.loading.loading", { stage: valueToStage(value) })
}
class="w-full flex flex-col gap-2"
>
<Progress.ValueLabel class="text-sm text-m-grey-400" />

View File

@@ -10,20 +10,20 @@ export function FeesModal(props: { icon?: boolean }) {
const i18n = useI18n();
return (
<MoreInfoModal
title={i18n.t("whats_with_the_fees")}
title={i18n.t("modals.more_info.whats_with_the_fees")}
linkText={
props.icon ? (
<img src={help} alt="help" class="w-4 h-4 cursor-pointer" />
) : (
i18n.t("why")
i18n.t("common.why")
)
}
>
<p>{i18n.t("more_info_modal_p1")}</p>
<p>{i18n.t("more_info_modal_p2")}</p>
<p>{i18n.t("modals.more_info.self_custodial")}</p>
<p>{i18n.t("modals.more_info.future_payments")}</p>
<p>
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/wiki/Understanding-liquidity">
{i18n.t("learn_more_about_liquidity")}
{i18n.t("modals.more_info.liquidity")}
</ExternalLink>
</p>
</MoreInfoModal>

View File

@@ -5,8 +5,10 @@ import { showToast } from "./Toaster";
import save from "~/assets/icons/save.svg";
import close from "~/assets/icons/close.svg";
import restore from "~/assets/icons/upload.svg";
import { useI18n } from "~/i18n/context";
export function OnboardWarning() {
const i18n = useI18n();
const [state, actions] = useMegaStore();
const [dismissedBackup, setDismissedBackup] = createSignal(
sessionStorage.getItem("dismissed_backup") ?? false
@@ -31,11 +33,13 @@ export function OnboardWarning() {
</div>
<div class="flex md:flex-row flex-col items-center gap-4">
<div class="flex flex-col">
<SmallHeader>Welcome!</SmallHeader>
<SmallHeader>
{i18n.t("modals.onboarding.welcome")}
</SmallHeader>
<p class="text-base font-light">
If you've used Mutiny before you can restore
from a backup. Otherwise you can skip this and
enjoy your new wallet!
{i18n.t(
"modals.onboarding.restore_from_backup"
)}
</p>
</div>
<Button
@@ -44,12 +48,14 @@ export function OnboardWarning() {
class="self-start md:self-auto"
onClick={() => {
showToast({
title: "Unimplemented",
description: "We don't do that yet"
title: i18n.t("common.error_unimplemented"),
description: i18n.t(
"modals.onboarding.not_available"
)
});
}}
>
Restore
{i18n.t("settings.restore.title")}
</Button>
</div>
<button
@@ -72,10 +78,11 @@ export function OnboardWarning() {
</div>
<div class="flex flex-row max-md:items-center justify-between gap-4">
<div class="flex flex-col">
<SmallHeader>Secure your funds</SmallHeader>
<SmallHeader>
{i18n.t("modals.onboarding.secure_your_funds")}
</SmallHeader>
<p class="text-base font-light max-md:hidden">
You have money stored in this browser. Let's
make sure you have a backup.
{i18n.t("modals.onboarding.make_backup")}
</p>
</div>
<div class="flex items-center">
@@ -85,7 +92,7 @@ export function OnboardWarning() {
class="self-auto"
href="/settings/backup"
>
Backup
{i18n.t("settings.backup.title")}
</ButtonLink>
</div>
</div>

View File

@@ -20,6 +20,7 @@ import { InfoBox } from "./InfoBox";
import eify from "~/utils/eify";
import { A } from "solid-start";
import { createDeepSignal } from "~/utils/deepSignal";
import { useI18n } from "~/i18n/context";
type PendingItem = {
id: string;
@@ -29,6 +30,7 @@ type PendingItem = {
};
export function PendingNwc() {
const i18n = useI18n();
const [state, _actions] = useMegaStore();
const [error, setError] = createSignal<Error>();
@@ -109,7 +111,7 @@ export function PendingNwc() {
return (
<Show when={pendingRequests() && pendingRequests()!.length > 0}>
<Card title="Pending Requests">
<Card title={i18n.t("settings.connections.pending_nwc.title")}>
<div class="p-1" />
<VStack>
<Show when={error()}>
@@ -183,7 +185,7 @@ export function PendingNwc() {
href="/settings/connections"
class="text-m-red active:text-m-red/80 font-semibold no-underline self-center"
>
Configure
{i18n.t("settings.connections.pending_nwc.configure_link")}
</A>
</Card>
</Show>

View File

@@ -1,7 +1,9 @@
import { Button, InnerCard, NiceP, VStack } from "~/components/layout";
import { useMegaStore } from "~/state/megaStore";
import { useI18n } from "~/i18n/context";
export function ResetRouter() {
const i18n = useI18n();
const [state, _] = useMegaStore();
async function reset() {
@@ -15,12 +17,9 @@ export function ResetRouter() {
return (
<InnerCard>
<VStack>
<NiceP>
Failing to make payments? Try resetting the lightning
router.
</NiceP>
<NiceP>{i18n.t("error.reset_router.payments_failing")}</NiceP>
<Button intent="red" onClick={reset}>
Reset Router
{i18n.t("error.reset_router.reset_router")}
</Button>
</VStack>
</InnerCard>

View File

@@ -1,8 +1,10 @@
import { createSignal } from "solid-js";
import { Button, InnerCard, NiceP, VStack } from "~/components/layout";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";
export function Restart() {
const i18n = useI18n();
const [state, _] = useMegaStore();
const [hasStopped, setHasStopped] = createSignal(false);
@@ -23,14 +25,14 @@ export function Restart() {
return (
<InnerCard>
<VStack>
<NiceP>
Something *extra* screwy going on? Stop the nodes!
</NiceP>
<NiceP>{i18n.t("error.restart.title")}</NiceP>
<Button
intent={hasStopped() ? "green" : "red"}
onClick={toggle}
>
{hasStopped() ? "Start" : "Stop"}
{hasStopped()
? i18n.t("error.restart.start")
: i18n.t("error.restart.stop")}
</Button>
</VStack>
</InnerCard>

View File

@@ -1,7 +1,9 @@
import { Button, InnerCard, NiceP, VStack } from "~/components/layout";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";
export function ResyncOnchain() {
const i18n = useI18n();
const [state, _] = useMegaStore();
async function reset() {
@@ -15,12 +17,9 @@ export function ResyncOnchain() {
return (
<InnerCard>
<VStack>
<NiceP>
On-chain balance seems incorrect? Try re-syncing the
on-chain wallet.
</NiceP>
<NiceP>{i18n.t("error.resync.incorrect_balance")}</NiceP>
<Button intent="red" onClick={reset}>
Resync wallet
{i18n.t("error.resync.resync_wallet")}
</Button>
</VStack>
</InnerCard>

View File

@@ -12,6 +12,7 @@ import { ImportExport } from "./ImportExport";
import { Logs } from "./Logs";
import { DeleteEverything } from "./DeleteEverything";
import { FeedbackLink } from "~/routes/Feedback";
import { useI18n } from "~/i18n/context";
function ErrorFooter() {
return (
@@ -26,86 +27,100 @@ function ErrorFooter() {
export default function SetupErrorDisplay(props: { initialError: Error }) {
// Error shouldn't be reactive, so we assign to it so it just gets rendered with the first value
const i18n = useI18n();
const error = props.initialError;
return (
<SafeArea>
<Switch>
<Match when={error.message.startsWith("Existing tab")}>
<Title>Multiple tabs detected</Title>
<Title>{i18n.t("error.on_boot.existing_tab.title")}</Title>
<DefaultMain>
<LargeHeader>Multiple tabs detected</LargeHeader>
<LargeHeader>
{i18n.t("error.on_boot.existing_tab.title")}
</LargeHeader>
<p class="bg-white/10 rounded-xl p-4 font-mono">
<span class="font-bold">{error.name}</span>:{" "}
{error.message}
</p>
<NiceP>
Mutiny currently only supports use in one tab at a
time. It looks like you have another tab open with
Mutiny running. Please close that tab and refresh
this page, or close this tab and refresh the other
one.
{i18n.t("error.on_boot.existing_tab.description")}
</NiceP>
<ErrorFooter />
</DefaultMain>
</Match>
<Match when={error.message.startsWith("Browser error")}>
<Title>Incompatible browser</Title>
<Title>
{i18n.t("error.on_boot.incompatible_browser.title")}
</Title>
<DefaultMain>
<LargeHeader>Incompatible browser detected</LargeHeader>
<LargeHeader>
{i18n.t(
"error.on_boot.incompatible_browser.header"
)}
</LargeHeader>
<p class="bg-white/10 rounded-xl p-4 font-mono">
<span class="font-bold">{error.name}</span>:{" "}
{error.message}
</p>
<NiceP>
Mutiny requires a modern browser that supports
WebAssembly, LocalStorage, and IndexedDB. Some
browsers disable these features in private mode.
{i18n.t(
"error.on_boot.incompatible_browser.description"
)}
</NiceP>
<NiceP>
Please make sure your browser supports all these
features, or consider trying another browser. You
might also try disabling certain extensions or
"shields" that block these features.
{i18n.t(
"error.on_boot.incompatible_browser.try_different_browser"
)}
</NiceP>
<NiceP>
(We'd love to support more private browsers, but we
have to save your wallet data to browser storage or
else you will lose funds.)
{i18n.t(
"error.on_boot.incompatible_browser.browser_storage"
)}
</NiceP>
<ExternalLink href="https://github.com/MutinyWallet/mutiny-web/wiki/Browser-Compatibility">
Supported Browsers
{i18n.t(
"error.on_boot.incompatible_browser.browsers_link"
)}
</ExternalLink>
<ErrorFooter />
</DefaultMain>
</Match>
<Match when={true}>
<Title>Failed to load</Title>
<Title>
{i18n.t("error.on_boot.loading_failed.title")}
</Title>
<DefaultMain>
<LargeHeader>Failed to load Mutiny</LargeHeader>
<LargeHeader>
{i18n.t("error.on_boot.loading_failed.header")}
</LargeHeader>
<p class="bg-white/10 rounded-xl p-4 font-mono">
<span class="font-bold">{error.name}</span>:{" "}
{error.message}
</p>
<NiceP>
Something went wrong while booting up Mutiny Wallet.
{i18n.t("error.on_boot.loading_failed.description")}
</NiceP>
<NiceP>
If your wallet seems broken, here are some tools to
try to debug and repair it.
{i18n.t(
"error.on_boot.loading_failed.repair_options"
)}
</NiceP>
<NiceP>
If you have any questions on what these buttons do,
please{" "}
{i18n.t("error.on_boot.loading_failed.questions")}{" "}
<ExternalLink href="https://matrix.to/#/#mutiny-community:lightninghackers.com">
reach out to us for support.
{i18n.t(
"error.on_boot.loading_failed.support_link"
)}
</ExternalLink>
</NiceP>
<ImportExport emergency />
<Logs />
<div class="rounded-xl p-4 flex flex-col gap-2 bg-m-red">
<SmallHeader>Danger zone</SmallHeader>
<SmallHeader>
{i18n.t("settings.danger_zone")}
</SmallHeader>
<DeleteEverything emergency />
</div>

View File

@@ -7,6 +7,7 @@ import shareBlack from "~/assets/icons/share-black.svg";
import eyeIcon from "~/assets/icons/eye.svg";
import { Show, createSignal } from "solid-js";
import { JsonModal } from "./JsonModal";
import { useI18n } from "~/i18n/context";
const STYLE =
"px-4 py-2 rounded-xl border-2 border-white flex gap-2 items-center font-semibold hover:text-m-blue transition-colors";
@@ -15,13 +16,14 @@ export function ShareButton(props: {
receiveString: string;
whiteBg?: boolean;
}) {
const i18n = useI18n();
async function share(receiveString: string) {
// If the browser doesn't support share we can just copy the address
if (!navigator.share) {
console.error("Share not supported");
}
const shareData: ShareData = {
title: "Mutiny Wallet",
title: i18n.t("common.title"),
text: receiveString
};
try {
@@ -33,7 +35,7 @@ export function ShareButton(props: {
return (
<button class={STYLE} onClick={(_) => share(props.receiveString)}>
<span>Share</span>
<span>{i18n.t("modals.share")}</span>
<img src={props.whiteBg ? shareBlack : shareIcon} alt="share" />
</button>
);
@@ -57,13 +59,14 @@ export function TruncateMiddle(props: { text: string; whiteBg?: boolean }) {
}
export function StringShower(props: { text: string }) {
const i18n = useI18n();
const [open, setOpen] = createSignal(false);
return (
<>
<JsonModal
open={open()}
plaintext={props.text}
title="Details"
title={i18n.t("modals.details")}
setOpen={setOpen}
/>
<div class="w-full grid grid-cols-[minmax(0,_1fr)_auto]">
@@ -81,6 +84,7 @@ export function CopyButton(props: {
title?: string;
whiteBg?: boolean;
}) {
const i18n = useI18n();
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
function handleCopy() {
@@ -89,7 +93,9 @@ export function CopyButton(props: {
return (
<button class={STYLE} onClick={handleCopy}>
{copied() ? "Copied" : props.title ?? "Copy"}
{copied()
? i18n.t("common.copied")
: props.title ?? i18n.t("common.copy")}
<img src={props.whiteBg ? copyBlack : copyIcon} alt="copy" />
</button>
);

View File

@@ -153,7 +153,7 @@ export const FullscreenLoader = () => {
<p class="max-w-[20rem] text-neutral-400">
{i18n.t("error.load_time.stuck")}{" "}
<A class="text-white" href="/emergencykit">
{i18n.t("error.load_time.emergency_link")}
{i18n.t("error.emergency_link")}
</A>
</p>
</Show>