commit 5e121d17e1a8addff56c0263f6baded90aa2f12f
parent 1639908ca0ee4963843e6105f9dc0447d6be9f8a
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Tue, 8 Sep 2026 07:14:34 +0200
fix: use English throughout wallet
Diffstat:
5 files changed, 56 insertions(+), 56 deletions(-)
diff --git a/wallet/app.js b/wallet/app.js
@@ -41,29 +41,29 @@ function logo() {
}
function renderWelcome() {
- app.innerHTML = `<section class="onboarding"><div class="hero">${logo()}<h1>Jouw iuna.<br><em>In jouw handen.</em></h1><p>Een lichte wallet die rechtstreeks met het iuna-netwerk praat. Je seed blijft versleuteld op dit apparaat.</p></div><div class="onboarding-actions"><button class="button" data-action="create">Nieuwe wallet maken</button><button class="button secondary" data-action="import">Bestaande seed gebruiken</button><div class="security-note"><span>◇</span><span>Self-custody betekent dat alleen jij je seed kunt herstellen. iuna kan hem niet terughalen.</span></div></div></section>`;
+ app.innerHTML = `<section class="onboarding"><div class="hero">${logo()}<h1>Your iuna.<br><em>In your hands.</em></h1><p>A lightweight wallet that connects directly to the iuna network. Your seed stays encrypted on this device.</p></div><div class="onboarding-actions"><button class="button" data-action="create">Create a new wallet</button><button class="button secondary" data-action="import">Use an existing seed</button><div class="security-note"><span>◇</span><span>Self-custody means only you can recover your seed. iuna cannot retrieve it for you.</span></div></div></section>`;
}
function renderLock() {
const record = storedWallet();
- app.innerHTML = `<section class="onboarding"><div class="lock-card">${logo()}<h1>Welkom terug.</h1><p class="view-copy">Ontgrendel je wallet op dit apparaat.</p><form id="unlock-form"><div class="field"><label for="password">Wachtwoord</label><input id="password" type="password" autocomplete="current-password" autofocus required></div><button class="button" type="submit">Ontgrendelen</button></form><button class="button ghost" data-action="forget" style="width:100%;margin-top:10px">Andere wallet gebruiken</button><p class="security-note">Wallet ${escapeHtml(record?.address?.slice(0, 8))}… is lokaal versleuteld.</p></div></section>`;
+ app.innerHTML = `<section class="onboarding"><div class="lock-card">${logo()}<h1>Welcome back.</h1><p class="view-copy">Unlock your wallet on this device.</p><form id="unlock-form"><div class="field"><label for="password">Password</label><input id="password" type="password" autocomplete="current-password" autofocus required></div><button class="button" type="submit">Unlock</button></form><button class="button ghost" data-action="forget" style="width:100%;margin-top:10px">Use a different wallet</button><p class="security-note">Wallet ${escapeHtml(record?.address?.slice(0, 8))}… is encrypted locally.</p></div></section>`;
}
function renderImport() {
- app.innerHTML = `<section class="onboarding"><div><button class="back" data-action="welcome">← Terug</button><p class="eyebrow">Wallet herstellen</p><h1 class="view-title">Bestaande seed</h1><p class="view-copy">Vul de 24 woorden in dezelfde volgorde in. Ze worden alleen lokaal verwerkt.</p><form id="import-form"><div class="field"><label for="seed">Seed phrase</label><textarea id="seed" autocomplete="off" autocapitalize="none" spellcheck="false" placeholder="woord 1 woord 2 woord 3 …" required></textarea></div><div class="field"><label for="new-password">Nieuw wallet-wachtwoord</label><input id="new-password" type="password" minlength="10" autocomplete="new-password" placeholder="Minimaal 10 tekens" required></div><button class="button" type="submit" style="width:100%">Wallet herstellen</button></form></div><div class="security-note" style="margin-top:auto"><span>◇</span><span>Je seed verlaat deze browser niet.</span></div></section>`;
+ app.innerHTML = `<section class="onboarding"><div><button class="back" data-action="welcome">← Back</button><p class="eyebrow">Recover wallet</p><h1 class="view-title">Existing seed</h1><p class="view-copy">Enter the 24 words in the same order. They are processed locally only.</p><form id="import-form"><div class="field"><label for="seed">Seed phrase</label><textarea id="seed" autocomplete="off" autocapitalize="none" spellcheck="false" placeholder="word 1 word 2 word 3 …" required></textarea></div><div class="field"><label for="new-password">New wallet password</label><input id="new-password" type="password" minlength="10" autocomplete="new-password" placeholder="At least 10 characters" required></div><button class="button" type="submit" style="width:100%">Recover wallet</button></form></div><div class="security-note" style="margin-top:auto"><span>◇</span><span>Your seed never leaves this browser.</span></div></section>`;
}
async function renderNewSeed() {
- app.innerHTML = `<section class="center-card"><div class="spinner"></div><p>Veilige seed maken…</p></section>`;
+ app.innerHTML = `<section class="center-card"><div class="spinner"></div><p>Creating a secure seed…</p></section>`;
const seed = await generateMnemonic();
const words = seed.split(" ").map((word) => `<div class="seed-word">${word}</div>`).join("");
- app.innerHTML = `<section class="onboarding"><div><button class="back" data-action="welcome">← Terug</button><p class="eyebrow">Stap 1 van 2</p><h1 class="view-title">Bewaar je seed.</h1><p class="view-copy">Schrijf deze 24 woorden op papier, in exact deze volgorde.</p><div class="seed-grid">${words}</div><div class="warning">Maak geen screenshot. Wie deze woorden heeft, heeft toegang tot je wallet.</div><button class="button secondary" data-action="copy-seed" style="width:100%">Seed kopiëren</button><button class="button" data-action="seed-saved" style="width:100%;margin-top:10px">Ik heb de woorden bewaard</button></div></section>`;
+ app.innerHTML = `<section class="onboarding"><div><button class="back" data-action="welcome">← Back</button><p class="eyebrow">Step 1 of 2</p><h1 class="view-title">Save your seed.</h1><p class="view-copy">Write these 24 words on paper in this exact order.</p><div class="seed-grid">${words}</div><div class="warning">Do not take a screenshot. Anyone with these words can access your wallet.</div><button class="button secondary" data-action="copy-seed" style="width:100%">Copy seed</button><button class="button" data-action="seed-saved" style="width:100%;margin-top:10px">I have saved the words</button></div></section>`;
app.dataset.pendingSeed = seed;
}
function renderPasswordSetup() {
const seed = app.dataset.pendingSeed;
- app.innerHTML = `<section class="onboarding" data-seed="${escapeHtml(seed)}"><div><button class="back" data-action="create">← Terug</button><p class="eyebrow">Stap 2 van 2</p><h1 class="view-title">Beveilig dit apparaat.</h1><p class="view-copy">Dit wachtwoord versleutelt je seed voordat hij in de browser wordt opgeslagen.</p><form id="create-form"><div class="field"><label for="create-password">Wachtwoord</label><input id="create-password" type="password" minlength="10" autocomplete="new-password" required></div><div class="field"><label for="confirm-password">Herhaal wachtwoord</label><input id="confirm-password" type="password" minlength="10" autocomplete="new-password" required></div><button class="button" type="submit" style="width:100%">Wallet openen</button></form></div></section>`;
+ app.innerHTML = `<section class="onboarding" data-seed="${escapeHtml(seed)}"><div><button class="back" data-action="create">← Back</button><p class="eyebrow">Step 2 of 2</p><h1 class="view-title">Secure this device.</h1><p class="view-copy">This password encrypts your seed before it is stored in the browser.</p><form id="create-form"><div class="field"><label for="create-password">Password</label><input id="create-password" type="password" minlength="10" autocomplete="new-password" required></div><div class="field"><label for="confirm-password">Repeat password</label><input id="confirm-password" type="password" minlength="10" autocomplete="new-password" required></div><button class="button" type="submit" style="width:100%">Open wallet</button></form></div></section>`;
app.dataset.pendingSeed = seed;
}
@@ -89,7 +89,7 @@ async function fetchWalletData() {
}
async function openWallet() {
- app.innerHTML = `<section class="center-card"><div class="spinner"></div><p>Verbinden met iuna…</p></section>`;
+ app.innerHTML = `<section class="center-card"><div class="spinner"></div><p>Connecting to iuna…</p></section>`;
try {
await fetchWalletData();
renderApp();
@@ -101,7 +101,7 @@ async function openWallet() {
}
function renderOffline(message) {
- app.innerHTML = `<section class="onboarding"><div class="lock-card">${logo()}<p class="eyebrow">Verbinding mislukt</p><h1 class="view-title">Endpoint niet bereikbaar.</h1><p class="view-copy">${escapeHtml(message)}</p><button class="button" data-action="retry" style="width:100%">Opnieuw proberen</button><button class="button ghost" data-action="lock" style="width:100%;margin-top:10px">Wallet vergrendelen</button><p class="security-note">Endpoint: ${escapeHtml(API_BASE)}</p></div></section>`;
+ app.innerHTML = `<section class="onboarding"><div class="lock-card">${logo()}<p class="eyebrow">Connection failed</p><h1 class="view-title">Endpoint unavailable.</h1><p class="view-copy">${escapeHtml(message)}</p><button class="button" data-action="retry" style="width:100%">Try again</button><button class="button ghost" data-action="lock" style="width:100%;margin-top:10px">Lock wallet</button><p class="security-note">Endpoint: ${escapeHtml(API_BASE)}</p></div></section>`;
}
async function refreshSilently() {
@@ -111,11 +111,11 @@ async function refreshSilently() {
function topbar() {
const online = state.status?.ready;
- return `<header class="topbar">${logo()}<div style="display:flex;align-items:center;gap:10px"><div class="network"><span class="dot ${online ? "live" : ""}"></span>${online ? "Mainnet" : "Synchroniseren"}</div><button class="icon-button" data-action="lock" aria-label="Vergrendel">${icon("lock")}</button></div></header>`;
+ return `<header class="topbar">${logo()}<div style="display:flex;align-items:center;gap:10px"><div class="network"><span class="dot ${online ? "live" : ""}"></span>${online ? "Mainnet" : "Syncing"}</div><button class="icon-button" data-action="lock" aria-label="Lock">${icon("lock")}</button></div></header>`;
}
function nav() {
- return `<nav class="bottom-nav">${[["home","Overzicht"],["send","Verstuur"],["receive","Ontvang"],["settings","Instellingen"]].map(([view,label]) => `<button class="nav-item ${state.view === view ? "active" : ""}" data-view="${view}"><span>${icon(view)}</span>${label}</button>`).join("")}</nav>`;
+ return `<nav class="bottom-nav">${[["home","Overview"],["send","Send"],["receive","Receive"],["settings","Settings"]].map(([view,label]) => `<button class="nav-item ${state.view === view ? "active" : ""}" data-view="${view}"><span>${icon(view)}</span>${label}</button>`).join("")}</nav>`;
}
function transactionInfo(item) {
@@ -129,14 +129,14 @@ function transactionInfo(item) {
if (tx.kind === "burn") return { title: "Burn", incoming: false, amount: tx.amount || 0, symbol: "×" };
const sent = tx.inputs?.some((input) => input.owner === owner);
const relevant = tx.outputs?.filter((output) => sent ? output.address !== owner : output.address === owner) || [];
- return { title: sent ? "Verstuurd" : "Ontvangen", incoming: !sent, amount: relevant.reduce((sum, output) => sum + Number(output.amount || 0), 0), symbol: sent ? "↗" : "↙" };
+ return { title: sent ? "Sent" : "Received", incoming: !sent, amount: relevant.reduce((sum, output) => sum + Number(output.amount || 0), 0), symbol: sent ? "↗" : "↙" };
}
function formatTransactionDate(item) {
- if (!item.timestamp_ms) return "In behandeling";
+ if (!item.timestamp_ms) return "Pending";
const timestamp = new Date(Number(item.timestamp_ms));
- if (Number.isNaN(timestamp.getTime())) return "Onbekende tijd";
- return timestamp.toLocaleString("nl-NL", {
+ if (Number.isNaN(timestamp.getTime())) return "Unknown time";
+ return timestamp.toLocaleString("en-GB", {
day: "numeric",
month: "short",
hour: "2-digit",
@@ -146,7 +146,7 @@ function formatTransactionDate(item) {
function activityList(limit) {
const items = state.transactions.slice(0, limit);
- if (!items.length) return '<div class="empty">Nog geen transacties.<br>Je nieuwe wallet is klaar voor gebruik.</div>';
+ if (!items.length) return '<div class="empty">No transactions yet.<br>Your new wallet is ready to use.</div>';
return `<div class="activity-list">${items.map((item) => {
const info = transactionInfo(item);
const date = formatTransactionDate(item);
@@ -155,23 +155,23 @@ function activityList(limit) {
}
function renderHome() {
- return `${topbar()}<section><p class="eyebrow">Beschikbaar saldo</p><h1 class="balance">${formatIuna(state.balance?.spendable, 6)} <span>IUNA</span></h1><p class="subbalance">${formatIuna(state.balance?.confirmed, 6)} bevestigd · blok ${escapeHtml(state.balance?.height)}</p><div class="actions"><button class="button" data-view="send">${icon("send")} Verstuur</button><button class="button secondary" data-view="receive">${icon("receive")} Ontvang</button></div><div class="section-head"><h2>Recente activiteit</h2><button data-view="activity">Bekijk alles</button></div><div class="panel">${activityList(5)}</div></section>`;
+ return `${topbar()}<section><p class="eyebrow">Available balance</p><h1 class="balance">${formatIuna(state.balance?.spendable, 6)} <span>IUNA</span></h1><p class="subbalance">${formatIuna(state.balance?.confirmed, 6)} confirmed · block ${escapeHtml(state.balance?.height)}</p><div class="actions"><button class="button" data-view="send">${icon("send")} Send</button><button class="button secondary" data-view="receive">${icon("receive")} Receive</button></div><div class="section-head"><h2>Recent activity</h2><button data-view="activity">View all</button></div><div class="panel">${activityList(5)}</div></section>`;
}
function renderSend() {
- return `${topbar()}<p class="eyebrow">Transactie</p><h1 class="view-title">Verstuur IUNA</h1><p class="view-copy">De transactie wordt op dit apparaat ondertekend.</p><form id="send-form" class="panel send-card"><div class="field"><label for="recipient">Ontvanger</label><input id="recipient" autocomplete="off" autocapitalize="none" spellcheck="false" placeholder="iuna1q…" required></div><div class="field"><label for="amount">Bedrag</label><div class="amount-wrap"><input id="amount" inputmode="decimal" placeholder="0,00" required><span>IUNA</span></div></div><div class="fee-line"><span>Beschikbaar</span><strong>${formatIuna(state.balance?.spendable)} IUNA</strong></div><div class="fee-line"><span>Tarief</span><strong>${escapeHtml(state.status.default_fee_per_byte)} µIUNA / byte</strong></div><button class="button" type="submit">Controleer transactie</button></form>`;
+ return `${topbar()}<p class="eyebrow">Transaction</p><h1 class="view-title">Send IUNA</h1><p class="view-copy">The transaction is signed on this device.</p><form id="send-form" class="panel send-card"><div class="field"><label for="recipient">Recipient</label><input id="recipient" autocomplete="off" autocapitalize="none" spellcheck="false" placeholder="iuna1q…" required></div><div class="field"><label for="amount">Amount</label><div class="amount-wrap"><input id="amount" inputmode="decimal" placeholder="0.00" required><span>IUNA</span></div></div><div class="fee-line"><span>Available</span><strong>${formatIuna(state.balance?.spendable)} IUNA</strong></div><div class="fee-line"><span>Fee rate</span><strong>${escapeHtml(state.status.default_fee_per_byte)} µIUNA / byte</strong></div><button class="button" type="submit">Review transaction</button></form>`;
}
function renderReceive() {
- return `${topbar()}<p class="eyebrow">Jouw adres</p><h1 class="view-title">Ontvang IUNA</h1><p class="view-copy">Deel dit mainnet-adres met de afzender.</p><div class="panel"><div class="receive-emblem">${icon("receive")}</div><p class="eyebrow">Ontvangstadres</p><div class="address-box"><code>${escapeHtml(state.address)}</code><button class="icon-button" data-action="copy-address" aria-label="Kopieer adres">${icon("copy")}</button></div><p class="security-note">Controleer altijd de eerste en laatste tekens wanneer je een adres deelt.</p></div>`;
+ return `${topbar()}<p class="eyebrow">Your address</p><h1 class="view-title">Receive IUNA</h1><p class="view-copy">Share this mainnet address with the sender.</p><div class="panel"><div class="receive-emblem">${icon("receive")}</div><p class="eyebrow">Receiving address</p><div class="address-box"><code>${escapeHtml(state.address)}</code><button class="icon-button" data-action="copy-address" aria-label="Copy address">${icon("copy")}</button></div><p class="security-note">Always verify the first and last characters when sharing an address.</p></div>`;
}
function renderActivity() {
- return `${topbar()}<p class="eyebrow">Wallet</p><h1 class="view-title">Activiteit</h1><p class="view-copy">Bevestigde en nog lopende transacties.</p><div class="panel">${activityList(50)}</div>`;
+ return `${topbar()}<p class="eyebrow">Wallet</p><h1 class="view-title">Activity</h1><p class="view-copy">Confirmed and pending transactions.</p><div class="panel">${activityList(50)}</div>`;
}
function renderSettings() {
- return `${topbar()}<p class="eyebrow">Wallet</p><h1 class="view-title">Instellingen</h1><div class="panel"><div class="setting"><h3>Netwerk</h3><p>${escapeHtml(state.status.network_id)} · blok ${escapeHtml(state.status.height)}</p></div><div class="setting"><h3>Public endpoint</h3><p>${escapeHtml(API_BASE)}</p></div><div class="setting"><h3>Lokale beveiliging</h3><p>AES-256-GCM · PBKDF2-SHA256 · 310.000 iteraties</p></div><div class="setting"><h3>Wallet-adres</h3><p style="word-break:break-all">${escapeHtml(state.address)}</p></div></div><button class="button ghost" data-action="lock" style="width:100%;margin-top:12px">Wallet vergrendelen</button><button class="button danger" data-action="forget" style="width:100%;margin-top:10px">Wallet van dit apparaat verwijderen</button><p class="security-note">iuna is experimentele software. Gebruik alleen bedragen die je kunt missen.</p>`;
+ return `${topbar()}<p class="eyebrow">Wallet</p><h1 class="view-title">Settings</h1><div class="panel"><div class="setting"><h3>Network</h3><p>${escapeHtml(state.status.network_id)} · block ${escapeHtml(state.status.height)}</p></div><div class="setting"><h3>Public endpoint</h3><p>${escapeHtml(API_BASE)}</p></div><div class="setting"><h3>Local security</h3><p>AES-256-GCM · PBKDF2-SHA256 · 310,000 iterations</p></div><div class="setting"><h3>Wallet address</h3><p style="word-break:break-all">${escapeHtml(state.address)}</p></div></div><button class="button ghost" data-action="lock" style="width:100%;margin-top:12px">Lock wallet</button><button class="button danger" data-action="forget" style="width:100%;margin-top:10px">Remove wallet from this device</button><p class="security-note">iuna is experimental software. Only use funds you can afford to lose.</p>`;
}
function renderApp() {
@@ -180,17 +180,17 @@ function renderApp() {
}
function renderConfirmation(transaction, fee, recipientAddress, amount) {
- app.innerHTML = `${topbar()}<button class="back" data-view="send">← Wijzigen</button><p class="eyebrow">Controle</p><h1 class="view-title">Klopt alles?</h1><div class="panel"><div class="detail-line"><span>Je verstuurt</span><strong>${formatIuna(amount)} IUNA</strong></div><div class="detail-line"><span>Naar</span><strong>${escapeHtml(recipientAddress.slice(0, 12))}…${escapeHtml(recipientAddress.slice(-8))}</strong></div><div class="detail-line"><span>Netwerkkosten</span><strong>${formatIuna(fee)} IUNA</strong></div><div class="detail-line"><span>Totaal</span><strong>${formatIuna(amount + fee)} IUNA</strong></div></div><button class="button" id="confirm-send" style="width:100%;margin-top:14px">Onderteken & verstuur</button><p class="security-note">Deze actie kan na verzending niet worden teruggedraaid.</p>${nav()}`;
+ app.innerHTML = `${topbar()}<button class="back" data-view="send">← Edit</button><p class="eyebrow">Review</p><h1 class="view-title">Does everything look right?</h1><div class="panel"><div class="detail-line"><span>You send</span><strong>${formatIuna(amount)} IUNA</strong></div><div class="detail-line"><span>To</span><strong>${escapeHtml(recipientAddress.slice(0, 12))}…${escapeHtml(recipientAddress.slice(-8))}</strong></div><div class="detail-line"><span>Network fee</span><strong>${formatIuna(fee)} IUNA</strong></div><div class="detail-line"><span>Total</span><strong>${formatIuna(amount + fee)} IUNA</strong></div></div><button class="button" id="confirm-send" style="width:100%;margin-top:14px">Sign & send</button><p class="security-note">This action cannot be reversed after submission.</p>${nav()}`;
document.querySelector("#confirm-send").addEventListener("click", async (event) => {
const button = event.currentTarget;
- button.disabled = true; button.innerHTML = '<span class="spinner"></span> Verzenden…';
+ button.disabled = true; button.innerHTML = '<span class="spinner"></span> Sending…';
try {
const result = await api("/transactions", { method: "POST", body: JSON.stringify(transaction) });
- toast(result.status === "accepted" ? "Transactie verstuurd" : "Transactie was al bekend");
+ toast(result.status === "accepted" ? "Transaction sent" : "Transaction was already known");
state.view = "home";
await fetchWalletData();
renderApp();
- } catch (error) { toast(error.message, true); button.disabled = false; button.textContent = "Opnieuw proberen"; }
+ } catch (error) { toast(error.message, true); button.disabled = false; button.textContent = "Try again"; }
});
}
@@ -203,12 +203,12 @@ app.addEventListener("click", async (event) => {
if (action === "create") await renderNewSeed();
if (action === "import") renderImport();
if (action === "seed-saved") renderPasswordSetup();
- if (action === "copy-seed") { await navigator.clipboard.writeText(app.dataset.pendingSeed); toast("Seed gekopieerd — wis je klembord na gebruik"); }
- if (action === "copy-address") { await navigator.clipboard.writeText(state.address); toast("Adres gekopieerd"); }
+ if (action === "copy-seed") { await navigator.clipboard.writeText(app.dataset.pendingSeed); toast("Seed copied — clear your clipboard after use"); }
+ if (action === "copy-address") { await navigator.clipboard.writeText(state.address); toast("Address copied"); }
if (action === "retry") await openWallet();
if (action === "lock") { window.clearInterval(state.timer); state.wallet = null; state.view = "home"; renderLock(); }
if (action === "forget") {
- if (window.confirm("Weet je zeker dat je de lokale wallet wilt verwijderen? Zorg dat je de seed hebt bewaard.")) {
+ if (window.confirm("Are you sure you want to remove the local wallet? Make sure you have saved the seed.")) {
localStorage.removeItem(STORAGE_KEY); window.clearInterval(state.timer); state.wallet = null; renderWelcome();
}
}
@@ -220,7 +220,7 @@ app.addEventListener("submit", async (event) => {
const button = form.querySelector('[type="submit"]');
button.disabled = true;
const original = button.textContent;
- button.innerHTML = '<span class="spinner"></span> Even geduld…';
+ button.innerHTML = '<span class="spinner"></span> Please wait…';
try {
if (form.id === "unlock-form") {
state.wallet = await decryptWallet(storedWallet(), form.password.value);
@@ -229,7 +229,7 @@ app.addEventListener("submit", async (event) => {
const seed = await validateMnemonic(form.seed.value);
await saveAndOpen(seed, form["new-password"].value);
} else if (form.id === "create-form") {
- if (form["create-password"].value !== form["confirm-password"].value) throw new Error("De wachtwoorden komen niet overeen");
+ if (form["create-password"].value !== form["confirm-password"].value) throw new Error("The passwords do not match");
await saveAndOpen(app.dataset.pendingSeed, form["create-password"].value);
} else if (form.id === "send-form") {
const amount = parseIuna(form.amount.value);
@@ -242,5 +242,5 @@ app.addEventListener("submit", async (event) => {
});
if (!window.isSecureContext || !crypto?.subtle) {
- app.innerHTML = '<section class="center-card"><div><h1>Veilige verbinding vereist</h1><p>Open deze wallet via HTTPS of localhost.</p></div></section>';
+ app.innerHTML = '<section class="center-card"><div><h1>Secure connection required</h1><p>Open this wallet over HTTPS or localhost.</p></div></section>';
} else if (storedWallet()) renderLock(); else renderWelcome();
diff --git a/wallet/index.html b/wallet/index.html
@@ -1,10 +1,10 @@
<!doctype html>
-<html lang="nl">
+<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#0f1012">
- <meta name="description" content="Een lichte, self-custody wallet voor iuna.">
+ <meta name="description" content="A lightweight, self-custody wallet for iuna.">
<title>iuna wallet</title>
<link rel="manifest" href="./manifest.webmanifest">
<link rel="icon" href="./icon.svg" type="image/svg+xml">
@@ -14,7 +14,7 @@
<body>
<div class="ambient ambient-one"></div><div class="ambient ambient-two"></div>
<main id="app" class="shell" aria-live="polite">
- <section id="loading" class="center-card"><img src="./icon.svg" width="48" height="48" alt="iuna"><p>Wallet laden…</p></section>
+ <section id="loading" class="center-card"><img src="./icon.svg" width="48" height="48" alt="iuna"><p>Loading wallet…</p></section>
</main>
<div id="toast" class="toast" role="status"></div>
<script type="module" src="./app.js"></script>
diff --git a/wallet/manifest.webmanifest b/wallet/manifest.webmanifest
@@ -5,6 +5,6 @@
"display": "standalone",
"background_color": "#0d1512",
"theme_color": "#0d1512",
- "description": "Een lichte, self-custody wallet voor iuna.",
+ "description": "A lightweight, self-custody wallet for iuna.",
"icons": [{ "src": "./icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any maskable" }]
}
diff --git a/wallet/mnemonic.js b/wallet/mnemonic.js
@@ -11,14 +11,14 @@ export async function generateMnemonic() {
export async function validateMnemonic(value) {
const words = normalizeSeed(value).split(" ");
- if (words.length !== 24) throw new Error("Een seed phrase moet precies 24 woorden bevatten");
+ if (words.length !== 24) throw new Error("A seed phrase must contain exactly 24 words");
const indexes = words.map((word) => WORDS.indexOf(word));
- if (indexes.some((index) => index < 0)) throw new Error("De seed phrase bevat een onbekend BIP-39 woord");
+ if (indexes.some((index) => index < 0)) throw new Error("The seed phrase contains an unknown BIP-39 word");
const bits = indexes.map((index) => index.toString(2).padStart(11, "0")).join("");
const entropyBits = bits.slice(0, 256);
const checksumBits = bits.slice(256);
const entropy = Uint8Array.from(entropyBits.match(/.{8}/g), (byte) => Number.parseInt(byte, 2));
const checksum = new Uint8Array(await crypto.subtle.digest("SHA-256", entropy));
- if (checksum[0].toString(2).padStart(8, "0") !== checksumBits) throw new Error("De checksum van de seed phrase klopt niet");
+ if (checksum[0].toString(2).padStart(8, "0") !== checksumBits) throw new Error("The seed phrase checksum is invalid");
return normalizeSeed(value);
}
diff --git a/wallet/wallet-core.js b/wallet/wallet-core.js
@@ -9,7 +9,7 @@ export function bytesToHex(bytes) {
}
export function hexToBytes(hex) {
- if (!/^[0-9a-f]*$/.test(hex) || hex.length % 2) throw new Error("Ongeldige hexwaarde");
+ if (!/^[0-9a-f]*$/.test(hex) || hex.length % 2) throw new Error("Invalid hexadecimal value");
return Uint8Array.from(hex.match(/.{2}/g) || [], (pair) => Number.parseInt(pair, 16));
}
@@ -36,7 +36,7 @@ export async function walletFromSeed(seedPhrase) {
try {
privateKey = await crypto.subtle.importKey("pkcs8", pkcs8, { name: "Ed25519" }, true, ["sign"]);
} catch {
- throw new Error("Deze browser ondersteunt geen veilige Ed25519-sleutels. Gebruik een recente Safari, Chrome, Edge of Firefox.");
+ throw new Error("This browser does not support secure Ed25519 keys. Use a recent version of Safari, Chrome, Edge, or Firefox.");
}
const jwk = await crypto.subtle.exportKey("jwk", privateKey);
const publicKey = fromBase64url(jwk.x);
@@ -48,7 +48,7 @@ export function normalizeSeed(seed) {
}
export async function encryptWallet(seedPhrase, password, publicKeyHex) {
- if (password.length < 10) throw new Error("Kies een wachtwoord van minimaal 10 tekens");
+ if (password.length < 10) throw new Error("Choose a password of at least 10 characters");
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const material = await crypto.subtle.importKey("raw", encoder.encode(password), "PBKDF2", false, ["deriveKey"]);
@@ -93,10 +93,10 @@ export async function decryptWallet(record, password) {
fromBase64url(record.ciphertext),
);
const wallet = await walletFromSeed(JSON.parse(new TextDecoder().decode(plaintext)).seed);
- if (wallet.publicKeyHex !== record.address) throw new Error("Adrescontrole mislukt");
+ if (wallet.publicKeyHex !== record.address) throw new Error("Address verification failed");
return wallet;
} catch {
- throw new Error("Onjuist wachtwoord of beschadigde wallet");
+ throw new Error("Incorrect password or damaged wallet");
}
}
@@ -125,13 +125,13 @@ function convertBits(data, from, to, pad) {
const result = [];
const max = (1 << to) - 1;
for (const value of data) {
- if (value >>> from) throw new Error("Ongeldige adresdata");
+ if (value >>> from) throw new Error("Invalid address data");
acc = ((acc << from) | value) & ((1 << (from + to - 1)) - 1);
bits += from;
while (bits >= to) { bits -= to; result.push((acc >>> bits) & max); }
}
if (pad && bits) result.push((acc << (to - bits)) & max);
- else if (!pad && (bits >= from || ((acc << (to - bits)) & max))) throw new Error("Ongeldige adrespadding");
+ else if (!pad && (bits >= from || ((acc << (to - bits)) & max))) throw new Error("Invalid address padding");
return result;
}
@@ -146,19 +146,19 @@ export function encodeAddress(publicKey, networkId = "iuna-mainnet-v1") {
export function decodeAddress(address, expectedHrp = "iuna") {
const normalized = address.trim().toLowerCase();
- if (address !== address.toLowerCase() && address !== address.toUpperCase()) throw new Error("Adres gebruikt hoofdletters en kleine letters door elkaar");
+ if (address !== address.toLowerCase() && address !== address.toUpperCase()) throw new Error("Address mixes uppercase and lowercase characters");
const separator = normalized.lastIndexOf("1");
- if (separator < 1 || separator + 7 > normalized.length || normalized.slice(0, separator) !== expectedHrp) throw new Error(`Dit is geen geldig ${expectedHrp}-adres`);
+ if (separator < 1 || separator + 7 > normalized.length || normalized.slice(0, separator) !== expectedHrp) throw new Error(`This is not a valid ${expectedHrp} address`);
const data = [...normalized.slice(separator + 1)].map((char) => {
const value = CHARSET.indexOf(char);
- if (value < 0) throw new Error("Adres bevat een ongeldig teken");
+ if (value < 0) throw new Error("Address contains an invalid character");
return value;
});
- if (polymod([...hrpExpand(expectedHrp), ...data]) !== BECH32M) throw new Error("De adres-checksum klopt niet");
+ if (polymod([...hrpExpand(expectedHrp), ...data]) !== BECH32M) throw new Error("Address checksum is invalid");
const payload = data.slice(0, -6);
- if (payload.shift() !== 0) throw new Error("Niet-ondersteunde adresversie");
+ if (payload.shift() !== 0) throw new Error("Unsupported address version");
const key = Uint8Array.from(convertBits(payload, 5, 8, false));
- if (key.length !== 32) throw new Error("Ongeldige adressleutel");
+ if (key.length !== 32) throw new Error("Invalid address key");
return bytesToHex(key);
}
@@ -240,7 +240,7 @@ export function selectInputs(utxos, amount, feeRate, owner, recipient) {
}
if (total >= amount + fee) return { inputs: selected, total, fee };
}
- throw new Error("Onvoldoende besteedbaar saldo voor bedrag en netwerkkosten");
+ throw new Error("Insufficient spendable balance for the amount and network fee");
}
export async function buildSignedTransfer({ wallet, status, utxos, recipientAddress, amount }) {
@@ -271,7 +271,7 @@ export async function buildSignedTransfer({ wallet, status, utxos, recipientAddr
export function parseIuna(value) {
const normalized = value.trim().replace(",", ".");
- if (!/^\d+(\.\d{0,6})?$/.test(normalized)) throw new Error("Vul een geldig bedrag in (maximaal 6 decimalen)");
+ if (!/^\d+(\.\d{0,6})?$/.test(normalized)) throw new Error("Enter a valid amount (up to 6 decimal places)");
const [whole, fraction = ""] = normalized.split(".");
return BigInt(whole) * MICRO_IUNA + BigInt(fraction.padEnd(6, "0"));
}
@@ -280,7 +280,7 @@ export function formatIuna(value, maximumFractionDigits = 6) {
const amount = BigInt(value || 0);
const whole = amount / MICRO_IUNA;
const fraction = (amount % MICRO_IUNA).toString().padStart(6, "0").slice(0, maximumFractionDigits).replace(/0+$/, "");
- return `${whole.toLocaleString("nl-NL")}${fraction ? `,${fraction}` : ""}`;
+ return `${whole.toLocaleString("en-US")}${fraction ? `.${fraction}` : ""}`;
}
export async function api(path, options = {}) {
@@ -299,13 +299,13 @@ export async function api(path, options = {}) {
},
});
} catch (error) {
- if (error.name === "AbortError") throw new Error(`Geen antwoord van iuna na ${(_timeoutMs || 8_000) / 1000} seconden`);
- throw new Error("Kan geen verbinding maken met het iuna-endpoint");
+ if (error.name === "AbortError") throw new Error(`No response from iuna after ${(_timeoutMs || 8_000) / 1000} seconds`);
+ throw new Error("Could not connect to the iuna endpoint");
} finally {
window.clearTimeout(timeout);
}
let body;
try { body = await response.json(); } catch { body = {}; }
- if (!response.ok) throw new Error(body.error || `Endpoint gaf status ${response.status}`);
+ if (!response.ok) throw new Error(body.error || `Endpoint returned status ${response.status}`);
return body;
}