feat(web3): use i18next (#5256)

This commit is contained in:
Bartłomiej Głownia 2023-11-18 01:44:30 +01:00 committed by GitHub
parent b6052bc3e5
commit c3ef639daf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 413 additions and 186 deletions

View File

@ -10,8 +10,9 @@ import en_fills from './locales/en/fills.json';
import en_funding_payments from './locales/en/funding-payments.json';
import en_governance from './locales/en/governance.json';
import en_trading from './locales/en/trading.json';
import en_markets from './locales/en/markets.json';
import en_web3 from './locales/en/web3.json';
export const locales = {
en: {
accounts: en_accounts,
@ -26,5 +27,6 @@ export const locales = {
governance: en_governance,
trading: en_trading,
markets: en_markets,
web3: en_web3,
},
};

View File

@ -0,0 +1,110 @@
{
"{{title}} complete": "{{title}} complete",
"{{title}} failed": "{{title}} failed",
"{{title}} pending": "{{title}} pending",
"Action required": "Action required",
"Approved": "Approved",
"Await Ethereum transaction": "Await Ethereum transaction",
"Awaiting confirmation": "Awaiting confirmation",
"Awaiting confirmations {{confirmations}}/{[requiredConfirmations}}": "Awaiting confirmations {{confirmations}}/{[requiredConfirmations}}",
"Awaiting Ethereum transaction {{confirmations}}/{{requiredConfirmations}} confirmations...": "Awaiting Ethereum transaction {{confirmations}}/{{requiredConfirmations}} confirmations...",
"Batch market instruction": "Batch market instruction",
"Cancel all orders": "Cancel all orders",
"Cancel all orders for <strong>{{marketName}}</strong>": "Cancel all orders for <strong>{{marketName}}</strong>",
"Cancel all stop orders": "Cancel all stop orders",
"Cancel all stop orders for <strong>{{marketName}}</strong>": "Cancel all stop orders for <strong>{{marketName}}</strong>",
"Cancel order": "Cancel order",
"Cancel order - {{status}}": "Cancel order - {{status}}",
"Cancel stop order": "Cancel stop order",
"Cannot be completed until {{time}}": "Cannot be completed until {{time}}",
"Change network": "Change network",
"Close": "Close",
"Close position for <strong>{{marketName}}</strong>": "Close position for <strong>{{marketName}}</strong>",
"Coinbase": "Coinbase",
"Complete withdrawal": "Complete withdrawal",
"Confirm transaction": "Confirm transaction",
"Confirm transaction in wallet": "Confirm transaction in wallet",
"Confirmed": "Confirmed",
"Confirmed in wallet": "Confirmed in wallet",
"Connect to your Ethereum wallet": "Connect to your Ethereum wallet",
"Connect wallet": "Connect wallet",
"Connect wallet to withdraw": "Connect wallet to withdraw",
"Copy": "Copy",
"Delayed": "Delayed",
"Deposit": "Deposit",
"Edit order": "Edit order",
"Edit order - {{status}}": "Edit order - {{status}}",
"Error": "Error",
"Error occurred": "Error occurred",
"Error: {{errorMessage}}": "Error: {{errorMessage}}",
"Ethereum transaction complete": "Ethereum transaction complete",
"Filled": "Filled",
"Funds unlocked": "Funds unlocked",
"Go to your Ethereum wallet and connect to the network {{networkName}}": "Go to your Ethereum wallet and connect to the network {{networkName}}",
"If the network is reset or has an outage, records of your withdrawal may be lost. It is recommended that you save these details in a safe place so you can still complete your withdrawal.": "If the network is reset or has an outage, records of your withdrawal may be lost. It is recommended that you save these details in a safe place so you can still complete your withdrawal.",
"Invalid asset source: {{source}}": "Invalid asset source: {{source}}",
"Loading": "Loading",
"MetaMask": "MetaMask",
"MetaMask, Brave or other injected web wallet": "MetaMask, Brave or other injected web wallet",
"No data": "No data",
"Order cancelled'": "Order cancelled'",
"Order expired'": "Order expired'",
"Order filled": "Order filled",
"Order parked": "Order parked",
"Order partially filled": "Order partially filled",
"Order rejected": "Order rejected",
"Order stopped": "Order stopped",
"Order submitted": "Order submitted",
"Pending approval": "Pending approval",
"Please go to your Vega wallet application and approve or reject the transaction.": "Please go to your Vega wallet application and approve or reject the transaction.",
"Please go to your wallet application and approve or reject the transaction.": "Please go to your wallet application and approve or reject the transaction.",
"Please wait for your transaction to be confirmed": "Please wait for your transaction to be confirmed",
"Please wait for your transaction to be confirmed.": "Please wait for your transaction to be confirmed.",
"Processing": "Processing",
"Processing deposit": "Processing deposit",
"Return": "Return",
"Save withdrawal details": "Save withdrawal details",
"save your withdrawal details": "save your withdrawal details",
"Something went wrong": "Something went wrong",
"Submission failed": "Submission failed",
"Submit order": "Submit order",
"Submit order - {{status}}": "Submit order - {{status}}",
"Submit stop order": "Submit stop order",
"The amount you're withdrawing has triggered a time delay": "The amount you're withdrawing has triggered a time delay",
"The connection to your Vega Wallet has been lost.": "The connection to your Vega Wallet has been lost.",
"The withdrawal has been approved.": "The withdrawal has been approved.",
"To {{address}}": "To {{address}}",
"To complete this withdrawal, connect the Ethereum wallet {{receiverAddress}}": "To complete this withdrawal, connect the Ethereum wallet {{receiverAddress}}",
"Transaction confirmed": "Transaction confirmed",
"Transfer": "Transfer",
"Transfer complete": "Transfer complete",
"Unknown": "Unknown",
"Vega confirmation": "Vega confirmation",
"Vega is confirming your transaction...": "Vega is confirming your transaction...",
"Verifying withdrawal approval": "Verifying withdrawal approval",
"Verifying...": "Verifying...",
"View in block explorer": "View in block explorer",
"View on Etherscan": "View on Etherscan",
"View transaction on Etherscan": "View transaction on Etherscan",
"Waiting for deposit confirmation.": "Waiting for deposit confirmation.",
"Wallet disconnected": "Wallet disconnected",
"WalletConnect": "WalletConnect",
"WalletConnect Legacy": "WalletConnect Legacy",
"WalletConnect v1": "WalletConnect v1",
"WalletConnect v2": "WalletConnect v2",
"Withdraw": "Withdraw",
"Withdraw {{amount}} {{symbol}}": "Withdraw {{amount}} {{symbol}}",
"Withdraw dependencies not met.": "Withdraw dependencies not met.",
"Withdraw failure": "Withdraw failure",
"Your {{timeInForce}} order was not filled and it has been stopped": "Your {{timeInForce}} order was not filled and it has been stopped",
"Your Ethereum wallet is connected to the wrong network.": "Your Ethereum wallet is connected to the wrong network.",
"Your funds have been unlocked for withdrawal.": "Your funds have been unlocked for withdrawal.",
"Your order has been rejected": "Your order has been rejected",
"Your order has been rejected because: {{rejectionReason}}": "Your order has been rejected because: {{rejectionReason}}",
"Your order has been stopped": "Your order has been stopped",
"Your order has been stopped because: {{rejectionReason}}": "Your order has been stopped because: {{rejectionReason}}",
"Your order was rejected.": "Your order was rejected.",
"Your transaction has been completed.": "Your transaction has been completed.",
"Your transaction has been confirmed": "Your transaction has been confirmed",
"Your transaction has been confirmed.": "Your transaction has been confirmed."
}

View File

@ -1,11 +1,12 @@
import { t } from '@vegaprotocol/i18n';
import { Link } from '@vegaprotocol/ui-toolkit';
import { EtherscanLink, useEnvironment } from '@vegaprotocol/environment';
import { EthTxStatus } from '../use-ethereum-transaction';
import { useT } from '../use-t';
const ACTIVE_CLASSES = 'text-black dark:text-white';
export const ConfirmRow = ({ status }: { status: EthTxStatus }) => {
const t = useT();
if (status === EthTxStatus.Requested) {
return (
<p className="text-black dark:text-white">
@ -32,6 +33,7 @@ export const TxRow = ({
requiredConfirmations,
highlightComplete = true,
}: TxRowProps) => {
const t = useT();
const { ETHERSCAN_URL } = useEnvironment();
if (status === EthTxStatus.Pending) {
@ -39,7 +41,8 @@ export const TxRow = ({
<p className={`flex justify-between ${ACTIVE_CLASSES}`}>
<span>
{t(
`Awaiting Ethereum transaction ${confirmations}/${requiredConfirmations} confirmations...`
`Awaiting Ethereum transaction {{confirmations}}/{{requiredConfirmations}} confirmations...`,
{ confirmations, requiredConfirmations }
)}
</span>
<Link
@ -82,6 +85,7 @@ interface ConfirmationEventRowProps {
}
export const ConfirmationEventRow = ({ status }: ConfirmationEventRowProps) => {
const t = useT();
if (status !== EthTxStatus.Complete && status !== EthTxStatus.Confirmed) {
return <p>{t('Vega confirmation')}</p>;
}

View File

@ -1,9 +1,9 @@
import { t } from '@vegaprotocol/i18n';
import { Button, Dialog, Icon, Intent, Loader } from '@vegaprotocol/ui-toolkit';
import { isEthereumError } from '../ethereum-error';
import type { EthTxState, TxError } from '../use-ethereum-transaction';
import { EthTxStatus } from '../use-ethereum-transaction';
import { ConfirmRow, TxRow, ConfirmationEventRow } from './dialog-rows';
import { useT } from '../use-t';
export interface EthereumTransactionDialogProps {
title: string;
@ -20,13 +20,14 @@ export const EthereumTransactionDialog = ({
onChange,
requiredConfirmations = 1,
}: EthereumTransactionDialogProps) => {
const t = useT();
const { status, error, confirmations, txHash } = transaction;
return (
<Dialog
open={transaction.dialogOpen}
onChange={onChange}
size="small"
{...getWrapperProps(title, status)}
{...getWrapperProps(title, status, t)}
>
<TransactionContent
status={status}
@ -44,11 +45,13 @@ export const getTransactionContent = ({
transaction,
requiredConfirmations,
reset,
t,
}: {
title: string;
transaction: EthTxState;
requiredConfirmations?: number;
reset: () => void;
t: ReturnType<typeof useT>;
}) => {
const { status, error, confirmations, txHash } = transaction;
const content = ({ returnLabel }: { returnLabel?: string }) => (
@ -75,7 +78,7 @@ export const getTransactionContent = ({
</>
);
return {
...getWrapperProps(title, status),
...getWrapperProps(title, status, t),
status,
Content: content,
};
@ -94,6 +97,7 @@ export const TransactionContent = ({
confirmations: number;
requiredConfirmations?: number;
}) => {
const t = useT();
if (status === EthTxStatus.Error) {
let errorMessage = '';
@ -105,7 +109,10 @@ export const TransactionContent = ({
return (
<p className="break-all">
{t('Error')}: {errorMessage}
{t('Error: {{errorMessage}}', {
nsSeparator: '*',
replace: { errorMessage },
})}
</p>
);
}
@ -128,7 +135,8 @@ export const TransactionContent = ({
type WrapperProps = { title: string; icon?: JSX.Element; intent?: Intent };
export const getWrapperProps = (
title: string,
status: EthTxStatus
status: EthTxStatus,
t: ReturnType<typeof useT>
): WrapperProps => {
const propsMap = {
[EthTxStatus.Default]: {
@ -137,17 +145,17 @@ export const getWrapperProps = (
intent: undefined,
},
[EthTxStatus.Error]: {
title: t(`${title} failed`),
title: t(`{{title}} failed`, { title }),
icon: <Icon name="warning-sign" />,
intent: Intent.Danger,
},
[EthTxStatus.Requested]: {
title: t('Confirm transaction'),
title: t('Confirm transaction', { title }),
icon: <Icon name="hand-up" />,
intent: Intent.Warning,
},
[EthTxStatus.Pending]: {
title: t(`${title} pending`),
title: t(`{{title}} pending`, { title }),
icon: (
<span className="mt-1">
<Loader size="small" />
@ -156,12 +164,12 @@ export const getWrapperProps = (
intent: Intent.None,
},
[EthTxStatus.Complete]: {
title: t(`${title} pending`),
title: t(`{{title}} pending`, { title }),
icon: <Loader size="small" />,
intent: Intent.None,
},
[EthTxStatus.Confirmed]: {
title: t(`${title} complete`),
title: t(`{{title}} complete`, { title }),
icon: <Icon name="tick" />,
intent: Intent.Success,
},

View File

@ -3,7 +3,6 @@ import { useEffect } from 'react';
import { useAssetsDataProvider } from '@vegaprotocol/assets';
import { EtherscanLink } from '@vegaprotocol/environment';
import { formatNumber, toBigNum } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type { Toast, ToastContent } from '@vegaprotocol/ui-toolkit';
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
import { Panel } from '@vegaprotocol/ui-toolkit';
@ -17,6 +16,7 @@ import { EthTxStatus } from './use-ethereum-transaction';
import { isEthereumError } from './ethereum-error';
import { TransactionContent } from './ethereum-transaction-dialog';
import { useEthTransactionStore } from './use-ethereum-transaction-store';
import { useT } from './use-t';
const intentMap: { [s in EthTxStatus]: Intent } = {
Default: Intent.Primary,
@ -34,6 +34,7 @@ const isDepositTransaction = (tx: EthStoredTxState) =>
tx.methodName === 'deposit_asset';
const EthTransactionDetails = ({ tx }: { tx: EthStoredTxState }) => {
const t = useT();
const { data: assets } = useAssetsDataProvider();
if (!assets) return null;
@ -64,8 +65,10 @@ const EthTransactionDetails = ({ tx }: { tx: EthStoredTxState }) => {
{tx.status === EthTxStatus.Pending && (
<>
<p className="mt-[2px]">
{t('Awaiting confirmations')}{' '}
{`(${tx.confirmations}/${tx.requiredConfirmations})`}
{t(
'Awaiting confirmations {{confirmations}}/{[requiredConfirmations}}',
tx
)}
</p>
<ProgressBar
value={(tx.confirmations / tx.requiredConfirmations) * 100}
@ -84,6 +87,7 @@ type EthTxToastContentProps = {
};
const EthTxRequestedToastContent = ({ tx }: EthTxToastContentProps) => {
const t = useT();
return (
<>
<ToastHeading>{t('Action required')}</ToastHeading>
@ -98,6 +102,7 @@ const EthTxRequestedToastContent = ({ tx }: EthTxToastContentProps) => {
};
const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
const t = useT();
return (
<>
<ToastHeading>{t('Awaiting confirmation')}</ToastHeading>
@ -111,6 +116,7 @@ const EthTxPendingToastContent = ({ tx }: EthTxToastContentProps) => {
};
const EthTxErrorToastContent = ({ tx }: EthTxToastContentProps) => {
const t = useT();
let errorMessage = '';
if (isEthereumError(tx.error)) {
@ -128,6 +134,7 @@ const EthTxErrorToastContent = ({ tx }: EthTxToastContentProps) => {
};
const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
const t = useT();
return (
<>
<ToastHeading>{t('Transaction confirmed')}</ToastHeading>
@ -141,11 +148,12 @@ const EthTxConfirmedToastContent = ({ tx }: EthTxToastContentProps) => {
};
const EthTxCompletedToastContent = ({ tx }: EthTxToastContentProps) => {
const t = useT();
const isDeposit = isDepositTransaction(tx);
return (
<>
<ToastHeading>
{t('Processing')} {isDeposit && t('deposit')}
{isDeposit ? t('Processing deposit') : t('Processing')}
</ToastHeading>
<p>
{t('Your transaction has been completed.')}{' '}

View File

@ -8,6 +8,7 @@ import {
EthereumTransactionDialog,
getTransactionContent,
} from './ethereum-transaction-dialog';
import { useT } from './use-t';
export enum EthTxStatus {
Default = 'Default',
@ -51,6 +52,7 @@ export const useEthereumTransaction = <
requiredConfirmations = 1,
requiresConfirmation = false
) => {
const t = useT();
const [transaction, _setTransaction] = useState<EthTxState>(initialState);
const setTransaction = useCallback((update: Partial<EthTxState>) => {
@ -173,8 +175,9 @@ export const useEthereumTransaction = <
transaction,
requiredConfirmations,
reset,
t,
}),
[methodName, requiredConfirmations, reset, transaction]
[methodName, requiredConfirmations, reset, transaction, t]
);
return { perform, transaction, reset, setConfirmed, Dialog, TxContent };

View File

@ -1,5 +1,4 @@
import { formatNumber, toBigNum } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type { Toast } from '@vegaprotocol/ui-toolkit';
import { ToastHeading } from '@vegaprotocol/ui-toolkit';
import { Panel } from '@vegaprotocol/ui-toolkit';
@ -15,6 +14,7 @@ import {
} from './use-ethereum-withdraw-approvals-store';
import { ApprovalStatus } from './use-ethereum-withdraw-approvals-store';
import { VerificationStatus } from './withdrawal-approval-status';
import { useT } from './use-t';
const intentMap: { [s in ApprovalStatus]: Intent } = {
Pending: Intent.Warning,
@ -29,6 +29,7 @@ const EthWithdrawalApprovalToastContent = ({
}: {
tx: EthWithdrawalApprovalState;
}) => {
const t = useT();
const isConnectionFailure =
tx.failureReason &&
[
@ -54,14 +55,17 @@ const EthWithdrawalApprovalToastContent = ({
title = t('Approved');
}
const num = formatNumber(
const amount = formatNumber(
toBigNum(tx.withdrawal.amount, tx.withdrawal.asset.decimals),
tx.withdrawal.asset.decimals
);
const details = isConnectionFailure ? null : (
<Panel>
<strong>
{t('Withdraw')} {num} {tx.withdrawal.asset.symbol}
{t('Withdraw {{amount}} {{symbol}}', {
amount,
symbol: tx.withdrawal.asset.symbol,
})}
</strong>
</Panel>
);

View File

@ -4,7 +4,6 @@ import { useEffect, useRef } from 'react';
import { addDecimal } from '@vegaprotocol/utils';
import { useGetWithdrawThreshold } from './use-get-withdraw-threshold';
import { useGetWithdrawDelay } from './use-get-withdraw-delay';
import { t } from '@vegaprotocol/i18n';
import { localLoggerFactory } from '@vegaprotocol/logger';
import { CollateralBridge } from '@vegaprotocol/smart-contracts';
@ -21,8 +20,10 @@ import {
useEthWithdrawApprovalsStore,
WithdrawalFailure,
} from './use-ethereum-withdraw-approvals-store';
import { useT } from './use-t';
export const useEthWithdrawApprovalsManager = () => {
const t = useT();
const getThreshold = useGetWithdrawThreshold();
const getDelay = useGetWithdrawDelay();
const { query } = useApolloClient();
@ -49,9 +50,12 @@ export const useEthWithdrawApprovalsManager = () => {
if (withdrawal.asset.source.__typename !== 'ERC20') {
update(transaction.id, {
status: ApprovalStatus.Error,
message: t(
`Invalid asset source: ${withdrawal.asset.source.__typename}`
),
message: t(`Invalid asset source: {{source}}`, {
nsSeparator: '*',
replace: {
source: withdrawal.asset.source.__typename,
},
}),
failureReason: WithdrawalFailure.InvalidAsset,
});
return;
@ -162,5 +166,6 @@ export const useEthWithdrawApprovalsManager = () => {
transaction,
update,
chainId,
t,
]);
};

View File

@ -0,0 +1,3 @@
import { useTranslation } from 'react-i18next';
export const ns = 'web3';
export const useT = () => useTranslation(ns).t;

View File

@ -418,32 +418,33 @@ describe('getVegaTransactionContentIntent', () => {
});
});
describe('getOrderToastTitle', () => {
const t = (v: string) => v;
it('should return the correct title', () => {
expect(getOrderToastTitle(Types.OrderStatus.STATUS_ACTIVE)).toBe(
expect(getOrderToastTitle(Types.OrderStatus.STATUS_ACTIVE, t)).toBe(
'Order submitted'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_FILLED)).toBe(
expect(getOrderToastTitle(Types.OrderStatus.STATUS_FILLED, t)).toBe(
'Order filled'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARTIALLY_FILLED)).toBe(
'Order partially filled'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARKED)).toBe(
expect(
getOrderToastTitle(Types.OrderStatus.STATUS_PARTIALLY_FILLED, t)
).toBe('Order partially filled');
expect(getOrderToastTitle(Types.OrderStatus.STATUS_PARKED, t)).toBe(
'Order parked'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_STOPPED)).toBe(
expect(getOrderToastTitle(Types.OrderStatus.STATUS_STOPPED, t)).toBe(
'Order stopped'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_CANCELLED)).toBe(
expect(getOrderToastTitle(Types.OrderStatus.STATUS_CANCELLED, t)).toBe(
'Order cancelled'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_EXPIRED)).toBe(
expect(getOrderToastTitle(Types.OrderStatus.STATUS_EXPIRED, t)).toBe(
'Order expired'
);
expect(getOrderToastTitle(Types.OrderStatus.STATUS_REJECTED)).toBe(
expect(getOrderToastTitle(Types.OrderStatus.STATUS_REJECTED, t)).toBe(
'Order rejected'
);
expect(getOrderToastTitle(undefined)).toBe(undefined);
expect(getOrderToastTitle(undefined, t)).toBe(undefined);
});
});
@ -480,38 +481,42 @@ describe('getOrderToastIntent', () => {
describe('getRejectionReason', () => {
it('should return the correct rejection reason for insufficient asset balance', () => {
expect(
getRejectionReason({
rejectionReason:
Types.OrderRejectionReason.ORDER_ERROR_INSUFFICIENT_ASSET_BALANCE,
status: Types.OrderStatus.STATUS_REJECTED,
id: '',
createdAt: undefined,
size: '',
price: '',
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
side: Types.Side.SIDE_BUY,
marketId: '',
remaining: '',
})
getRejectionReason(
{
rejectionReason:
Types.OrderRejectionReason.ORDER_ERROR_INSUFFICIENT_ASSET_BALANCE,
status: Types.OrderStatus.STATUS_REJECTED,
id: '',
createdAt: undefined,
size: '',
price: '',
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
side: Types.Side.SIDE_BUY,
marketId: '',
remaining: '',
},
(v) => v
)
).toBe('Insufficient asset balance');
});
it('should return the correct rejection reason when order is stopped', () => {
expect(
getRejectionReason({
rejectionReason: null,
status: Types.OrderStatus.STATUS_STOPPED,
id: '',
createdAt: undefined,
size: '',
price: '',
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
side: Types.Side.SIDE_BUY,
marketId: '',
remaining: '',
})
).toBe(
'Your Fill or Kill (FOK) order was not filled and it has been stopped'
);
getRejectionReason(
{
rejectionReason: null,
status: Types.OrderStatus.STATUS_STOPPED,
id: '',
createdAt: undefined,
size: '',
price: '',
timeInForce: Types.OrderTimeInForce.TIME_IN_FORCE_FOK,
side: Types.Side.SIDE_BUY,
marketId: '',
remaining: '',
},
(v) => v
)
).toBe('Your {{timeInForce}} order was not filled and it has been stopped');
});
});

View File

@ -43,7 +43,6 @@ import {
formatTrigger,
MAXGOINT64,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useAssetsMapProvider } from '@vegaprotocol/assets';
import { useEthWithdrawApprovalsStore } from './use-ethereum-withdraw-approvals-store';
import { DApp, EXPLORER_TX, useLinks } from '@vegaprotocol/environment';
@ -58,26 +57,31 @@ import { OrderStatusMapping } from '@vegaprotocol/types';
import { Size } from '@vegaprotocol/datagrid';
import { useWithdrawalApprovalDialog } from './withdrawal-approval-dialog';
import * as Schema from '@vegaprotocol/types';
import { Trans } from 'react-i18next';
import { useT } from './use-t';
export const getRejectionReason = (
order: OrderTxUpdateFieldsFragment
order: OrderTxUpdateFieldsFragment,
t: ReturnType<typeof useT>
): string | null => {
switch (order.status) {
case Schema.OrderStatus.STATUS_STOPPED:
return t(
`Your ${
Schema.OrderTimeInForceMapping[order.timeInForce]
} order was not filled and it has been stopped`
`Your {{timeInForce}} order was not filled and it has been stopped`,
{
timeInForce: Schema.OrderTimeInForceMapping[order.timeInForce],
}
);
default:
return order.rejectionReason
? t(Schema.OrderRejectionReasonMapping[order.rejectionReason])
? Schema.OrderRejectionReasonMapping[order.rejectionReason]
: '';
}
};
export const getOrderToastTitle = (
status?: Schema.OrderStatus
status: Schema.OrderStatus | undefined,
t: ReturnType<typeof useT>
): string | undefined => {
if (!status) {
return;
@ -212,6 +216,7 @@ const SubmitOrderDetails = ({
data: OrderSubmission;
order?: OrderTxUpdateFieldsFragment;
}) => {
const t = useT();
const { data: markets } = useMarketsMapProvider();
const market = markets?.[order?.marketId || ''];
if (!market) return null;
@ -224,9 +229,9 @@ const SubmitOrderDetails = ({
<Panel>
<h4>
{order
? t(
`Submit order - ${OrderStatusMapping[order.status].toLowerCase()}`
)
? t(`Submit order - {{status}}`, {
status: OrderStatusMapping[order.status].toLowerCase(),
})
: t('Submit order')}
</h4>
<p>{market?.tradableInstrument.instrument.code}</p>
@ -294,6 +299,7 @@ const SubmitStopOrderSetup = ({
};
const SubmitStopOrderDetails = ({ data }: { data: StopOrdersSubmission }) => {
const t = useT();
const { data: markets } = useMarketsMapProvider();
const marketId =
data.fallsBelow?.orderSubmission.marketId ||
@ -335,6 +341,7 @@ const EditOrderDetails = ({
data: OrderAmendment;
order?: OrderTxUpdateFieldsFragment;
}) => {
const t = useT();
const { data: orderById } = useOrderByIdQuery({
variables: { orderId: data.orderId },
fetchPolicy: 'no-cache',
@ -376,7 +383,9 @@ const EditOrderDetails = ({
<Panel title={data.orderId}>
<h4>
{order
? t(`Edit order - ${OrderStatusMapping[order.status].toLowerCase()}`)
? t(`Edit order - {{status}}`, {
status: OrderStatusMapping[order.status].toLowerCase(),
})
: t('Edit order')}
</h4>
<p>{market?.tradableInstrument.instrument.code}</p>
@ -395,6 +404,7 @@ const CancelOrderDetails = ({
orderId: string;
order?: OrderTxUpdateFieldsFragment;
}) => {
const t = useT();
const { data: orderById } = useOrderByIdQuery({
variables: { orderId },
});
@ -421,9 +431,9 @@ const CancelOrderDetails = ({
<Panel title={orderId}>
<h4>
{order
? t(
`Cancel order - ${OrderStatusMapping[order.status].toLowerCase()}`
)
? t(`Cancel order - {{status}}`, {
status: OrderStatusMapping[order.status].toLowerCase(),
})
: t('Cancel order')}
</h4>
<p>{market?.tradableInstrument.instrument.code}</p>
@ -435,6 +445,7 @@ const CancelOrderDetails = ({
};
const CancelStopOrderDetails = ({ stopOrderId }: { stopOrderId: string }) => {
const t = useT();
const { data: orderById } = useStopOrderByIdQuery({
variables: { stopOrderId },
});
@ -473,6 +484,7 @@ const CancelStopOrderDetails = ({ stopOrderId }: { stopOrderId: string }) => {
};
export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
const t = useT();
const { data: assets } = useAssetsMapProvider();
const { data: markets } = useMarketsMapProvider();
@ -480,14 +492,17 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
const transactionDetails = tx.body;
const asset = assets?.[transactionDetails.withdrawSubmission.asset];
if (asset) {
const num = formatNumber(
const amount = formatNumber(
toBigNum(transactionDetails.withdrawSubmission.amount, asset.decimals),
asset.decimals
);
return (
<Panel>
<strong>
{t('Withdraw')} {num} {asset.symbol}
{t('Withdraw {{amount}} {{symbol}}', {
amount,
symbol: asset.symbol,
})}
</strong>
</Panel>
);
@ -526,7 +541,10 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
if (marketName) {
return (
<Panel>
{t('Cancel all orders for')} <strong>{marketName}</strong>
<Trans
defaults="Cancel all orders for <strong>{{marketName}}</strong>"
values={{ marketName }}
/>
</Panel>
);
}
@ -556,7 +574,10 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
if (marketName) {
return (
<Panel>
{t('Cancel all stop orders for')} <strong>{marketName}</strong>
<Trans
defaults="Cancel all stop orders for <strong>{{marketName}}</strong>"
values={{ marketName }}
/>
</Panel>
);
}
@ -584,8 +605,12 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
if (market) {
return (
<Panel>
{t('Close position for')}{' '}
<strong>{market.tradableInstrument.instrument.code}</strong>
<Trans
defaults="Close position for <strong>{{instrumentCode}}</strong>"
values={{
instrumentCode: market.tradableInstrument.instrument.code,
}}
/>
{tx.order?.remaining && (
<p>
{t('Filled')}{' '}
@ -621,9 +646,7 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
return (
<Panel>
<h4>{t('Transfer')}</h4>
<p>
{t('To')} {truncateByChars(to)}
</p>
<p>{t('To {{address}}', { address: truncateByChars(to) })}</p>
<p>
{value} {transferAsset.symbol}
</p>
@ -637,19 +660,25 @@ export const VegaTransactionDetails = ({ tx }: { tx: VegaStoredTxState }) => {
type VegaTxToastContentProps = { tx: VegaStoredTxState };
const VegaTxRequestedToastContent = ({ tx }: VegaTxToastContentProps) => (
<>
<ToastHeading>{t('Action required')}</ToastHeading>
<p>
{t(
'Please go to your Vega wallet application and approve or reject the transaction.'
)}
</p>
<VegaTransactionDetails tx={tx} />
</>
);
const VegaTxRequestedToastContent = ({ tx }: VegaTxToastContentProps) => {
const t = useT();
return (
<>
<ToastHeading>{t('Action required')}</ToastHeading>
<p>
{t(
'Please go to your Vega wallet application and approve or reject the transaction.'
)}
</p>
<VegaTransactionDetails tx={tx} />
</>
);
};
const VegaTxPendingToastContentProps = ({ tx }: VegaTxToastContentProps) => {
const VegaTxPendingToastContentProps = (
{ tx }: VegaTxToastContentProps,
t: ReturnType<typeof useT>
) => {
const explorerLink = useLinks(DApp.Explorer);
return (
<>
@ -671,6 +700,7 @@ const VegaTxPendingToastContentProps = ({ tx }: VegaTxToastContentProps) => {
};
const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
const t = useT();
const { createEthWithdrawalApproval } = useEthWithdrawApprovalsStore(
(state) => ({
createEthWithdrawalApproval: state.create,
@ -696,24 +726,6 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
</p>
);
const dialogTrigger = (
// It has to stay as <a> due to the word breaking issue
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<a
href="#"
className="inline underline underline-offset-4 cursor-pointer text-inherit break-words"
data-testid="toast-withdrawal-details"
onClick={(e) => {
e.preventDefault();
if (tx.withdrawal?.id) {
useWithdrawalApprovalDialog.getState().open(tx.withdrawal?.id);
}
}}
>
{t('save your withdrawal details')}
</a>
);
return (
<>
<ToastHeading>{t('Funds unlocked')}</ToastHeading>
@ -729,7 +741,28 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
)}
{/* TODO: Delay message - This withdrawal is subject to a delay. Come back in 5 days to complete the withdrawal. */}
<p className="break-words">
{t('You can')} {dialogTrigger} {t('for extra security.')}
<Trans
defaults="You can <0>save your withdrawal details</0> for extra security."
components={[
// It has to stay as <a> due to the word breaking issue
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<a
href="#"
className="inline underline underline-offset-4 cursor-pointer text-inherit break-words"
data-testid="toast-withdrawal-details"
onClick={(e) => {
e.preventDefault();
if (tx.withdrawal?.id) {
useWithdrawalApprovalDialog
.getState()
.open(tx.withdrawal?.id);
}
}}
>
save your withdrawal details
</a>,
]}
/>
</p>
<VegaTransactionDetails tx={tx} />
{completeWithdrawalButton}
@ -738,26 +771,31 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
}
if (tx.order && tx.order.rejectionReason) {
const rejectionReason = getRejectionReason(tx.order);
const rejectionReason = getRejectionReason(tx.order, t);
return (
<>
<ToastHeading>{getOrderToastTitle(tx.order.status)}</ToastHeading>
<ToastHeading>{getOrderToastTitle(tx.order.status, t)}</ToastHeading>
{rejectionReason ? (
<p>
{t('Your order has been %s because: %s', [
tx.order.status === Schema.OrderStatus.STATUS_STOPPED
? 'stopped'
: 'rejected',
rejectionReason,
])}
{tx.order.status === Schema.OrderStatus.STATUS_STOPPED
? t('Your order has been stopped because: {{rejectionReason}}', {
nsSeparator: '*',
replace: {
rejectionReason,
},
})
: t('Your order has been rejected because: {{rejectionReason}}', {
nsSeparator: '*',
replace: {
rejectionReason,
},
})}
</p>
) : (
<p>
{t('Your order has been %s.', [
tx.order.status === Schema.OrderStatus.STATUS_STOPPED
? 'stopped'
: 'rejected',
])}
{tx.order.status === Schema.OrderStatus.STATUS_STOPPED
? t('Your order has been stopped')
: t('Your order has been rejected')}
</p>
)}
{tx.txHash && (
@ -778,7 +816,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
if (isOrderSubmissionTransaction(tx.body) && tx.order?.rejectionReason) {
return (
<div>
<h3 className="font-bold">{getOrderToastTitle(tx.order.status)}</h3>
<h3 className="font-bold">{getOrderToastTitle(tx.order.status, t)}</h3>
<p>{t('Your order was rejected.')}</p>
{tx.txHash && (
<p className="break-all">
@ -799,7 +837,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
return (
<div>
<h3 className="font-bold">{t('Transfer complete')}</h3>
<p>{t('Your transaction has been confirmed ')}</p>
<p>{t('Your transaction has been confirmed')}</p>
{tx.txHash && (
<p className="break-all">
<ExternalLink
@ -819,10 +857,10 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
<>
<ToastHeading>
{tx.order?.status
? getOrderToastTitle(tx.order.status)
? getOrderToastTitle(tx.order.status, t)
: t('Confirmed')}
</ToastHeading>
<p>{t('Your transaction has been confirmed ')}</p>
<p>{t('Your transaction has been confirmed')}</p>
{tx.txHash && (
<p className="break-all">
<ExternalLink
@ -839,6 +877,7 @@ const VegaTxCompleteToastsContent = ({ tx }: VegaTxToastContentProps) => {
};
const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
const t = useT();
let label = t('Error occurred');
let errorMessage =
tx.error instanceof WalletError
@ -847,7 +886,7 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
const reconnectVegaWallet = useReconnectVegaWallet();
const orderRejection = tx.order && getRejectionReason(tx.order);
const orderRejection = tx.order && getRejectionReason(tx.order, t);
const walletNoConnectionCodes = [
ClientErrors.NO_SERVICE.code,
ClientErrors.NO_CLIENT.code,
@ -857,10 +896,13 @@ const VegaTxErrorToastContent = ({ tx }: VegaTxToastContentProps) => {
walletNoConnectionCodes.includes(tx.error.code);
if (orderRejection) {
label = getOrderToastTitle(tx.order?.status) || t('Order rejected');
errorMessage = t('Your order has been rejected because: %s', [
orderRejection || tx.order?.rejectionReason || ' ',
]);
label = getOrderToastTitle(tx.order?.status, t) || t('Order rejected');
errorMessage = t(
'Your order has been rejected because: {{rejectionReason}}',
{
rejectionReason: orderRejection || tx.order?.rejectionReason || ' ',
}
);
}
if (walletError) {
label = t('Wallet disconnected');

View File

@ -1,4 +1,3 @@
import { t } from '@vegaprotocol/i18n';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import {
Dialog,
@ -16,6 +15,7 @@ import type { Web3ReactHooks } from '@web3-react/core';
import { useWeb3ConnectStore } from './web3-connect-store';
import { theme } from '@vegaprotocol/tailwindcss-config';
import classNames from 'classnames';
import { useT } from './use-t';
interface Web3ConnectDialogProps {
dialogOpen: boolean;
@ -29,33 +29,36 @@ export const Web3ConnectDialog = ({
setDialogOpen,
connectors,
desiredChainId,
}: Web3ConnectDialogProps) => (
<Dialog
open={dialogOpen}
onChange={setDialogOpen}
onInteractOutside={(e) => {
// do not close dialog when clicked outside (wallet connect modal)
e.preventDefault();
}}
intent={Intent.None}
title={t('Connect to your Ethereum wallet')}
size="small"
>
<ul className="grid grid-cols-2 gap-2" data-testid="web3-connector-list">
{connectors.map((connector, i) => (
<li key={i} className="mb-2 last:mb-0">
<ConnectButton
connector={connector}
desiredChainId={desiredChainId}
onClick={() => {
setDialogOpen(false);
}}
/>
</li>
))}
</ul>
</Dialog>
);
}: Web3ConnectDialogProps) => {
const t = useT();
return (
<Dialog
open={dialogOpen}
onChange={setDialogOpen}
onInteractOutside={(e) => {
// do not close dialog when clicked outside (wallet connect modal)
e.preventDefault();
}}
intent={Intent.None}
title={t('Connect to your Ethereum wallet')}
size="small"
>
<ul className="grid grid-cols-2 gap-2" data-testid="web3-connector-list">
{connectors.map((connector, i) => (
<li key={i} className="mb-2 last:mb-0">
<ConnectButton
connector={connector}
desiredChainId={desiredChainId}
onClick={() => {
setDialogOpen(false);
}}
/>
</li>
))}
</ul>
</Dialog>
);
};
const ConnectButton = ({
connector,
@ -66,9 +69,10 @@ const ConnectButton = ({
desiredChainId?: number;
onClick?: () => void;
}) => {
const t = useT();
const [connectorInstance, { useIsActivating }] = connector;
const isActivating = useIsActivating();
const info = getConnectorInfo(connectorInstance);
const info = getConnectorInfo(connectorInstance, t);
const [, setEagerConnector] = useLocalStorage(ETHEREUM_EAGER_CONNECT);
return (
<button
@ -120,7 +124,7 @@ export const Web3ConnectUncontrolledDialog = () => {
);
};
function getConnectorInfo(connector: Connector) {
function getConnectorInfo(connector: Connector, t: ReturnType<typeof useT>) {
if (connector instanceof MetaMask) {
return {
icon: <VegaIcon name={VegaIconNames.METAMASK} size={32} />,

View File

@ -1,4 +1,3 @@
import { t } from '@vegaprotocol/i18n';
import {
Button,
CopyWithTooltip,
@ -13,6 +12,7 @@ import {
import { useWithdrawalApprovalQuery } from './__generated__/WithdrawalApproval';
import omit from 'lodash/omit';
import { create } from 'zustand';
import { useT } from './use-t';
type WithdrawalApprovalDialogProps = {
withdrawalId: string | undefined;
@ -28,6 +28,7 @@ export const WithdrawalApprovalDialog = ({
onChange,
asJson,
}: WithdrawalApprovalDialogProps) => {
const t = useT();
return (
<Dialog
title={t('Save withdrawal details')}
@ -48,8 +49,7 @@ export const WithdrawalApprovalDialog = ({
<div className="pr-8">
<p>
{t(
`If the network is reset or has an outage, records of your withdrawal
may be lost. It is recommended that you save these details in a safe place so you can still complete your withdrawal.`
`If the network is reset or has an outage, records of your withdrawal may be lost. It is recommended that you save these details in a safe place so you can still complete your withdrawal.`
)}
</p>
{withdrawalId ? (
@ -80,16 +80,20 @@ type WithdrawalApprovalDialogContentProps = {
asJson: boolean;
};
const NoDataContent = ({ msg = t('No data') }) => (
<div className="py-12" data-testid="splash">
<Splash>{msg}</Splash>
</div>
);
const NoDataContent = ({ msg }: { msg?: string }) => {
const t = useT();
return (
<div className="py-12" data-testid="splash">
<Splash>{msg || t('No data')}</Splash>
</div>
);
};
const WithdrawalApprovalDialogContent = ({
withdrawalId,
asJson,
}: WithdrawalApprovalDialogContentProps) => {
const t = useT();
const { data, loading } = useWithdrawalApprovalQuery({
variables: {
withdrawalId,

View File

@ -1,4 +1,3 @@
import { t } from '@vegaprotocol/i18n';
import {
getDateTimeFormat,
resolveNetworkName,
@ -13,12 +12,14 @@ import {
import { useEthereumConfig } from './use-ethereum-config';
import { Button, useToasts } from '@vegaprotocol/ui-toolkit';
import { useWeb3ConnectStore } from './web3-connect-store';
import { useT } from './use-t';
export const VerificationStatus = ({
state,
}: {
state: EthWithdrawalApprovalState;
}) => {
const t = useT();
const { config } = useEthereumConfig();
const openDialog = useWeb3ConnectStore((state) => state.open);
const remove = useToasts((state) => state.remove);
@ -38,9 +39,14 @@ export const VerificationStatus = ({
return state.failureReason === WithdrawalFailure.NoConnection ? (
<>
<p>
{t('To complete this withdrawal, connect the Ethereum wallet %s', [
truncateByChars(state.withdrawal.details?.receiverAddress || ' '),
])}
{t(
'To complete this withdrawal, connect the Ethereum wallet {{receiverAddress}}',
{
receiverAddress: truncateByChars(
state.withdrawal.details?.receiverAddress || ' '
),
}
)}
</p>
<Button
onClick={() => {
@ -56,9 +62,12 @@ export const VerificationStatus = ({
<>
<p>{t('Your Ethereum wallet is connected to the wrong network.')}</p>
<p className="mt-2">
{t('Go to your Ethereum wallet and connect to the network %s', [
resolveNetworkName(config?.chain_id),
])}
{t(
'Go to your Ethereum wallet and connect to the network {{networkName}}',
{
networkName: resolveNetworkName(config?.chain_id),
}
)}
</p>
</>
);
@ -75,7 +84,9 @@ export const VerificationStatus = ({
return (
<>
<p>{t("The amount you're withdrawing has triggered a time delay")}</p>
<p>{t(`Cannot be completed until ${formattedTime}`)}</p>
<p>
{t(`Cannot be completed until {{time}}`, { time: formattedTime })}
</p>
</>
);
}

View File

@ -1 +1,15 @@
import '@testing-library/jest-dom';
import { locales } from '@vegaprotocol/i18n';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
// Set up i18n instance so that components have the correct default
// en translations
i18n.use(initReactI18next).init({
// we init with resources
resources: locales,
fallbackLng: 'en',
ns: ['web3'],
defaultNS: 'web3',
});