translation pass 2

This commit is contained in:
benalleng
2023-07-21 13:45:27 -04:00
committed by Paul Miller
parent 43647cb403
commit 7c9c992084
29 changed files with 573 additions and 228 deletions

View File

@@ -3,6 +3,7 @@ import { Card, VStack } from "~/components/layout";
import { useMegaStore } from "~/state/megaStore";
import { satsToUsd } from "~/utils/conversions";
import { AmountEditable } from "./AmountEditable";
import { useI18n } from "~/i18n/context";
const noop = () => {
// do nothing
@@ -25,6 +26,7 @@ export const InlineAmount: ParentComponent<{
sign?: string;
fiat?: boolean;
}> = (props) => {
const i18n = useI18n();
const prettyPrint = createMemo(() => {
const parsed = Number(props.amount);
if (isNaN(parsed)) {
@@ -39,12 +41,15 @@ export const InlineAmount: ParentComponent<{
{props.sign ? `${props.sign} ` : ""}
{props.fiat ? "$" : ""}
{prettyPrint()}{" "}
<span class="text-sm">{props.fiat ? "USD" : "SATS"}</span>
<span class="text-sm">
{props.fiat ? i18n.t("common.usd") : i18n.t("common.sats")}
</span>
</div>
);
};
function USDShower(props: { amountSats: string; fee?: string }) {
const i18n = useI18n();
const [state, _] = useMegaStore();
const amountInUsd = () =>
satsToUsd(state.price, add(props.amountSats, props.fee), true);
@@ -54,7 +59,7 @@ function USDShower(props: { amountSats: string; fee?: string }) {
<KeyValue gray key="">
<div class="self-end">
~{amountInUsd()}&nbsp;
<span class="text-sm">USD</span>
<span class="text-sm">{i18n.t("common.usd")}</span>
</div>
</KeyValue>
</Show>

View File

@@ -69,13 +69,19 @@ function SingleDigitButton(props: {
onClear: () => void;
fiat: boolean;
}) {
const i18n = useI18n();
let holdTimer: number;
const holdThreshold = 500;
function onHold() {
holdTimer = setTimeout(() => {
props.onClear();
}, holdThreshold);
if (
props.character === "DEL" ||
props.character === i18n.t("char.del")
) {
holdTimer = setTimeout(() => {
props.onClear();
}, holdThreshold);
}
}
function endHold() {
@@ -130,7 +136,7 @@ function BigScalingText(props: { text: string; fiat: boolean }) {
>
{props.text}&nbsp;
<span class="text-xl">
{props.fiat ? "USD" : `${i18n.t("common.sats")}`}
{props.fiat ? i18n.t("common.usd") : i18n.t("common.sats")}
</span>
</h1>
);
@@ -142,7 +148,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 ? "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"}
@@ -214,7 +220,7 @@ export const AmountEditable: ParentComponent<{
"9",
".",
"0",
`${i18n.t("char.del")}`
i18n.t("char.del")
];
const displaySats = () => toDisplayHandleNaN(localSats(), false);
@@ -243,9 +249,13 @@ export const AmountEditable: ParentComponent<{
if ((state.balance?.lightning || 0n) === 0n) {
const network = state.mutiny_wallet?.get_network() as Network;
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 i18n.t("receive.amount_editable.receive_too_small", {
amount: "50,000"
});
} else {
return i18n.t("amount_editable_first_payment_10k_or_greater");
return i18n.t("receive.amount_editable.receive_too_small", {
amount: "10,000"
});
}
}
@@ -255,7 +265,7 @@ export const AmountEditable: ParentComponent<{
}
if (parsed > (inboundCapacity() || 0)) {
return "A lightning setup fee will be charged if paid over lightning.";
return i18n.t("receive.amount_editable.setup_fee_lightning");
}
return undefined;
@@ -269,10 +279,10 @@ export const AmountEditable: ParentComponent<{
if (parsed >= 2099999997690000) {
// If over 21 million bitcoin, warn that too much
return i18n.t("more_than_21m");
return i18n.t("receive.amount_editable.more_than_21m");
} else if (parsed >= 4000000) {
// If over 4 million sats, warn that it's a beta bro
return i18n.t("too_big_for_beta");
return i18n.t("receive.amount_editable.too_big_for_beta");
}
};
@@ -285,7 +295,7 @@ export const AmountEditable: ParentComponent<{
let sane;
if (character === "DEL") {
if (character === "DEL" || character === i18n.t("char.del")) {
if (localValue().length <= 1) {
sane = "0";
} else {
@@ -424,7 +434,7 @@ export const AmountEditable: ParentComponent<{
when={localSats() !== "0"}
fallback={
<div class="inline-block font-semibold">
{i18n.t("set_amount")}
{i18n.t("receive.amount_editable.set_amount")}
</div>
}
>
@@ -540,7 +550,7 @@ export const AmountEditable: ParentComponent<{
}}
class="py-2 px-4 rounded-lg bg-white/10"
>
MAX
{i18n.t("receive.amount_editable.max")}
</button>
</Show>
</div>
@@ -561,7 +571,7 @@ export const AmountEditable: ParentComponent<{
class="w-full flex-none"
onClick={handleSubmit}
>
{i18n.t("set_amount")}
{i18n.t("receive.amount_editable.set_amount")}
</Button>
</div>
</Dialog.Content>

View File

@@ -3,10 +3,12 @@ import { createSignal } from "solid-js";
import { ConfirmDialog } from "~/components/Dialog";
import { Button } from "~/components/layout";
import { showToast } from "~/components/Toaster";
import { useI18n } from "~/i18n/context";
import { useMegaStore } from "~/state/megaStore";
import eify from "~/utils/eify";
export function DeleteEverything(props: { emergency?: boolean }) {
const i18n = useI18n();
const [state, actions] = useMegaStore();
async function confirmReset() {
@@ -34,7 +36,14 @@ export function DeleteEverything(props: { emergency?: boolean }) {
await MutinyWallet.import_json("{}");
}
showToast({ title: "Deleted", description: `Deleted all data` });
showToast({
title: i18n.t(
"settings.emergency_kit.delete_everything.deleted"
),
description: i18n.t(
"settings.emergency_kit.delete_everything.deleted_description"
)
});
setTimeout(() => {
window.location.href = "/";
@@ -50,14 +59,16 @@ export function DeleteEverything(props: { emergency?: boolean }) {
return (
<>
<Button onClick={confirmReset}>Delete Everything</Button>
<Button onClick={confirmReset}>
{i18n.t("settings.emergency_kit.delete_everything.delete")}
</Button>
<ConfirmDialog
loading={confirmLoading()}
open={confirmOpen()}
onConfirm={resetNode}
onCancel={() => setConfirmOpen(false)}
>
This will delete your node's state. This can't be undone!
{i18n.t("settings.emergency_kit.delete_everything.confirm")}
</ConfirmDialog>
</>
);

View File

@@ -15,8 +15,10 @@ import { ConfirmDialog } from "./Dialog";
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { InfoBox } from "./InfoBox";
import { TextField } from "./layout/TextField";
import { useI18n } from "~/i18n/context";
export function ImportExport(props: { emergency?: boolean }) {
const i18n = useI18n();
const [state, _] = useMegaStore();
const [error, setError] = createSignal<Error>();
@@ -44,7 +46,11 @@ export function ImportExport(props: { emergency?: boolean }) {
try {
setError(undefined);
if (!password()) {
throw new Error("Password is required");
throw new Error(
i18n.t(
"settings.emergency_kit.import_export.error_password"
)
);
}
const json = await MutinyWallet.export_json(password());
downloadTextFile(json || "", "mutiny-state.json");
@@ -77,11 +83,23 @@ export function ImportExport(props: { emergency?: boolean }) {
if (result) {
resolve(result);
} else {
reject(new Error("No text found in file"));
reject(
new Error(
i18n.t(
"settings.emergency_kit.import_export.error_no_text"
)
)
);
}
};
fileReader.onerror = (_e) =>
reject(new Error("File read error"));
reject(
new Error(
i18n.t(
"settings.emergency_kit.import_export.error_read_file"
)
)
);
fileReader.readAsText(file, "UTF-8");
});
@@ -130,25 +148,35 @@ export function ImportExport(props: { emergency?: boolean }) {
return (
<>
<InnerCard title="Export wallet state">
<InnerCard
title={i18n.t("settings.emergency_kit.import_export.title")}
>
<NiceP>
You can export your entire Mutiny Wallet state to a file and
import it into a new browser. It usually works!
{i18n.t("settings.emergency_kit.import_export.tip")}
</NiceP>
<NiceP>
<strong>Important caveats:</strong> after exporting don't do
any operations in the original browser. If you do, you'll
need to export again. After a successful import, a best
practice is to clear the state of the original browser just
to make sure you don't create conflicts.
<strong>
{i18n.t(
"settings.emergency_kit.import_export.caveat_header"
)}
</strong>{" "}
{i18n.t("settings.emergency_kit.import_export.caveat")}
</NiceP>
<div />
<Show when={error()}>
<InfoBox accent="red">{error()?.message}</InfoBox>
</Show>
<VStack>
<Button onClick={handleSave}>Save State As File</Button>
<Button onClick={uploadFile}>Import State From File</Button>
<Button onClick={handleSave}>
{i18n.t(
"settings.emergency_kit.import_export.save_state"
)}
</Button>
<Button onClick={uploadFile}>
{i18n.t(
"settings.emergency_kit.import_export.import_state"
)}
</Button>
</VStack>
</InnerCard>
<ConfirmDialog
@@ -157,11 +185,14 @@ export function ImportExport(props: { emergency?: boolean }) {
onConfirm={importJson}
onCancel={() => setConfirmOpen(false)}
>
Do you want to replace your state with {files()[0].name}?
{i18n.t("settings.emergency_kit.import_export.confirm_replace")}{" "}
{files()[0].name}?
</ConfirmDialog>
{/* TODO: this is pretty redundant with the DecryptDialog, could make a shared component */}
<SimpleDialog
title="Enter your password to decrypt"
title={i18n.t(
"settings.emergency_kit.import_export.confirm_replace"
)}
open={exportDecrypt()}
>
<form onSubmit={savePassword}>
@@ -180,7 +211,9 @@ export function ImportExport(props: { emergency?: boolean }) {
<InfoBox accent="red">{error()?.message}</InfoBox>
</Show>
<Button intent="blue" onClick={savePassword}>
Decrypt Wallet
{i18n.t(
"settings.emergency_kit.import_export.decrypt_wallet"
)}
</Button>
</div>
</form>

View File

@@ -1,8 +1,10 @@
import { MutinyWallet } from "@mutinywallet/mutiny-wasm";
import { Button, InnerCard, NiceP, VStack } from "~/components/layout";
import { useI18n } from "~/i18n/context";
import { downloadTextFile } from "~/utils/download";
export function Logs() {
const i18n = useI18n();
async function handleSave() {
try {
const logs = await MutinyWallet.get_logs();
@@ -18,11 +20,13 @@ export function Logs() {
}
return (
<InnerCard title="Download debug logs">
<InnerCard title={i18n.t("settings.emergency_kit.logs.title")}>
<VStack>
<NiceP>Something screwy going on? Check out the logs!</NiceP>
<NiceP>
{i18n.t("settings.emergency_kit.logs.something_screwy")}
</NiceP>
<Button intent="green" onClick={handleSave}>
Download Logs
{i18n.t("settings.emergency_kit.logs.download_logs")}
</Button>
</VStack>
</InnerCard>

View File

@@ -15,7 +15,7 @@ export function FeesModal(props: { icon?: boolean }) {
props.icon ? (
<img src={help} alt="help" class="w-4 h-4 cursor-pointer" />
) : (
i18n.t("why?")
i18n.t("why")
)
}
>

View File

@@ -1,5 +1,12 @@
import { onCleanup, onMount } from "solid-js";
import { BarcodeScanner, BarcodeFormat, CameraPermissionState, CameraPermissionType, CameraPluginPermissions, PermissionStates } from '@mutinywallet/barcode-scanner';
import {
BarcodeScanner,
BarcodeFormat,
CameraPermissionState,
CameraPermissionType,
CameraPluginPermissions,
PermissionStates
} from "@mutinywallet/barcode-scanner";
import QrScanner from "qr-scanner";
export default function Scanner(props: { onResult: (result: string) => void }) {
@@ -12,7 +19,8 @@ export default function Scanner(props: { onResult: (result: string) => void }) {
const startScan = async () => {
// Check camera permission
const permissions: PermissionStates = await BarcodeScanner.checkPermissions();
const permissions: PermissionStates =
await BarcodeScanner.checkPermissions();
if (permissions.camera === "granted") {
const callback = (result: ScanResult, err?: any) => {
if (err) {
@@ -24,10 +32,14 @@ export default function Scanner(props: { onResult: (result: string) => void }) {
handleResult({ data: result.content }); // pass the raw scanned content
}
};
await BarcodeScanner.start({ targetedFormats: [BarcodeFormat.QR_CODE] }, callback);
await BarcodeScanner.start(
{ targetedFormats: [BarcodeFormat.QR_CODE] },
callback
);
} else if (permissions.camera === "prompt") {
// Request permission if it has not been asked before
const requestedPermissions: PermissionStates = await BarcodeScanner.requestPermissions();
const requestedPermissions: PermissionStates =
await BarcodeScanner.requestPermissions();
if (requestedPermissions.camera === "granted") {
// If user grants permission, start the scan
await startScan();
@@ -35,7 +47,7 @@ export default function Scanner(props: { onResult: (result: string) => void }) {
} else if (permissions.camera === "denied") {
// Handle the scenario when user denies the permission
// Maybe show a user friendly message here
console.log('Camera permission was denied');
console.log("Camera permission was denied");
}
};

View File

@@ -1,11 +1,13 @@
import { For, Match, Switch, createMemo, createSignal } from "solid-js";
import { useCopy } from "~/utils/useCopy";
import copyIcon from "~/assets/icons/copy.svg";
import { useI18n } from "~/i18n/context";
export function SeedWords(props: {
words: string;
setHasSeen?: (hasSeen: boolean) => void;
}) {
const i18n = useI18n();
const [shouldShow, setShouldShow] = createSignal(false);
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
@@ -30,7 +32,9 @@ export function SeedWords(props: {
class="cursor-pointer flex w-full justify-center"
onClick={toggleShow}
>
<code class="text-red">TAP TO REVEAL SEED WORDS</code>
<code class="text-red">
{i18n.t("settings.backup.seed_words.reveal")}
</code>
</div>
</Match>
@@ -40,7 +44,9 @@ export function SeedWords(props: {
class="cursor-pointer flex w-full justify-center"
onClick={toggleShow}
>
<code class="text-red">HIDE</code>
<code class="text-red">
{i18n.t("settings.backup.seed_words.hide")}
</code>
</div>
<ol class="overflow-hidden columns-2 w-full list-decimal list-inside">
<For each={splitWords()}>
@@ -59,8 +65,12 @@ export function SeedWords(props: {
<div class="flex items-center gap-2">
<span>
{copied()
? "Copied!"
: "Dangerously Copy to Clipboard"}
? i18n.t(
"settings.backup.seed_words.copied"
)
: i18n.t(
"settings.backup.seed_words.copy"
)}
</span>
<img
src={copyIcon}

View File

@@ -1,10 +1,12 @@
import { Back } from "~/assets/svg/Back";
import { useI18n } from "~/i18n/context";
export function BackButton(props: {
onClick: () => void;
title?: string;
showOnDesktop?: boolean;
}) {
const i18n = useI18n();
return (
<button
onClick={() => props.onClick()}
@@ -12,7 +14,7 @@ export function BackButton(props: {
classList={{ "md:!flex": props.showOnDesktop }}
>
<Back />
{props.title ? props.title : "Home"}
{props.title ? props.title : i18n.t("common.home")}
</button>
);
}

View File

@@ -1,14 +1,16 @@
import { A } from "solid-start";
import { Back } from "~/assets/svg/Back";
import { useI18n } from "~/i18n/context";
export function BackLink(props: { href?: string; title?: string }) {
const i18n = useI18n();
return (
<A
href={props.href ? props.href : "/"}
class="text-m-red active:text-m-red/80 text-xl font-semibold no-underline md:hidden flex items-center"
>
<Back />
{props.title ? props.title : "Home"}
{props.title ? props.title : i18n.t("common.home")}
</A>
);
}

View File

@@ -1,11 +1,13 @@
import { useLocation, useNavigate } from "solid-start";
import { BackButton } from "./BackButton";
import { useI18n } from "~/i18n/context";
type StateWithPrevious = {
previous?: string;
};
export function BackPop() {
const i18n = useI18n();
const navigate = useNavigate();
const location = useLocation();
@@ -15,7 +17,7 @@ export function BackPop() {
return (
<BackButton
title="Back"
title={i18n.t("common.back")}
onClick={() => navigate(backPath())}
showOnDesktop
/>

View File

@@ -1,5 +1,6 @@
import { Progress } from "@kobalte/core";
import { SmallHeader } from ".";
import { useI18n } from "~/i18n/context";
export default function formatNumber(num: number) {
const map = [
@@ -21,19 +22,24 @@ export default function formatNumber(num: number) {
}
export function ProgressBar(props: { value: number; max: number }) {
const i18n = useI18n();
return (
<Progress.Root
value={props.value}
minValue={0}
maxValue={props.max}
getValueLabel={({ value, max }) =>
`${formatNumber(value)} of ${formatNumber(max)} sats sent`
`${formatNumber(value)} ${i18n.t(
"send.progress_bar.of"
)} ${formatNumber(max)} ${i18n.t(
"send.progress_bar.sats_sent"
)}`
}
class="w-full flex flex-col gap-2"
>
<div class="flex justify-between">
<Progress.Label>
<SmallHeader>Sending...</SmallHeader>
<SmallHeader>{i18n.t("send.sending")}</SmallHeader>
</Progress.Label>
<Progress.ValueLabel class="text-sm font-semibold uppercase" />
</div>

View File

@@ -25,6 +25,7 @@ import { A } from "solid-start";
import down from "~/assets/icons/down.svg";
import { DecryptDialog } from "../DecryptDialog";
import { LoadingIndicator } from "~/components/LoadingIndicator";
import { useI18n } from "~/i18n/context";
export { Button, ButtonLink, Linkify };
@@ -138,6 +139,7 @@ export const DefaultMain: ParentComponent = (props) => {
};
export const FullscreenLoader = () => {
const i18n = useI18n();
const [waitedTooLong, setWaitedTooLong] = createSignal(false);
setTimeout(() => {
@@ -149,10 +151,9 @@ export const FullscreenLoader = () => {
<LoadingSpinner wide />
<Show when={waitedTooLong()}>
<p class="max-w-[20rem] text-neutral-400">
Stuck on this screen? Try reloading. If that doesn't work,
check out the{" "}
{i18n.t("error.load_time.stuck")}{" "}
<A class="text-white" href="/emergencykit">
emergency kit.
{i18n.t("error.load_time.emergency_link")}
</A>
</p>
</Show>