diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/wallet')
14 files changed, 537 insertions, 228 deletions
diff --git a/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx index d4158973e..229fab7be 100644 --- a/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx +++ b/packages/taler-wallet-webextension/src/wallet/AddNewActionView.tsx @@ -1,4 +1,4 @@ -import { classifyTalerUri, TalerUriType } from "@gnu-taler/taler-util"; +import { classifyTalerUri, TalerUriType, i18n } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Button, ButtonSuccess, InputWithLabel } from "../components/styled"; @@ -8,20 +8,20 @@ export interface Props { onCancel: () => void; } -function buttonLabelByTalerType(type: TalerUriType): string { +function buttonLabelByTalerType(type: TalerUriType): VNode { switch (type) { case TalerUriType.TalerNotifyReserve: - return "Open reserve page"; + return <i18n.Translate>Open reserve page</i18n.Translate>; case TalerUriType.TalerPay: - return "Open pay page"; + return <i18n.Translate>Open pay page</i18n.Translate>; case TalerUriType.TalerRefund: - return "Open refund page"; + return <i18n.Translate>Open refund page</i18n.Translate>; case TalerUriType.TalerTip: - return "Open tip page"; + return <i18n.Translate>Open tip page</i18n.Translate>; case TalerUriType.TalerWithdraw: - return "Open withdraw page"; + return <i18n.Translate>Open withdraw page</i18n.Translate>; } - return ""; + return <Fragment />; } export function AddNewActionView({ onCancel }: Props): VNode { @@ -47,7 +47,9 @@ export function AddNewActionView({ onCancel }: Props): VNode { </InputWithLabel> </section> <footer> - <Button onClick={onCancel}>Back</Button> + <Button onClick={onCancel}> + <i18n.Translate>Back</i18n.Translate> + </Button> {uriType !== TalerUriType.Unknown && ( <ButtonSuccess onClick={() => { diff --git a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx index daea9e3bd..a5821d48b 100644 --- a/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/BackupPage.tsx @@ -14,7 +14,7 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { i18n, Timestamp } from "@gnu-taler/taler-util"; +import { i18n, Timestamp, Translate } from "@gnu-taler/taler-util"; import { ProviderInfo, ProviderPaymentPaid, @@ -71,7 +71,10 @@ export function BackupPage({ onAddProvider }: Props): VNode { } if (status.hasError) { return ( - <LoadingError title="Could not load backup providers" error={status} /> + <LoadingError + title={<i18n.Translate>Could not load backup providers</i18n.Translate>} + error={status} + /> ); } @@ -122,7 +125,9 @@ export function BackupView({ ))} {!providers.length && ( <Centered style={{ marginTop: 100 }}> - <BoldLight>No backup providers configured</BoldLight> + <BoldLight> + <i18n.Translate>No backup providers configured</i18n.Translate> + </BoldLight> <ButtonSuccess onClick={onAddProvider}> <i18n.Translate>Add provider</i18n.Translate> </ButtonSuccess> @@ -140,7 +145,9 @@ export function BackupView({ <i18n.Translate>Sync now</i18n.Translate> )} </ButtonPrimary> - <ButtonSuccess onClick={onAddProvider}>Add provider</ButtonSuccess> + <ButtonSuccess onClick={onAddProvider}> + <i18n.Translate>Add provider</i18n.Translate> + </ButtonSuccess> </div> </footer> )} @@ -176,10 +183,14 @@ function BackupLayout(props: TransactionLayoutProps): VNode { </a> {dateStr && ( - <SmallText style={{ marginTop: 5 }}>Last synced: {dateStr}</SmallText> + <SmallText style={{ marginTop: 5 }}> + <i18n.Translate>Last synced</i18n.Translate>: {dateStr} + </SmallText> )} {!dateStr && ( - <SmallLightText style={{ marginTop: 5 }}>Not synced</SmallLightText> + <SmallLightText style={{ marginTop: 5 }}> + <i18n.Translate>Not synced</i18n.Translate> + </SmallLightText> )} </div> <div> @@ -196,7 +207,9 @@ function BackupLayout(props: TransactionLayoutProps): VNode { function ExpirationText({ until }: { until: Timestamp }): VNode { return ( <Fragment> - <CenteredText> Expires in </CenteredText> + <CenteredText> + <i18n.Translate>Expires in</i18n.Translate> + </CenteredText> <CenteredBoldText {...{ color: colorByTimeToExpire(until) }}> {" "} {daysUntil(until)}{" "} diff --git a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx index 0ca07816e..96644be28 100644 --- a/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx +++ b/packages/taler-wallet-webextension/src/wallet/CreateManualWithdraw.tsx @@ -19,7 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { AmountJson, Amounts, i18n } from "@gnu-taler/taler-util"; +import { AmountJson, Amounts, i18n, Translate } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { ErrorMessage } from "../components/ErrorMessage"; @@ -99,14 +99,20 @@ export function CreateManualWithdraw({ if (!initialExchange) { return ( <section> - <h2>Manual Withdrawal</h2> + <h2> + <i18n.Translate>Manual Withdrawal</i18n.Translate> + </h2> <LightText> - Choose a exchange from where the coins will be withdrawn. The exchange - will send the coins to this wallet after receiving a wire transfer - with the correct subject. + <i18n.Translate> + Choose a exchange from where the coins will be withdrawn. The + exchange will send the coins to this wallet after receiving a wire + transfer with the correct subject. + </i18n.Translate> </LightText> <Centered style={{ marginTop: 100 }}> - <BoldLight>No exchange configured</BoldLight> + <BoldLight> + <i18n.Translate>No exchange configured</i18n.Translate> + </BoldLight> <ButtonSuccess onClick={onAddExchange}> <i18n.Translate>Add exchange</i18n.Translate> </ButtonSuccess> @@ -118,20 +124,26 @@ export function CreateManualWithdraw({ return ( <Fragment> <section> - <ErrorMessage - title={error && "Can't create the reserve"} - description={error} - /> - <h2>Manual Withdrawal</h2> + {error && ( + <ErrorMessage + title={<i18n.Translate>Can't create the reserve</i18n.Translate>} + description={error} + /> + )} + <h2> + <i18n.Translate>Manual Withdrawal</i18n.Translate> + </h2> <LightText> - Choose a exchange from where the coins will be withdrawn. The exchange - will send the coins to this wallet after receiving a wire transfer - with the correct subject. + <i18n.Translate> + Choose a exchange from where the coins will be withdrawn. The + exchange will send the coins to this wallet after receiving a wire + transfer with the correct subject. + </i18n.Translate> </LightText> <p> <Input> <SelectList - label="Currency" + label={<i18n.Translate>Currency</i18n.Translate>} list={currencyMap} name="currency" value={currency} @@ -140,7 +152,7 @@ export function CreateManualWithdraw({ </Input> <Input> <SelectList - label="Exchange" + label={<i18n.Translate>Exchange</i18n.Translate>} list={exchangeMap} name="currency" value={exchange} @@ -149,12 +161,14 @@ export function CreateManualWithdraw({ </Input> <div style={{ display: "flex", justifyContent: "space-between" }}> <LinkPrimary onClick={onAddExchange} style={{ marginLeft: "auto" }}> - <i18n.Translate>Add exchange</i18n.Translate> + <i18n.Translate>Add Exchange</i18n.Translate> </LinkPrimary> </div> {currency && ( <InputWithLabel invalid={!!amount && !parsedAmount}> - <label>Amount</label> + <label> + <i18n.Translate>Amount</i18n.Translate> + </label> <div> <span>{currency}</span> <input @@ -173,7 +187,7 @@ export function CreateManualWithdraw({ disabled={!parsedAmount || !exchange} onClick={() => onCreate(exchange, parsedAmount!)} > - Start withdrawal + <i18n.Translate>Start withdrawal</i18n.Translate> </ButtonPrimary> </footer> </Fragment> diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx b/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx index abe830e87..d1d618e9f 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx @@ -19,6 +19,7 @@ import { Amounts, AmountString, PaytoUri, + i18n, } from "@gnu-taler/taler-util"; import { DepositFee } from "@gnu-taler/taler-wallet-core/src/operations/deposits"; import { Fragment, h, VNode } from "preact"; @@ -132,13 +133,23 @@ export function View({ }, [amount]); if (!balance) { - return <div>no balance</div>; + return ( + <div> + <i18n.Translate>no balance</i18n.Translate> + </div> + ); } if (!knownBankAccounts || !knownBankAccounts.length) { return ( <WarningBox> - <p>There is no known bank account to send money to</p> - <ButtonBoxWarning>Withdraw</ButtonBoxWarning> + <p> + <i18n.Translate> + There is no known bank account to send money to + </i18n.Translate> + </p> + <ButtonBoxWarning> + <i18n.Translate>Withdraw</i18n.Translate> + </ButtonBoxWarning> </WarningBox> ); } @@ -162,11 +173,13 @@ export function View({ return ( <Fragment> - <h2>Send {currency} to your account</h2> + <h2> + <i18n.Translate>Send {currency} to your account</i18n.Translate> + </h2> <section> <Input> <SelectList - label="Bank account IBAN number" + label={<i18n.Translate>Bank account IBAN number</i18n.Translate>} list={accountMap} name="account" value={String(accountIdx)} @@ -174,7 +187,9 @@ export function View({ /> </Input> <InputWithLabel invalid={!!error}> - <label>Amount</label> + <label> + <i18n.Translate>Amount</i18n.Translate> + </label> <div> <span>{currency}</span> <input @@ -196,7 +211,9 @@ export function View({ { <Fragment> <InputWithLabel> - <label>Deposit fee</label> + <label> + <i18n.Translate>Deposit fee</i18n.Translate> + </label> <div> <span>{currency}</span> <input @@ -208,7 +225,9 @@ export function View({ </InputWithLabel> <InputWithLabel> - <label>Total deposit</label> + <label> + <i18n.Translate>Total deposit</i18n.Translate> + </label> <div> <span>{currency}</span> <input @@ -224,10 +243,14 @@ export function View({ <footer> <div /> {unableToDeposit ? ( - <ButtonPrimary disabled>Deposit</ButtonPrimary> + <ButtonPrimary disabled> + <i18n.Translate>Deposit</i18n.Translate> + </ButtonPrimary> ) : ( <ButtonPrimary onClick={() => onSend(accountURI, amountStr)}> - Deposit {Amounts.stringifyValue(totalToDeposit)} {currency} + <i18n.Translate> + Deposit {Amounts.stringifyValue(totalToDeposit)} {currency} + </i18n.Translate> </ButtonPrimary> )} </footer> diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx index 562a2c956..1ffca827b 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeAddConfirm.tsx @@ -1,4 +1,4 @@ -import { i18n } from "@gnu-taler/taler-util"; +import { i18n, Translate } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Button, ButtonSuccess, ButtonWarning } from "../components/styled"; @@ -84,9 +84,11 @@ export function View({ return ( <Fragment> <section> - <h1>Review terms of service</h1> + <h1> + <i18n.Translate>Review terms of service</i18n.Translate> + </h1> <div> - Exchange URL: + <i18n.Translate>Exchange URL</i18n.Translate>: <a href={url} target="_blank" rel="noreferrer"> {url} </a> @@ -118,17 +120,17 @@ export function View({ <Fragment> {needsReview && !reviewed && ( <ButtonSuccess disabled upperCased onClick={onConfirm}> - {i18n.str`Add exchange`} + <i18n.Translate>Add exchange</i18n.Translate> </ButtonSuccess> )} {(terms.status === "accepted" || (needsReview && reviewed)) && ( <ButtonSuccess upperCased onClick={onConfirm}> - {i18n.str`Add exchange`} + <i18n.Translate>Add exchange</i18n.Translate> </ButtonSuccess> )} {terms.status === "notfound" && ( <ButtonWarning upperCased onClick={onConfirm}> - {i18n.str`Add exchange anyway`} + <i18n.Translate>Add exchange anyway</i18n.Translate> </ButtonWarning> )} </Fragment> diff --git a/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx b/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx index c4ba4f2a3..7199ce90c 100644 --- a/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ExchangeSetUrl.tsx @@ -2,6 +2,7 @@ import { canonicalizeBaseUrl, i18n, TalerConfigResponse, + Translate, } from "@gnu-taler/taler-util"; import { Fragment, h } from "preact"; import { useEffect, useState } from "preact/hooks"; @@ -91,32 +92,50 @@ export function ExchangeSetUrlPage({ <Fragment> <section> {!expectedCurrency ? ( - <h1>Add new exchange</h1> + <h1> + <i18n.Translate>Add new exchange</i18n.Translate> + </h1> ) : ( - <h2>Add exchange for {expectedCurrency}</h2> + <h2> + <i18n.Translate>Add exchange for {expectedCurrency}</i18n.Translate> + </h2> )} {!result && ( - <LightText>Enter the URL of an exchange you trust.</LightText> + <LightText> + <i18n.Translate> + Enter the URL of an exchange you trust. + </i18n.Translate> + </LightText> )} {result && ( <LightText> - An exchange has been found! Review the information and click next + <i18n.Translate> + An exchange has been found! Review the information and click next + </i18n.Translate> </LightText> )} {result && expectedCurrency && expectedCurrency !== result.currency && ( <WarningBox> - This exchange doesn't match the expected currency{" "} - <b>{expectedCurrency}</b> + <i18n.Translate> + This exchange doesn't match the expected currency + <b>{expectedCurrency}</b> + </i18n.Translate> </WarningBox> )} - <ErrorMessage - title={error && "Unable to add this exchange"} - description={error} - /> - <ErrorMessage - title={confirmationError && "Unable to add this exchange"} - description={confirmationError} - /> + {error && ( + <ErrorMessage + title={ + <i18n.Translate>Unable to verify this exchange</i18n.Translate> + } + description={error} + /> + )} + {confirmationError && ( + <ErrorMessage + title={<i18n.Translate>Unable to add this exchange</i18n.Translate>} + description={confirmationError} + /> + )} <p> <Input invalid={!!error}> <label>URL</label> @@ -127,15 +146,23 @@ export function ExchangeSetUrlPage({ onInput={(e) => updateEndpoint(e.currentTarget.value)} /> </Input> - {loading && <div>loading... </div>} + {loading && ( + <div> + <i18n.Translate>loading</i18n.Translate>... + </div> + )} {result && !loading && ( <Fragment> <Input> - <label>Version</label> + <label> + <i18n.Translate>Version</i18n.Translate> + </label> <input type="text" disabled value={result.version} /> </Input> <Input> - <label>Currency</label> + <label> + <i18n.Translate>Currency</i18n.Translate> + </label> <input type="text" disabled value={result.currency} /> </Input> </Fragment> diff --git a/packages/taler-wallet-webextension/src/wallet/History.tsx b/packages/taler-wallet-webextension/src/wallet/History.tsx index 2fae07525..e0a1c588e 100644 --- a/packages/taler-wallet-webextension/src/wallet/History.tsx +++ b/packages/taler-wallet-webextension/src/wallet/History.tsx @@ -19,6 +19,7 @@ import { Balance, NotificationType, Transaction, + i18n, } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -66,7 +67,11 @@ export function HistoryPage({ if (transactionQuery.hasError) { return ( <LoadingError - title="Could not load the list of transactions" + title={ + <i18n.Translate> + Could not load the list of transactions + </i18n.Translate> + } error={transactionQuery} /> ); @@ -193,21 +198,23 @@ export function HistoryView({ style={{ marginLeft: 0, marginTop: 8 }} onClick={() => goToWalletManualWithdraw(selectedCurrency)} > - Withdraw + <i18n.Translate>Withdraw</i18n.Translate> </ButtonPrimary> {currencyAmount && Amounts.isNonZero(currencyAmount) && ( <ButtonBoxPrimary style={{ marginLeft: 0, marginTop: 8 }} onClick={() => goToWalletDeposit(selectedCurrency)} > - Deposit + <i18n.Translate>Deposit</i18n.Translate> </ButtonBoxPrimary> )} </div> </div> </section> {datesWithTransaction.length === 0 ? ( - <section>There is no history for this currency</section> + <section> + <i18n.Translate>There is no history for this currency</i18n.Translate> + </section> ) : ( <section> {datesWithTransaction.map((d, i) => { diff --git a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx index 1f8603794..d9a1544a7 100644 --- a/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ManualWithdrawPage.tsx @@ -19,6 +19,7 @@ import { AmountJson, Amounts, NotificationType, + i18n, } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -95,7 +96,11 @@ export function ManualWithdrawPage({ currency, onCancel }: Props): VNode { if (state.hasError) { return ( <LoadingError - title="Could not load the list of known exchanges" + title={ + <i18n.Translate> + Could not load the list of known exchanges + </i18n.Translate> + } error={state} /> ); diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx index 44d1049b9..6bb5b6836 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderAddPage.tsx @@ -19,6 +19,7 @@ import { BackupBackupProviderTerms, canonicalizeBaseUrl, i18n, + Translate, } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; @@ -113,15 +114,29 @@ export function SetUrlView({ return ( <Fragment> <section> - <h1> Add backup provider</h1> - <ErrorMessage - title={error && "Could not get provider information"} - description={error} - /> - <LightText> Backup providers may charge for their service</LightText> + <h1> + <i18n.Translate>Add backup provider</i18n.Translate> + </h1> + {error && ( + <ErrorMessage + title={ + <i18n.Translate> + Could not get provider information + </i18n.Translate> + } + description={error} + /> + )} + <LightText> + <i18n.Translate> + Backup providers may charge for their service + </i18n.Translate> + </LightText> <p> <Input invalid={urlError}> - <label>URL</label> + <label> + <i18n.Translate>URL</i18n.Translate> + </label> <input type="text" placeholder="https://" @@ -130,7 +145,9 @@ export function SetUrlView({ /> </Input> <Input> - <label>Name</label> + <label> + <i18n.Translate>Name</i18n.Translate> + </label> <input type="text" disabled={name === undefined} @@ -142,7 +159,7 @@ export function SetUrlView({ </section> <footer> <Button onClick={onCancel}> - <i18n.Translate> < Back</i18n.Translate> + < <i18n.Translate>Back</i18n.Translate> </Button> <ButtonPrimary disabled={!value && !urlError} @@ -177,29 +194,43 @@ export function ConfirmProviderView({ return ( <Fragment> <section> - <h1>Review terms of service</h1> + <h1> + <i18n.Translate>Review terms of service</i18n.Translate> + </h1> <div> - Provider URL:{" "} + <i18n.Translate>Provider URL</i18n.Translate>:{" "} <a href={url} target="_blank"> {url} </a> </div> <SmallLightText> - Please review and accept this provider's terms of service + <i18n.Translate> + Please review and accept this provider's terms of service + </i18n.Translate> </SmallLightText> - <h2>1. Pricing</h2> + <h2> + 1. <i18n.Translate>Pricing</i18n.Translate> + </h2> <p> - {Amounts.isZero(provider.annual_fee) - ? "free of charge" - : `${provider.annual_fee} per year of service`} + {Amounts.isZero(provider.annual_fee) ? ( + <i18n.Translate>free of charge</i18n.Translate> + ) : ( + <i18n.Translate> + {provider.annual_fee} per year of service + </i18n.Translate> + )} </p> - <h2>2. Storage</h2> + <h2> + 2. <i18n.Translate>Storage</i18n.Translate> + </h2> <p> - {provider.storage_limit_in_megabytes} megabytes of storage per year of - service + <i18n.Translate> + {provider.storage_limit_in_megabytes} megabytes of storage per year + of service + </i18n.Translate> </p> <Checkbox - label="Accept terms of service" + label={<i18n.Translate>Accept terms of service</i18n.Translate>} name="terms" onToggle={() => setAccepted((old) => !old)} enabled={accepted} @@ -207,7 +238,7 @@ export function ConfirmProviderView({ </section> <footer> <Button onClick={onCancel}> - <i18n.Translate> < Back</i18n.Translate> + < <i18n.Translate>Back</i18n.Translate> </Button> <ButtonPrimary disabled={!accepted} onClick={onConfirm}> <i18n.Translate>Add provider</i18n.Translate> diff --git a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx index 239a7b31d..65049d6b6 100644 --- a/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ProviderDetailPage.tsx @@ -22,6 +22,8 @@ import { } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, VNode } from "preact"; import { ErrorMessage } from "../components/ErrorMessage"; +import { Loading } from "../components/Loading"; +import { LoadingError } from "../components/LoadingError"; import { Button, ButtonDestructive, @@ -52,35 +54,24 @@ export function ProviderDetailPage({ pid: providerURL, onBack }: Props): VNode { const state = useAsyncAsHook(getProviderInfo); if (!state) { - return ( - <div> - <i18n.Translate>Loading...</i18n.Translate> - </div> - ); + return <Loading />; } if (state.hasError) { return ( - <div> - <i18n.Translate> - There was an error loading the provider detail for "{providerURL}" - </i18n.Translate> - </div> + <LoadingError + title={ + <i18n.Translate> + There was an error loading the provider detail for "{providerURL}" + </i18n.Translate> + } + error={state} + /> ); } - if (state.response === null) { - onBack(); - return ( - <div> - <i18n.Translate> - There is not known provider with url "{providerURL}". Redirecting - back... - </i18n.Translate> - </div> - ); - } return ( <ProviderView + url={providerURL} info={state.response} onSync={async () => wxApi.syncOneProvider(providerURL)} onDelete={async () => wxApi.removeProvider(providerURL).then(onBack)} @@ -93,7 +84,8 @@ export function ProviderDetailPage({ pid: providerURL, onBack }: Props): VNode { } export interface ViewProps { - info: ProviderInfo; + url: string; + info: ProviderInfo | null; onDelete: () => void; onSync: () => void; onBack: () => void; @@ -102,12 +94,32 @@ export interface ViewProps { export function ProviderView({ info, + url, onDelete, onSync, onBack, onExtend, }: ViewProps): VNode { - const lb = info?.lastSuccessfulBackupTimestamp; + if (info === null) { + return ( + <Fragment> + <section> + <p> + <i18n.Translate> + There is not known provider with url "{url}". + </i18n.Translate> + </p> + </section> + <footer> + <Button onClick={onBack}> + < <i18n.Translate>Back</i18n.Translate> + </Button> + <div /> + </footer> + </Fragment> + ); + } + const lb = info.lastSuccessfulBackupTimestamp; const isPaid = info.paymentStatus.type === ProviderPaymentType.Paid || info.paymentStatus.type === ProviderPaymentType.TermsChanged; @@ -125,7 +137,10 @@ export function ProviderView({ </header> <section> <p> - <b>Last backup:</b> <Time timestamp={lb} format="dd MMMM yyyy" /> + <b> + <i18n.Translate>Last backup</i18n.Translate>: + </b>{" "} + <Time timestamp={lb} format="dd MMMM yyyy" /> </p> <ButtonPrimary onClick={onSync}> <i18n.Translate>Back up</i18n.Translate> @@ -133,7 +148,11 @@ export function ProviderView({ {info.terms && ( <Fragment> <p> - <b>Provider fee:</b> {info.terms && info.terms.annualFee} per year + <b> + <i18n.Translate>Provider fee</i18n.Translate>: + </b>{" "} + {info.terms && info.terms.annualFee}{" "} + <i18n.Translate>per year</i18n.Translate> </p> </Fragment> )} @@ -187,11 +206,11 @@ export function ProviderView({ </section> <footer> <Button onClick={onBack}> - <i18n.Translate> < back</i18n.Translate> + < <i18n.Translate>back</i18n.Translate> </Button> <div> <ButtonDestructive onClick={onDelete}> - <i18n.Translate>remove provider</i18n.Translate> + <i18n.Translate>Remove provider</i18n.Translate> </ButtonDestructive> </div> </footer> @@ -201,7 +220,14 @@ export function ProviderView({ function Error({ info }: { info: ProviderInfo }): VNode { if (info.lastError) { - return <ErrorMessage title={info.lastError.hint} />; + return ( + <ErrorMessage + title={ + <i18n.Translate>This provider has reported an error</i18n.Translate> + } + description={info.lastError.hint} + /> + ); } if (info.backupProblem) { switch (info.backupProblem.type) { @@ -219,7 +245,11 @@ function Error({ info }: { info: ProviderInfo }): VNode { /> ); case "backup-unreadable": - return <ErrorMessage title="Backup is not readable" />; + return ( + <ErrorMessage + title={<i18n.Translate>Backup is not readable</i18n.Translate>} + /> + ); default: return ( <ErrorMessage @@ -239,16 +269,20 @@ function Error({ info }: { info: ProviderInfo }): VNode { function descriptionByStatus(status: ProviderPaymentStatus): VNode { switch (status.type) { - // return i18n.str`no enough balance to make the payment` - // return i18n.str`not paid yet` case ProviderPaymentType.Paid: case ProviderPaymentType.TermsChanged: if (status.paidUntil.t_ms === "never") { - return <span>{i18n.str`service paid`}</span>; + return ( + <span> + <i18n.Translate>service paid</i18n.Translate> + </span> + ); } return ( <Fragment> - <b>Backup valid until:</b>{" "} + <b> + <i18n.Translate>Backup valid until</i18n.Translate>: + </b>{" "} <Time timestamp={status.paidUntil} format="dd MMM yyyy" /> </Fragment> ); diff --git a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx index 7ccef2daa..2c0e2fd31 100644 --- a/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx +++ b/packages/taler-wallet-webextension/src/wallet/ReserveCreated.tsx @@ -1,4 +1,4 @@ -import { AmountJson, Amounts, parsePaytoUri } from "@gnu-taler/taler-util"; +import { AmountJson, parsePaytoUri, i18n } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { BankDetailsByPaytoType } from "../components/BankDetailsByPaytoType"; import { QR } from "../components/QR"; @@ -22,15 +22,25 @@ export function ReserveCreated({ const paytoURI = parsePaytoUri(payto); // const url = new URL(paytoURI?.targetPath); if (!paytoURI) { - return <div>could not parse payto uri from exchange {payto}</div>; + return ( + <div> + <i18n.Translate> + could not parse payto uri from exchange {payto} + </i18n.Translate> + </div> + ); } return ( <Fragment> <section> - <h1>Exchange is ready for withdrawal!</h1> + <h1> + <i18n.Translate>Exchange is ready for withdrawal</i18n.Translate> + </h1> <p> - To complete the process you need to wire{" "} - <b>{amountToString(amount)}</b> to the exchange bank account + <i18n.Translate> + To complete the process you need to wire + <b>{amountToString(amount)}</b> to the exchange bank account + </i18n.Translate> </p> <BankDetailsByPaytoType amount={amountToString(amount)} @@ -40,23 +50,27 @@ export function ReserveCreated({ /> <p> <WarningBox> - Make sure to use the correct subject, otherwise the money will not - arrive in this wallet. + <i18n.Translate> + Make sure to use the correct subject, otherwise the money will not + arrive in this wallet. + </i18n.Translate> </WarningBox> </p> </section> <section> <p> - Alternative, you can also scan this QR code or open{" "} - <a href={payto}>this link</a> if you have a banking app installed that - supports RFC 8905 + <i18n.Translate> + Alternative, you can also scan this QR code or open + <a href={payto}>this link</a> if you have a banking app installed + that supports RFC 8905 + </i18n.Translate> </p> <QR text={payto} /> </section> <footer> <div /> <ButtonDestructive onClick={onCancel}> - Cancel withdrawal + <i18n.Translate>Cancel withdrawal</i18n.Translate> </ButtonDestructive> </footer> </Fragment> diff --git a/packages/taler-wallet-webextension/src/wallet/Settings.tsx b/packages/taler-wallet-webextension/src/wallet/Settings.tsx index ff47620eb..8456ca550 100644 --- a/packages/taler-wallet-webextension/src/wallet/Settings.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Settings.tsx @@ -14,16 +14,19 @@ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { ExchangeListItem, i18n } from "@gnu-taler/taler-util"; +import { ExchangeListItem, i18n, Translate } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { Checkbox } from "../components/Checkbox"; +import { SelectList } from "../components/SelectList"; import { DestructiveText, + Input, LinkPrimary, SuccessText, WarningText, } from "../components/styled"; import { useDevContext } from "../context/devContext"; +import { useTranslationContext } from "../context/translation"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; import { useBackupDeviceName } from "../hooks/useBackupDeviceName"; import { useExtendedPermissions } from "../hooks/useExtendedPermissions"; @@ -36,13 +39,13 @@ export function SettingsPage(): VNode { const [permissionsEnabled, togglePermissions] = useExtendedPermissions(); const { devMode, toggleDevMode } = useDevContext(); const { name, update } = useBackupDeviceName(); - const [lang, changeLang] = useLang(); + // const [lang, changeLang] = useLang(); const exchangesHook = useAsyncAsHook(wxApi.listExchanges); return ( <SettingsView - lang={lang} - changeLang={changeLang} + // lang={lang} + // changeLang={changeLang} knownExchanges={ !exchangesHook || exchangesHook.hasError ? [] @@ -59,8 +62,8 @@ export function SettingsPage(): VNode { } export interface ViewProps { - lang: string; - changeLang: (s: string) => void; + // lang: string; + // changeLang: (s: string) => void; deviceName: string; setDeviceName: (s: string) => Promise<void>; permissionsEnabled: boolean; @@ -77,33 +80,65 @@ export function SettingsView({ developerMode, toggleDeveloperMode, }: ViewProps): VNode { + const { lang, supportedLang, changeLanguage } = useTranslationContext(); + return ( <Fragment> <section> <h2> - <i18n.Translate>Permissions</i18n.Translate> + <i18n.Translate>Display</i18n.Translate> + </h2> + <Input> + <SelectList + label={<i18n.Translate>Current Language</i18n.Translate>} + list={supportedLang} + name="lang" + value={lang} + onChange={(v) => changeLanguage(v)} + /> + </Input> + + <h2> + <i18n.Translate>Navigator</i18n.Translate> </h2> <Checkbox - label="Automatically open wallet based on page content" + label={ + <i18n.Translate> + Automatically open wallet based on page content + </i18n.Translate> + } name="perm" - description="(Enabling this option below will make using the wallet faster, but requires more permissions from your browser.)" + description={ + <i18n.Translate> + Enabling this option below will make using the wallet faster, but + requires more permissions from your browser. + </i18n.Translate> + } enabled={permissionsEnabled} onToggle={togglePermissions} /> <h2> - <i18n.Translate>Known exchanges</i18n.Translate> + <i18n.Translate>Trust</i18n.Translate> </h2> {!knownExchanges || !knownExchanges.length ? ( - <div>No exchange yet!</div> + <div> + <i18n.Translate>No exchange yet</i18n.Translate> + </div> ) : ( <Fragment> <table> <thead> <tr> - <th>currency</th> - <th>url</th> - <th>term of service</th> + <th> + <i18n.Translate>Currency</i18n.Translate> + </th> + <th> + <i18n.Translate>URL</i18n.Translate> + </th> + <th> + <i18n.Translate>Term of Service</i18n.Translate> + </th> </tr> </thead> <tbody> @@ -116,12 +151,24 @@ export function SettingsView({ ); switch (status) { case "accepted": - return <SuccessText>ok</SuccessText>; + return ( + <SuccessText> + <i18n.Translate>ok</i18n.Translate> + </SuccessText> + ); case "changed": - return <WarningText>changed!</WarningText>; + return ( + <WarningText> + <i18n.Translate>changed</i18n.Translate> + </WarningText> + ); case "new": case "notfound": - return <DestructiveText>not accepted</DestructiveText>; + return ( + <DestructiveText> + <i18n.Translate>not accepted</i18n.Translate> + </DestructiveText> + ); } } return ( @@ -143,15 +190,19 @@ export function SettingsView({ <div style={{ display: "flex", justifyContent: "space-between" }}> <div /> <LinkPrimary href={Pages.settings_exchange_add}> - Add an exchange + <i18n.Translate>Add an exchange</i18n.Translate> </LinkPrimary> </div> - <h2>Config</h2> + <h2>Troubleshooting</h2> <Checkbox - label="Developer mode" + label={<i18n.Translate>Developer mode</i18n.Translate>} name="devMode" - description="(More options and information useful for debugging)" + description={ + <i18n.Translate> + (More options and information useful for debugging) + </i18n.Translate> + } enabled={developerMode} onToggle={toggleDeveloperMode} /> diff --git a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx index 423a641a3..cae70d60d 100644 --- a/packages/taler-wallet-webextension/src/wallet/Transaction.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Transaction.tsx @@ -47,7 +47,6 @@ import { } from "../components/styled"; import { Time } from "../components/Time"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; -import { Pages } from "../NavigationBar"; import * as wxApi from "../wxApi"; interface Props { @@ -76,7 +75,11 @@ export function TransactionPage({ tid, goToWalletHistory }: Props): VNode { if (state.hasError) { return ( <LoadingError - title="Could not load the transaction information" + title={ + <i18n.Translate> + Could not load the transaction information + </i18n.Translate> + } error={state} /> ); @@ -139,11 +142,17 @@ export function TransactionView({ <Fragment> <section style={{ padding: 8, textAlign: "center" }}> <ErrorTalerOperation - title="There was an error trying to complete the transaction" + title={ + <i18n.Translate> + There was an error trying to complete the transaction + </i18n.Translate> + } error={transaction?.error} /> {transaction.pending && ( - <WarningBox>This transaction is not completed</WarningBox> + <WarningBox> + <i18n.Translate>This transaction is not completed</i18n.Translate> + </WarningBox> )} </section> <section> @@ -151,16 +160,16 @@ export function TransactionView({ </section> <footer> <Button onClick={onBack}> - <i18n.Translate> < Back </i18n.Translate> + < <i18n.Translate> Back </i18n.Translate> </Button> <div> {showRetry ? ( <ButtonPrimary onClick={onRetry}> - <i18n.Translate>retry</i18n.Translate> + <i18n.Translate>Retry</i18n.Translate> </ButtonPrimary> ) : null} <ButtonDestructive onClick={doCheckBeforeForget}> - <i18n.Translate> Forget </i18n.Translate> + <i18n.Translate>Forget</i18n.Translate> </ButtonDestructive> </div> </footer> @@ -184,24 +193,30 @@ export function TransactionView({ {confirmBeforeForget ? ( <Overlay> <CenteredDialog> - <header>Caution!</header> + <header> + <i18n.Translate>Caution!</i18n.Translate> + </header> <section> - If you have already wired money to the exchange you will loose - the chance to get the coins form it. + <i18n.Translate> + If you have already wired money to the exchange you will loose + the chance to get the coins form it. + </i18n.Translate> </section> <footer> <Button onClick={() => setConfirmBeforeForget(false)}> - <i18n.Translate> Cancel </i18n.Translate> + <i18n.Translate>Cancel</i18n.Translate> </Button> <ButtonDestructive onClick={onDelete}> - <i18n.Translate> Confirm </i18n.Translate> + <i18n.Translate>Confirm</i18n.Translate> </ButtonDestructive> </footer> </CenteredDialog> </Overlay> ) : undefined} - <h2>Withdrawal</h2> + <h2> + <i18n.Translate>Withdrawal</i18n.Translate> + </h2> <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" /> {transaction.pending ? ( transaction.withdrawalDetails.type === @@ -217,19 +232,21 @@ export function TransactionView({ /> <p> <WarningBox> - Make sure to use the correct subject, otherwise the money will - not arrive in this wallet. + <i18n.Translate> + Make sure to use the correct subject, otherwise the money + will not arrive in this wallet. + </i18n.Translate> </WarningBox> </p> <Part big - title="Total withdrawn" + title={<i18n.Translate>Total withdrawn</i18n.Translate>} text={amountToString(transaction.amountEffective)} kind="positive" /> <Part big - title="Exchange fee" + title={<i18n.Translate>Exchange fee</i18n.Translate>} text={amountToString(fee)} kind="negative" /> @@ -239,34 +256,40 @@ export function TransactionView({ {!transaction.withdrawalDetails.confirmed && transaction.withdrawalDetails.bankConfirmationUrl ? ( <InfoBox> - The bank is waiting for confirmation. Go to the - <a - href={transaction.withdrawalDetails.bankConfirmationUrl} - target="_blank" - rel="noreferrer" - > - bank site - </a> + <i18n.Translate> + The bank is waiting for confirmation. Go to the + <a + href={transaction.withdrawalDetails.bankConfirmationUrl} + target="_blank" + rel="noreferrer" + > + <i18n.Translate>bank site</i18n.Translate> + </a> + </i18n.Translate> </InfoBox> ) : undefined} {transaction.withdrawalDetails.confirmed && ( - <InfoBox>Waiting for the coins to arrive</InfoBox> + <InfoBox> + <i18n.Translate> + Waiting for the coins to arrive + </i18n.Translate> + </InfoBox> )} <Part big - title="Total withdrawn" + title={<i18n.Translate>Total withdrawn</i18n.Translate>} text={amountToString(transaction.amountEffective)} kind="positive" /> <Part big - title="Chosen amount" + title={<i18n.Translate>Chosen amount</i18n.Translate>} text={amountToString(transaction.amountRaw)} kind="neutral" /> <Part big - title="Exchange fee" + title={<i18n.Translate>Exchange fee</i18n.Translate>} text={amountToString(fee)} kind="negative" /> @@ -276,26 +299,26 @@ export function TransactionView({ <Fragment> <Part big - title="Total withdrawn" + title={<i18n.Translate>Total withdrawn</i18n.Translate>} text={amountToString(transaction.amountEffective)} kind="positive" /> <Part big - title="Chosen amount" + title={<i18n.Translate>Chosen amount</i18n.Translate>} text={amountToString(transaction.amountRaw)} kind="neutral" /> <Part big - title="Exchange fee" + title={<i18n.Translate>Exchange fee</i18n.Translate>} text={amountToString(fee)} kind="negative" /> </Fragment> )} <Part - title="Exchange" + title={<i18n.Translate>Exchange</i18n.Translate>} text={new URL(transaction.exchangeBaseUrl).hostname} kind="neutral" /> @@ -315,30 +338,41 @@ export function TransactionView({ return ( <TransactionTemplate> - <h2>Payment </h2> + <h2> + <i18n.Translate>Payment</i18n.Translate> + </h2> <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" /> <br /> <Part big - title="Total paid" + title={<i18n.Translate>Total paid</i18n.Translate>} text={amountToString(transaction.amountEffective)} kind="negative" /> <Part big - title="Purchase amount" + title={<i18n.Translate>Purchase amount</i18n.Translate>} text={amountToString(transaction.amountRaw)} kind="neutral" /> - <Part big title="Fee" text={amountToString(fee)} kind="negative" /> <Part - title="Merchant" + big + title={<i18n.Translate>Fee</i18n.Translate>} + text={amountToString(fee)} + kind="negative" + /> + <Part + title={<i18n.Translate>Merchant</i18n.Translate>} text={transaction.info.merchant.name} kind="neutral" /> - <Part title="Purchase" text={transaction.info.summary} kind="neutral" /> <Part - title="Receipt" + title={<i18n.Translate>Purchase</i18n.Translate>} + text={transaction.info.summary} + kind="neutral" + /> + <Part + title={<i18n.Translate>Receipt</i18n.Translate>} text={`#${transaction.info.orderId}`} kind="neutral" /> @@ -375,22 +409,29 @@ export function TransactionView({ ).amount; return ( <TransactionTemplate> - <h2>Deposit </h2> + <h2> + <i18n.Translate>Deposit</i18n.Translate> + </h2> <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" /> <br /> <Part big - title="Total send" + title={<i18n.Translate>Total send</i18n.Translate>} text={amountToString(transaction.amountEffective)} kind="neutral" /> <Part big - title="Deposit amount" + title={<i18n.Translate>Deposit amount</i18n.Translate>} text={amountToString(transaction.amountRaw)} kind="positive" /> - <Part big title="Fee" text={amountToString(fee)} kind="negative" /> + <Part + big + title={<i18n.Translate>Fee</i18n.Translate>} + text={amountToString(fee)} + kind="negative" + /> </TransactionTemplate> ); } @@ -402,22 +443,29 @@ export function TransactionView({ ).amount; return ( <TransactionTemplate> - <h2>Refresh</h2> + <h2> + <i18n.Translate>Refresh</i18n.Translate> + </h2> <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" /> <br /> <Part big - title="Total refresh" + title={<i18n.Translate>Total refresh</i18n.Translate>} text={amountToString(transaction.amountEffective)} kind="negative" /> <Part big - title="Refresh amount" + title={<i18n.Translate>Refresh amount</i18n.Translate>} text={amountToString(transaction.amountRaw)} kind="neutral" /> - <Part big title="Fee" text={amountToString(fee)} kind="negative" /> + <Part + big + title={<i18n.Translate>Fee</i18n.Translate>} + text={amountToString(fee)} + kind="negative" + /> </TransactionTemplate> ); } @@ -429,22 +477,29 @@ export function TransactionView({ ).amount; return ( <TransactionTemplate> - <h2>Tip</h2> + <h2> + <i18n.Translate>Tip</i18n.Translate> + </h2> <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" /> <br /> <Part big - title="Total tip" + title={<i18n.Translate>Total tip</i18n.Translate>} text={amountToString(transaction.amountEffective)} kind="positive" /> <Part big - title="Received amount" + title={<i18n.Translate>Received amount</i18n.Translate>} text={amountToString(transaction.amountRaw)} kind="neutral" /> - <Part big title="Fee" text={amountToString(fee)} kind="negative" /> + <Part + big + title={<i18n.Translate>Fee</i18n.Translate>} + text={amountToString(fee)} + kind="negative" + /> </TransactionTemplate> ); } @@ -456,30 +511,41 @@ export function TransactionView({ ).amount; return ( <TransactionTemplate> - <h2>Refund</h2> + <h2> + <i18n.Translate>Refund</i18n.Translate> + </h2> <Time timestamp={transaction.timestamp} format="dd MMMM yyyy, HH:mm" /> <br /> <Part big - title="Total refund" + title={<i18n.Translate>Total refund</i18n.Translate>} text={amountToString(transaction.amountEffective)} kind="positive" /> <Part big - title="Refund amount" + title={<i18n.Translate>Refund amount</i18n.Translate>} text={amountToString(transaction.amountRaw)} kind="neutral" /> - <Part big title="Fee" text={amountToString(fee)} kind="negative" /> <Part - title="Merchant" + big + title={<i18n.Translate>Fee</i18n.Translate>} + text={amountToString(fee)} + kind="negative" + /> + <Part + title={<i18n.Translate>Merchant</i18n.Translate>} text={transaction.info.merchant.name} kind="neutral" /> - <Part title="Purchase" text={transaction.info.summary} kind="neutral" /> <Part - title="Receipt" + title={<i18n.Translate>Purchase</i18n.Translate>} + text={transaction.info.summary} + kind="neutral" + /> + <Part + title={<i18n.Translate>Receipt</i18n.Translate>} text={`#${transaction.info.orderId}`} kind="neutral" /> diff --git a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx index b180fdd05..37ad97afd 100644 --- a/packages/taler-wallet-webextension/src/wallet/Welcome.tsx +++ b/packages/taler-wallet-webextension/src/wallet/Welcome.tsx @@ -17,10 +17,10 @@ /** * Welcome page, shown on first installs. * - * @author Florian Dold + * @author sebasjm */ -import { WalletDiagnostics } from "@gnu-taler/taler-util"; +import { i18n, WalletDiagnostics } from "@gnu-taler/taler-util"; import { Fragment, h, VNode } from "preact"; import { Checkbox } from "../components/Checkbox"; import { Diagnostics } from "../components/Diagnostics"; @@ -54,24 +54,44 @@ export function View({ }: ViewProps): VNode { return ( <Fragment> - <h1>Browser Extension Installed!</h1> + <h1> + <i18n.Translate>Browser Extension Installed!</i18n.Translate> + </h1> <div> - <p>Thank you for installing the wallet.</p> + <p> + <i18n.Translate>Thank you for installing the wallet.</i18n.Translate> + </p> <Diagnostics diagnostics={diagnostics} timedOut={timedOut} /> - <h2>Permissions</h2> + <h2> + <i18n.Translate>Permissions</i18n.Translate> + </h2> <Checkbox - label="Automatically open wallet based on page content" + label={ + <i18n.Translate> + Automatically open wallet based on page content + </i18n.Translate> + } name="perm" - description="(Enabling this option below will make using the wallet faster, but requires more permissions from your browser.)" + description={ + <i18n.Translate> + (Enabling this option below will make using the wallet faster, but + requires more permissions from your browser.) + </i18n.Translate> + } enabled={permissionsEnabled} onToggle={togglePermissions} /> - <h2>Next Steps</h2> + <h2> + <i18n.Translate>Next Steps</i18n.Translate> + </h2> <a href="https://demo.taler.net/" style={{ display: "block" }}> - Try the demo » + <i18n.Translate>Try the demo</i18n.Translate> » </a> <a href="https://demo.taler.net/" style={{ display: "block" }}> - Learn how to top up your wallet balance » + <i18n.Translate> + Learn how to top up your wallet balance + </i18n.Translate>{" "} + » </a> </div> </Fragment> |
