taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit e0f8d1a576aa1489ba62cc3ed17f5ea3e6b2e0e6
parent 1f8b707ecb19cb858d8618f62efa73e45a77cf91
Author: Florian Dold <dold@taler.net>
Date:   Sun, 13 Sep 2026 17:15:39 +0200

wallet web UI: add translator context to financial screens

Explain ambiguous balance, payment, withdrawal and transaction labels
where they are displayed, including fees and the direction of transfers.

Diffstat:
Mpackages/wallet-webui/src/screens/ActionScreen.tsx | 40+++++++++++++++++++++++-----------------
Mpackages/wallet-webui/src/screens/BalanceScreen.tsx | 106++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mpackages/wallet-webui/src/screens/BankConfirmationScreen.tsx | 24++++++++++++++++--------
Mpackages/wallet-webui/src/screens/DepositScreen.tsx | 38++++++++++++++++++++++++--------------
Mpackages/wallet-webui/src/screens/PaymentScreen.tsx | 74+++++++++++++++++++++++++++++++++++++++++---------------------------------
Mpackages/wallet-webui/src/screens/PaymentTemplateScreen.tsx | 6+++---
Mpackages/wallet-webui/src/screens/PeerCreateScreen.tsx | 24+++++++++++++++---------
Mpackages/wallet-webui/src/screens/PeerReceiveScreen.tsx | 8++++++--
Mpackages/wallet-webui/src/screens/TransactionDetailScreen.tsx | 6+++---
Mpackages/wallet-webui/src/screens/TransactionScreen.tsx | 8++++++--
Mpackages/wallet-webui/src/screens/WireTransferScreen.tsx | 22++++++++++++++--------
Mpackages/wallet-webui/src/screens/WithdrawalReviewScreen.tsx | 20+++++++++++++++-----
Mpackages/wallet-webui/src/screens/WithdrawalScreen.tsx | 38++++++++++++++++++++++++++++----------
13 files changed, 267 insertions(+), 147 deletions(-)

diff --git a/packages/wallet-webui/src/screens/ActionScreen.tsx b/packages/wallet-webui/src/screens/ActionScreen.tsx @@ -7,19 +7,19 @@ import type { ErrorPresentation } from "../ui/error.js"; function actionSourceLabel(source: string): string { switch (source) { - // The wallet received the Taler URI from a link the user opened. + // Translators: The wallet received the Taler URI from a link the user opened. case "link": return i18n.str`a link`; - // The wallet discovered the Taler URI in metadata embedded in a web page. + // Translators: The wallet discovered the Taler URI in metadata embedded in a web page. case "metadata": return i18n.str`web page metadata`; - // The user pasted the Taler URI into the wallet. + // Translators: The user pasted the Taler URI into the wallet. case "paste": return i18n.str`pasted text`; - // The wallet obtained the Taler URI by scanning a QR code. + // Translators: The wallet obtained the Taler URI by scanning a QR code. case "qr": return i18n.str`a QR code`; - // The browser passed the Taler URI to the wallet as a registered protocol handler. + // Translators: The browser passed the Taler URI to the wallet as a registered protocol handler. case "protocol": return i18n.str`a browser protocol handler`; default: @@ -36,21 +36,23 @@ function actionKindLabel(kind: string): string { case "refund": return i18n.str`Refund`; case "pay-push": + // Translators: Opening a push-payment link receives money in this wallet. return i18n.str`Receive money`; case "pay-pull": + // Translators: Noun naming a request for money, not a command to request money. return i18n.str`Payment request`; case "pay-template": - return i18n.str`Payment template`; + return /* Translators: A merchant-provided form that the payer fills in to create an order; not a saved payment method. */ i18n.str`Payment template`; case "restore": - return i18n.str`Restore wallet`; + return /* Translators: Open a wallet backup-provider recovery link; this does not undo a payment or reset the wallet. */ i18n.str`Restore wallet`; case "dev-experiment": return i18n.str`Developer experiment`; case "withdraw-exchange": - return i18n.str`Exchange withdrawal`; + return /* Translators: Obtaining digital cash from a Taler exchange using a link supplied by that exchange. */ i18n.str`Exchange withdrawal`; case "withdrawal-transfer-result": - return i18n.str`Withdrawal transfer result`; + return /* Translators: A bank-reported result of the transfer funding a digital-cash withdrawal; the wallet still verifies its own transaction. */ i18n.str`Withdrawal transfer result`; case "add-exchange": - return i18n.str`Add exchange`; + return /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Add exchange`; case "add-contact": return i18n.str`Add contact`; default: @@ -73,14 +75,16 @@ export function ActionScreen(props: { }) { return ( <FocusedPage> - <h1 class="mb-4 text-2xl font-bold">{i18n.str`Wallet action`}</h1> + <h1 class="mb-4 text-2xl font-bold">{ + /* Translators: An operation opened through a Taler link, such as payment, withdrawal, or accepting a transfer. */ i18n.str`Wallet action` + }</h1> {props.state === "error" ? ( <> <ErrorCard error={ props.error ?? { message: - // Prepared means parsed and staged for review; the underlying action has not completed. + // Translators: Prepared means parsed and staged for review; the underlying action has not completed. props.detail ?? i18n.str`The action could not be prepared.`, } } @@ -94,7 +98,9 @@ export function ActionScreen(props: { </Button> {props.onDiscard && ( <Button tone="danger" onClick={props.onDiscard}> - {i18n.str`Discard action`} + { + /* Translators: Remove a stored Taler link from the action inbox without performing the operation it describes. */ i18n.str`Discard action` + } </Button> )} </div> @@ -119,7 +125,7 @@ export function ActionScreen(props: { {props.source && ( <p class="text-sm text-muted"> { - // Describes how the wallet received the Taler URI. + // Translators: Describes how the wallet received the Taler URI. i18n.str`Detected from ${actionSourceLabel(props.source)}` } </p> @@ -127,11 +133,11 @@ export function ActionScreen(props: { <h2 class="mt-1 text-xl font-semibold"> {props.actionKind ? actionKindLabel(props.actionKind) - : i18n.str`GNU Taler action`} + : /* Translators: An operation encoded in a Taler link. Keep the product name GNU Taler unchanged. */ i18n.str`GNU Taler action`} </h2> <p class="mt-3 text-muted"> {props.detail ?? - // Generic action-inbox guidance; the next step varies by Taler URI type. + // Translators: Generic action-inbox guidance; the next step varies by Taler URI type. i18n.str`Review the wallet-provided details before confirming the next step.`} </p> <div class="mt-5 flex gap-2"> @@ -154,7 +160,7 @@ export function ActionScreen(props: { <> <p role="status"> {props.detail ?? - // Prepared means parsed and staged; it does not mean the underlying action completed. + // Translators: Prepared means parsed and staged; it does not mean the underlying action completed. i18n.str`The action was prepared successfully.`} </p> <div class="mt-5"> diff --git a/packages/wallet-webui/src/screens/BalanceScreen.tsx b/packages/wallet-webui/src/screens/BalanceScreen.tsx @@ -51,7 +51,7 @@ function BalanceAmount(props: { balance: BalanceView }) { class={props.balance.legacy ? "text-secondary line-through" : undefined} aria-label={ props.balance.legacy - ? i18n.str`Legacy balance ${props.balance.available}, unavailable for normal use` + ? /* Translators: Legacy funds belong to a previous cryptographic identity of an exchange, not merely an old wallet software version. */ i18n.str`Legacy balance ${props.balance.available}, unavailable for normal use` : undefined } > @@ -123,8 +123,12 @@ export function RefreshBalanceDetails(props: { role="status" class="rounded-lg border border-warning p-3" > - <p class="font-semibold">{i18n.str`Coins close to expiration`}</p> - <p>{i18n.str`${r.amount} at ${r.exchange} expires on ${r.expiration}.`}</p> + <p class="font-semibold">{ + /* Translators: Digital coins must be renewed before their cryptographic validity ends to avoid losing their value. */ i18n.str`Coins close to expiration` + }</p> + <p>{ + /* Translators: %1$s is a monetary amount; %2$s identifies its exchange; %3$s is the expiry date. */ i18n.str`${r.amount} at ${r.exchange} expires on ${r.expiration}.` + }</p> <p>{r.reason}</p> <p>{i18n.str`Keep the wallet open to allow renewal. Funds can expire if renewal does not complete in time.`}</p> </div> @@ -135,9 +139,15 @@ export function RefreshBalanceDetails(props: { role="status" class="rounded-lg border border-outlineVariant p-3" > - <p class="font-semibold">{i18n.str`Coins renewed`}</p> - <p>{i18n.str`${r.amount} at ${r.exchange} was close to expiration and has been renewed until ${r.expiration}.`}</p> - <p>{i18n.str`Open the wallet again before ${r.nextDate} to check renewal. This date depends on the coins' validity periods.`}</p> + <p class="font-semibold">{ + /* Translators: Expiring digital coins have been reissued with a later validity period. */ i18n.str`Coins renewed` + }</p> + <p>{ + /* Translators: %1$s is a monetary amount; %2$s identifies its exchange; %3$s is the new validity end date. */ i18n.str`${r.amount} at ${r.exchange} was close to expiration and has been renewed until ${r.expiration}.` + }</p> + <p>{ + /* Translators: %1$s is a date by which the user should reopen the wallet to let it renew expiring digital cash. */ i18n.str`Open the wallet again before ${r.nextDate} to check renewal. This date depends on the coins' validity periods.` + }</p> {props.onDismiss && ( <Button tone="secondary" @@ -147,19 +157,29 @@ export function RefreshBalanceDetails(props: { </div> ))} <details class="rounded-lg border border-outlineVariant p-3"> - <summary class="cursor-pointer font-semibold">{i18n.str`Cost of keeping this balance`}</summary> + <summary class="cursor-pointer font-semibold">{ + /* Translators: The cost of renewing digital cash to keep it spendable over time, not a bank-account maintenance fee. */ i18n.str`Cost of keeping this balance` + }</summary> {refresh.bound !== undefined ? ( - <p class="mt-2">{i18n.str`Conditional upper bound over 365 days: ${refresh.bound}.`}</p> + <p class="mt-2">{ + /* Translators: %1$s is a monetary upper bound on renewal costs for the current balance under the assumptions shown below; it is not a bill or an expected charge. */ i18n.str`Conditional upper bound over 365 days: ${refresh.bound}.` + }</p> ) : ( <div class="mt-2"> - <p>{i18n.str`Annual cost bound unavailable.`}</p> + <p>{ + /* Translators: The wallet cannot calculate an upper bound for annual digital-coin renewal costs. */ i18n.str`Annual cost bound unavailable.` + }</p> {refresh.unavailableReasons.map((r) => ( <p key={r}>{r}</p> ))} </div> )} - <p class="mt-2">{i18n.str`This is an upper bound, not an expected charge. It assumes compatible coins remain continuously available with today's values, fees and lifetime bounds, and that renewal finishes before withdrawal validity ends.`}</p> - <p class="mt-2">{i18n.str`Future exchange offerings are a projection, not a promise. The bound covers keeping the current balance without payments or additional withdrawals. It excludes initial withdrawal fees, payment-related renewal, and losses from expiration or outages.`}</p> + <p class="mt-2">{ + /* Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. */ i18n.str`This is an upper bound, not an expected charge. It assumes compatible coins remain continuously available with today's values, fees and lifetime bounds, and that renewal finishes before withdrawal validity ends.` + }</p> + <p class="mt-2">{ + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Future exchange offerings are a projection, not a promise. The bound covers keeping the current balance without payments or additional withdrawals. It excludes initial withdrawal fees, payment-related renewal, and losses from expiration or outages.` + }</p> </details> </div> ); @@ -172,9 +192,13 @@ function PendingAmounts(props: { balance: BalanceView; compact?: boolean }) { return ( <div class={`flex flex-wrap items-center gap-2 ${props.compact ? "text-xs" : "text-sm"}`} - aria-label={i18n.str`Pending balance changes`} + aria-label={ + /* Translators: Incoming or outgoing amounts from transactions that have not settled yet. */ i18n.str`Pending balance changes` + } > - <span class="mr-auto font-medium">{i18n.str`Pending:`}</span> + <span class="mr-auto font-medium">{ + /* Translators: Introduces changes to a wallet balance that have not settled yet. */ i18n.str`Pending:` + }</span> {incoming && ( <span class="rounded-full bg-successContainer px-3 py-1 font-medium text-onSuccessContainer"> + &nbsp;{props.balance.pendingIncomingValue} @@ -213,7 +237,7 @@ function balancePointAmount(currency: string, value: number): string { } function transactionCountLabel(count: number): string { - // Number of completed transactions grouped into one balance-chart bucket. + // Translators: Number of completed transactions grouped into one balance-chart bucket. return i18n.plural( count, i18n.lazy`${count} transaction`, @@ -222,13 +246,13 @@ function transactionCountLabel(count: number): string { } function balancePointHeading(point: BalanceTrendPoint): string { - // The first point of reconstructed visible history, not the account-opening balance. + // Translators: The first point of reconstructed visible history, not the account-opening balance. return point.kind === "start" ? i18n.str`Start balance` : point.label; } function balancePointActivity(point: BalanceTrendPoint): string { if (point.kind === "start") { - // The oldest point that can be reconstructed from visible wallet transactions. + // Translators: The oldest point that can be reconstructed from visible wallet transactions. return i18n.str`Beginning of visible history`; } if (point.kind === "current") return i18n.str`Current available balance`; @@ -309,7 +333,9 @@ function BalanceTrend(props: { return ( <figure class="group mt-4" - aria-label={i18n.str`Recent available balance history for ${props.balance.scopeLabel}`} + aria-label={ + /* Translators: %1$s names a currency or payment scope whose available balance is shown in the chart. */ i18n.str`Recent available balance history for ${props.balance.scopeLabel}` + } > <svg class="h-auto w-full overflow-visible rounded text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary" @@ -460,7 +486,9 @@ function BalanceTrendSection(props: { <div class="mt-4 border-t border-outlineVariant pt-3"> <div class="flex justify-end"> <label> - <span class="sr-only">{i18n.str`Balance history period`}</span> + <span class="sr-only">{ + /* Translators: The time interval shown by the balance-history chart. */ i18n.str`Balance history period` + }</span> <select value={props.period} onChange={(event) => @@ -471,7 +499,9 @@ function BalanceTrendSection(props: { <option value="day">{i18n.str`Day`}</option> <option value="week">{i18n.str`Week`}</option> <option value="month">{i18n.str`Month`}</option> - <option value="quarter">{i18n.str`Quarter`}</option> + <option value="quarter">{ + /* Translators: A three-month period in the balance-history chart, not a fraction of a currency unit. */ i18n.str`Quarter` + }</option> </select> </label> </div> @@ -561,7 +591,7 @@ function ActivityCard(props: { class="rounded-full bg-secondaryContainer px-4 py-1.5 text-sm font-medium text-onSecondaryContainer hover:brightness-95" > { - // Link from a transaction card to all transactions for the selected balance. + // Translators: Link from a transaction card to all transactions for the selected balance. i18n.str`All` }{" "} &nbsp;→ @@ -726,7 +756,7 @@ export function BalanceScreen(props: { const sendActionLabel = i18n.ctx("wallet action")`Send`; // Translators: Wallet action that requests digital cash from another person. const requestActionLabel = i18n.ctx("wallet action")`Request`; - // Translators: Wallet action that obtains digital cash from a bank or exchange. + // Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. const withdrawActionLabel = i18n.ctx("wallet action")`Withdraw`; // Translators: Wallet action that sends digital cash to an external bank account. const depositActionLabel = i18n.ctx("wallet action")`Deposit`; @@ -747,11 +777,13 @@ export function BalanceScreen(props: { <Card class="flex min-h-52 flex-col justify-between md:px-8 md:py-7"> <div> <h1 class="font-semibold">{i18n.str`Welcome to Taler Wallet!`}</h1> - <p class="mt-3">{i18n.str`Withdraw digital cash to make your first payment.`}</p> + <p class="mt-3">{ + /* Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. */ i18n.str`Withdraw digital cash to make your first payment.` + }</p> </div> - <Button - onClick={() => props.onWithdraw()} - >{i18n.str`Withdraw digital cash`}</Button> + <Button onClick={() => props.onWithdraw()}>{ + /* Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. */ i18n.str`Withdraw digital cash` + }</Button> </Card> <Card class="flex min-h-52 flex-col justify-between md:px-8 md:py-7"> <div> @@ -818,7 +850,7 @@ export function BalanceScreen(props: { <h1 id="selected-balance-title" aria-label={ - // The placeholder is a fully formatted monetary amount. + // Translators: The placeholder is a fully formatted monetary amount. i18n.str`Available balance ${selected.available}` } class="mt-3 flex items-baseline gap-8 text-3xl" @@ -838,7 +870,9 @@ export function BalanceScreen(props: { onClick={() => props.onManageLegacyBalance!(legacyExchangeUrl(selected)!) } - >{i18n.str`Manage legacy balance`}</Button> + >{ + /* Translators: Legacy funds belong to a previous cryptographic identity of an exchange, not merely an old wallet software version. */ i18n.str`Manage legacy balance` + }</Button> </div> )} </div> @@ -905,7 +939,7 @@ export function BalanceScreen(props: { <h1 id="mobile-balance-title" aria-label={ - // The placeholder is a fully formatted monetary amount. + // Translators: The placeholder is a fully formatted monetary amount. i18n.str`Available balance ${selected.available}` } class="mt-4 flex items-baseline gap-5 text-3xl" @@ -942,14 +976,16 @@ export function BalanceScreen(props: { > <WalletIcon name="map" class="h-8 w-8" /> { - // Opens merchant locations; the placeholder is a currency code. + // Translators: Opens merchant locations; the placeholder is a currency code. i18n.str`Where to pay with ${selected.currency}` } </button> )} <div class="flex items-center justify-center gap-3" - aria-label={i18n.str`Balance selector`} + aria-label={ + /* Translators: A control that selects the currency or payment scope whose balance is shown. */ i18n.str`Balance selector` + } > <button type="button" @@ -965,7 +1001,7 @@ export function BalanceScreen(props: { key={balance.scopeId} onClick={() => select(balance.scopeId)} aria-label={ - // Selects a wallet balance; the placeholder is its scope label. + // Translators: Selects a wallet balance; the placeholder is its scope label. i18n.str`Show ${balance.scopeLabel}` } aria-current={ @@ -1067,14 +1103,18 @@ export function BalanceScreen(props: { {props.onShowDiscounts && ( <BenefitAction icon="discount" - label={i18n.str`Discounts`} + label={ + /* Translators: Merchant-issued discount tokens available in this wallet. */ i18n.str`Discounts` + } onClick={props.onShowDiscounts} /> )}{" "} {props.onShowPasses && ( <BenefitAction icon="subscriptions" - label={i18n.str`Passes`} + label={ + /* Translators: Merchant-issued access or subscription tokens, such as an admission pass; not passwords or travel documents. */ i18n.str`Passes` + } onClick={props.onShowPasses} /> )}{" "} diff --git a/packages/wallet-webui/src/screens/BankConfirmationScreen.tsx b/packages/wallet-webui/src/screens/BankConfirmationScreen.tsx @@ -21,7 +21,7 @@ export function BankConfirmationScreen(props: { </p> <h1 class="mt-1 text-2xl font-medium"> {status === "confirmed" - ? // The funding transfer from the user's bank to the exchange was accepted. + ? // Translators: The funding transfer from the user's bank to the exchange was accepted. i18n.str`Bank transfer confirmed` : status === "aborted" ? i18n.str`No bank transfer will be made` @@ -46,22 +46,28 @@ export function BankConfirmationScreen(props: { <Card class="border-success bg-successContainer"> <h2 class="text-lg font-semibold"> { - // The funding transfer from the user's bank to the exchange was accepted. + // Translators: The funding transfer from the user's bank to the exchange was accepted. i18n.str`The bank accepted the transfer` } </h2> - <p class="mt-2 text-secondary">{i18n.str`The wallet will finish withdrawing the digital cash in the background.`}</p> + <p class="mt-2 text-secondary">{ + /* Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. */ i18n.str`The wallet will finish withdrawing the digital cash in the background.` + }</p> </Card> ) : status === "aborted" ? ( <Card class="border-warning bg-warningContainer"> - <h2 class="text-lg font-semibold">{i18n.str`The bank canceled this withdrawal`}</h2> - <p class="mt-2 text-secondary">{i18n.str`Return to the wallet to start another withdrawal.`}</p> + <h2 class="text-lg font-semibold">{ + /* Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. */ i18n.str`The bank canceled this withdrawal` + }</h2> + <p class="mt-2 text-secondary">{ + /* Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. */ i18n.str`Return to the wallet to start another withdrawal.` + }</p> </Card> ) : props.hasConfirmationUrl ? ( <Card> <h2 class="text-lg font-semibold"> { - // The user still needs to authorize the funding transfer on the bank website. + // Translators: The user still needs to authorize the funding transfer on the bank website. i18n.str`One final bank step` } </h2> @@ -71,11 +77,13 @@ export function BankConfirmationScreen(props: { <Card> <h2 class="text-lg font-semibold"> { - // The bank has the funding request and the wallet is waiting for confirmation. + // Translators: The bank has the funding request and the wallet is waiting for confirmation. i18n.str`Waiting for the bank` } </h2> - <p class="mt-2 text-secondary">{i18n.str`The bank has the withdrawal request. The wallet will update when the transfer is confirmed.`}</p> + <p class="mt-2 text-secondary">{ + /* Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. */ i18n.str`The bank has the withdrawal request. The wallet will update when the transfer is confirmed.` + }</p> </Card> )} <div class="flex flex-wrap justify-end gap-3 border-t border-outlineVariant pt-5"> diff --git a/packages/wallet-webui/src/screens/DepositScreen.tsx b/packages/wallet-webui/src/screens/DepositScreen.tsx @@ -128,7 +128,7 @@ export function DepositScreen(props: { <div> <h1 class="text-2xl font-medium"> { - // Deposit means converting wallet digital cash into a transfer to an external account. + // Translators: Deposit means converting wallet digital cash into a transfer to an external account. i18n.str`Deposit to a bank account` } </h1> @@ -197,7 +197,7 @@ export function DepositScreen(props: { <label> <span class="mb-1 block text-sm text-secondary"> { - // Saved destination account that will receive the deposit. + // Translators: Saved destination account that will receive the deposit. i18n.str`Bank account` } </span> @@ -230,7 +230,9 @@ export function DepositScreen(props: { {destination && <DestinationDetails destination={destination} />} {!destination && ( <div class="rounded-xl bg-secondaryContainer p-4"> - <p>{i18n.str`Add a bank account before making a deposit.`}</p> + <p>{ + /* Translators: A deposit sends digital cash from this wallet to an external account, usually a bank account; it does not add funds to the wallet. */ i18n.str`Add a bank account before making a deposit.` + }</p> {props.onManageAccounts && ( <div class="mt-3"> <Button @@ -263,7 +265,9 @@ export function DepositScreen(props: { props.working } > - {props.working ? i18n.str`Checking…` : i18n.str`Review deposit`} + {props.working + ? i18n.str`Checking…` + : /* Translators: A deposit sends digital cash from this wallet to an external account, usually a bank account; it does not add funds to the wallet. */ i18n.str`Review deposit`} </Button> </div> </form> @@ -273,7 +277,7 @@ export function DepositScreen(props: { <Card class="text-center"> <p class="text-sm text-secondary"> { - // Net amount credited to the destination after applicable fees. + // Translators: Net amount credited to the destination after applicable fees. i18n.str`Bank account receives` } </p> @@ -288,23 +292,29 @@ export function DepositScreen(props: { <dl class="grid grid-cols-2 gap-3 text-sm"> <dt class="text-secondary"> { - // Requested face amount before wallet costs. + // Translators: Requested face amount before wallet costs. i18n.str`Requested deposit` } </dt> <dd class="text-right">{props.review.amount}</dd> <dt class="text-secondary"> { - // Total digital-cash debit including fees. + // Translators: Total digital-cash debit including fees. i18n.str`Wallet spends` } </dt> <dd class="text-right font-semibold">{props.review.totalCost}</dd> - <dt class="text-secondary">{i18n.str`Coin fees`}</dt> + <dt class="text-secondary">{ + /* Translators: Fees charged for spending the digital coins used for the deposit. */ i18n.str`Coin fees` + }</dt> <dd class="text-right">{props.review.coinFee}</dd> - <dt class="text-secondary">{i18n.str`Wire fees`}</dt> + <dt class="text-secondary">{ + /* Translators: Fees for transferring the deposited funds from a Taler exchange to a bank account. */ i18n.str`Wire fees` + }</dt> <dd class="text-right">{props.review.wireFee}</dd> - <dt class="text-secondary">{i18n.str`Renewal fees`}</dt> + <dt class="text-secondary">{ + /* Translators: Fees for reissuing digital coins, including change left after a payment; not fees to reload the UI. */ i18n.str`Renewal fees` + }</dt> <dd class="text-right">{props.review.refreshFee}</dd> </dl> </Card> @@ -315,13 +325,13 @@ export function DepositScreen(props: { )} <div class="flex justify-end gap-3"> <Button tone="secondary" onClick={props.onBack}>{ - // Returns from review to editing the amount or destination. + // Translators: Returns from review to editing the amount or destination. i18n.str`Change` }</Button> <Button onClick={props.onConfirm} disabled={props.working}> {props.working - ? i18n.str`Depositing…` - : i18n.str`Confirm deposit`} + ? /* Translators: A deposit sends digital cash from this wallet to an external account, usually a bank account; it does not add funds to the wallet. */ i18n.str`Depositing…` + : /* Translators: A deposit sends digital cash from this wallet to an external account, usually a bank account; it does not add funds to the wallet. */ i18n.str`Confirm deposit`} </Button> </div> </> @@ -335,7 +345,7 @@ function DestinationDetails(props: { destination: DepositDestinationView }) { <div class="rounded-xl bg-surfaceContainerLow p-4" aria-label={ - // Accessible label for the external account receiving the transfer. + // Translators: Accessible label for the external account receiving the transfer. i18n.str`Destination account` } > diff --git a/packages/wallet-webui/src/screens/PaymentScreen.tsx b/packages/wallet-webui/src/screens/PaymentScreen.tsx @@ -78,7 +78,7 @@ function ChoiceInstrument(props: { <span class="min-w-0 flex-1"> <span class="block font-medium"> {instrument.count > 1 - ? // Quantity and merchant-provided token name; translators may reorder them. + ? // Translators: Quantity and merchant-provided token name; translators may reorder them. i18n.str`${instrument.count} × ${instrument.name}` : instrument.name} </span> @@ -91,7 +91,7 @@ function ChoiceInstrument(props: { {"availability" in instrument && instrument.availability && ( <span class="mt-2 block text-sm"> { - // First placeholder is the number owned; second is the number required. + // Translators: First placeholder is the number owned; second is the number required. i18n.str`${instrument.availability.available} of ${instrument.availability.requested} available` } </span> @@ -116,7 +116,7 @@ function ChoiceDetails(props: { choice: PaymentChoiceView }) { <div> <p class="mb-2 text-sm font-semibold"> { - // Merchant-issued tokens consumed in addition to digital cash for this payment. + // Translators: Merchant-issued tokens consumed in addition to digital cash for this payment. i18n.str`Spend now:` } </p> @@ -134,7 +134,7 @@ function ChoiceDetails(props: { choice: PaymentChoiceView }) { <div> <p class="mb-2 text-sm font-semibold"> { - // Merchant-issued tokens or receipts received as outputs of the purchase. + // Translators: Merchant-issued tokens or receipts received as outputs of the purchase. i18n.str`Get with your purchase:` } </p> @@ -189,7 +189,9 @@ export function PaymentOptions(props: { <> {props.choices && props.choices.length > 1 && ( <fieldset class="space-y-3"> - <legend class="mb-2 font-semibold">{i18n.str`Payment options`}</legend> + <legend class="mb-2 font-semibold">{ + /* Translators: Alternative offers for the same purchase, possibly with different prices or required merchant-issued tokens. */ i18n.str`Payment options` + }</legend> {props.choices.map((choice) => ( <label key={choice.index} @@ -212,7 +214,7 @@ export function PaymentOptions(props: { choice.amountEffective !== choice.amountRaw && ( <span class="block text-sm text-secondary"> { - // Placeholder is the fully formatted effective payment amount. + // Translators: Placeholder is the fully formatted effective payment amount. i18n.str`Total including fees: ${choice.amountEffective}` } </span> @@ -221,9 +223,9 @@ export function PaymentOptions(props: { <span class="mt-2 block text-sm text-error"> {choice.unavailableReason ?? (choice.availableBalance - ? // Token means a merchant-issued discount or subscription token, not an authentication token. + ? // Translators: Token means a merchant-issued discount or subscription token, not an authentication token. i18n.str`Insufficient balance or missing required token (${choice.availableBalance} available)` - : i18n.str`Insufficient balance or missing required token`)} + : /* Translators: The purchase cannot be paid because money or a merchant-issued discount/pass token is missing. Token does not mean authentication token. */ i18n.str`Insufficient balance or missing required token`)} </span> )} {choice.tokenWarning && ( @@ -282,7 +284,7 @@ export function PaymentOptions(props: { /> <span> { - // Opts into collecting a signed Donau receipt for a qualifying charitable payment. + // Translators: Opts into collecting a signed Donau receipt for a qualifying charitable payment. i18n.str`Collect a donation receipt for this payment` } </span> @@ -291,8 +293,8 @@ export function PaymentOptions(props: { <> <p class="mt-2 text-sm"> {selected.donationReceipt.status === "setup" - ? i18n.str`This payment can provide a donation receipt. Configure one of the supported donation authorities first.` - : i18n.str`This payment option does not support the configured donation authority.`} + ? /* Translators: The donation authority is the Donau service issuing donation receipts and statements, not the tax authority. */ i18n.str`This payment can provide a donation receipt. Configure one of the supported donation authorities first.` + : /* Translators: The selected purchase offer does not accept the Donau donation-receipt service configured by the user. */ i18n.str`This payment option does not support the configured donation authority.`} </p> {props.onConfigureDonau && selected.donationReceipt.donauUrls[0] && ( @@ -338,8 +340,8 @@ export function PaymentOptions(props: { {props.reclaiming ? i18n.str`Continuing with this wallet…` : props.unclaiming - ? i18n.str`Continue with this wallet` - : i18n.str`Continue with another wallet`} + ? /* Translators: Continue paying the same merchant order in this wallet, including taking it back from a handoff to another wallet. */ i18n.str`Continue with this wallet` + : /* Translators: Release the merchant order so another wallet can claim and pay it; no money is sent between wallets. */ i18n.str`Continue with another wallet`} </Button> )} {props.onCancel && ( @@ -353,14 +355,14 @@ export function PaymentOptions(props: { )} {selected && !selected.payable && props.onRecoveryAction && ( <Button onClick={props.onRecoveryAction}> - {props.recoveryActionLabel ?? i18n.str`Review exchange`} + {props.recoveryActionLabel ?? + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Review exchange`} </Button> )} {selected && !selected.payable && !props.onRecoveryAction && ( - <Button - tone="secondary" - onClick={props.onWithdraw} - >{i18n.str`Withdraw funds`}</Button> + <Button tone="secondary" onClick={props.onWithdraw}>{ + /* Translators: Add digital cash to this wallet by withdrawing it from a bank or exchange. */ i18n.str`Withdraw funds` + }</Button> )} <Button onClick={() => props.onConfirm(props.useDonau === true)} @@ -375,7 +377,7 @@ export function PaymentOptions(props: { {props.state === "paying" ? i18n.str`Paying…` : selected - ? // Button label; the placeholder is a fully formatted payment amount. + ? // Translators: Button label; the placeholder is a fully formatted payment amount. i18n.str`Pay ${selected.amountRaw}` : i18n.str`Pay`} </Button> @@ -392,11 +394,12 @@ export function PaymentHandoff(props: { }) { return ( <Card class="border-primary bg-primaryContainer text-onPrimaryContainer"> - <h2 class="text-xl font-semibold">{i18n.str`Continue in another wallet`}</h2> - <p - role="status" - class="mt-2" - >{i18n.str`Scan this QR code with the other wallet. This screen will close the code automatically when that wallet claims the payment.`}</p> + <h2 class="text-xl font-semibold">{ + /* Translators: The user is handing the merchant payment to another wallet, not transferring money to that wallet. */ i18n.str`Continue in another wallet` + }</h2> + <p role="status" class="mt-2">{ + /* Translators: Claiming a merchant order assigns it to a wallet for payment; it does not mean that payment has completed. */ i18n.str`Scan this QR code with the other wallet. This screen will close the code automatically when that wallet claims the payment.` + }</p> <div class="mt-5"> <QrFrame content={props.talerPayUri} @@ -636,11 +639,12 @@ export function PaymentScreen(props: { {props.state === "handed-off" && ( <Card class="border-primary bg-primaryContainer text-onPrimaryContainer"> - <h2 class="text-xl font-semibold">{i18n.str`Payment continued in another wallet`}</h2> - <p - role="status" - class="mt-2" - >{i18n.str`The other wallet claimed this payment. You can safely close this screen.`}</p> + <h2 class="text-xl font-semibold">{ + /* Translators: The order was handed to another wallet; this does not mean that the other wallet has finished paying. */ i18n.str`Payment continued in another wallet` + }</h2> + <p role="status" class="mt-2">{ + /* Translators: Claiming a merchant order assigns it to a wallet for payment; it does not mean that payment has completed. */ i18n.str`The other wallet claimed this payment. You can safely close this screen.` + }</p> <div class="mt-5"> <Button onClick={props.onBack}>{i18n.str`Back to wallet`}</Button> </div> @@ -651,7 +655,7 @@ export function PaymentScreen(props: { <Card class="border-primary bg-primaryContainer text-onPrimaryContainer"> <h2 class="font-semibold"> { - // The merchant submission succeeded, but the wallet transaction is still finalizing. + // Translators: The merchant submission succeeded, but the wallet transaction is still finalizing. i18n.str`Payment submitted` } </h2> @@ -723,7 +727,7 @@ export function PaymentScreen(props: { <Card> <h2 class="font-semibold"> { - // Point-of-sale code shown to the merchant after successful payment. + // Translators: Point-of-sale code shown to the merchant after successful payment. i18n.str`Merchant confirmation code` } </h2> @@ -749,8 +753,12 @@ export function PaymentScreen(props: { {confirmUnclaim && props.onUnclaim && ( <ConfirmationDialog title={i18n.str`Continue with another wallet?`} - description={i18n.str`This wallet will release the payment so another wallet can claim it. You can continue with this wallet again until another wallet claims the order.`} - cancelLabel={i18n.str`Keep payment here`} + description={ + /* Translators: Claiming a merchant order assigns it to a wallet for payment; it does not mean that payment has completed. */ i18n.str`This wallet will release the payment so another wallet can claim it. You can continue with this wallet again until another wallet claims the order.` + } + cancelLabel={ + /* Translators: Cancel the handoff and keep the merchant payment assigned to this wallet. */ i18n.str`Keep payment here` + } confirmLabel={i18n.str`Continue with another wallet`} working={props.unclaiming} onCancel={() => setConfirmUnclaim(false)} diff --git a/packages/wallet-webui/src/screens/PaymentTemplateScreen.tsx b/packages/wallet-webui/src/screens/PaymentTemplateScreen.tsx @@ -44,7 +44,7 @@ export function PaymentTemplateScreen(props: { type="button" onClick={props.onCancel} aria-label={ - // Accessible label for closing a merchant-defined payment-template form. + // Translators: Accessible label for closing a merchant-defined payment-template form. i18n.str`Cancel payment template` } class="grid h-11 w-11 place-items-center rounded-full text-2xl hover:bg-secondaryContainer" @@ -54,7 +54,7 @@ export function PaymentTemplateScreen(props: { <div> <h1 class="text-2xl font-medium"> { - // The merchant supplied a payment template for the payer to complete. + // Translators: The merchant supplied a payment template for the payer to complete. i18n.str`Create payment` } </h1> @@ -119,7 +119,7 @@ export function PaymentTemplateScreen(props: { <label class="block"> <span class="mb-1 block text-sm font-medium"> { - // Short purchase or order description, not a price summary. + // Translators: Short purchase or order description, not a price summary. i18n.str`Summary` } </span> diff --git a/packages/wallet-webui/src/screens/PeerCreateScreen.tsx b/packages/wallet-webui/src/screens/PeerCreateScreen.tsx @@ -77,7 +77,7 @@ function ExpirationDescription(props: { }) { const value = props.expiration.t_s === "never" - ? i18n.str`Never` + ? /* Translators: A peer-payment link with no expiration date. */ i18n.str`Never` : relativeExpirationLabel(props.expiration); const headline = props.onToggle ? ( <ExpirationToggle @@ -269,27 +269,29 @@ export function PeerCreateScreen(props: { {send ? // Translators: Heading for creating a push-payment claim link for a recipient. i18n.str`Send money` - : // Translators: Heading for creating a pull-payment invoice link for a payer. + : // Translators: Heading for creating a pull-payment request link for a payer. i18n.str`Request money`} </h1> <p class="text-sm text-secondary"> {send - ? i18n.str`Create a payment link for the recipient` + ? /* Translators: Create a peer-push link. The person who opens it can receive the money already set aside by the sender. */ i18n.str`Create a payment link for the recipient` : /* Translators: Create a peer-pull request. The person who opens it is being asked to pay this wallet. */ i18n.str`Create a payment request link`} </p> </div> </div> {props.loading && ( <Card> - <p role="status">{i18n.str`Loading currency and payment scopes…`}</p> + <p role="status">{ + /* Translators: A payment scope groups funds by currency and exchange, merchant, or acceptance restrictions. */ i18n.str`Loading currency and payment scopes…` + }</p> </Card> )} {!props.loading && scopeUnavailable && ( <Card> <p role="alert"> {send - ? i18n.str`Your wallet isn’t ready to send money yet. Add an exchange or withdraw digital cash to get started.` - : i18n.str`Your wallet isn’t ready to request money yet. Add an exchange or withdraw digital cash to get started.`} + ? /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Your wallet isn’t ready to send money yet. Add an exchange or withdraw digital cash to get started.` + : /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Your wallet isn’t ready to request money yet. Add an exchange or withdraw digital cash to get started.`} </p> </Card> )} @@ -459,7 +461,9 @@ export function PeerCreateScreen(props: { onChange={() => setExpirationMode("custom")} class="mr-2" /> - {i18n.str`Custom`} + { + /* Translators: A user-chosen expiration date and time for a peer-payment link. */ i18n.str`Custom` + } </label> </div> {expirationMode === "custom" && ( @@ -564,7 +568,7 @@ export function PeerCreateScreen(props: { <div class="mt-4"> <Button tone="secondary" onClick={props.onResolveBlock}> {props.blockedActionLabel ?? - i18n.str`Resolve exchange requirement`} + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Resolve exchange requirement`} </Button> </div> )} @@ -583,7 +587,9 @@ export function PeerCreateScreen(props: { Boolean(props.blockedReason) } > - {props.working ? i18n.str`Creating…` : i18n.str`Create link`} + {props.working + ? i18n.str`Creating…` + : /* Translators: Confirm creation of a peer payment or payment request. For sending, this commits the money to the transfer. */ i18n.str`Create link`} </Button> </div> </> diff --git a/packages/wallet-webui/src/screens/PeerReceiveScreen.tsx b/packages/wallet-webui/src/screens/PeerReceiveScreen.tsx @@ -30,7 +30,9 @@ export function PeerReceiveScreen(props: { type="button" onClick={props.onCancel} disabled={props.cancelling} - aria-label={i18n.str`Cancel peer payment`} + aria-label={ + /* Translators: Cancel accepting a peer transfer or paying a peer request in this wallet. */ i18n.str`Cancel peer payment` + } class="grid h-11 w-11 place-items-center rounded-full text-2xl hover:bg-secondaryContainer" > ← @@ -43,7 +45,9 @@ export function PeerReceiveScreen(props: { : // Translators: Noun heading for reviewing another person's request for money. i18n.str`Payment request`} </h1> - <p class="text-sm text-secondary">{i18n.str`Review the peer payment before continuing`}</p> + <p class="text-sm text-secondary">{ + /* Translators: Peer payments transfer digital cash between people using links. A shared link can offer money to receive or request money to be paid. */ i18n.str`Review the peer payment before continuing` + }</p> </div> </div> {props.loading && ( diff --git a/packages/wallet-webui/src/screens/TransactionDetailScreen.tsx b/packages/wallet-webui/src/screens/TransactionDetailScreen.tsx @@ -216,13 +216,13 @@ export function TransactionDetailScreen(props: { <Card> <h2 class="font-semibold"> {transaction.continuationAction.kind === "deposit-kyc-auth" - ? i18n.str`Bank account authentication required` + ? /* Translators: The user must prove control of the destination bank account with a small transfer from that account. */ i18n.str`Bank account authentication required` : i18n.str`Bank transfer still required`} </h2> <p class="mt-2 text-sm text-secondary"> {transaction.continuationAction.kind === "deposit-kyc-auth" - ? i18n.str`Make a small authentication transfer from the deposit account before the exchange can send money to it.` - : i18n.str`View the bank details, amount, and required payment reference for this withdrawal.`} + ? /* Translators: Bank-account authentication uses a small transfer from the specified account to prove control of it. KYC means identity verification. */ i18n.str`Make a small authentication transfer from the deposit account before the exchange can send money to it.` + : /* Translators: Reopen existing transfer instructions; the user is not being asked to open a new bank account. */ i18n.str`View the bank details, amount, and required payment reference for this withdrawal.`} </p> <div class="mt-4"> <Button onClick={() => props.onContinue?.()}> diff --git a/packages/wallet-webui/src/screens/TransactionScreen.tsx b/packages/wallet-webui/src/screens/TransactionScreen.tsx @@ -32,7 +32,9 @@ export function TransactionScreen(props: { return ( <div class="mx-auto max-w-3xl space-y-5"> <div> - <h1 class="text-2xl font-bold">{i18n.str`History`}</h1> + <h1 class="text-2xl font-bold">{ + /* Translators: Wallet transaction history, including both completed and pending transactions. */ i18n.str`History` + }</h1> <p class="text-secondary"> {props.scopeLabel ? // Translators: %1$s is a localized payment-scope label. @@ -52,7 +54,9 @@ export function TransactionScreen(props: { > <option value="all">{i18n.str`All activity`}</option> <option value="pending">{i18n.str`In progress`}</option> - <option value="completed">{i18n.str`Finished`}</option> + <option value="completed">{ + /* Translators: History filter for transactions that are no longer pending, including failures and cancellations. */ i18n.str`Finished` + }</option> </select> </label> </div> diff --git a/packages/wallet-webui/src/screens/WireTransferScreen.tsx b/packages/wallet-webui/src/screens/WireTransferScreen.tsx @@ -121,11 +121,11 @@ export function WireTransferScreen(props: { <h1 class="text-2xl font-medium"> {isKycAuth ? i18n.str`Verify bank account` - : i18n.str`Pending withdrawal`} + : /* Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. */ i18n.str`Pending withdrawal`} </h1> <p class="break-all text-sm italic text-secondary"> {isKycAuth - ? i18n.str`Deposit account authentication` + ? /* Translators: Bank-account authentication uses a small transfer from the specified account to prove control of it. KYC means identity verification. */ i18n.str`Deposit account authentication` : props.exchange} </p> </div> @@ -133,7 +133,9 @@ export function WireTransferScreen(props: { {isKycAuth ? ( <Card class="border-warning bg-warningContainer"> <h2 class="font-semibold">{i18n.str`Transfer from this exact bank account`}</h2> - <p class="mt-2 text-sm">{i18n.str`Make one of the small authentication transfers below from the account shown here. A transfer from another account will not authenticate the deposit.`}</p> + <p class="mt-2 text-sm">{ + /* Translators: Bank-account authentication uses a small transfer from the specified account to prove control of it. KYC means identity verification. */ i18n.str`Make one of the small authentication transfers below from the account shown here. A transfer from another account will not authenticate the deposit.` + }</p> {props.debitPaytoUri && ( <p class="mt-3 break-all font-medium">{props.debitPaytoUri}</p> )} @@ -153,8 +155,8 @@ export function WireTransferScreen(props: { <Card class="border-error bg-errorContainer"> <p role="alert" class="text-onErrorContainer"> {isKycAuth - ? i18n.str`The exchange did not provide a usable authentication transfer option. The deposit remains pending in history.` - : i18n.str`The exchange did not provide a usable bank transfer option. The pending transaction remains in history.`} + ? /* Translators: Bank-account authentication uses a small transfer from the specified account to prove control of it. KYC means identity verification. */ i18n.str`The exchange did not provide a usable authentication transfer option. The deposit remains pending in history.` + : /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`The exchange did not provide a usable bank transfer option. The pending transaction remains in history.`} </p> </Card> ) : ( @@ -285,7 +287,9 @@ export function WireTransferScreen(props: { {option.restrictionHints && option.restrictionHints.length > 0 && ( <div class="border-l-4 border-warning pl-4"> - <p class="font-semibold">{i18n.str`Required source account`}</p> + <p class="font-semibold">{ + /* Translators: The bank account from which the transfer must be sent. */ i18n.str`Required source account` + }</p> <ul class="mt-2 list-disc space-y-1 pl-5 text-sm"> {option.restrictionHints.map((hint) => ( <li key={hint}>{hint}</li> @@ -322,7 +326,7 @@ export function WireTransferScreen(props: { {!blockedReason && option.fields.length > 0 && ( <div> <h4 class="text-lg font-semibold">{i18n.str`Wire transfer instructions`}</h4> - {/* Translators: “subject” means the mandatory bank-transfer reference, not an email subject. */} + {/* Translators: The payment reference identifies this bank transfer to the exchange. */} <p class="mt-2 text-sm text-secondary">{i18n.str`Copy these details exactly into your banking app. The payment reference is required.`}</p> <dl class="mt-5 space-y-5"> {option.fields.map((field) => ( @@ -357,7 +361,9 @@ export function WireTransferScreen(props: { )} <div class="flex justify-end"> <Button onClick={props.onDone}> - {isKycAuth ? i18n.str`Back to deposit` : i18n.str`View wallet`} + {isKycAuth + ? /* Translators: A deposit sends digital cash from this wallet to an external account, usually a bank account; it does not add funds to the wallet. */ i18n.str`Back to deposit` + : i18n.str`View wallet`} </Button> </div> </div> diff --git a/packages/wallet-webui/src/screens/WithdrawalReviewScreen.tsx b/packages/wallet-webui/src/screens/WithdrawalReviewScreen.tsx @@ -35,13 +35,17 @@ export function WithdrawalReviewScreen(props: { type="button" onClick={props.onBack} disabled={props.cancelling} - aria-label={i18n.str`Edit withdrawal`} + aria-label={ + /* Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. */ i18n.str`Edit withdrawal` + } class="grid h-11 w-11 place-items-center rounded-full text-2xl hover:bg-secondaryContainer" > ← </button> <div> - <h1 class="text-2xl font-medium">{i18n.str`Review withdrawal`}</h1> + <h1 class="text-2xl font-medium">{ + /* Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. */ i18n.str`Review withdrawal` + }</h1> <p class="break-all text-sm italic text-secondary"> {props.exchange} </p> @@ -99,11 +103,17 @@ export function WithdrawalReviewScreen(props: { <dl class="mt-3 grid grid-cols-1 gap-x-4 gap-y-1 text-onWarningContainer sm:grid-cols-2"> <dt>{i18n.str`Current balance`}</dt> <dd>{props.balanceKyc.currentBalance}</dd> - <dt>{i18n.str`Balance after withdrawal`}</dt> + <dt>{ + /* Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. */ i18n.str`Balance after withdrawal` + }</dt> <dd>{props.balanceKyc.projectedBalance}</dd> - <dt>{i18n.str`Balance limit`}</dt> + <dt>{ + /* Translators: Maximum wallet balance allowed by this exchange without further requirements. */ i18n.str`Balance limit` + }</dt> <dd>{props.balanceKyc.threshold}</dd> - <dt>{i18n.str`Remaining balance allowance`}</dt> + <dt>{ + /* Translators: Amount still available below the exchange's balance limit, not spendable wallet funds. */ i18n.str`Remaining balance allowance` + }</dt> <dd>{props.balanceKyc.remaining}</dd> </dl> )} diff --git a/packages/wallet-webui/src/screens/WithdrawalScreen.tsx b/packages/wallet-webui/src/screens/WithdrawalScreen.tsx @@ -85,25 +85,33 @@ export function WithdrawalScreen(props: { </button> <div> <h1 class="text-2xl font-medium"> - {props.title ?? i18n.str`Withdraw`} + {props.title ?? + /* Translators: Withdrawing adds digital cash to this wallet, usually funded by a bank transfer; it does not cash out the wallet to a bank account. */ i18n.str`Withdraw`} </h1> {(props.subtitle || props.demo) && ( <p class="text-sm text-secondary"> - {props.subtitle ?? i18n.str`Demo cash`} + {props.subtitle ?? + /* Translators: Fictional digital cash for demonstrations, without real monetary value. */ i18n.str`Demo cash`} </p> )} </div> </div> {props.loading && ( <Card> - <p role="status">{i18n.str`Loading available exchanges…`}</p> + <p role="status">{ + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Loading available exchanges…` + }</p> </Card> )} {props.error && <ErrorCard error={props.error} />} {!props.loading && !props.error && providers.length === 0 && ( <Card> - <h2 class="font-semibold">{i18n.str`No exchange is configured`}</h2> - <p class="mt-2 text-secondary">{i18n.str`Add an exchange or start an integrated withdrawal from your bank.`}</p> + <h2 class="font-semibold">{ + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`No exchange is configured` + }</h2> + <p class="mt-2 text-secondary">{ + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Add an exchange or start an integrated withdrawal from your bank.` + }</p> </Card> )} {provider && ( @@ -116,7 +124,9 @@ export function WithdrawalScreen(props: { }} > <label class="mx-auto block max-w-xs"> - <span class="sr-only">{i18n.str`Currency and exchange`}</span> + <span class="sr-only">{ + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Currency and exchange` + }</span> <select value={provider.exchangeBaseUrl} disabled={props.providerEditable === false} @@ -137,7 +147,9 @@ export function WithdrawalScreen(props: { {props.cashAcceptor ? ( <Card class="border-warning bg-warningContainer"> <h2 class="font-semibold">{i18n.str`Amount selected at cash acceptor`}</h2> - <p class="mt-2 text-sm">{i18n.str`The bank will report the amount after you deposit cash. Confirm the exchange now, then finish the withdrawal at the cash acceptor.`}</p> + <p class="mt-2 text-sm">{ + /* Translators: A cash acceptor is a facility where the user pays physical cash to fund a digital-cash withdrawal into this wallet. */ i18n.str`The bank will report the amount after you deposit cash. Confirm the exchange now, then finish the withdrawal at the cash acceptor.` + }</p> </Card> ) : ( <MoneyAmountInput @@ -174,7 +186,9 @@ export function WithdrawalScreen(props: { {props.cashAcceptor && (provider.ageRestrictionOptions?.length ?? 0) > 0 && ( <label class="block"> - <span class="mb-2 block text-sm font-semibold">{i18n.str`Minimum age for withdrawn coins`}</span> + <span class="mb-2 block text-sm font-semibold">{ + /* Translators: Age restriction attached to the digital coins, not how long the coins have existed. */ i18n.str`Minimum age for withdrawn coins` + }</span> <select aria-label={i18n.str`Minimum age for withdrawn coins`} value={restrictAge ?? ""} @@ -202,7 +216,9 @@ export function WithdrawalScreen(props: { <dl class="grid grid-cols-2 gap-3 text-sm"> {props.maximumAmount && ( <> - <dt class="text-secondary">{i18n.str`Maximum from bank`}</dt> + <dt class="text-secondary">{ + /* Translators: The largest withdrawal amount permitted by the bank, not the user's bank balance. */ i18n.str`Maximum from bank` + }</dt> <dd class="text-right"> {provider.currency} {props.maximumAmount} </dd> @@ -210,7 +226,9 @@ export function WithdrawalScreen(props: { )} {props.bankFee && ( <> - <dt class="text-secondary">{i18n.str`Bank fee`}</dt> + <dt class="text-secondary">{ + /* Translators: A fee charged by the bank for the withdrawal, separate from exchange fees. */ i18n.str`Bank fee` + }</dt> <dd class="text-right">{props.bankFee}</dd> </> )}