commit 5b8d82b462d878087b8fbac4beabb0d813723015 parent e0f8d1a576aa1489ba62cc3ed17f5ea3e6b2e0e6 Author: Florian Dold <dold@taler.net> Date: Sun, 13 Sep 2026 17:15:48 +0200 wallet web UI: explain settings and recovery terms to translators Add source notes for account management, exchange information, donation receipts, wallet recovery and browser storage terminology. Diffstat:
15 files changed, 281 insertions(+), 144 deletions(-)
diff --git a/packages/wallet-webui/src/screens/BankAccountsScreen.tsx b/packages/wallet-webui/src/screens/BankAccountsScreen.tsx @@ -28,15 +28,15 @@ type GuidedMethod = BankAccountSaveValues["method"]; function methodLabel(method: GuidedMethod): string { switch (method) { case "iban": - // Account-entry method; may accept an IBAN or domestic BBAN depending on exchange support. + // Translators: Account-entry method; may accept an IBAN or domestic BBAN depending on exchange support. return i18n.str`IBAN / bank account number`; case "x-taler-bank": - // Account using the payto x-taler-bank method. + // Translators: Account using the payto x-taler-bank method. return i18n.str`Taler-enabled bank`; case "bitcoin": return i18n.str`Bitcoin`; case "advanced": - // Expert option for entering a complete payto URI directly. + // Translators: Expert option for entering a complete payto URI directly. return i18n.str`Advanced payto URI`; } } @@ -169,7 +169,7 @@ export function BankAccountsScreen(props: { <h1 class="text-2xl font-medium">{i18n.str`Bank accounts`}</h1> <p class="text-sm text-secondary"> { - // Deposit means sending wallet digital cash to an external account. + // Translators: Deposit means sending wallet digital cash to an external account. i18n.str`Destinations for deposits` } </p> @@ -217,7 +217,7 @@ export function BankAccountsScreen(props: { {account.currencies.length > 0 && ( <p class="mt-2 text-xs text-secondary"> { - // The placeholder is a comma-separated list of currency codes accepted by this destination. + // Translators: The placeholder is a comma-separated list of currency codes accepted by this destination. i18n.str`Currencies: ${account.currencies.join(", ")}` } </p> @@ -235,12 +235,12 @@ export function BankAccountsScreen(props: { tone="danger" onClick={() => setForgetAccount(account)} >{ - // Removes only the locally saved destination; it does not close the real bank account. + // Translators: Removes only the locally saved destination; it does not close the real bank account. i18n.str`Forget` }</Button> <OverflowMenu label={ - // Accessible menu label; the placeholder is the user-defined account label. + // Translators: Accessible menu label; the placeholder is the user-defined account label. i18n.str`More actions for ${account.label}` } items={[ @@ -283,7 +283,9 @@ export function BankAccountsScreen(props: { </span> <span> <span class="block font-semibold">{i18n.str`Add bank account`}</span> - <span class="block text-sm font-normal text-secondary">{i18n.str`Save another destination for deposits`}</span> + <span class="block text-sm font-normal text-secondary">{ + /* 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`Save another destination for deposits` + }</span> </span> </button> )} @@ -302,7 +304,9 @@ export function BankAccountsScreen(props: { )} <p class={`${props.editing ? "mt-1 " : ""}text-sm text-secondary`} - >{i18n.str`Only add an account you control. Deposits cannot be reversed.`}</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`Only add an account you control. Deposits cannot be reversed.` + }</p> </div> {!props.editing && ( <label> @@ -328,7 +332,7 @@ export function BankAccountsScreen(props: { <label> <span class="mb-1 block text-sm text-secondary"> { - // Recipient or owner name encoded in the payto URI. + // Translators: Recipient or owner name encoded in the payto URI. i18n.str`Account holder name` } </span> @@ -345,7 +349,7 @@ export function BankAccountsScreen(props: { <label> <span class="mb-1 block text-sm text-secondary"> {wireType?.preferredEntryType === "bban" - ? // Domestic Basic Bank Account Number, as opposed to IBAN. + ? // Translators: Domestic Basic Bank Account Number, as opposed to IBAN. i18n.str`Bank account number (BBAN)` : i18n.str`IBAN`} </span> @@ -390,7 +394,7 @@ export function BankAccountsScreen(props: { <label> <span class="mb-1 block text-sm text-secondary"> { - // Recipient or owner name encoded in the payto URI. + // Translators: Recipient or owner name encoded in the payto URI. i18n.str`Account holder name` } </span> @@ -407,7 +411,7 @@ export function BankAccountsScreen(props: { <label> <span class="mb-1 block text-sm text-secondary"> { - // Host of the selected Taler-enabled bank. + // Translators: Host of the selected Taler-enabled bank. i18n.str`Bank` } </span> @@ -440,7 +444,7 @@ export function BankAccountsScreen(props: { <label> <span class="mb-1 block text-sm text-secondary"> { - // Account identifier at the Taler-enabled bank, not the user's saved label. + // Translators: Account identifier at the Taler-enabled bank, not the user's saved label. i18n.str`Account name` } </span> @@ -478,7 +482,7 @@ export function BankAccountsScreen(props: { value={paytoUri} onInput={(event) => setPaytoUri(event.currentTarget.value)} placeholder={ - // Example URI syntax; keep the payto scheme literal. + // Translators: Example URI syntax; keep the payto scheme literal. i18n.str`payto://iban/…` } autoComplete="off" @@ -489,7 +493,7 @@ export function BankAccountsScreen(props: { <label> <span class="mb-1 block text-sm text-secondary"> { - // User-defined local name for this saved destination. + // Translators: User-defined local name for this saved destination. i18n.str`Label` } </span> @@ -503,7 +507,7 @@ export function BankAccountsScreen(props: { <label> <span class="mb-1 block text-sm text-secondary"> { - // Optional comma-separated currency-code restriction for this destination. + // Translators: Optional comma-separated currency-code restriction for this destination. i18n.str`Currencies (optional)` } </span> @@ -511,7 +515,7 @@ export function BankAccountsScreen(props: { value={currencies} onInput={(event) => setCurrencies(event.currentTarget.value)} placeholder={ - // Example currency codes; keep the codes unchanged. + // Translators: Example currency codes; keep the codes unchanged. i18n.str`CHF, EUR` } class="w-full rounded-xl border border-outline bg-surface px-3 py-3" @@ -547,14 +551,16 @@ export function BankAccountsScreen(props: { <span class="block font-medium text-onSurface"> {forgetAccount.label} </span> - <span class="mt-2 block">{i18n.str`The account will be removed from your saved deposit destinations. Existing transaction records are not deleted.`}</span> + <span class="mt-2 block">{ + /* 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`The account will be removed from your saved deposit destinations. Existing transaction records are not deleted.` + }</span> </> } cancelLabel={i18n.str`Keep account`} confirmLabel={ props.working ? i18n.str`Forgetting…` - : // Removes only the locally saved destination; it does not close the real bank account. + : // Translators: Removes only the locally saved destination; it does not close the real bank account. i18n.str`Forget account` } tone="danger" diff --git a/packages/wallet-webui/src/screens/DonauSettingsScreen.tsx b/packages/wallet-webui/src/screens/DonauSettingsScreen.tsx @@ -52,11 +52,13 @@ export function DonauSettingsScreen(props: { <div> <h1 class="text-2xl font-bold"> { - // Donau receipts document qualifying charitable payments for tax reporting. + // Translators: Donau receipts document qualifying charitable payments for tax reporting. i18n.str`Donation receipts` } </h1> - <p class="text-secondary">{i18n.str`Connect a donation authority to collect annual statements.`}</p> + <p class="text-secondary">{ + /* Translators: The donation authority is the Donau service issuing donation receipts and statements, not the tax authority. */ i18n.str`Connect a donation authority to collect annual statements.` + }</p> </div> {props.loading ? ( <Card> @@ -77,7 +79,7 @@ export function DonauSettingsScreen(props: { > <div> <label class="mb-1 block font-semibold" htmlFor="donau-url">{ - // Base URL of the Donau service that issues donation receipts. + // Translators: Base URL of the Donau service that issues donation receipts. i18n.str`Donation authority URL` }</label> <input @@ -92,7 +94,7 @@ export function DonauSettingsScreen(props: { </div> <div> <label class="mb-1 block font-semibold" htmlFor="tax-payer-id">{ - // Identifier supplied to the donation authority for tax reporting. + // Translators: Identifier supplied to the donation authority for tax reporting. i18n.str`Taxpayer identifier` }</label> <input @@ -104,7 +106,9 @@ export function DonauSettingsScreen(props: { onInput={(event) => setTaxPayerId(event.currentTarget.value)} class="min-h-11 w-full rounded-xl border border-outline bg-surface px-3 py-2 font-mono focus:outline-none focus:ring-2 focus:ring-primary" /> - <p class="mt-1 text-sm text-secondary">{i18n.str`Stored only in this wallet and sent to the configured donation authority when requesting receipts.`}</p> + <p class="mt-1 text-sm text-secondary">{ + /* Translators: The donation authority is the Donau service issuing donation receipts and statements, not the tax authority. */ i18n.str`Stored only in this wallet and sent to the configured donation authority when requesting receipts.` + }</p> </div> <div class="flex flex-wrap gap-2"> <Button type="submit" disabled={!valid || props.working}> @@ -140,7 +144,7 @@ export function DonauSettingsScreen(props: { (props.summaries?.length ?? 0) > 0 && ( <section class="space-y-3" aria-labelledby="donation-totals-title"> <h2 id="donation-totals-title" class="text-lg font-semibold">{ - // Monetary totals for annual donation receipts, not receipt counts. + // Translators: Monetary totals for annual donation receipts, not receipt counts. i18n.str`Annual receipt totals` }</h2> <div class="grid gap-3 sm:grid-cols-2"> @@ -153,26 +157,26 @@ export function DonauSettingsScreen(props: { > <span class="block font-semibold"> {summary.legalDomain ?? - // Fallback title when the financial/legal domain is unavailable. + // Translators: Fallback title when the financial/legal domain is unavailable. i18n.str`Donation statement`}{" "} · {summary.year} </span> <span class="mt-1 block text-sm text-secondary"> { - // The placeholder is a formatted monetary amount, not a number of receipts. + // Translators: The placeholder is a formatted monetary amount, not a number of receipts. i18n.str`Available receipt value: ${summary.amountAvailable}` } </span> <span class="mt-1 block text-sm text-secondary"> { - // The placeholder is a formatted monetary amount, not a number of receipts. + // Translators: The placeholder is a formatted monetary amount, not a number of receipts. i18n.str`Submitted receipt value: ${summary.amountSubmitted}` } </span> {summary.amountStatement && ( <span class="mt-1 block text-sm"> { - // The placeholder is the statement's formatted monetary total, not its date. + // Translators: The placeholder is the statement's formatted monetary total, not its date. i18n.str`Latest statement total: ${summary.amountStatement}` } </span> diff --git a/packages/wallet-webui/src/screens/DonauStatementsScreen.tsx b/packages/wallet-webui/src/screens/DonauStatementsScreen.tsx @@ -40,7 +40,7 @@ export function DonauStatementsScreen(props: { <div> <h1 class="text-2xl font-bold"> { - // Signed annual Donau donation statements intended for tax reporting. + // Translators: Signed annual Donau donation statements intended for tax reporting. i18n.str`Donation statements` } </h1> @@ -64,7 +64,7 @@ export function DonauStatementsScreen(props: { class="mb-5 flex flex-wrap justify-center gap-2" role="tablist" aria-label={ - // Accessible label for the year-selection tabs. + // Translators: Accessible label for the year-selection tabs. i18n.str`Statement year` } > @@ -85,7 +85,7 @@ export function DonauStatementsScreen(props: { <QrFrame content={statement.uri} alt={ - // QR-code alternative text; the placeholder is the statement year. + // Translators: QR-code alternative text; the placeholder is the statement year. i18n.str`Donation statement for ${statement.year}` } /> @@ -94,21 +94,21 @@ export function DonauStatementsScreen(props: { <dd>{statement.year}</dd> <dt class="text-secondary"> { - // Total monetary amount covered by the annual donation statement. + // Translators: Total monetary amount covered by the annual donation statement. i18n.str`Total` } </dt> <dd class="font-semibold">{statement.total}</dd> <dt class="text-secondary"> { - // Financial or legal jurisdiction reported by Donau, not an Internet domain name. + // Translators: Financial or legal jurisdiction reported by Donau, not an Internet domain name. i18n.str`Jurisdiction` } </dt> <dd>{statement.legalDomain}</dd> <dt class="text-secondary"> { - // Donau service that signed and issued the statement. + // Translators: Donau service that signed and issued the statement. i18n.str`Donation authority` } </dt> @@ -116,7 +116,7 @@ export function DonauStatementsScreen(props: { </dl> <div class="mt-5"> <Button onClick={() => props.onCopy(statement.uri)}>{ - // Copies the signed statement URI, not the visible text on this card. + // Translators: Copies the signed statement URI, not the visible text on this card. i18n.str`Copy statement` }</Button> </div> diff --git a/packages/wallet-webui/src/screens/ExchangeDetailScreen.tsx b/packages/wallet-webui/src/screens/ExchangeDetailScreen.tsx @@ -56,29 +56,31 @@ function ExchangeDeleteDialog(props: { <ConfirmationDialog title={ purge - ? i18n.str`Force-remove exchange data?` - : i18n.str`Remove exchange?` + ? /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Force-remove exchange data?` + : /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Remove exchange?` } description={ <> <span class="block break-all text-sm">{props.exchange.url}</span> <span class={`mt-4 block ${purge ? "font-medium text-error" : ""}`}> {purge - ? i18n.str`This exchange still has coins or withdrawal records. Force removal permanently deletes its digital cash, withdrawals, and related transaction history from this wallet. This cannot be undone.` - : i18n.str`The wallet found no coins or withdrawal records associated with this exchange. Its cached details and trust settings will be removed.`} + ? /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`This exchange still has coins or withdrawal records. Force removal permanently deletes its digital cash, withdrawals, and related transaction history from this wallet. This cannot be undone.` + : /* 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 wallet found no coins or withdrawal records associated with this exchange. Its cached details and trust settings will be removed.`} </span> {purge && ( <span class="mt-3 block text-sm">{i18n.str`Cancel and export the wallet database first if you might need this data.`}</span> )} </> } - cancelLabel={i18n.str`Keep exchange`} + cancelLabel={ + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Keep exchange` + } confirmLabel={ props.working ? i18n.str`Removing…` : purge - ? i18n.str`Force-remove exchange` - : i18n.str`Remove exchange` + ? /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Force-remove exchange` + : /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Remove exchange` } tone="danger" working={props.working} @@ -127,13 +129,17 @@ export function ExchangeDetailScreen(props: { <button type="button" onClick={props.onBack} - aria-label={i18n.str`Back to exchanges`} + aria-label={ + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Back to exchanges` + } 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`Exchange details`}</h1> + <h1 class="text-2xl font-medium">{ + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Exchange details` + }</h1> {exchange && ( <p class="break-all text-sm text-secondary">{exchange.url}</p> )} @@ -141,7 +147,9 @@ export function ExchangeDetailScreen(props: { </div> {props.loading && ( <Card> - <p role="status">{i18n.str`Loading exchange details…`}</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 exchange details…` + }</p> </Card> )} {props.error && <ErrorCard error={props.error} />} @@ -158,15 +166,21 @@ export function ExchangeDetailScreen(props: { <dl class="grid gap-3 text-sm sm:grid-cols-[11rem_1fr]"> <dt class="text-secondary">{i18n.str`Currency`}</dt> <dd>{exchange.currency}</dd> - <dt class="text-secondary">{i18n.str`Update status`}</dt> + <dt class="text-secondary">{ + /* Translators: The status of fetching information from an exchange, not updating the wallet application. */ i18n.str`Update status` + }</dt> <dd>{exchangeUpdateStatusLabel(exchange.updateStatus)}</dd> - <dt class="text-secondary">{i18n.str`Entry status`}</dt> + <dt class="text-secondary">{ + /* Translators: How an exchange became known to the wallet: built in, discovered, or already used. */ i18n.str`Entry status` + }</dt> <dd>{exchangeEntryStatusLabel(exchange.entryStatus)}</dd> - <dt class="text-secondary">{i18n.str`Terms`}</dt> + <dt class="text-secondary">{ + /* Translators: The exchange's terms of service. */ i18n.str`Terms` + }</dt> <dd>{exchangeTosStatusLabel(exchange.tosStatus)}</dd> <dt class="text-secondary"> { - // Cryptographic identity of the exchange. + // Translators: Cryptographic identity of the exchange. i18n.str`Master public key` } </dt> @@ -175,14 +189,14 @@ export function ExchangeDetailScreen(props: { </dd> <dt class="text-secondary"> { - // Count of the exchange's payto endpoints, not the user's saved bank accounts. + // Translators: Count of the exchange's payto endpoints, not the user's saved bank accounts. i18n.str`Bank accounts` } </dt> <dd>{exchange.paytoUris.length}</dd> <dt class="text-secondary"> { - // Independent auditors that verify this Taler exchange. + // Translators: Independent auditors that verify this Taler exchange. i18n.str`Auditors` } </dt> @@ -195,50 +209,70 @@ export function ExchangeDetailScreen(props: { <dt class="text-secondary">{i18n.str`Fees`}</dt> <dd> {exchange.noFees - ? // The exchange currently advertises no applicable operation fees. + ? // Translators: No fee entries are published here; this does not promise that every operation is free. i18n.str`No fees advertised` - : i18n.str`See current exchange terms and operation reviews`} + : /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`See current exchange terms and operation reviews`} </dd> </dl> </Card> {(exchange.peerPaymentsDisabled || exchange.directDepositsDisabled) && ( <Card class="border-warning bg-warningContainer"> - <h2 class="font-semibold text-onWarningContainer">{i18n.str`Availability`}</h2> + <h2 class="font-semibold text-onWarningContainer">{ + /* Translators: Which operations this exchange supports, such as peer payments and bank deposits. */ i18n.str`Availability` + }</h2> <ul class="mt-2 list-disc space-y-1 pl-5 text-sm text-onWarningContainer"> {exchange.peerPaymentsDisabled && ( - <li>{i18n.str`Peer payments are disabled.`}</li> + <li>{ + /* 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`Peer payments are disabled.` + }</li> )} {exchange.directDepositsDisabled && ( - <li>{i18n.str`Direct deposits are disabled.`}</li> + <li>{ + /* 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`Direct deposits are disabled.` + }</li> )} </ul> </Card> )} {exchange.lastError && ( <ErrorCard - title={i18n.str`The last exchange update failed`} + title={ + /* 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 last exchange update failed` + } error={exchange.lastError} /> )} {exchange.keyChange && ( <Card class="border-error bg-errorContainer"> - <h2 class="font-semibold text-onErrorContainer">{i18n.str`Exchange identity changed`}</h2> - <p class="mt-2 text-sm text-onErrorContainer">{i18n.str`The security identity of ${exchangeHostname(exchange.url)} has changed unexpectedly.`}</p> - <p class="mt-3 text-sm text-onErrorContainer">{i18n.str`Only trust the new identity if the exchange operator has confirmed this change. Otherwise, wait and try again later.`}</p> + <h2 class="font-semibold text-onErrorContainer">{ + /* Translators: The exchange has replaced its cryptographic master key; its URL may be unchanged. */ i18n.str`Exchange identity changed` + }</h2> + <p class="mt-2 text-sm text-onErrorContainer">{ + /* Translators: %1$s identifies the exchange whose cryptographic master key changed. */ i18n.str`The security identity of ${exchangeHostname(exchange.url)} has changed unexpectedly.` + }</p> + <p class="mt-3 text-sm text-onErrorContainer">{ + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Only trust the new identity if the exchange operator has confirmed this change. Otherwise, wait and try again later.` + }</p> <p class="mt-3 text-sm font-medium text-onErrorContainer">{i18n.str`Trusting it may make digital cash issued under the previous identity unusable in this wallet.`}</p> <dl class="mt-4 grid gap-2 text-xs"> - <dt>{i18n.str`Previous key (${exchange.keyChange.supersededCurrency})`}</dt> + <dt>{ + /* Translators: %1$s is the old exchange identity's fingerprint or encoded public key. */ i18n.str`Previous key (${exchange.keyChange.supersededCurrency})` + }</dt> <dd class="break-all font-mono"> {exchange.keyChange.supersededMasterPub} </dd> - <dt>{i18n.str`Current key (${exchange.keyChange.currentCurrency})`}</dt> + <dt>{ + /* Translators: %1$s is the new exchange identity's fingerprint or encoded public key. */ i18n.str`Current key (${exchange.keyChange.currentCurrency})` + }</dt> <dd class="break-all font-mono"> {exchange.keyChange.currentMasterPub} </dd> </dl> {!exchange.keyChange.sharesDenominations && ( - <p class="mt-3 font-semibold text-onErrorContainer">{i18n.str`The new exchange does not claim support for the wallet's older denominations.`}</p> + <p class="mt-3 font-semibold text-onErrorContainer">{ + /* Translators: A denomination is a digital-coin type defined by its value, validity period, and exchange signing key. */ i18n.str`The new exchange does not claim support for the wallet's older denominations.` + }</p> )} <div class="mt-4 flex flex-wrap justify-end gap-3"> <Button @@ -250,14 +284,20 @@ export function ExchangeDetailScreen(props: { tone="danger" onClick={props.onConfirmKeyChange} disabled={props.working} - >{i18n.str`Accept new identity`}</Button> + >{ + /* Translators: Trust the exchange's replacement cryptographic master key; this does not verify the wallet user's personal identity. */ i18n.str`Accept new identity` + }</Button> </div> </Card> )} {(exchange.legacyMasterPubs?.length ?? 0) > 0 && ( <Card class="border-warning bg-warningContainer text-onWarningContainer"> - <h2 class="font-semibold">{i18n.str`Legacy exchange balances`}</h2> - <p class="mt-2 text-sm">{i18n.str`These funds and cached keys belong to master keys this exchange has replaced.`}</p> + <h2 class="font-semibold">{ + /* Translators: Legacy funds belong to a previous cryptographic identity of an exchange, not merely an old wallet software version. */ i18n.str`Legacy exchange balances` + }</h2> + <p class="mt-2 text-sm">{ + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`These funds and cached keys belong to master keys this exchange has replaced.` + }</p> {(exchange.legacyBalances?.length ?? 0) > 0 && ( <ul class="mt-4 space-y-3"> {exchange.legacyBalances?.map((balance) => ( @@ -290,7 +330,9 @@ export function ExchangeDetailScreen(props: { !props.onPurgeLegacyBalances } onClick={() => setConfirmingLegacyPurge(true)} - >{i18n.str`Purge legacy balances`}</Button> + >{ + /* Translators: Legacy funds belong to a previous cryptographic identity of an exchange, not merely an old wallet software version. */ i18n.str`Purge legacy balances` + }</Button> </div> </Card> )} @@ -356,9 +398,15 @@ function LegacyPurgeDialog(props: { const [acknowledged, setAcknowledged] = useState(false); return ( <ConfirmationDialog - title={i18n.str`Purge legacy balances?`} - description={i18n.str`This permanently deletes all digital cash and cached key data issued under historical master keys at this exchange. The current exchange and retained transaction history remain. This cannot be undone.`} - cancelLabel={i18n.str`Keep legacy balances`} + title={ + /* Translators: Legacy funds belong to a previous cryptographic identity of an exchange, not merely an old wallet software version. */ i18n.str`Purge legacy balances?` + } + description={ + /* Translators: Legacy funds belong to a previous cryptographic identity of an exchange, not merely an old wallet software version. */ i18n.str`This permanently deletes all digital cash and cached key data issued under historical master keys at this exchange. The current exchange and retained transaction history remain. This cannot be undone.` + } + cancelLabel={ + /* Translators: Legacy funds belong to a previous cryptographic identity of an exchange, not merely an old wallet software version. */ i18n.str`Keep legacy balances` + } confirmLabel={ props.working ? i18n.str`Purging…` : i18n.str`Purge legacy balances` } @@ -375,7 +423,9 @@ function LegacyPurgeDialog(props: { onChange={(event) => setAcknowledged(event.currentTarget.checked)} class="mt-0.5 h-5 w-5 shrink-0 accent-error" /> - <span>{i18n.str`I understand that these legacy balances will be permanently lost.`}</span> + <span>{ + /* Translators: Legacy funds belong to a previous cryptographic identity of an exchange, not merely an old wallet software version. */ i18n.str`I understand that these legacy balances will be permanently lost.` + }</span> </label> </ConfirmationDialog> ); diff --git a/packages/wallet-webui/src/screens/ExchangesScreen.tsx b/packages/wallet-webui/src/screens/ExchangesScreen.tsx @@ -41,10 +41,12 @@ export function ExchangesScreen(props: { ← </button> <div> - <h1 class="text-2xl font-medium">{i18n.str`Exchanges`}</h1> + <h1 class="text-2xl font-medium">{ + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Exchanges` + }</h1> <p class="text-sm text-secondary"> { - // Taler exchanges issue and manage the wallet's digital cash. + // Translators: Taler exchanges issue and manage the wallet's digital cash. i18n.str`Service providers known to this wallet` } </p> @@ -52,7 +54,9 @@ export function ExchangesScreen(props: { </div> {props.loading && ( <Card> - <p role="status">{i18n.str`Loading 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 exchanges…` + }</p> </Card> )} {props.error && <ErrorCard error={props.error} />} @@ -77,7 +81,7 @@ export function ExchangesScreen(props: { </span> <span class="mt-3 block text-xs text-secondary"> { - // First placeholder is how the exchange became known; second is its terms-of-service state. + // Translators: First placeholder is how the exchange became known; second is its terms-of-service state. i18n.str`${exchangeEntryStatusLabel(exchange.entryStatus)} · terms ${exchangeTosStatusLabel(exchange.tosStatus)}` } </span> @@ -96,11 +100,13 @@ export function ExchangesScreen(props: { }} > <Card class="form-field-stack space-y-5"> - <h2 class="font-semibold">{i18n.str`Add an exchange`}</h2> + <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`Add an exchange` + }</h2> <label> <span class="mb-1 block text-sm text-secondary"> { - // Accepts either an HTTPS base URL or a taler://add-exchange URI. + // Translators: Accepts either an HTTPS base URL or a taler://add-exchange URI. i18n.str`Exchange URL or Taler add-exchange URI` } </span> diff --git a/packages/wallet-webui/src/screens/ImportRecoveryScreen.tsx b/packages/wallet-webui/src/screens/ImportRecoveryScreen.tsx @@ -42,13 +42,13 @@ export function ImportRecoveryScreen(props: { <div> <h1 class="text-2xl font-medium"> { - // Imports and replaces the complete wallet database, including keys, cash, and history. + // Translators: Imports and replaces the complete wallet database, including keys, cash, and history. i18n.str`Import wallet database` } </h1> <p class="text-sm text-secondary"> { - // The flow first creates a local backup that can restore the pre-import wallet. + // Translators: The flow first creates a local backup that can restore the pre-import wallet. i18n.str`Staged import with local recovery` } </p> @@ -67,7 +67,7 @@ export function ImportRecoveryScreen(props: { <dd class="text-right">{props.format}</dd> <dt class="text-secondary"> { - // Number of database tables or object stores in the import file. + // Translators: Number of database tables or object stores in the import file. i18n.str`Collections` } </dt> @@ -76,7 +76,7 @@ export function ImportRecoveryScreen(props: { <> <dt class="text-secondary"> { - // Total database rows or records in the import file. + // Translators: Total database rows or records in the import file. i18n.str`Records` } </dt> @@ -88,7 +88,7 @@ export function ImportRecoveryScreen(props: { <Card class="border-warning bg-warningContainer"> <h2 class="font-semibold text-onWarningContainer"> { - // Warning: the import replaces all current wallet keys, digital cash, and history. + // Translators: Warning: the import replaces all current wallet keys, digital cash, and history. i18n.str`This replaces the current wallet` } </h2> @@ -100,7 +100,7 @@ export function ImportRecoveryScreen(props: { onClick={props.onCancel} >{i18n.str`Cancel`}</Button> <Button tone="danger" onClick={props.onImport}>{ - // Downloads the pre-import backup before replacing the wallet database. + // Translators: Downloads the pre-import backup before replacing the wallet database. i18n.str`Download backup and import` }</Button> </div> @@ -110,7 +110,7 @@ export function ImportRecoveryScreen(props: { <Card> <p role="status"> { - // Core reads are local diagnostic wallet-core database reads, not network requests. + // Translators: Core reads are local diagnostic wallet-core database reads, not network requests. i18n.str`Backing up the current wallet, importing the file, and verifying core reads…` } </p> @@ -126,7 +126,7 @@ export function ImportRecoveryScreen(props: { <h2 class="font-semibold text-onSuccessContainer">{i18n.str`Import verified`}</h2> <p role="status" class="mt-2 text-onSuccessContainer"> {props.successMessage ?? - // Verification means wallet-core reopened the database and read diagnostics and balances. + // Translators: Verification means wallet-core reopened the database and read diagnostics and balances. i18n.str`Wallet-core reopened the imported records and completed diagnostic and balance reads.`} </p> <div class="mt-5"> @@ -137,7 +137,9 @@ export function ImportRecoveryScreen(props: { {props.stage === "failure" && ( <> <ErrorCard - title={i18n.str`Import needs recovery`} + title={ + /* Translators: An imported wallet database could not be verified; the user may restore the pre-import backup. */ i18n.str`Import needs recovery` + } error={ props.error ?? { message: i18n.str`The imported wallet could not be verified.`, @@ -149,7 +151,7 @@ export function ImportRecoveryScreen(props: { <div class="flex flex-wrap justify-end gap-3"> {props.recoveryAvailable && ( <Button tone="danger" onClick={props.onRestore}>{ - // Replaces the partial import with the backup created immediately before importing. + // Translators: Replaces the partial import with the backup created immediately before importing. i18n.str`Restore pre-import wallet` }</Button> )} diff --git a/packages/wallet-webui/src/screens/PopupScreen.tsx b/packages/wallet-webui/src/screens/PopupScreen.tsx @@ -59,7 +59,9 @@ export function PopupScreen(props: { ))} {!props.loading && !props.error && props.balances?.length === 0 && ( <p class="rounded-xl bg-surfaceVariant p-3 text-sm text-secondary"> - {i18n.str`No balance yet. Open the wallet to add an exchange or withdraw digital cash.`} + { + /* 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 balance yet. Open the wallet to add an exchange or withdraw digital cash.` + } </p> )} </main> diff --git a/packages/wallet-webui/src/screens/QrScannerScreen.tsx b/packages/wallet-webui/src/screens/QrScannerScreen.tsx @@ -24,7 +24,9 @@ export function QrScannerScreen(props: { <video ref={props.videoRef} class="aspect-square w-full rounded-xl bg-black object-cover" - aria-label={i18n.str`QR scanner camera preview`} + aria-label={ + /* Translators: This text belongs to the developer screen catalogue and describes a preview, not a live financial operation. */ i18n.str`QR scanner camera preview` + } playsInline muted /> diff --git a/packages/wallet-webui/src/screens/RecoveryScreen.tsx b/packages/wallet-webui/src/screens/RecoveryScreen.tsx @@ -31,13 +31,19 @@ export function RecoveryScreen(props: { <div class="mx-auto w-full max-w-2xl"> <h1 class="mb-4 text-2xl font-bold">{i18n.str`Storage recovery required`}</h1> <Card> - <p role="alert">{i18n.str`Wallet storage cannot be opened safely. A stale or unverified generation was not opened automatically.`}</p> + <p role="alert">{ + /* Translators: A storage generation is a complete version of the local wallet database, not a separate account. */ i18n.str`Wallet storage cannot be opened safely. A stale or unverified generation was not opened automatically.` + }</p> <p class="mt-3 text-sm text-muted">{props.reason}</p> {props.backend && ( <dl class="mt-4 grid grid-cols-2 gap-2 text-sm"> - <dt class="text-secondary">{i18n.str`Last selected backend`}</dt> + <dt class="text-secondary">{ + /* Translators: Backend here means the local browser storage engine, such as SQLite or IndexedDB, not a remote server. */ i18n.str`Last selected backend` + }</dt> <dd>{props.backend}</dd> - <dt class="text-secondary">{i18n.str`Generation`}</dt> + <dt class="text-secondary">{ + /* Translators: A storage generation is a complete version of the local wallet database, not a separate account. */ i18n.str`Generation` + }</dt> <dd>{props.generation}</dd> </dl> )} @@ -45,7 +51,9 @@ export function RecoveryScreen(props: { props.recoveryDiscoveryErrors.length > 0 && ( <div class="mt-5 border-l-4 border-warning pl-4"> <h2 class="font-semibold">{i18n.str`Storage discovery is incomplete`}</h2> - <p class="mt-2 text-sm text-secondary">{i18n.str`Starting an empty wallet is disabled until every storage backend can be checked safely.`}</p> + <p class="mt-2 text-sm text-secondary">{ + /* Translators: Backend here means the local browser storage engine, such as SQLite or IndexedDB, not a remote server. */ i18n.str`Starting an empty wallet is disabled until every storage backend can be checked safely.` + }</p> <ul class="mt-2 list-disc space-y-1 pl-5 text-sm"> {props.recoveryDiscoveryErrors.map((message) => ( <li key={message}>{message}</li> @@ -55,8 +63,12 @@ export function RecoveryScreen(props: { )} {props.recoverySource && ( <div class="mt-5"> - <h2 class="font-semibold">{i18n.str`Restore the retained wallet generation`}</h2> - <p class="mt-2 text-sm text-secondary">{i18n.str`The previous storage generation is retained and will be verified before it becomes active again.`}</p> + <h2 class="font-semibold">{ + /* Translators: A storage generation is a complete version of the local wallet database, not a separate account. */ i18n.str`Restore the retained wallet generation` + }</h2> + <p class="mt-2 text-sm text-secondary">{ + /* Translators: A storage generation is a complete version of the local wallet database, not a separate account. */ i18n.str`The previous storage generation is retained and will be verified before it becomes active again.` + }</p> <div class="mt-3"> <Button disabled={props.busy} onClick={props.onRestoreSource}> {/* Translators: %1$s is the storage backend name; %2$s is a generation number. */} @@ -67,7 +79,9 @@ export function RecoveryScreen(props: { )} {props.recoveryCandidates && props.recoveryCandidates.length > 0 && ( <div class="mt-5"> - <h2 class="font-semibold">{i18n.str`Discovered wallet generations`}</h2> + <h2 class="font-semibold">{ + /* Translators: A storage generation is a complete version of the local wallet database, not a separate account. */ i18n.str`Discovered wallet generations` + }</h2> {/* Translators: wallet-core is the name of the wallet's background subsystem. */} <p class="mt-2 text-sm text-secondary">{i18n.str`Choose only the generation you expect to contain the latest wallet activity. It is verified before wallet-core opens it.`}</p> <div class="mt-3 space-y-2"> @@ -79,8 +93,8 @@ export function RecoveryScreen(props: { onClick={() => props.onRecoverGeneration?.(candidate)} > {candidate.backend === "sqlite" - ? i18n.str`Verify and use SQLite generation ${candidate.generation}` - : i18n.str`Verify and use IndexedDB generation ${candidate.generation}`} + ? /* Translators: A storage generation is a complete version of the local wallet database, not a separate account. */ i18n.str`Verify and use SQLite generation ${candidate.generation}` + : /* Translators: A storage generation is a complete version of the local wallet database, not a separate account. */ i18n.str`Verify and use IndexedDB generation ${candidate.generation}`} </Button> ))} </div> @@ -89,7 +103,9 @@ export function RecoveryScreen(props: { {props.canInitializeEmpty && (props.recoveryDiscoveryErrors?.length ?? 0) === 0 && ( <div class="mt-5"> - <h2 class="font-semibold">{i18n.str`No recoverable generation was found`}</h2> + <h2 class="font-semibold">{ + /* Translators: A storage generation is a complete version of the local wallet database, not a separate account. */ i18n.str`No recoverable generation was found` + }</h2> <p class="mt-2 text-sm text-secondary">{i18n.str`Starting a new wallet does not delete inaccessible browser storage. Continue only if this browser session should use an empty wallet.`}</p> <div class="mt-3"> <Button diff --git a/packages/wallet-webui/src/screens/SettingsScreen.tsx b/packages/wallet-webui/src/screens/SettingsScreen.tsx @@ -52,7 +52,7 @@ function storageBackendLabel(backend: string): string { function storageMigrationStateLabel(state: string): string { switch (state) { case "idle": - return i18n.str`Idle`; + return /* Translators: No wallet-storage migration or recovery is currently running. */ i18n.str`Idle`; case "requesting-persistence": case "stopping-wallet": case "converting": @@ -61,9 +61,9 @@ function storageMigrationStateLabel(state: string): string { case "reopening-wallet": return storageMigrationStageLabel(state); case "cleanup-pending": - return i18n.str`Cleanup pending`; + return /* Translators: A wallet-storage migration has finished, but an old local database copy still needs removal. */ i18n.str`Cleanup pending`; case "recovery-required": - return i18n.str`Recovery required`; + return /* Translators: The wallet database needs recovery before normal use can continue. */ i18n.str`Recovery required`; default: return state; } @@ -133,7 +133,9 @@ function ClearWalletDatabaseDialog(props: { return ( <ConfirmationDialog title={i18n.str`Clear the wallet database?`} - description={i18n.str`This permanently removes all digital cash, private keys, transaction history, exchanges, bank accounts and wallet settings. Any money in this wallet will be lost. This cannot be undone.`} + description={ + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`This permanently removes all digital cash, private keys, transaction history, exchanges, bank accounts and wallet settings. Any money in this wallet will be lost. This cannot be undone.` + } cancelLabel={i18n.str`Keep wallet data`} confirmLabel={ props.working ? i18n.str`Clearing…` : i18n.str`Clear wallet database` @@ -239,12 +241,16 @@ export function SettingsScreen(props: { /> <SettingsNavigationCard title={i18n.str`Bank accounts`} - description={i18n.str`Manage deposit destinations`} + description={ + /* 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`Manage deposit destinations` + } onClick={props.onOpenAccounts} /> <SettingsNavigationCard title={i18n.str`Donation receipts`} - description={i18n.str`Configure your donation authority and view statements`} + description={ + /* Translators: The donation authority is the Donau service issuing donation receipts and statements, not the tax authority. */ i18n.str`Configure your donation authority and view statements` + } onClick={props.onOpenDonau} /> </div> @@ -350,7 +356,9 @@ export function SettingsScreen(props: { <p class="mt-3 text-sm text-secondary">{i18n.str`This wallet uses temporary in-memory demo data. Reloading restores the original fictional wallet.`}</p> ) : ( <dl class="mt-3 grid grid-cols-2 gap-2 text-sm"> - <dt class="text-secondary">{i18n.str`Backend`}</dt> + <dt class="text-secondary">{ + /* Translators: Backend here means the local browser storage engine, such as SQLite or IndexedDB, not a remote server. */ i18n.str`Backend` + }</dt> <dd> {props.backend ? storageBackendLabel(props.backend) @@ -366,7 +374,9 @@ export function SettingsScreen(props: { </dd> {props.persistence && ( <> - <dt class="text-secondary">{i18n.str`Persistent storage`}</dt> + <dt class="text-secondary">{ + /* Translators: Browser storage protection prevents automatic deletion of wallet data; it is not encryption or a backup. */ i18n.str`Persistent storage` + }</dt> <dd>{storageDurabilityLabel(props.persistence.durability)}</dd> </> )} @@ -375,11 +385,13 @@ export function SettingsScreen(props: { </Card> {!props.demo && props.persistence && ( <Card> - <h2 class="font-semibold">{i18n.str`Protection from automatic deletion`}</h2> + <h2 class="font-semibold">{ + /* Translators: Browser storage protection prevents automatic deletion of wallet data; it is not encryption or a backup. */ i18n.str`Protection from automatic deletion` + }</h2> <p class="my-3 text-sm text-secondary"> {props.persistence?.durability === "persistent" - ? i18n.str`The browser has protected this origin from automatic storage eviction. You can still erase it explicitly by clearing site data.` - : i18n.str`This wallet currently uses best-effort browser storage, which may be removed automatically under storage pressure.`} + ? /* Translators: Browser storage protection prevents automatic deletion of wallet data; it is not encryption or a backup. */ i18n.str`The browser has protected this origin from automatic storage eviction. You can still erase it explicitly by clearing site data.` + : /* Translators: Browser storage protection prevents automatic deletion of wallet data; it is not encryption or a backup. */ i18n.str`This wallet currently uses best-effort browser storage, which may be removed automatically under storage pressure.`} </p> {props.persistence?.durability !== "persistent" && ( <div class="flex flex-wrap gap-2"> @@ -389,7 +401,7 @@ export function SettingsScreen(props: { > {props.persistenceBusy ? i18n.str`Requesting…` - : i18n.str`Protect wallet data`} + : /* Translators: Browser storage protection prevents automatic deletion of wallet data; it is not encryption or a backup. */ i18n.str`Protect wallet data`} </Button> {props.persistence?.installAvailable && ( <Button tone="secondary" onClick={props.onInstallPwa}> @@ -409,13 +421,17 @@ export function SettingsScreen(props: { <dl class="mt-3 grid grid-cols-2 gap-2 text-xs"> {props.persistence.usageBytes !== undefined && ( <> - <dt class="text-secondary">{i18n.str`Estimated usage`}</dt> + <dt class="text-secondary">{ + /* Translators: Estimated browser storage space currently used by this wallet. */ i18n.str`Estimated usage` + }</dt> <dd>{formatStorageSize(props.persistence.usageBytes)}</dd> </> )} {props.persistence.quotaBytes !== undefined && ( <> - <dt class="text-secondary">{i18n.str`Estimated quota`}</dt> + <dt class="text-secondary">{ + /* Translators: Estimated browser storage capacity available to this wallet. */ i18n.str`Estimated quota` + }</dt> <dd>{formatStorageSize(props.persistence.quotaBytes)}</dd> </> )} @@ -424,7 +440,9 @@ export function SettingsScreen(props: { </Card> )} <Card> - <h2 class="font-semibold">{i18n.str`Diagnostics`}</h2> + <h2 class="font-semibold">{ + /* Translators: Technical information for troubleshooting; does not provide a recoverable copy of the wallet's money. */ i18n.str`Diagnostics` + }</h2> <p class="my-3 text-sm text-secondary">{i18n.str`Save privacy-scrubbed wallet statistics for troubleshooting. This is not a wallet database backup.`}</p> <div class="mb-4 grid gap-3 sm:grid-cols-2"> <label class="block"> @@ -446,7 +464,9 @@ export function SettingsScreen(props: { </label> <label class="block"> <span class="mb-1 block text-sm font-medium"> - {i18n.str`Maximum transactions`} + { + /* Translators: Maximum number of transaction records included in the diagnostic export. */ i18n.str`Maximum transactions` + } </span> <input type="number" @@ -480,7 +500,9 @@ export function SettingsScreen(props: { {!props.demo && ( <Card> <h2 class="font-semibold">{i18n.str`Database export and import`}</h2> - <p class="my-3 text-sm text-secondary">{i18n.str`Exports contain sensitive wallet data. Store them securely. Import replaces wallet records only after wallet-core validates the dump.`}</p> + <p class="my-3 text-sm text-secondary">{ + /* Translators: wallet-core is the background wallet engine; keep the component name unchanged. */ i18n.str`Exports contain sensitive wallet data. Store them securely. Import replaces wallet records only after wallet-core validates the dump.` + }</p> <div class="flex flex-wrap gap-2"> <Button onClick={props.onExport} @@ -525,7 +547,9 @@ export function SettingsScreen(props: { <Card class="border-error"> <h2 class="font-semibold">{i18n.str`Stale wallet storage copies`}</h2> <p class="my-3 text-sm text-secondary"> - {i18n.str`These inactive generations are not recovery copies tracked by the current wallet. They are never opened or deleted automatically.`} + { + /* Translators: A storage generation is a complete version of the local wallet database, not a separate account. */ i18n.str`These inactive generations are not recovery copies tracked by the current wallet. They are never opened or deleted automatically.` + } </p> <ul class="mb-3 list-inside list-disc text-sm"> {props.staleGenerations?.map((generation) => ( @@ -589,11 +613,12 @@ export function SettingsScreen(props: { aria-labelledby="test-money-heading" class="mt-5 border-t border-outlineVariant pt-5" > - <h2 - id="test-money-heading" - class="font-semibold" - >{i18n.str`Test money`}</h2> - <p class="my-3 text-sm text-secondary">{i18n.str`Withdraw play money directly from the public test services. No bank transfer is required.`}</p> + <h2 id="test-money-heading" class="font-semibold">{ + /* Translators: Fictional currency for testing, without real monetary value. */ i18n.str`Test money` + }</h2> + <p class="my-3 text-sm 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`Withdraw play money directly from the public test services. No bank transfer is required.` + }</p> <div class="flex flex-wrap gap-2"> <Button tone="secondary" @@ -656,7 +681,9 @@ export function SettingsScreen(props: { )} {confirmingDeveloperMode && ( <ConfirmationDialog - title={i18n.str`Enable wallet-core developer mode?`} + title={ + /* Translators: wallet-core is the background wallet engine; keep the component name unchanged. */ i18n.str`Enable wallet-core developer mode?` + } description={i18n.str`Experimental actions can deliberately alter wallet behavior or data. Only continue with experiment links from a developer you trust.`} cancelLabel={i18n.str`Keep disabled`} confirmLabel={i18n.str`Enable developer mode`} @@ -672,7 +699,9 @@ export function SettingsScreen(props: { {confirmingStaleDeletion && ( <ConfirmationDialog title={i18n.str`Delete stale wallet storage copies?`} - description={i18n.str`Only inactive generations that are not tracked as the active wallet or its current recovery copy will be deleted. This cannot be undone.`} + description={ + /* Translators: A storage generation is a complete version of the local wallet database, not a separate account. */ i18n.str`Only inactive generations that are not tracked as the active wallet or its current recovery copy will be deleted. This cannot be undone.` + } cancelLabel={i18n.str`Keep copies`} confirmLabel={i18n.str`Delete stale copies`} tone="danger" diff --git a/packages/wallet-webui/src/screens/ShoppingScreen.tsx b/packages/wallet-webui/src/screens/ShoppingScreen.tsx @@ -15,10 +15,14 @@ export function ShoppingScreen(props: { return ( <div class="space-y-5"> <div> - <p class="text-sm font-semibold uppercase tracking-wide text-secondary">{i18n.str`Shop discovery`}</p> + <p class="text-sm font-semibold uppercase tracking-wide text-secondary">{ + /* Translators: Find merchants accepting the selected currency through exchange-recommended directories. */ i18n.str`Shop discovery` + }</p> {/* Translators: %1$s is a currency code; “Taler” is used only when no currency is selected. */} <h1 class="text-2xl font-bold">{i18n.str`Where to pay with ${props.currency || "Taler"}`}</h1> - <p class="text-secondary">{i18n.str`These independent directories are suggested by exchanges used by this wallet.`}</p> + <p class="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`These independent directories are suggested by exchanges used by this wallet.` + }</p> </div> {props.loading ? ( <Card> @@ -36,12 +40,14 @@ export function ShoppingScreen(props: { <li key={url}> <Card class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"> <div class="min-w-0"> - <p class="font-semibold">{i18n.str`Shopping directory`}</p> + <p class="font-semibold">{ + /* Translators: An external directory of merchants that accept Taler payments. */ i18n.str`Shopping directory` + }</p> <p class="mt-1 break-all text-sm text-secondary">{url}</p> </div> - <Button - onClick={() => props.onOpen(url)} - >{i18n.str`Open directory`}</Button> + <Button onClick={() => props.onOpen(url)}>{ + /* Translators: Open an external merchant directory website, not a filesystem folder. */ i18n.str`Open directory` + }</Button> </Card> </li> ))} diff --git a/packages/wallet-webui/src/screens/StorageMigrationCard.tsx b/packages/wallet-webui/src/screens/StorageMigrationCard.tsx @@ -10,11 +10,11 @@ export type StorageMigrationViewStage = | "reopening-wallet"; const stageLabels: Record<StorageMigrationViewStage, string> = { - "requesting-persistence": i18n.str`Requesting durable browser storage`, + "requesting-persistence": /* Translators: Browser storage protection prevents automatic deletion of wallet data; it is not encryption or a backup. */ i18n.str`Requesting durable browser storage`, "stopping-wallet": i18n.str`Stopping wallet activity`, converting: i18n.str`Copying wallet records`, - verifying: i18n.str`Verifying the new generation`, - activating: i18n.str`Activating the verified generation`, + verifying: /* Translators: A storage generation is a complete version of the local wallet database, not a separate account. */ i18n.str`Verifying the new generation`, + activating: /* Translators: A storage generation is a complete version of the local wallet database, not a separate account. */ i18n.str`Activating the verified generation`, // Translators: wallet-core is the name of the wallet's background subsystem. "reopening-wallet": i18n.str`Reopening wallet-core`, }; @@ -71,7 +71,9 @@ export function StorageMigrationCard(props: { ) : ( <div class="mt-3 space-y-3 text-sm"> <p class="text-secondary"> - {i18n.str`SQLite uses the browser's private origin storage and can improve large-wallet operations. Changing this setting converts and verifies the complete wallet before activating the new backend. It never dual-writes or silently opens the retained recovery copy.`}{" "} + { + /* Translators: Backend here means the local browser storage engine, such as SQLite or IndexedDB, not a remote server. */ i18n.str`SQLite uses the browser's private origin storage and can improve large-wallet operations. Changing this setting converts and verifies the complete wallet before activating the new backend. It never dual-writes or silently opens the retained recovery copy.` + }{" "} </p> <div class="flex min-h-12 items-center justify-between gap-4 rounded-xl border border-outlineVariant px-4 py-3"> <span> @@ -108,7 +110,9 @@ export function StorageMigrationCard(props: { </div> {props.backend === "indexeddb" && ( <p class="text-xs text-secondary"> - {i18n.str`Turning this on first requests durable browser storage. If that request is denied, conversion does not start.`}{" "} + { + /* Translators: Browser storage protection prevents automatic deletion of wallet data; it is not encryption or a backup. */ i18n.str`Turning this on first requests durable browser storage. If that request is denied, conversion does not start.` + }{" "} </p> )} {props.backupExpiresAt && ( @@ -118,7 +122,9 @@ export function StorageMigrationCard(props: { )} {props.backend === "sqlite" && ( <p class="text-xs text-secondary"> - {i18n.str`Turning this off converts the current SQLite wallet into a fresh IndexedDB generation before switching.`}{" "} + { + /* Translators: A storage generation is a complete version of the local wallet database, not a separate account. */ i18n.str`Turning this off converts the current SQLite wallet into a fresh IndexedDB generation before switching.` + }{" "} </p> )} {props.stage && ( @@ -132,7 +138,9 @@ export function StorageMigrationCard(props: { </p> )} <dl class="grid grid-cols-2 gap-2 text-xs"> - <dt class="text-secondary">{i18n.str`Durable storage`}</dt> + <dt class="text-secondary">{ + /* Translators: Browser storage protection prevents automatic deletion of wallet data; it is not encryption or a backup. */ i18n.str`Durable storage` + }</dt> <dd>{storageDurabilityLabel(props.durability)}</dd> {props.backupExpiresAt && ( <> diff --git a/packages/wallet-webui/src/screens/TermsScreen.tsx b/packages/wallet-webui/src/screens/TermsScreen.tsx @@ -14,9 +14,9 @@ export function exchangeTermsStatusLabel(status: string | undefined): string { case "pending": return i18n.str`Pending`; case "proposed": - return i18n.str`Not yet accepted`; + return /* Translators: Status of terms of service that the user has not agreed to; feminine plural in French and Italian. */ i18n.str`Not yet accepted`; case "accepted": - return i18n.str`Accepted`; + return /* Translators: The user has agreed to the exchange's terms of service; this is not a payment status. */ i18n.str`Accepted`; case "missing-tos": return i18n.str`Terms unavailable`; default: @@ -50,7 +50,9 @@ export function TermsScreen(props: { <button type="button" onClick={props.onBack} - aria-label={i18n.str`Back to exchange`} + aria-label={ + /* Translators: An exchange is a GNU Taler service issuing and redeeming digital cash, not an action that converts one currency into another. */ i18n.str`Back to exchange` + } class="grid h-11 w-11 place-items-center rounded-full text-2xl hover:bg-secondaryContainer" > ← diff --git a/packages/wallet-webui/src/screens/TokenListScreen.tsx b/packages/wallet-webui/src/screens/TokenListScreen.tsx @@ -105,7 +105,9 @@ export function TokenListScreen(props: { </span> )} {props.kind === "passes" && item.state === "active" && ( - <span class="shrink-0 rounded-full bg-secondaryContainer px-2.5 py-1 text-xs font-medium text-onSecondaryContainer">{i18n.str`Active`}</span> + <span class="shrink-0 rounded-full bg-secondaryContainer px-2.5 py-1 text-xs font-medium text-onSecondaryContainer">{ + /* Translators: A merchant-issued pass whose validity period includes the present time. */ i18n.str`Active` + }</span> )} </div> <div diff --git a/packages/wallet-webui/src/screens/UriInputScreen.tsx b/packages/wallet-webui/src/screens/UriInputScreen.tsx @@ -26,7 +26,9 @@ export function UriInputScreen(props: { </button> <div> <h1 class="text-2xl font-bold">{i18n.str`Enter a wallet link`}</h1> - <p class="mt-1 text-secondary">{i18n.str`Paste a Taler payment, withdrawal, refund, or peer-payment link.`}</p> + <p class="mt-1 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`Paste a Taler payment, withdrawal, refund, or peer-payment link.` + }</p> </div> </div> <Card>