feat(trading): i18n (#5126)

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
Bartłomiej Głownia 2023-11-15 06:10:06 +01:00 committed by GitHub
parent f891caf08b
commit a070504d2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
108 changed files with 3102 additions and 1863 deletions

View File

@ -1,6 +1,9 @@
import { useMemo } from 'react';
import { type AssetFieldsFragment } from '@vegaprotocol/assets';
import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import {
useAssetTypeMapping,
useAssetStatusMapping,
type AssetFieldsFragment,
} from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { type AgGridReact } from 'ag-grid-react';
@ -15,6 +18,8 @@ type AssetsTableProps = {
data: AssetFieldsFragment[] | null;
};
export const AssetsTable = ({ data }: AssetsTableProps) => {
const assetTypeMapping = useAssetTypeMapping();
const assetStatusMapping = useAssetStatusMapping();
const navigate = useNavigate();
const ref = useRef<AgGridReact>(null);
const showColumnsOnDesktop = () => {
@ -47,14 +52,14 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
field: 'source.__typename',
hide: window.innerWidth < BREAKPOINT_MD,
valueFormatter: ({ value }: { value?: string }) =>
value ? AssetTypeMapping[value].value : '',
value ? assetTypeMapping[value].value : '',
},
{
headerName: t('Status'),
field: 'status',
hide: window.innerWidth < BREAKPOINT_MD,
valueFormatter: ({ value }: { value?: string }) =>
value ? AssetStatusMapping[value].value : '',
value ? assetStatusMapping[value].value : '',
},
{
colId: 'actions',
@ -69,7 +74,7 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
value ? (
<ButtonLink
onClick={(e) => {
onClick={() => {
navigate(value);
}}
>
@ -80,7 +85,7 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
),
},
],
[navigate]
[navigate, assetStatusMapping, assetTypeMapping]
);
return (

View File

@ -4,7 +4,7 @@ import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet';
import { FLAGS, useEnvironment } from '@vegaprotocol/environment';
import { useWeb3React } from '@web3-react/core';
import React from 'react';
import React, { Suspense } from 'react';
import { useTranslation } from 'react-i18next';
import { SplashError } from './components/splash-error';
@ -164,13 +164,14 @@ export const AppLoader = ({ children }: { children: React.ReactElement }) => {
);
}
if (!loaded) {
return (
const loading = (
<Splash>
<SplashLoader />
</Splash>
);
}
return children;
if (!loaded) {
return loading;
}
return <Suspense fallback={loading}>{children}</Suspense>;
};

View File

@ -42,6 +42,7 @@ import {
useNodeSwitcherStore,
DocsLinks,
NodeFailure,
AppLoader as Loader,
} from '@vegaprotocol/environment';
import { ENV } from './config';
import type { InMemoryCacheConfig } from '@apollo/client';
@ -352,9 +353,11 @@ function App() {
useInitializeEnv();
return (
<React.Suspense fallback={<Loader />}>
<NetworkLoader cache={cache}>
<AppContainer />
</NetworkLoader>
</React.Suspense>
);
}

View File

@ -0,0 +1 @@
../../../../libs/i18n/src/locales

View File

@ -1,29 +1,41 @@
import type { Module } from 'i18next';
import i18n from 'i18next';
import HttpBackend from 'i18next-http-backend';
import LocizeBackend from 'i18next-locize-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
import dev from './translations/dev.json';
const isInDev = process.env.NODE_ENV === 'development';
const useLocize = isInDev && !!process.env.NX_USE_LOCIZE;
const backend = useLocize
? {
projectId: '96ac1231-4bdd-455a-b9d7-f5322a2e7430',
apiKey: process.env.NX_LOCIZE_API_KEY,
referenceLng: 'en',
}
: {
loadPath: '/assets/locales/{{lng}}/{{ns}}.json',
};
const Backend: Module = useLocize ? LocizeBackend : HttpBackend;
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
// we init with resources
resources: {
en: {
translations: {
...dev,
},
},
},
lng: undefined,
lng: 'en',
fallbackLng: 'en',
debug: true,
supportedLngs: ['en'],
load: 'languageOnly',
debug: isInDev,
// have a common namespace used around the full app
ns: ['translations'],
defaultNS: 'translations',
ns: ['governance'],
defaultNS: 'governance',
keySeparator: false, // we use content as keys
backend,
saveMissing: useLocize && !!process.env.NX_LOCIZE_API_KEY,
interpolation: {
escapeValue: false,
},

View File

@ -3,7 +3,7 @@
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
import dev from './i18n/translations/dev.json';
import { locales } from '@vegaprotocol/i18n';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import ResizeObserver from 'resize-observer-polyfill';
@ -12,16 +12,10 @@ import ResizeObserver from 'resize-observer-polyfill';
// en translations
i18n.use(initReactI18next).init({
// we init with resources
resources: {
en: {
translations: {
...dev,
},
},
},
resources: locales,
fallbackLng: 'en',
ns: ['translations'],
defaultNS: 'translations',
ns: ['governance'],
defaultNS: 'governance',
});
global.ResizeObserver = ResizeObserver;

View File

@ -0,0 +1,14 @@
export const useTranslation = () => ({
t: (label: string, replacements?: Record<string, string>) => {
let translatedLabel = label;
if (typeof replacements === 'object' && replacements !== null) {
Object.keys(replacements).forEach((key) => {
translatedLabel = translatedLabel.replace(
`{{${key}}}`,
replacements[key]
);
});
}
return translatedLabel;
},
});

View File

@ -1,6 +1,6 @@
import { useSearchParams } from 'react-router-dom';
import { TransferContainer } from '@vegaprotocol/accounts';
import { GetStarted } from '../../components/welcome-dialog';
import { GetStarted } from '../../components/welcome-dialog/get-started';
export const Transfer = () => {
const [searchParams] = useSearchParams();

View File

@ -1,5 +1,5 @@
import { useSearchParams } from 'react-router-dom';
import { GetStarted } from '../../components/welcome-dialog';
import { GetStarted } from '../../components/welcome-dialog/get-started';
import { WithdrawContainer } from '../../components/withdraw-container';
export const Withdraw = () => {

View File

@ -10,7 +10,7 @@ import {
} from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import { VegaWalletProvider } from '@vegaprotocol/wallet';
import type { ReactNode } from 'react';
import { Suspense, type ReactNode } from 'react';
import { Web3Provider } from './web3-provider';
export const Bootstrapper = ({ children }: { children: ReactNode }) => {
@ -36,6 +36,7 @@ export const Bootstrapper = ({ children }: { children: ReactNode }) => {
}
return (
<Suspense fallback={<AppLoader />}>
<NetworkLoader
cache={cacheConfig}
skeleton={<AppLoader />}
@ -71,6 +72,7 @@ export const Bootstrapper = ({ children }: { children: ReactNode }) => {
</Web3Provider>
</NodeGuard>
</NetworkLoader>
</Suspense>
);
};

View File

@ -0,0 +1,81 @@
import type { Module } from 'i18next';
import i18n from 'i18next';
import HttpBackend from 'i18next-http-backend';
import LocizeBackend from 'i18next-locize-backend';
import type { HttpBackendOptions, RequestCallback } from 'i18next-http-backend';
import LanguageDetector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
const isInDev = process.env.NODE_ENV === 'development';
const useLocize = isInDev && !!process.env.NX_USE_LOCIZE;
const backend = useLocize
? {
projectId: '96ac1231-4bdd-455a-b9d7-f5322a2e7430',
apiKey: process.env.NX_LOCIZE_API_KEY,
referenceLng: 'en',
}
: {
loadPath: '/locales/{{lng}}/{{ns}}.json',
request: (
options: HttpBackendOptions,
url: string,
payload: string,
callback: RequestCallback
) => {
if (typeof window === 'undefined') {
callback(false, { status: 200, data: {} });
return;
}
fetch(url).then((response) => {
if (!response.ok) {
return callback(response.statusText || 'Error', {
status: response.status,
data: {},
});
}
response
.text()
.then((data) => {
callback(null, { status: response.status, data });
})
.catch((error) => callback(error, { status: 200, data: {} }));
});
},
};
const Backend: Module = useLocize ? LocizeBackend : HttpBackend;
i18n
.use(Backend)
.use(LanguageDetector)
.use(initReactI18next)
.init({
lng: 'en',
fallbackLng: 'en',
supportedLngs: ['en'],
load: 'languageOnly',
// have a common namespace used around the full app
ns: [
'accounts',
'assets',
'candles-chart',
'datagrid',
'deal-ticket',
'deposits',
'environment',
'fills',
'funding-payments',
'trading',
],
defaultNS: 'trading',
keySeparator: false, // we use content as keys
backend,
debug: isInDev,
saveMissing: useLocize && !!process.env.NX_LOCIZE_API_KEY,
interpolation: {
escapeValue: false,
},
});
export default i18n;

View File

@ -3,7 +3,7 @@ import Head from 'next/head';
import type { AppProps } from 'next/app';
import { t } from '@vegaprotocol/i18n';
import {
envTriggerMapping,
useEnvTriggerMapping,
Networks,
NodeSwitcherDialog,
useEnvironment,
@ -32,6 +32,7 @@ import { SSRLoader } from './ssr-loader';
import { PartyActiveOrdersHandler } from './party-active-orders-handler';
import { MaybeConnectEagerly } from './maybe-connect-eagerly';
import { TransactionHandlers } from './transaction-handlers';
import '../lib/i18n';
const DEFAULT_TITLE = t('Welcome to Vega trading!');
@ -39,7 +40,7 @@ const Title = () => {
const { pageTitle } = usePageTitleStore((store) => ({
pageTitle: store.pageTitle,
}));
const envTriggerMapping = useEnvTriggerMapping();
const { VEGA_ENV } = useEnvironment();
const networkName = envTriggerMapping[VEGA_ENV];

1
apps/trading/public/locales Symbolic link
View File

@ -0,0 +1 @@
../../../libs/i18n/src/locales

View File

@ -1,5 +1,5 @@
import { ETHERSCAN_ADDRESS, useEtherscanLink } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import { useT } from './use-t';
import {
ActionsDropdown,
TradingDropdownCopyItem,
@ -27,7 +27,7 @@ export const AccountsActionsDropdown = ({
}) => {
const etherscanLink = useEtherscanLink();
const openAssetDialog = useAssetDetailsDialogStore((store) => store.open);
const t = useT();
return (
<ActionsDropdown>
<TradingDropdownItem

View File

@ -1,6 +1,6 @@
import { useRef, memo, useState, useCallback } from 'react';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useT } from './use-t';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { type AgGridReact } from 'ag-grid-react';
import {
@ -22,6 +22,7 @@ const AccountBreakdown = ({
partyId: string;
onMarketClick?: (marketId: string, metaKey?: boolean) => void;
}) => {
const t = useT();
const gridRef = useRef<AgGridReact>(null);
const { data } = useDataProvider({
dataProvider: aggregatedAccountDataProvider,
@ -45,10 +46,10 @@ const AccountBreakdown = ({
</h1>
{data && (
<p className="mb-2 text-sm">
{t('You have %s %s in total.', [
addDecimalsFormatNumber(data.total, data.asset.decimals),
data.asset.symbol,
])}
{t('You have {{value}} {{symbol}} in total.', {
value: addDecimalsFormatNumber(data.total, data.asset.decimals),
symbol: data.asset.symbol,
})}
</p>
)}
<BreakdownTable
@ -118,6 +119,7 @@ export const AccountManager = ({
onMarketClick,
gridProps,
}: AccountManagerProps) => {
const t = useT();
const [breakdownAssetId, setBreakdownAssetId] = useState<string>();
const { data, error } = useDataProvider({
dataProvider: aggregatedAccountsDataProvider,

View File

@ -5,7 +5,7 @@ import {
isNumeric,
toBigNum,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useT } from './use-t';
import type {
VegaICellRendererParams,
VegaValueFormatterParams,
@ -96,6 +96,7 @@ export const AccountTable = ({
pinnedAsset,
...props
}: AccountTableProps) => {
const t = useT();
const pinnedRow = useMemo(() => {
if (!pinnedAsset) {
return;
@ -191,7 +192,7 @@ export const AccountTable = ({
<>
<span className="underline">{valueFormatted}</span>
<span className="inline-block ml-2 w-14 text-muted">
{t('0.00%')}
{(0).toFixed(2)}%
</span>
</>
);
@ -310,6 +311,7 @@ export const AccountTable = ({
onClickTransfer,
isReadOnly,
showDepositButton,
t,
]);
const data = rowData?.filter((data) => data.asset.id !== pinnedAsset?.id);

View File

@ -3,7 +3,7 @@ import {
addDecimalsFormatNumber,
addDecimalsFormatNumberQuantum,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useT } from './use-t';
import { Intent, TooltipCellComponent } from '@vegaprotocol/ui-toolkit';
import { type AgGridReact, type AgGridReactProps } from 'ag-grid-react';
import { type AccountFields } from './accounts-data-provider';
@ -31,6 +31,7 @@ interface BreakdownTableProps extends AgGridReactProps {
const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
({ data }, ref) => {
const t = useT();
const coldefs = useMemo(() => {
const defs: ColDef[] = [
{
@ -53,7 +54,7 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
}
/>
) : (
'None'
t('None')
);
},
},
@ -126,7 +127,7 @@ const BreakdownTable = forwardRef<AgGridReact, BreakdownTableProps>(
},
];
return defs;
}, []);
}, [t]);
return (
<AgGrid

View File

@ -4,9 +4,10 @@ import { Tooltip, ExternalLink } from '@vegaprotocol/ui-toolkit';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { marketMarginDataProvider } from './margin-data-provider';
import { useAssetsMapProvider } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import { useT, ns } from './use-t';
import { useAccountBalance } from './use-account-balance';
import { useMarketAccountBalance } from './use-market-account-balance';
import { Trans } from 'react-i18next';
const MarginHealthChartTooltipRow = ({
label,
@ -58,6 +59,7 @@ export const MarginHealthChartTooltip = ({
decimals: number;
marginAccountBalance?: string;
}) => {
const t = useT();
const tooltipContent = [
<MarginHealthChartTooltipRow
key={'maintenance'}
@ -169,14 +171,23 @@ export const MarginHealthChart = ({
return (
<div data-testid="margin-health-chart">
{addDecimalsFormatNumber(
(BigInt(marginAccountBalance) - BigInt(maintenanceLevel)).toString(),
decimals
)}{' '}
{t('above')}{' '}
<Trans
defaults="{{balance}} above <0>maintenance level</0>"
components={[
<ExternalLink href="https://docs.vega.xyz/testnet/concepts/trading-on-vega/positions-margin#margin-level-maintenance">
{t('maintenance level')}
</ExternalLink>
maintenance level
</ExternalLink>,
]}
values={{
balance: addDecimalsFormatNumber(
(
BigInt(marginAccountBalance) - BigInt(maintenanceLevel)
).toString(),
decimals
),
}}
ns={ns}
/>
<Tooltip description={tooltip}>
<div
data-testid="margin-health-chart-track"

View File

@ -1,7 +1,8 @@
import sortBy from 'lodash/sortBy';
import * as Schema from '@vegaprotocol/types';
import { Trans } from 'react-i18next';
import { truncateByChars } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { ns, useT } from './use-t';
import {
NetworkParams,
useNetworkParams,
@ -21,6 +22,7 @@ export const ALLOWED_ACCOUNTS = [
];
export const TransferContainer = ({ assetId }: { assetId?: string }) => {
const t = useT();
const { pubKey, pubKeys } = useVegaWallet();
const { params } = useNetworkParams([
NetworkParams.transfer_fee_factor,
@ -50,16 +52,20 @@ export const TransferContainer = ({ assetId }: { assetId?: string }) => {
return (
<>
<p className="mb-4 text-sm" data-testid="transfer-intro-text">
{t('Transfer funds to another Vega key')}
{pubKey && (
<>
{t(' from ')}
<Lozenge className="font-mono">
{truncateByChars(pubKey || '')}
</Lozenge>
</>
{pubKey ? (
<Trans
i18nKey="TRANSFER_FUNDS_TO_ANOTHER_KNOWN_VEGA_KEY"
defaults="Transfer funds to another Vega key <0>{{pubKey}}</0>. If you are at all unsure, stop and seek advice."
ns={ns}
components={[<Lozenge className="font-mono">pubKey</Lozenge>]}
values={{ pubKey: truncateByChars(pubKey || '') }}
/>
) : (
t('TRANSFER_FUNDS_TO_ANOTHER_VEGA_KEY', {
defaultValue:
'Transfer funds to another Vega key. If you are at all unsure, stop and seek advice.',
})
)}
{t('. If you are at all unsure, stop and seek advice.')}
</p>
<TransferForm
pubKey={pubKey}

View File

@ -8,7 +8,7 @@ import {
addDecimalsFormatNumber,
toBigNum,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useT } from './use-t';
import {
TradingFormGroup,
TradingInput,
@ -66,6 +66,7 @@ export const TransferForm = ({
accounts,
minQuantumMultiple,
}: TransferFormProps) => {
const t = useT();
const {
control,
register,
@ -300,7 +301,7 @@ export const TransferForm = ({
</TradingInputError>
)}
</TradingFormGroup>
<TradingFormGroup label="To Vega key" labelFor="toVegaKey">
<TradingFormGroup label={t('To Vega key')} labelFor="toVegaKey">
<AddressField
onChange={() => {
setValue('toVegaKey', '');
@ -317,7 +318,10 @@ export const TransferForm = ({
{t('Please select')}
</option>
{pubKeys?.map((pk) => {
const text = pk === pubKey ? t('Current key: ') + pk : pk;
const text =
pk === pubKey
? t('Current key: {{pubKey}}', { pubKey: pk }) + pk
: pk;
return (
<option key={pk} value={pk}>
@ -351,7 +355,7 @@ export const TransferForm = ({
</TradingInputError>
)}
</TradingFormGroup>
<TradingFormGroup label="Amount" labelFor="amount">
<TradingFormGroup label={t('Amount')} labelFor="amount">
<TradingInput
id="amount"
autoComplete="off"
@ -473,6 +477,7 @@ export const TransferFee = ({
fee?: string;
decimals?: number;
}) => {
const t = useT();
if (!feeFactor || !amount || !transferAmount || !fee) return null;
if (
isNaN(Number(feeFactor)) ||
@ -490,8 +495,8 @@ export const TransferFee = ({
<div className="flex flex-wrap items-center justify-between gap-1">
<Tooltip
description={t(
`The transfer fee is set by the network parameter transfer.fee.factor, currently set to %s`,
[feeFactor]
`The transfer fee is set by the network parameter transfer.fee.factor, currently set to {{feeFactor}}`,
{ feeFactor }
)}
>
<div>{t('Transfer fee')}</div>
@ -546,6 +551,7 @@ export const AddressField = ({
mode,
onChange,
}: AddressInputProps) => {
const t = useT();
const isInput = mode === 'input';
return (
<>

View File

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

View File

@ -1,6 +1,21 @@
import '@testing-library/jest-dom';
import ResizeObserver from 'resize-observer-polyfill';
import { defaultFallbackInView } from 'react-intersection-observer';
import { locales } from '@vegaprotocol/i18n';
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
defaultFallbackInView(true);
global.ResizeObserver = ResizeObserver;
// 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: ['accounts'],
defaultNS: 'accounts',
});
global.ResizeObserver = ResizeObserver;

View File

@ -1,4 +1,4 @@
import { t } from '@vegaprotocol/i18n';
import { useT } from './use-t';
import {
Button,
Dialog,
@ -56,6 +56,7 @@ export const AssetDetailsDialog = ({
onChange,
asJson = false,
}: AssetDetailsDialogProps) => {
const t = useT();
const { data: asset } = useAssetDataProvider(assetId);
const assetSymbol = asset?.symbol || '';
@ -77,7 +78,7 @@ export const AssetDetailsDialog = ({
</div>
);
const title = asset
? t(`Asset details - ${asset.symbol}`)
? t('Asset details - {{symbol}}', asset)
: t('Asset not found');
return (
@ -100,8 +101,8 @@ export const AssetDetailsDialog = ({
{content}
<p className="my-4 text-xs">
{t(
'There is 1 unit of the settlement asset (%s) to every 1 quote unit.',
[assetSymbol]
'There is 1 unit of the settlement asset ({{assetSymbol}}) to every 1 quote unit.',
{ assetSymbol }
)}
</p>
<div className="w-1/4">

View File

@ -1,10 +1,10 @@
import { render, screen } from '@testing-library/react';
import { render, screen, renderHook } from '@testing-library/react';
import * as Schema from '@vegaprotocol/types';
import type { Asset } from './asset-data-provider';
import {
AssetDetail,
AssetDetailsTable,
rows,
useRows,
testId,
} from './asset-details-table';
import { generateBuiltinAsset, generateERC20Asset } from './test-helpers';
@ -67,6 +67,8 @@ describe('AssetDetailsTable', () => {
it.each(cases)(
"displays the available asset's data of %p with correct labels",
async (_type, asset, details) => {
const { result } = renderHook(() => useRows());
const rows = result.current;
render(<AssetDetailsTable asset={asset} />);
for (const detail of details) {
expect(

View File

@ -1,6 +1,6 @@
import { EtherscanLink } from '@vegaprotocol/environment';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useT } from './use-t';
import type * as Schema from '@vegaprotocol/types';
import type { KeyValueTableRowProps } from '@vegaprotocol/ui-toolkit';
import { VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
@ -10,7 +10,7 @@ import {
KeyValueTableRow,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import { useMemo, type ReactNode } from 'react';
import type { Asset } from './asset-data-provider';
import { WITHDRAW_THRESHOLD_TOOLTIP_TEXT } from './constants';
@ -52,7 +52,12 @@ const num = (asset: Asset, n: string | undefined | null) => {
return addDecimalsFormatNumber(n, asset.decimals);
};
export const rows: Rows = [
export const useRows = () => {
const t = useT();
const AssetTypeMapping = useAssetTypeMapping();
const AssetStatusMapping = useAssetStatusMapping();
return useMemo<Rows>(
() => [
{
key: AssetDetail.ID,
label: t('ID'),
@ -73,7 +78,8 @@ export const rows: Rows = [
label: t('Type'),
tooltip: '',
value: (asset) => AssetTypeMapping[asset.source.__typename].value,
valueTooltip: (asset) => AssetTypeMapping[asset.source.__typename].tooltip,
valueTooltip: (asset) =>
AssetTypeMapping[asset.source.__typename].tooltip,
},
{
key: AssetDetail.NAME,
@ -134,7 +140,9 @@ export const rows: Rows = [
{
key: AssetDetail.WITHDRAWAL_THRESHOLD,
label: t('Withdrawal threshold'),
tooltip: WITHDRAW_THRESHOLD_TOOLTIP_TEXT,
tooltip: t('WITHDRAW_THRESHOLD_TOOLTIP_TEXT', {
defaultValue: WITHDRAW_THRESHOLD_TOOLTIP_TEXT,
}),
value: (asset) =>
num(asset, (asset.source as Schema.ERC20).withdrawThreshold),
},
@ -144,7 +152,8 @@ export const rows: Rows = [
tooltip: t(
'The lifetime deposit limit per address. Note: this is a temporary measure that can be changed or removed through governance'
),
value: (asset) => num(asset, (asset.source as Schema.ERC20).lifetimeLimit),
value: (asset) =>
num(asset, (asset.source as Schema.ERC20).lifetimeLimit),
},
{
key: AssetDetail.MAX_FAUCET_AMOUNT_MINT,
@ -197,11 +206,18 @@ export const rows: Rows = [
tooltip: t(
'The rewards acquired based on the market proposer reward in this asset'
),
value: (asset) => num(asset, asset.marketProposerRewardAccount?.balance),
value: (asset) =>
num(asset, asset.marketProposerRewardAccount?.balance),
},
];
],
[t, AssetTypeMapping, AssetStatusMapping]
);
};
export const AssetStatusMapping: Mapping = {
export const useAssetStatusMapping = () => {
const t = useT();
return useMemo<Mapping>(
() => ({
STATUS_ENABLED: {
value: t('Enabled'),
tooltip: t('Asset can be used on the Vega network'),
@ -218,17 +234,26 @@ export const AssetStatusMapping: Mapping = {
value: t('Rejected'),
tooltip: t('Asset has been rejected'),
},
}),
[t]
);
};
export const AssetTypeMapping: Mapping = {
export const useAssetTypeMapping = () => {
const t = useT();
return useMemo<Mapping>(
() => ({
BuiltinAsset: {
value: 'Builtin asset',
value: t('Builtin asset'),
tooltip: t('A Vega builtin asset'),
},
ERC20: {
value: 'ERC20',
value: t('ERC20'),
tooltip: t('An asset originated from an Ethereum ERC20 Token'),
},
}),
[t]
);
};
export const testId = (detail: AssetDetail, field: 'label' | 'value') =>
@ -248,7 +273,7 @@ export const AssetDetailsTable = ({
? { className: 'break-all', title: value }
: {};
const details = rows.map((r) => ({
const details = useRows().map((r) => ({
...r,
value: r.value(asset),
valueTooltip: r.valueTooltip?.(asset),

View File

@ -1,7 +1,7 @@
import { TradingOption, truncateMiddle } from '@vegaprotocol/ui-toolkit';
import type { AssetFieldsFragment } from './__generated__/Asset';
import classNames from 'classnames';
import { t } from '@vegaprotocol/i18n';
import { useT } from './use-t';
import type { ReactNode } from 'react';
type AssetOptionProps = {
@ -15,8 +15,9 @@ export const Balance = ({
}: {
balance?: string;
symbol: string;
}) =>
balance ? (
}) => {
const t = useT();
return balance ? (
<div className="mt-1 font-alpha" data-testid="asset-balance">
{balance} {symbol}
</div>
@ -25,6 +26,7 @@ export const Balance = ({
{t('Fetching balance…')}
</div>
);
};
export const AssetOption = ({ asset, balance }: AssetOptionProps) => {
return (

View File

@ -1,8 +1,5 @@
import { t } from '@vegaprotocol/i18n';
export const WITHDRAW_THRESHOLD_TOOLTIP_TEXT = t(
"The maximum you can withdraw instantly. There's no limit on the size of a withdrawal, but all withdrawals over the threshold will have a delay time added to them"
);
export const WITHDRAW_THRESHOLD_TOOLTIP_TEXT =
"The maximum you can withdraw instantly. There's no limit on the size of a withdrawal, but all withdrawals over the threshold will have a delay time added to them";
// List of defunct and no longer used assets that were created for various testnets
export const DENY_LIST: Record<string, string[]> = {

View File

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

View File

@ -6,12 +6,12 @@ import { useMemo } from 'react';
import debounce from 'lodash/debounce';
import AutoSizer from 'react-virtualized-auto-sizer';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/i18n';
import {
STUDY_SIZE,
useCandlesChartSettings,
} from './use-candles-chart-settings';
import { useT } from './use-t';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
export type CandlesChartContainerProps = {
marketId: string;
@ -25,6 +25,7 @@ export const CandlesChartContainer = ({
const client = useApolloClient();
const { pubKey } = useVegaWallet();
const { theme } = useThemeSwitcher();
const t = useT();
const {
interval,

View File

@ -22,8 +22,8 @@ import {
} from '@vegaprotocol/ui-toolkit';
import { type IconName } from '@blueprintjs/icons';
import { IconNames } from '@blueprintjs/icons';
import { t } from '@vegaprotocol/i18n';
import { useCandlesChartSettings } from './use-candles-chart-settings';
import { useT } from './use-t';
const chartTypeIcon = new Map<ChartType, IconName>([
[ChartType.AREA, IconNames.TIMELINE_AREA_CHART],
@ -43,6 +43,7 @@ export const CandlesMenu = () => {
setStudies,
setOverlays,
} = useCandlesChartSettings();
const t = useT();
const triggerClasses = 'text-xs';
const contentAlign = 'end';
const triggerButtonProps = { size: 'extra-small' } as const;
@ -53,7 +54,10 @@ export const CandlesMenu = () => {
trigger={
<TradingDropdownTrigger className={triggerClasses}>
<TradingButton {...triggerButtonProps}>
{t(`Interval: ${intervalLabels[interval]}`)}
{t('Interval: {{interval}}', {
replace: { interval: intervalLabels[interval] },
nsSeparator: '|',
})}
</TradingButton>
</TradingDropdownTrigger>
}

View File

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

View File

@ -0,0 +1,15 @@
export const useTranslation = () => ({
t: (label: string, replacements?: Record<string, string>) => {
const replace =
replacements?.['replace'] && typeof replacements === 'object'
? replacements?.['replace']
: replacements;
let translatedLabel = replacements?.['defaultValue'] || label;
if (typeof replace === 'object' && replace !== null) {
Object.keys(replace).forEach((key) => {
translatedLabel = translatedLabel.replace(`{{${key}}}`, replace[key]);
});
}
return translatedLabel;
},
});

View File

@ -1,14 +1,12 @@
import type { AgGridReactProps, AgReactUiProps } from 'ag-grid-react';
import { AgGridReact } from 'ag-grid-react';
import { useThemeSwitcher } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/i18n';
import classNames from 'classnames';
import type { ColDef } from 'ag-grid-community';
import { useT } from '../use-t';
const defaultProps: AgGridReactProps = {
enableCellTextSelection: true,
overlayLoadingTemplate: t('Loading...'),
overlayNoRowsTemplate: t('No data'),
suppressCellFocus: true,
suppressColumnMoveAnimation: true,
};
@ -26,6 +24,7 @@ export const AgGridThemed = ({
style?: React.CSSProperties;
gridRef?: React.ForwardedRef<AgGridReact>;
}) => {
const t = useT();
const { theme } = useThemeSwitcher();
const wrapperClasses = classNames('vega-ag-grid', 'w-full h-full', {
@ -38,6 +37,8 @@ export const AgGridThemed = ({
<AgGridReact
defaultColDef={defaultColDef}
ref={gridRef}
overlayLoadingTemplate={t('Loading...')}
overlayNoRowsTemplate={t('No data')}
{...defaultProps}
{...props}
/>

View File

@ -1,9 +1,9 @@
import type { MouseEvent } from 'react';
import { useMemo } from 'react';
import { useCallback } from 'react';
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { useT } from '../use-t';
interface OrderTypeCellProps {
value?: Schema.OrderType;
@ -17,6 +17,7 @@ export const OrderTypeCell = ({
onClick,
}: OrderTypeCellProps) => {
const id = order?.market?.id ?? '';
const t = useT();
const label = useMemo(() => {
if (!order) {
@ -25,7 +26,9 @@ export const OrderTypeCell = ({
if (!value) return '-';
if (order?.icebergOrder) {
return t('%s (Iceberg)', [Schema.OrderTypeMapping[value]]);
return t('{{orderType}} (Iceberg)', {
orderType: Schema.OrderTypeMapping[value],
});
}
if (order?.peggedOrder) {
@ -37,14 +40,18 @@ export const OrderTypeCell = ({
order.peggedOrder?.offset,
order.market.decimalPlaces
);
return t('%s %s %s Peg limit', [reference, side, offset]);
return t('{{reference}} {{side}} {{offset}} Peg limit', {
reference,
side,
offset,
});
}
if (order?.liquidityProvision) {
return t('Liquidity provision');
}
return Schema.OrderTypeMapping[value];
}, [order, value]);
}, [order, value, t]);
const handleOnClick = useCallback(
(ev: MouseEvent<HTMLButtonElement>) => {

View File

@ -14,8 +14,8 @@ import {
isValid,
} from 'date-fns';
import { formatForInput } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { TradingInputError } from '@vegaprotocol/ui-toolkit';
import { useT } from '../use-t';
const defaultValue: DateRange = {};
export interface DateRangeFilterProps extends IFilterParams {
@ -27,6 +27,7 @@ export interface DateRangeFilterProps extends IFilterParams {
export const DateRangeFilter = forwardRef(
(props: DateRangeFilterProps, ref) => {
const t = useT();
const defaultDates = props?.defaultValue || defaultValue;
const [value, setValue] = useState<DateRange>(defaultDates);
const valueRef = useRef<DateRange>(value);
@ -119,8 +120,10 @@ export const DateRangeFilter = forwardRef(
) {
setError(
t(
'The earliest data that can be queried is %s days ago.',
String(props.maxSubDays)
'The earliest data that can be queried is {{maxSubDays}} days ago.',
{
maxSubDays: String(props.maxSubDays),
}
)
);
return false;
@ -137,8 +140,8 @@ export const DateRangeFilter = forwardRef(
) {
setError(
t(
'The maximum time range that can be queried is %s days.',
String(props.maxDaysRange)
'The maximum time range that can be queried is {{maxDaysRange}} days.',
{ maxDaysRange: String(props.maxDaysRange) }
)
);
return false;

View File

@ -7,10 +7,11 @@ import {
useRef,
} from 'react';
import type { IDoesFilterPassParams, IFilterParams } from 'ag-grid-community';
import { t } from '@vegaprotocol/i18n';
import { useT } from '../use-t';
export const SetFilter = forwardRef(
(props: IFilterParams & { readonly?: boolean }, ref) => {
const t = useT();
const [value, setValue] = useState<string[]>([]);
const valueRef = useRef(value);
const { readonly } = props;

View File

@ -30,15 +30,6 @@ describe('Pagination', () => {
expect(mockOnLoad).toHaveBeenCalled();
});
it('renders message for a single row', async () => {
const mockOnLoad = jest.fn();
const count = 1;
render(<Pagination {...props} count={count} onLoad={mockOnLoad} />);
expect(screen.getByText(`${count} row loaded`)).toBeInTheDocument();
await userEvent.click(screen.getByRole('button', { name: 'Load more' }));
expect(mockOnLoad).toHaveBeenCalled();
});
it('renders the data rentention message', () => {
render(<Pagination {...props} showRetentionMessage={true} />);
expect(screen.getByText(/data node retention/)).toBeInTheDocument();

View File

@ -1,5 +1,5 @@
import { TradingButton as Button } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { useT } from './use-t';
export const Pagination = ({
count,
@ -14,16 +14,19 @@ export const Pagination = ({
hasDisplayedRows: boolean;
showRetentionMessage: boolean;
}) => {
const t = useT();
let rowMessage = '';
if (count && !pageInfo?.hasNextPage) {
rowMessage = t('all %s rows loaded', count.toString());
rowMessage = t('paginationAllLoaded', {
replace: { count },
defaultValue: 'All {{count}} rows loaded',
});
} else {
if (count === 1) {
rowMessage = t('%s row loaded', count.toString());
} else {
rowMessage = t('%s rows loaded', count.toString());
}
rowMessage = t('paginationLoaded', {
replace: { count },
defaultValue: '{{count}} rows loaded',
});
}
return (

View File

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

View File

@ -0,0 +1,14 @@
export const useTranslation = () => ({
t: (label: string, replacements?: Record<string, string>) => {
let translatedLabel = label;
if (typeof replacements === 'object' && replacements !== null) {
Object.keys(replacements).forEach((key) => {
translatedLabel = translatedLabel.replace(
`{{${key}}}`,
replacements[key]
);
});
}
return translatedLabel;
},
});

View File

@ -1,5 +1,4 @@
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
Intent,
VegaIcon,
@ -7,6 +6,7 @@ import {
Tooltip,
TradingButton,
} from '@vegaprotocol/ui-toolkit';
import { useT } from '../../use-t';
interface Props {
margin: string;
@ -20,18 +20,19 @@ interface Props {
}
export const MarginWarning = ({ margin, balance, asset, onDeposit }: Props) => {
const t = useT();
const description = (
<div className="flex flex-col items-start gap-2 p-2">
<p className="text-sm">
{t('%s %s is currently required.', [
addDecimalsFormatNumber(margin, asset.decimals),
asset.symbol,
])}
{t('{{amount}} {{assetSymbol}} is currently required.', {
amount: addDecimalsFormatNumber(margin, asset.decimals),
assetSymbol: asset.symbol,
})}
</p>
<p className="text-sm">
{t('You have only %s.', [
addDecimalsFormatNumber(balance, asset.decimals),
])}
{t('You have only {{amount}}.', {
amount: addDecimalsFormatNumber(balance, asset.decimals),
})}
</p>
<TradingButton
@ -41,7 +42,7 @@ export const MarginWarning = ({ margin, balance, asset, onDeposit }: Props) => {
data-testid="deal-ticket-deposit-dialog-button"
type="button"
>
{t('Deposit %s', [asset.symbol])}
{t('Deposit {{assetSymbol}}', { assetSymbol: asset.symbol })}
</TradingButton>
</div>
);

View File

@ -1,5 +1,5 @@
import { Intent, Notification } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { useT } from '../../use-t';
interface ZeroBalanceErrorProps {
asset: {
@ -13,6 +13,7 @@ export const ZeroBalanceError = ({
asset,
onDeposit,
}: ZeroBalanceErrorProps) => {
const t = useT();
return (
<Notification
intent={Intent.Warning}
@ -20,8 +21,8 @@ export const ZeroBalanceError = ({
message={
<>
{t(
'You need %s in your wallet to trade in this market. ',
asset.symbol
'You need {{symbol}} in your wallet to trade in this market.',
asset
)}
</>
}

View File

@ -10,9 +10,9 @@ import {
useMarketPrice,
} from '@vegaprotocol/markets';
import { AsyncRendererInline } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { DealTicket } from './deal-ticket';
import { FLAGS } from '@vegaprotocol/environment';
import { useT } from '../../use-t';
interface DealTicketContainerProps {
marketId: string;
@ -24,6 +24,7 @@ export const DealTicketContainer = ({
marketId,
...props
}: DealTicketContainerProps) => {
const t = useT();
const showStopOrder = useDealTicketFormValues((state) =>
isStopOrderType(state.formValues[marketId]?.type)
);

View File

@ -1,5 +1,4 @@
import { useCallback, useState } from 'react';
import { t } from '@vegaprotocol/i18n';
import { getAsset, getQuoteName } from '@vegaprotocol/markets';
import type { OrderSubmissionBody } from '@vegaprotocol/wallet';
import { useVegaWallet } from '@vegaprotocol/wallet';
@ -41,8 +40,10 @@ import classNames from 'classnames';
import BigNumber from 'bignumber.js';
import { FeesBreakdown } from '../fees-breakdown';
import { getTotalDiscountFactor, getDiscountedFee } from '../discounts';
import { useT, ns } from '../../use-t';
import { Trans } from 'react-i18next';
const emptyValue = '-';
export const emptyValue = '-';
export interface DealTicketFeeDetailsProps {
assetSymbol: string;
@ -57,6 +58,7 @@ export const DealTicketFeeDetails = ({
market,
isMarketInAuction,
}: DealTicketFeeDetailsProps) => {
const t = useT();
const feeEstimate = useEstimateFees(order, isMarketInAuction);
const asset = getAsset(market);
const { decimals: assetDecimals, quantum } = asset;
@ -98,7 +100,8 @@ export const DealTicketFeeDetails = ({
<div className="flex flex-col gap-2">
<p>
{t(
`An estimate of the most you would be expected to pay in fees, in the market's settlement asset ${assetSymbol}. Fees estimated are "taker" fees and will only be payable if the order trades aggressively. Rebate equal to the maker portion will be paid to the trader if the order trades passively.`
'An estimate of the most you would be expected to pay in fees, in the market\'s settlement asset {{assetSymbol}}. Fees estimated are "taker" fees and will only be payable if the order trades aggressively. Rebate equal to the maker portion will be paid to the trader if the order trades passively.',
{ assetSymbol }
)}
</p>
<FeesBreakdown
@ -136,6 +139,7 @@ export const DealTicketMarginDetails = ({
positionEstimate,
side,
}: DealTicketMarginDetailsProps) => {
const t = useT();
const [breakdownDialog, setBreakdownDialog] = useState(false);
const { pubKey: partyId } = useVegaWallet();
const { data: currentMargins } = useDataProvider({
@ -211,7 +215,11 @@ export const DealTicketMarginDetails = ({
quantum
)}
symbol={assetSymbol}
labelDescription={DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT(assetSymbol)}
labelDescription={t(
'DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT',
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
{ assetSymbol }
)}
/>
);
projectedMargin = (
@ -228,7 +236,10 @@ export const DealTicketMarginDetails = ({
quantum
)}
symbol={assetSymbol}
labelDescription={EST_TOTAL_MARGIN_TOOLTIP_TEXT}
labelDescription={t(
'EST_TOTAL_MARGIN_TOOLTIP_TEXT',
EST_TOTAL_MARGIN_TOOLTIP_TEXT
)}
/>
);
}
@ -307,7 +318,13 @@ export const DealTicketMarginDetails = ({
className="flex items-center justify-between w-full gap-2"
>
<div className="flex items-center text-left gap-1">
<Tooltip description={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}>
<Tooltip
description={t(
'MARGIN_DIFF_TOOLTIP_TEXT',
MARGIN_DIFF_TOOLTIP_TEXT,
{ assetSymbol }
)}
>
<span className="text-muted">{t('Margin required')}</span>
</Tooltip>
@ -346,15 +363,27 @@ export const DealTicketMarginDetails = ({
quantum
)}
symbol={assetSymbol}
labelDescription={TOTAL_MARGIN_AVAILABLE(
formatValue(generalAccountBalance, assetDecimals, quantum),
formatValue(marginAccountBalance, assetDecimals, quantum),
formatValue(
labelDescription={t(
'TOTAL_MARGIN_AVAILABLE',
TOTAL_MARGIN_AVAILABLE,
{
generalAccountBalance: formatValue(
generalAccountBalance,
assetDecimals,
quantum
),
marginAccountBalance: formatValue(
marginAccountBalance,
assetDecimals,
quantum
),
marginMaintenance: formatValue(
currentMargins?.maintenanceLevel,
assetDecimals,
quantum
),
assetSymbol
assetSymbol,
}
)}
/>
{deductionFromCollateral}
@ -368,7 +397,10 @@ export const DealTicketMarginDetails = ({
}
value={formatValue(marginAccountBalance, assetDecimals)}
symbol={assetSymbol}
labelDescription={MARGIN_ACCOUNT_TOOLTIP_TEXT}
labelDescription={t(
'MARGIN_ACCOUNT_TOOLTIP_TEXT',
MARGIN_ACCOUNT_TOOLTIP_TEXT
)}
formattedValue={formatValue(
marginAccountBalance,
assetDecimals,
@ -386,16 +418,26 @@ export const DealTicketMarginDetails = ({
symbol={quoteName}
labelDescription={
<>
<span>{LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT}</span>{' '}
<span>
{t('For full details please see ')}
{t(
'LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT',
LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT
)}
</span>{' '}
<span>
<Trans
defaults="For full details please see <0>liquidation price estimate documentation</0>."
components={[
<ExternalLink
href={
'https://github.com/vegaprotocol/specs/blob/master/non-protocol-specs/0012-NP-LIPE-liquidation-price-estimate.md'
}
>
{t('liquidation price estimate documentation.')}
</ExternalLink>
liquidation price estimate documentation
</ExternalLink>,
]}
ns={ns}
/>
</span>
</>
}

View File

@ -0,0 +1,354 @@
import { useCallback, useState } from 'react';
import { getAsset, getQuoteName } from '@vegaprotocol/markets';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
import { formatRange, formatValue } from '@vegaprotocol/utils';
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
import { useDataProvider } from '@vegaprotocol/data-provider';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import * as Schema from '@vegaprotocol/types';
import {
MARGIN_DIFF_TOOLTIP_TEXT,
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
TOTAL_MARGIN_AVAILABLE,
LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT,
EST_TOTAL_MARGIN_TOOLTIP_TEXT,
MARGIN_ACCOUNT_TOOLTIP_TEXT,
} from '../../constants';
import { KeyValue } from './key-value';
import {
Accordion,
AccordionChevron,
AccordionPanel,
ExternalLink,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames';
import { useT, ns } from '../../use-t';
import { Trans } from 'react-i18next';
import type { DealTicketMarginDetailsProps } from './deal-ticket-fee-details';
import { emptyValue } from './deal-ticket-fee-details';
export const DealTicketMarginDetails = ({
marginAccountBalance,
generalAccountBalance,
assetSymbol,
market,
onMarketClick,
positionEstimate,
side,
}: DealTicketMarginDetailsProps) => {
const t = useT();
const [breakdownDialog, setBreakdownDialog] = useState(false);
const { pubKey: partyId } = useVegaWallet();
const { data: currentMargins } = useDataProvider({
dataProvider: marketMarginDataProvider,
variables: { marketId: market.id, partyId: partyId || '' },
skip: !partyId,
});
const liquidationEstimate = positionEstimate?.liquidation;
const marginEstimate = positionEstimate?.margin;
const totalBalance =
BigInt(generalAccountBalance || '0') + BigInt(marginAccountBalance || '0');
const asset = getAsset(market);
const { decimals: assetDecimals, quantum } = asset;
let marginRequiredBestCase: string | undefined = undefined;
let marginRequiredWorstCase: string | undefined = undefined;
if (marginEstimate) {
if (currentMargins) {
marginRequiredBestCase = (
BigInt(marginEstimate.bestCase.initialLevel) -
BigInt(currentMargins.initialLevel)
).toString();
if (marginRequiredBestCase.startsWith('-')) {
marginRequiredBestCase = '0';
}
marginRequiredWorstCase = (
BigInt(marginEstimate.worstCase.initialLevel) -
BigInt(currentMargins.initialLevel)
).toString();
if (marginRequiredWorstCase.startsWith('-')) {
marginRequiredWorstCase = '0';
}
} else {
marginRequiredBestCase = marginEstimate.bestCase.initialLevel;
marginRequiredWorstCase = marginEstimate.worstCase.initialLevel;
}
}
const totalMarginAvailable = (
currentMargins
? totalBalance - BigInt(currentMargins.maintenanceLevel)
: totalBalance
).toString();
let deductionFromCollateral = null;
let projectedMargin = null;
if (marginAccountBalance) {
const deductionFromCollateralBestCase =
BigInt(marginEstimate?.bestCase.initialLevel ?? 0) -
BigInt(marginAccountBalance);
const deductionFromCollateralWorstCase =
BigInt(marginEstimate?.worstCase.initialLevel ?? 0) -
BigInt(marginAccountBalance);
deductionFromCollateral = (
<KeyValue
indent
label={t('Deduction from collateral')}
value={formatRange(
deductionFromCollateralBestCase > 0
? deductionFromCollateralBestCase.toString()
: '0',
deductionFromCollateralWorstCase > 0
? deductionFromCollateralWorstCase.toString()
: '0',
assetDecimals
)}
formattedValue={formatValue(
deductionFromCollateralWorstCase > 0
? deductionFromCollateralWorstCase.toString()
: '0',
assetDecimals,
quantum
)}
symbol={assetSymbol}
labelDescription={t(
'DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT',
DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT,
{ assetSymbol }
)}
/>
);
projectedMargin = (
<KeyValue
label={t('Projected margin')}
value={formatRange(
marginEstimate?.bestCase.initialLevel,
marginEstimate?.worstCase.initialLevel,
assetDecimals
)}
formattedValue={formatValue(
marginEstimate?.worstCase.initialLevel,
assetDecimals,
quantum
)}
symbol={assetSymbol}
labelDescription={t(
'EST_TOTAL_MARGIN_TOOLTIP_TEXT',
EST_TOTAL_MARGIN_TOOLTIP_TEXT
)}
/>
);
}
let liquidationPriceEstimate = emptyValue;
let liquidationPriceEstimateRange = emptyValue;
if (liquidationEstimate) {
const liquidationEstimateBestCaseIncludingBuyOrders = BigInt(
liquidationEstimate.bestCase.including_buy_orders.replace(/\..*/, '')
);
const liquidationEstimateBestCaseIncludingSellOrders = BigInt(
liquidationEstimate.bestCase.including_sell_orders.replace(/\..*/, '')
);
const liquidationEstimateBestCase =
side === Schema.Side.SIDE_BUY
? liquidationEstimateBestCaseIncludingBuyOrders
: liquidationEstimateBestCaseIncludingSellOrders;
const liquidationEstimateWorstCaseIncludingBuyOrders = BigInt(
liquidationEstimate.worstCase.including_buy_orders.replace(/\..*/, '')
);
const liquidationEstimateWorstCaseIncludingSellOrders = BigInt(
liquidationEstimate.worstCase.including_sell_orders.replace(/\..*/, '')
);
const liquidationEstimateWorstCase =
side === Schema.Side.SIDE_BUY
? liquidationEstimateWorstCaseIncludingBuyOrders
: liquidationEstimateWorstCaseIncludingSellOrders;
liquidationPriceEstimate = formatValue(
liquidationEstimateWorstCase.toString(),
market.decimalPlaces,
undefined,
market.decimalPlaces
);
liquidationPriceEstimateRange = formatRange(
(liquidationEstimateBestCase < liquidationEstimateWorstCase
? liquidationEstimateBestCase
: liquidationEstimateWorstCase
).toString(),
(liquidationEstimateBestCase > liquidationEstimateWorstCase
? liquidationEstimateBestCase
: liquidationEstimateWorstCase
).toString(),
market.decimalPlaces,
undefined,
market.decimalPlaces
);
}
const onAccountBreakdownDialogClose = useCallback(
() => setBreakdownDialog(false),
[]
);
const quoteName = getQuoteName(market);
return (
<div className="flex flex-col w-full gap-2">
<Accordion>
<AccordionPanel
itemId="margin"
trigger={
<AccordionPrimitive.Trigger
data-testid="accordion-toggle"
className={classNames(
'w-full pt-2',
'flex items-center gap-2 text-xs',
'group'
)}
>
<div
data-testid={`deal-ticket-fee-margin-required`}
key={'value-dropdown'}
className="flex items-center justify-between w-full gap-2"
>
<div className="flex items-center text-left gap-1">
<Tooltip
description={t(
'MARGIN_DIFF_TOOLTIP_TEXT',
MARGIN_DIFF_TOOLTIP_TEXT,
{ assetSymbol }
)}
>
<span className="text-muted">{t('Margin required')}</span>
</Tooltip>
<AccordionChevron size={10} />
</div>
<Tooltip
description={
formatRange(
marginRequiredBestCase,
marginRequiredWorstCase,
assetDecimals
) ?? '-'
}
>
<div className="font-mono text-right">
{formatValue(
marginRequiredWorstCase,
assetDecimals,
quantum
)}{' '}
{assetSymbol || ''}
</div>
</Tooltip>
</div>
</AccordionPrimitive.Trigger>
}
>
<div className="flex flex-col w-full gap-2">
<KeyValue
label={t('Total margin available')}
indent
value={formatValue(totalMarginAvailable, assetDecimals)}
formattedValue={formatValue(
totalMarginAvailable,
assetDecimals,
quantum
)}
symbol={assetSymbol}
labelDescription={t(
'TOTAL_MARGIN_AVAILABLE',
TOTAL_MARGIN_AVAILABLE,
{
generalAccountBalance: formatValue(
generalAccountBalance,
assetDecimals,
quantum
),
marginAccountBalance: formatValue(
marginAccountBalance,
assetDecimals,
quantum
),
marginMaintenance: formatValue(
currentMargins?.maintenanceLevel,
assetDecimals,
quantum
),
assetSymbol,
}
)}
/>
{deductionFromCollateral}
<KeyValue
label={t('Current margin allocation')}
indent
onClick={
generalAccountBalance
? () => setBreakdownDialog(true)
: undefined
}
value={formatValue(marginAccountBalance, assetDecimals)}
symbol={assetSymbol}
labelDescription={t(
'MARGIN_ACCOUNT_TOOLTIP_TEXT',
MARGIN_ACCOUNT_TOOLTIP_TEXT
)}
formattedValue={formatValue(
marginAccountBalance,
assetDecimals,
quantum
)}
/>
</div>
</AccordionPanel>
</Accordion>
{projectedMargin}
<KeyValue
label={t('Liquidation')}
value={liquidationPriceEstimateRange}
formattedValue={liquidationPriceEstimate}
symbol={quoteName}
labelDescription={
<>
<span>
{t(
'LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT',
LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT
)}
</span>{' '}
<span>
<Trans
defaults="For full details please see <0>liquidation price estimate documentation</0>."
components={[
<ExternalLink
href={
'https://github.com/vegaprotocol/specs/blob/master/non-protocol-specs/0012-NP-LIPE-liquidation-price-estimate.md'
}
>
liquidation price estimate documentation
</ExternalLink>,
]}
ns={ns}
/>
</span>
</>
}
/>
{partyId && (
<AccountBreakdownDialog
assetId={breakdownDialog ? asset.id : undefined}
partyId={partyId}
onMarketClick={onMarketClick}
onClose={onAccountBreakdownDialogClose}
/>
)}
</div>
);
};

View File

@ -2,13 +2,13 @@ import { Controller, type Control } from 'react-hook-form';
import type { Market } from '@vegaprotocol/markets';
import type { OrderFormValues } from '../../hooks/use-form-values';
import { toDecimal, validateAmount } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
TradingFormGroup,
TradingInput,
TradingInputError,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import { useT } from '../../use-t';
export interface DealTicketSizeIcebergProps {
control: Control<OrderFormValues>;
@ -27,6 +27,7 @@ export const DealTicketSizeIceberg = ({
size,
peakSize,
}: DealTicketSizeIcebergProps) => {
const t = useT();
const sizeStep = toDecimal(market?.positionDecimalPlaces);
const renderPeakSizeError = () => {
@ -81,13 +82,15 @@ export const DealTicketSizeIceberg = ({
required: t('You need to provide a peak size'),
min: {
value: sizeStep,
message: t('Peak size cannot be lower than ' + sizeStep),
message: t('Peak size cannot be lower than {{stepSize}}', {
sizeStep,
}),
},
max: {
value: size,
message: t(
'Peak size cannot be greater than the size (%s) ',
[size]
'Peak size cannot be greater than the size ({{size}})',
{ size }
),
},
validate: validateAmount(sizeStep, 'peakSize'),
@ -138,14 +141,15 @@ export const DealTicketSizeIceberg = ({
min: {
value: sizeStep,
message: t(
'Minimum visible size cannot be lower than ' + sizeStep
'Minimum visible size cannot be lower than {{sizeStep}}',
{ sizeStep }
),
},
max: peakSize && {
value: peakSize,
message: t(
'Minimum visible size cannot be greater than the peak size (%s)',
[peakSize]
'Minimum visible size cannot be greater than the peak size ({{peakSize}})',
{ peakSize }
),
},
validate: validateAmount(sizeStep, 'minimumVisibleSize'),

View File

@ -34,7 +34,6 @@ import {
getQuoteName,
type Market,
} from '@vegaprotocol/markets';
import { t } from '@vegaprotocol/i18n';
import { ExpirySelector } from './expiry-selector';
import { SideSelector } from './side-selector';
import { timeInForceLabel } from '@vegaprotocol/orders';
@ -60,6 +59,7 @@ import { NOTIONAL_SIZE_TOOLTIP_TEXT } from '../../constants';
import { KeyValue } from './key-value';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { stopOrdersProvider } from '@vegaprotocol/orders';
import { useT } from '../../use-t';
export interface StopOrderProps {
market: Market;
@ -109,6 +109,7 @@ const Trigger = ({
marketPrice?: string | null;
decimalPlaces: number;
}) => {
const t = useT();
const triggerType = watch(oco ? 'ocoTriggerType' : 'triggerType');
const triggerDirection = watch('triggerDirection');
const isPriceTrigger = triggerType === 'price';
@ -161,7 +162,9 @@ const Trigger = ({
required: t('You need provide a price'),
min: {
value: priceStep,
message: t('Price cannot be lower than ' + priceStep),
message: t('Price cannot be lower than {{priceStep}}', {
priceStep,
}),
},
validate: validateAmount(priceStep, 'Price'),
}}
@ -244,8 +247,8 @@ const Trigger = ({
min: {
value: trailingPercentOffsetStep,
message: t(
'Trailing percent offset cannot be lower than ' +
trailingPercentOffsetStep
'Trailing percent offset cannot be lower than {{trailingPercentOffsetStep}}',
{ trailingPercentOffsetStep }
),
},
max: {
@ -338,6 +341,7 @@ const Size = ({
isLimitType: boolean;
assetUnit?: string;
}) => {
const t = useT();
return (
<Controller
name={oco ? 'ocoSize' : 'size'}
@ -346,7 +350,7 @@ const Size = ({
required: t('You need to provide a size'),
min: {
value: sizeStep,
message: t('Size cannot be lower than ' + sizeStep),
message: t('Size cannot be lower than {{sizeStep}}', { sizeStep }),
},
validate: validateAmount(sizeStep, 'Size'),
}}
@ -397,6 +401,7 @@ const Price = ({
quoteName: string;
oco?: boolean;
}) => {
const t = useT();
if (watch(oco ? 'ocoType' : 'type') === Schema.OrderType.TYPE_MARKET) {
return null;
}
@ -409,7 +414,7 @@ const Price = ({
required: t('You need provide a price'),
min: {
value: priceStep,
message: t('Price cannot be lower than ' + priceStep),
message: t('Price cannot be lower than {{priceStep}}', { priceStep }),
},
validate: validateAmount(priceStep, 'Price'),
}}
@ -452,7 +457,9 @@ const TimeInForce = ({
}: {
control: Control<StopOrderFormValues>;
oco?: boolean;
}) => (
}) => {
const t = useT();
return (
<Controller
name={oco ? 'ocoTimeInForce' : 'timeInForce'}
control={control}
@ -491,9 +498,12 @@ const TimeInForce = ({
);
}}
/>
);
);
};
const ReduceOnly = () => (
const ReduceOnly = () => {
const t = useT();
return (
<Tooltip description={<span>{t(REDUCE_ONLY_TOOLTIP)}</span>}>
<div>
<Checkbox
@ -504,7 +514,8 @@ const ReduceOnly = () => (
/>
</div>
</Tooltip>
);
);
};
const NotionalAndFees = ({
market,
@ -522,6 +533,7 @@ const NotionalAndFees = ({
> &
Pick<StopOrderProps, 'market' | 'marketPrice'> &
Pick<StopOrderFormValues, 'triggerType' | 'triggerPrice'>) => {
const t = useT();
const quoteName = getQuoteName(market);
const asset = getAsset(market);
const isPriceTrigger = triggerType === 'price';
@ -548,7 +560,11 @@ const NotionalAndFees = ({
value={formatValue(notionalSize, market.decimalPlaces)}
formattedValue={formatValue(notionalSize, market.decimalPlaces)}
symbol={quoteName}
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
labelDescription={t(
'NOTIONAL_SIZE_TOOLTIP_TEXT',
NOTIONAL_SIZE_TOOLTIP_TEXT,
{ quoteName }
)}
/>
<DealTicketFeeDetails
order={{
@ -566,7 +582,8 @@ const NotionalAndFees = ({
);
};
const formatSizeAtPrice = ({
const formatSizeAtPrice = (
{
assetUnit,
decimalPlaces,
positionDecimalPlaces,
@ -575,40 +592,45 @@ const formatSizeAtPrice = ({
side,
size,
type,
}: Pick<StopOrderFormValues, 'price' | 'side' | 'size' | 'type'> & {
}: Pick<StopOrderFormValues, 'price' | 'side' | 'size' | 'type'> & {
assetUnit?: string;
decimalPlaces: number;
positionDecimalPlaces: number;
quoteName: string;
}) =>
},
t: ReturnType<typeof useT>
) =>
`${formatValue(
removeDecimal(size, positionDecimalPlaces),
positionDecimalPlaces
)} ${assetUnit} @ ${
type === Schema.OrderType.TYPE_MARKET
? 'market'
? t('sizeAtPrice-market', 'market')
: `${formatValue(
removeDecimal(price || '0', decimalPlaces),
decimalPlaces
)} ${quoteName}`
}`;
const formatTrigger = ({
const formatTrigger = (
{
decimalPlaces,
triggerDirection,
triggerPrice,
triggerTrailingPercentOffset,
triggerType,
quoteName,
}: Pick<
}: Pick<
StopOrderFormValues,
| 'triggerDirection'
| 'triggerType'
| 'triggerPrice'
| 'triggerTrailingPercentOffset'
> & {
> & {
decimalPlaces: number;
quoteName: string;
}) =>
},
t: ReturnType<typeof useT>
) =>
`${
triggerDirection ===
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE
@ -620,9 +642,12 @@ const formatTrigger = ({
removeDecimal(triggerPrice || '', decimalPlaces),
decimalPlaces
)} ${quoteName}`
: `${(Number(triggerTrailingPercentOffset) || 0).toFixed(1)}% ${t(
'trailing'
)}`
: t('{{triggerTrailingPercentOffset}}% trailing', {
triggerTrailingPercentOffset: (
Number(triggerTrailingPercentOffset) || 0
).toFixed(1),
})
}
}`;
const SubmitButton = ({
@ -662,13 +687,15 @@ const SubmitButton = ({
| 'type'
> &
Pick<StopOrderProps, 'market'> & { assetUnit?: string }) => {
const t = useT();
const quoteName = getQuoteName(market);
const risesAbove =
triggerDirection ===
Schema.StopOrderTriggerDirection.TRIGGER_DIRECTION_RISES_ABOVE;
const subLabel = oco ? (
<>
{formatSizeAtPrice({
{formatSizeAtPrice(
{
assetUnit,
decimalPlaces: market.decimalPlaces,
positionDecimalPlaces: market.positionDecimalPlaces,
@ -677,8 +704,11 @@ const SubmitButton = ({
side,
size: risesAbove ? size : ocoSize,
type,
})}{' '}
{formatTrigger({
},
t
)}{' '}
{formatTrigger(
{
decimalPlaces: market.decimalPlaces,
quoteName,
triggerDirection:
@ -688,9 +718,12 @@ const SubmitButton = ({
? triggerTrailingPercentOffset
: ocoTriggerTrailingPercentOffset,
triggerType: risesAbove ? triggerType : ocoTriggerType,
})}
},
t
)}
<br />
{formatSizeAtPrice({
{formatSizeAtPrice(
{
assetUnit,
decimalPlaces: market.decimalPlaces,
positionDecimalPlaces: market.positionDecimalPlaces,
@ -699,8 +732,11 @@ const SubmitButton = ({
side,
size: !risesAbove ? size : ocoSize,
type: ocoType,
})}{' '}
{formatTrigger({
},
t
)}{' '}
{formatTrigger(
{
decimalPlaces: market.decimalPlaces,
quoteName,
triggerDirection:
@ -710,11 +746,14 @@ const SubmitButton = ({
? triggerTrailingPercentOffset
: ocoTriggerTrailingPercentOffset,
triggerType: !risesAbove ? triggerType : ocoTriggerType,
})}
},
t
)}
</>
) : (
<>
{formatSizeAtPrice({
{formatSizeAtPrice(
{
assetUnit,
decimalPlaces: market.decimalPlaces,
positionDecimalPlaces: market.positionDecimalPlaces,
@ -723,17 +762,22 @@ const SubmitButton = ({
side,
size,
type,
})}
},
t
)}
<br />
{t('Trigger')}{' '}
{formatTrigger({
{formatTrigger(
{
decimalPlaces: market.decimalPlaces,
quoteName,
triggerDirection,
triggerPrice,
triggerTrailingPercentOffset,
triggerType,
})}
},
t
)}
</>
);
return (
@ -756,6 +800,7 @@ const SubmitButton = ({
};
export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
const t = useT();
const { pubKey, isReadOnly } = useVegaWallet();
const setType = useDealTicketFormValues((state) => state.setType);
const updateStoredFormValues = useDealTicketFormValues(
@ -1087,14 +1132,14 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_SUBMIT
}
id="expiryStrategy-submit"
label={'Submit'}
label={t('Submit')}
/>
<Radio
value={
Schema.StopOrderExpiryStrategy.EXPIRY_STRATEGY_CANCELS
}
id="expiryStrategy-cancel"
label={'Cancel'}
label={t('Cancel')}
/>
</RadioGroup>
);
@ -1107,7 +1152,11 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
control={control}
rules={{
required: t('You need provide a expiry time/date'),
validate: validateExpiration,
validate: validateExpiration(
t(
'The expiry date that you have entered appears to be in the past'
)
),
}}
render={({ field }) => {
const { value, onChange: onSelect } = field;
@ -1131,8 +1180,8 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
intent={Intent.Warning}
testId={'stop-order-warning-limit'}
message={t(
'There is a limit of %s active stop orders per market. Orders submitted above the limit will be immediately rejected.',
[MAX_NUMBER_OF_ACTIVE_STOP_ORDERS.toString()]
'There is a limit of {{maxNumberOfOrders}} active stop orders per market. Orders submitted above the limit will be immediately rejected.',
{ maxNumberOfOrders: MAX_NUMBER_OF_ACTIVE_STOP_ORDERS.toString() }
)}
/>
</div>

View File

@ -1,12 +1,9 @@
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { type FormEventHandler } from 'react';
import { memo, useCallback, useEffect, useRef, useMemo } from 'react';
import { Controller, useController, useForm } from 'react-hook-form';
import {
DealTicketFeeDetails,
DealTicketMarginDetails,
} from './deal-ticket-fee-details';
import { DealTicketFeeDetails } from './deal-ticket-fee-details';
import { DealTicketMarginDetails } from './deal-ticket-margin-details';
import { ExpirySelector } from './expiry-selector';
import { SideSelector } from './side-selector';
import { TimeInForceSelector } from './time-in-force-selector';
@ -45,7 +42,6 @@ import {
} from '@vegaprotocol/markets';
import {
validateExpiration,
validateMarketState,
validateMarketTradingMode,
validateTimeInForce,
validateType,
@ -79,6 +75,7 @@ import noop from 'lodash/noop';
import { isNonPersistentOrder } from '../../utils/time-in-force-persistance';
import { KeyValue } from './key-value';
import { DocsLinks } from '@vegaprotocol/environment';
import { useT } from '../../use-t';
export const REDUCE_ONLY_TOOLTIP =
'"Reduce only" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.';
@ -142,6 +139,7 @@ export const DealTicket = ({
submit,
onDeposit,
}: DealTicketProps) => {
const t = useT();
const { pubKey, isReadOnly } = useVegaWallet();
const setType = useDealTicketFormValues((state) => state.setType);
const storedFormValues = useDealTicketFormValues(
@ -287,7 +285,28 @@ export const DealTicket = ({
};
}
const marketStateError = validateMarketState(marketState);
let marketStateError: true | string = true;
if (
[
Schema.MarketState.STATE_SETTLED,
Schema.MarketState.STATE_REJECTED,
Schema.MarketState.STATE_TRADING_TERMINATED,
Schema.MarketState.STATE_CANCELLED,
Schema.MarketState.STATE_CLOSED,
].includes(marketState)
) {
marketStateError = t(
`This market is {{marketState}} and not accepting orders`,
{
marketState:
marketState === Schema.MarketState.STATE_TRADING_TERMINATED
? t('terminated')
: t(Schema.MarketStateMapping[marketState]).toLowerCase(),
}
);
}
if (marketStateError !== true) {
return {
message: marketStateError,
@ -307,7 +326,10 @@ export const DealTicket = ({
};
}
const marketTradingModeError = validateMarketTradingMode(marketTradingMode);
const marketTradingModeError = validateMarketTradingMode(
marketTradingMode,
t('Trading terminated')
);
if (marketTradingModeError !== true) {
return {
message: marketTradingModeError,
@ -317,6 +339,7 @@ export const DealTicket = ({
return undefined;
}, [
t,
marketState,
marketTradingMode,
generalAccountBalance,
@ -404,7 +427,7 @@ export const DealTicket = ({
required: t('You need to provide a size'),
min: {
value: sizeStep,
message: t('Size cannot be lower than ' + sizeStep),
message: t('Size cannot be lower than {{sizeStep}}', { sizeStep }),
},
validate: validateAmount(sizeStep, 'Size'),
deps: ['peakSize', 'minimumVisibleSize'],
@ -440,7 +463,9 @@ export const DealTicket = ({
required: t('You need provide a price'),
min: {
value: priceStep,
message: t('Price cannot be lower than ' + priceStep),
message: t('Price cannot be lower than {{priceStep}}', {
priceStep,
}),
},
validate: validateAmount(priceStep, 'Price'),
}}
@ -477,7 +502,11 @@ export const DealTicket = ({
value={formatValue(notionalSize, market.decimalPlaces)}
formattedValue={formatValue(notionalSize, market.decimalPlaces)}
symbol={quoteName}
labelDescription={NOTIONAL_SIZE_TOOLTIP_TEXT(quoteName)}
labelDescription={t(
'NOTIONAL_SIZE_TOOLTIP_TEXT',
NOTIONAL_SIZE_TOOLTIP_TEXT,
{ quoteName }
)}
/>
<DealTicketFeeDetails
order={
@ -501,7 +530,7 @@ export const DealTicket = ({
<TimeInForceSelector
value={field.value}
orderType={type}
onSelect={(value) => {
onSelect={(value: Schema.OrderTimeInForce) => {
// If GTT is selected and no expiresAt time is set, or its
// behind current time then reset the value to current time
const now = Date.now();
@ -534,7 +563,11 @@ export const DealTicket = ({
control={control}
rules={{
required: t('You need provide a expiry time/date'),
validate: validateExpiration,
validate: validateExpiration(
t(
'The expiry date that you have entered appears to be in the past'
)
),
}}
render={({ field }) => (
<ExpirySelector
@ -629,10 +662,10 @@ export const DealTicket = ({
<Tooltip
description={
<p>
{t(`Trade only a fraction of the order size at once.
After the peak size of the order has traded, the size is reset. This is repeated until the order is cancelled, expires, or its full volume trades away.
For example, an iceberg order with a size of 1000 and a peak size of 100 will effectively be split into 10 orders with a size of 100 each.
Note that the full volume of the order is not hidden and is still reflected in the order book.`)}{' '}
{t(
'ICEBERG_TOOLTIP',
'Trade only a fraction of the order size at once. After the peak size of the order has traded, the size is reset. This is repeated until the order is cancelled, expires, or its full volume trades away. For example, an iceberg order with a size of 1000 and a peak size of 100 will effectively be split into 10 orders with a size of 100 each. Note that the full volume of the order is not hidden and is still reflected in the order book.'
)}{' '}
<ExternalLink href={DocsLinks?.ICEBERG_ORDERS}>
{t('Find out more')}
</ExternalLink>{' '}
@ -731,13 +764,14 @@ interface SummaryMessageProps {
export const NoWalletWarning = ({
isReadOnly,
}: Pick<SummaryMessageProps, 'isReadOnly'>) => {
const t = useT();
if (isReadOnly) {
return (
<div className="mb-2">
<InputError testId="deal-ticket-error-message-summary">
{
{t(
'You need to connect your own wallet to start trading on this market'
}
)}
</InputError>
</div>
);
@ -756,6 +790,7 @@ const SummaryMessage = memo(
pubKey,
onDeposit,
}: SummaryMessageProps) => {
const t = useT();
// Specific error UI for if balance is so we can
// render a deposit dialog
if (isReadOnly || !pubKey) {

View File

@ -4,8 +4,8 @@ import {
TradingInputError,
} from '@vegaprotocol/ui-toolkit';
import { formatForInput } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useRef } from 'react';
import { useT } from '../../use-t';
interface ExpirySelectorProps {
value?: string;
@ -18,6 +18,7 @@ export const ExpirySelector = ({
onSelect,
errorMessage,
}: ExpirySelectorProps) => {
const t = useT();
const minDateRef = useRef(new Date());
return (

View File

@ -1,19 +1,19 @@
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import * as RadioGroup from '@radix-ui/react-radio-group';
import classNames from 'classnames';
import { useT } from '../../use-t';
interface SideSelectorProps {
value: Schema.Side;
onValueChange: (side: Schema.Side) => void;
}
const toggles = [
export const SideSelector = (props: SideSelectorProps) => {
const t = useT();
const toggles = [
{ label: t('Long'), value: Schema.Side.SIDE_BUY },
{ label: t('Short'), value: Schema.Side.SIDE_SELL },
];
export const SideSelector = (props: SideSelectorProps) => {
];
return (
<RadioGroup.Root
name="order-side"

View File

@ -2,15 +2,16 @@ import {
TradingFormGroup,
TradingInputError,
TradingSelect,
Tooltip,
TextChildrenTooltip as Tooltip,
SimpleGrid,
} from '@vegaprotocol/ui-toolkit';
import * as Schema from '@vegaprotocol/types';
import { t } from '@vegaprotocol/i18n';
import { timeInForceLabel } from '@vegaprotocol/orders';
import { compileGridData } from '../trading-mode-tooltip';
import { MarketModeValidationType } from '../../constants';
import type { Market, StaticMarketData } from '@vegaprotocol/markets';
import { Trans } from 'react-i18next';
import { useT, ns } from '../../use-t';
interface TimeInForceSelectorProps {
value: Schema.OrderTimeInForce;
@ -36,6 +37,7 @@ export const TimeInForceSelector = ({
marketData,
errorMessage,
}: TimeInForceSelectorProps) => {
const t = useT();
const options =
orderType === Schema.OrderType.TYPE_LIMIT
? typeLimitOptions
@ -44,25 +46,30 @@ export const TimeInForceSelector = ({
const renderError = (errorType: string) => {
if (errorType === MarketModeValidationType.Auction) {
return t(
`Until the auction ends, you can only place GFA, GTT, or GTC limit orders`
'Until the auction ends, you can only place GFA, GTT, or GTC limit orders'
);
}
if (errorType === MarketModeValidationType.LiquidityMonitoringAuction) {
return (
<span>
{t('This market is in auction until it reaches')}{' '}
<Trans
i18nKey="TIME_IN_FORCE_SELECTOR_LIQUIDITY_MONITORING_AUCTION"
defaults="This market is in auction until it reaches <0>sufficient liquidity</0>. Until the auction ends, you can only place GFA, GTT, or GTC limit orders."
ns={ns}
components={[
<Tooltip
description={
<SimpleGrid grid={compileGridData(market, marketData)} />
<SimpleGrid
grid={compileGridData(t, market, marketData, t)}
/>
}
>
<span>{t('sufficient liquidity')}</span>
</Tooltip>
{'. '}
{t(
`Until the auction ends, you can only place GFA, GTT, or GTC limit orders`
)}
sufficient liquidity
</Tooltip>,
]}
t={t}
/>
</span>
);
}
@ -70,18 +77,23 @@ export const TimeInForceSelector = ({
if (errorType === MarketModeValidationType.PriceMonitoringAuction) {
return (
<span>
{t('This market is in auction due to')}{' '}
<Trans
i18nKey="TIME_IN_FORCE_SELECTOR_PRICE_MONITORING_AUCTION"
defaults="This market is in auction due to <0>high price volatility</0>. Until the auction ends, you can only place GFA, GTT, or GTC limit orders."
ns={ns}
components={[
<Tooltip
description={
<SimpleGrid grid={compileGridData(market, marketData)} />
<SimpleGrid
grid={compileGridData(t, market, marketData, t)}
/>
}
>
<span>{t('high price volatility')}</span>
</Tooltip>
{'. '}
{t(
`Until the auction ends, you can only place GFA, GTT, or GTC limit orders`
)}
high price volatility
</Tooltip>,
]}
t={t}
/>
</span>
);
}

View File

@ -1,7 +1,7 @@
import {
TradingInputError,
SimpleGrid,
Tooltip,
TextChildrenTooltip as Tooltip,
TradingDropdown,
TradingDropdownContent,
TradingDropdownItemIndicator,
@ -12,7 +12,6 @@ import {
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import type { Market, StaticMarketData } from '@vegaprotocol/markets';
import { compileGridData } from '../trading-mode-tooltip';
import { MarketModeValidationType } from '../../constants';
@ -20,6 +19,8 @@ import { DealTicketType } from '../../hooks/use-form-values';
import * as RadioGroup from '@radix-ui/react-radio-group';
import classNames from 'classnames';
import { FLAGS } from '@vegaprotocol/environment';
import { Trans } from 'react-i18next';
import { useT, ns } from '../../use-t';
interface TypeSelectorProps {
value: DealTicketType;
@ -29,19 +30,28 @@ interface TypeSelectorProps {
errorMessage?: string;
}
const toggles = [
const useToggles = () => {
const t = useT();
return [
{ label: t('Limit'), value: DealTicketType.Limit },
{ label: t('Market'), value: DealTicketType.Market },
];
const options = [
];
};
const useOptions = () => {
const t = useT();
return [
{ label: t('Stop Limit'), value: DealTicketType.StopLimit },
{ label: t('Stop Market'), value: DealTicketType.StopMarket },
];
];
};
export const TypeToggle = ({
value,
onValueChange,
}: Pick<TypeSelectorProps, 'onValueChange' | 'value'>) => {
const t = useT();
const options = useOptions();
const toggles = useToggles();
const selectedOption = options.find((t) => t.value === value);
return (
<RadioGroup.Root
@ -84,7 +94,7 @@ export const TypeToggle = ({
>
<button className="flex gap-1">
<span className="text-ellipsis whitespace-nowrap shrink overflow-hidden">
{t(selectedOption ? selectedOption.label : 'Stop')}
{selectedOption ? selectedOption.label : t('Stop')}
</span>
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} size={14} />
</button>
@ -107,7 +117,7 @@ export const TypeToggle = ({
id={`order-type-${itemValue}`}
data-testid={`order-type-${itemValue}`}
>
{t(label)}
{label}
<TradingDropdownItemIndicator />
</TradingDropdownRadioItem>
))}
@ -127,6 +137,7 @@ export const TypeSelector = ({
marketData,
errorMessage,
}: TypeSelectorProps) => {
const t = useT();
const renderError = (errorType: MarketModeValidationType) => {
if (errorType === MarketModeValidationType.Auction) {
return t('Only limit orders are permitted when market is in auction');
@ -135,16 +146,21 @@ export const TypeSelector = ({
if (errorType === MarketModeValidationType.LiquidityMonitoringAuction) {
return (
<span>
{t('This market is in auction until it reaches')}{' '}
<Trans
i18nKey="TYPE_SELECTOR_LIQUIDITY_MONITORING_AUCTION"
defaults="This market is in auction until it reaches <0>sufficient liquidity</0>. Only limit orders are permitted when market is in auction."
ns={ns}
components={[
<Tooltip
description={
<SimpleGrid grid={compileGridData(market, marketData)} />
<SimpleGrid grid={compileGridData(t, market, marketData)} />
}
>
<span>{t('sufficient liquidity')}</span>
</Tooltip>
{'. '}
{t('Only limit orders are permitted when market is in auction')}
sufficient liquidity
</Tooltip>,
]}
t={t}
/>
</span>
);
}
@ -152,16 +168,21 @@ export const TypeSelector = ({
if (errorType === MarketModeValidationType.PriceMonitoringAuction) {
return (
<span>
{t('This market is in auction due to')}{' '}
<Trans
i18nKey="TYPE_SELECTOR_PRICE_MONITORING_AUCTION"
defaults="This market is in auction due to <0>high price volatility</0>. Only limit orders are permitted when market is in auction."
ns={ns}
components={[
<Tooltip
description={
<SimpleGrid grid={compileGridData(market, marketData)} />
<SimpleGrid grid={compileGridData(t, market, marketData)} />
}
>
<span>{t('high price volatility')}</span>
</Tooltip>
{'. '}
{t('Only limit orders are permitted when market is in auction')}
sufficient liquidity
</Tooltip>,
]}
t={t}
/>
</span>
);
}

View File

@ -4,9 +4,9 @@ import {
addDecimalsFormatNumber,
formatNumberPercentage,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import BigNumber from 'bignumber.js';
import { getDiscountedFee } from '../discounts';
import { useT } from '../../use-t';
const formatValue = (
value: string | number | null | undefined,
@ -59,6 +59,7 @@ export const FeesBreakdown = ({
referralDiscountFactor?: string;
volumeDiscountFactor?: string;
}) => {
const t = useT();
if (!fees || !totalFeeAmount || totalFeeAmount === '0') return null;
const { discountedFee: discountedInfrastructureFee } = getDiscountedFee(

View File

@ -2,15 +2,16 @@ import {
getDateTimeFormat,
addDecimalsFormatNumber,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { Link as UILink } from '@vegaprotocol/ui-toolkit';
import type { SimpleGridProps } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import { Link } from 'react-router-dom';
import { getAsset, type Market, type MarketData } from '@vegaprotocol/markets';
import type { useT } from '../../use-t';
export const compileGridData = (
t: ReturnType<typeof useT>,
market: Pick<
Market,
'id' | 'tradableInstrument' | 'decimalPlaces' | 'positionDecimalPlaces'

View File

@ -4,11 +4,11 @@ import classNames from 'classnames';
import { useProposalOfMarketQuery } from '@vegaprotocol/proposals';
import { DocsLinks } from '@vegaprotocol/environment';
import { getDateTimeFormat } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import { ExternalLink, SimpleGrid } from '@vegaprotocol/ui-toolkit';
import { compileGridData } from './compile-grid-data';
import { useMarket, useStaticMarketData } from '@vegaprotocol/markets';
import { useT } from '../../use-t';
type TradingModeTooltipProps = {
marketId?: string;
@ -23,6 +23,7 @@ export const TradingModeTooltip = ({
skip,
skipGrid,
}: TradingModeTooltipProps) => {
const t = useT();
const { data: market } = useMarket(marketId);
const { data: marketData } = useStaticMarketData(marketId, skip);
const { marketTradingMode, trigger } = marketData || {};
@ -43,7 +44,7 @@ export const TradingModeTooltip = ({
);
const compiledGrid =
!skipGrid && compileGridData(market, marketData, onSelect);
!skipGrid && compileGridData(t, market, marketData, onSelect);
switch (marketTradingMode) {
case Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS: {
@ -88,10 +89,9 @@ export const TradingModeTooltip = ({
>
{`${
Schema.MarketTradingModeMapping[marketTradingMode]
}: ${t(
'Closing on %s',
getDateTimeFormat().format(enactmentDate)
)}`}
}: ${t('Closing on {{time}}', {
time: getDateTimeFormat().format(enactmentDate),
})}`}
</span>
)}
<span>
@ -118,7 +118,7 @@ export const TradingModeTooltip = ({
return (
<section data-testid="trading-mode-suspended-via-governance">
{t(
`This market has been suspended via a governance vote and can be resumed or terminated by further votes.`
'This market has been suspended via a governance vote and can be resumed or terminated by further votes.'
)}
{DocsLinks && (
<ExternalLink href={DocsLinks.MARKET_LIFECYCLE} className="ml-1">

View File

@ -1,75 +1,32 @@
import { t } from '@vegaprotocol/i18n';
export const EST_MARGIN_TOOLTIP_TEXT = `A fraction of the notional position size, in the market's settlement asset {{assetSymbol}}, to cover any potential losses that you may incur. For example, for a notional size of $500, if the margin requirement is 10%, then the estimated margin would be approximately $50.`;
export const EST_TOTAL_MARGIN_TOOLTIP_TEXT =
'Estimated total margin that will cover open positions, active orders and this order.';
export const MARGIN_ACCOUNT_TOOLTIP_TEXT = 'Margin account balance.';
export const MARGIN_DIFF_TOOLTIP_TEXT =
"The additional margin required for your new position (taking into account volume and open orders), compared to your current margin. Measured in the market's settlement asset ({{assetSymbol}}).";
export const DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT =
'To cover the required margin, this amount will be drawn from your general ({{assetSymbol}}) account.';
export const EST_MARGIN_TOOLTIP_TEXT = (settlementAsset: string) =>
t(
`A fraction of the notional position size, in the market's settlement asset %s, to cover any potential losses that you may incur.
export const TOTAL_MARGIN_AVAILABLE =
'Total margin available = general {{assetSymbol}} balance ({{generalAccountBalance}} {{assetSymbol}}) + margin balance ({{marginAccountBalance}} {{assetSymbol}}) - maintenance level ({{marginMaintenance}} {{assetSymbol}}).';
For example, for a notional size of $500, if the margin requirement is 10%, then the estimated margin would be approximately $50.`,
[settlementAsset]
);
export const EST_TOTAL_MARGIN_TOOLTIP_TEXT = t(
'Estimated total margin that will cover open positions, active orders and this order.'
);
export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance.');
export const MARGIN_DIFF_TOOLTIP_TEXT = (settlementAsset: string) =>
t(
"The additional margin required for your new position (taking into account volume and open orders), compared to your current margin. Measured in the market's settlement asset (%s).",
[settlementAsset]
);
export const DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT = (
settlementAsset: string
) =>
t(
'To cover the required margin, this amount will be drawn from your general (%s) account.',
[settlementAsset]
);
export const CONTRACTS_MARGIN_TOOLTIP_TEXT =
'The number of contracts determines how many units of the futures contract to buy or sell. For example, this is similar to buying one share of a listed company. The value of 1 contract is equivalent to the price of the contract. For example, if the current price is $50, then one contract is worth $50.';
export const EST_CLOSEOUT_TOOLTIP_TEXT =
'If the price drops below this number, measured in the market price quote unit {{quote}}, you will be closed out, based on your current position and account balance.';
export const NOTIONAL_SIZE_TOOLTIP_TEXT =
'The notional size represents the position size in the settlement asset {{quoteName}} of the futures contract. This is calculated by multiplying the number of contracts by the prices of the contract. For example 10 contracts traded at a price of $50 has a notional size of $500.';
export const EST_FEES_TOOLTIP_TEXT =
'When you execute a new buy or sell order, you must pay a small amount of commission to the network for doing so. This fee is used to provide income to the node operates of the network and market makers who make prices on the futures market you are trading.';
export const TOTAL_MARGIN_AVAILABLE = (
generalAccountBalance: string,
marginAccountBalance: string,
marginMaintenance: string,
settlementAsset: string
) =>
t(
'Total margin available = general %s balance (%s) + margin balance (%s) - maintenance level (%s).',
[
settlementAsset,
`${generalAccountBalance} ${settlementAsset}`,
`${marginAccountBalance} ${settlementAsset}`,
`${marginMaintenance} ${settlementAsset}`,
]
);
export const LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT =
'This is an approximation for the liquidation price for that particular contract position, assuming nothing else changes, which may affect your margin and collateral balances.';
export const CONTRACTS_MARGIN_TOOLTIP_TEXT = t(
'The number of contracts determines how many units of the futures contract to buy or sell. For example, this is similar to buying one share of a listed company. The value of 1 contract is equivalent to the price of the contract. For example, if the current price is $50, then one contract is worth $50.'
);
export const EST_CLOSEOUT_TOOLTIP_TEXT = (quote: string) =>
t(
`If the price drops below this number, measured in the market price quote unit %s, you will be closed out, based on your current position and account balance.`,
[quote]
);
export const NOTIONAL_SIZE_TOOLTIP_TEXT = (settlementAsset: string) =>
t(
`The notional size represents the position size in the settlement asset %s of the futures contract. This is calculated by multiplying the number of contracts by the prices of the contract.
export const EST_SLIPPAGE =
'When you execute a trade on Vega, the price obtained in the market may differ from the best available price displayed at the time of placing the trade. The estimated slippage shows the difference between the best available price and the estimated execution price, determined by market liquidity and your chosen order size.';
For example 10 contracts traded at a price of $50 has a notional size of $500.`,
[settlementAsset]
);
export const EST_FEES_TOOLTIP_TEXT = t(
'When you execute a new buy or sell order, you must pay a small amount of commission to the network for doing so. This fee is used to provide income to the node operates of the network and market makers who make prices on the futures market you are trading.'
);
export const LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT = t(
'This is an approximation for the liquidation price for that particular contract position, assuming nothing else changes, which may affect your margin and collateral balances.'
);
export const EST_SLIPPAGE = t(
'When you execute a trade on Vega, the price obtained in the market may differ from the best available price displayed at the time of placing the trade. The estimated slippage shows the difference between the best available price and the estimated execution price, determined by market liquidity and your chosen order size.'
);
export const ERROR_SIZE_DECIMAL = t(
'The size field accepts up to X decimal places.'
);
export const ERROR_SIZE_DECIMAL =
'The size field accepts up to X decimal places.';
export enum MarketModeValidationType {
PriceMonitoringAuction = 'PriceMonitoringAuction',

View File

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

View File

@ -1,6 +1,5 @@
export * from './get-default-order';
export * from './validate-expiration';
export * from './validate-market-state';
export * from './validate-market-trading-mode';
export * from './validate-time-in-force';
export * from './validate-type';

View File

@ -1,13 +1,13 @@
import { t } from '@vegaprotocol/i18n';
import type { Validate } from 'react-hook-form';
export const validateExpiration: Validate<string | undefined, object> = (
value?: string
) => {
export const validateExpiration: (
errorMessage: string
) => Validate<string | undefined, object> =
(errorMessage: string) => (value?: string) => {
const now = new Date();
const valueAsDate = value ? new Date(value) : now;
if (now > valueAsDate) {
return t('The expiry date that you have entered appears to be in the past');
return errorMessage;
}
return true;
};
};

View File

@ -1,37 +0,0 @@
import { t } from '@vegaprotocol/i18n';
import { MarketState, MarketStateMapping } from '@vegaprotocol/types';
export const validateMarketState = (state: MarketState) => {
if (
[
MarketState.STATE_SETTLED,
MarketState.STATE_REJECTED,
MarketState.STATE_TRADING_TERMINATED,
MarketState.STATE_CANCELLED,
MarketState.STATE_CLOSED,
].includes(state)
) {
return t(
`This market is ${marketTranslations(state)} and not accepting orders`
);
}
if (state === MarketState.STATE_PROPOSED) {
return t(
`This market is ${marketTranslations(
state
)} and only accepting liquidity commitment orders`
);
}
return true;
};
const marketTranslations = (marketState: MarketState) => {
switch (marketState) {
case MarketState.STATE_TRADING_TERMINATED:
return t('terminated');
default:
return t(MarketStateMapping[marketState]).toLowerCase();
}
};

View File

@ -1,11 +1,11 @@
import { t } from '@vegaprotocol/i18n';
import { MarketTradingMode } from '@vegaprotocol/types';
export const validateMarketTradingMode = (
marketTradingMode: MarketTradingMode
marketTradingMode: MarketTradingMode,
errorMessage: string
) => {
if (marketTradingMode === MarketTradingMode.TRADING_MODE_NO_TRADING) {
return t('Trading terminated');
return errorMessage;
}
return true;

View File

@ -0,0 +1,14 @@
export const useTranslation = () => ({
t: (label: string, replacements?: Record<string, string>) => {
let translatedLabel = label;
if (typeof replacements === 'object' && replacements !== null) {
Object.keys(replacements).forEach((key) => {
translatedLabel = translatedLabel.replace(
`{{${key}}}`,
replacements[key]
);
});
}
return translatedLabel;
},
});

View File

@ -1,6 +1,5 @@
import type { Asset } from '@vegaprotocol/assets';
import { EtherscanLink } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import { Intent, Notification } from '@vegaprotocol/ui-toolkit';
import {
formatNumber,
@ -11,6 +10,7 @@ import type { EthStoredTxState } from '@vegaprotocol/web3';
import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3';
import BigNumber from 'bignumber.js';
import type { DepositBalances } from './use-deposit-balances';
import { useT } from './use-t';
interface ApproveNotificationProps {
isActive: boolean;
@ -33,6 +33,7 @@ export const ApproveNotification = ({
approveTxId,
intent = Intent.Warning,
}: ApproveNotificationProps) => {
const t = useT();
const tx = useEthTransactionStore((state) => {
return state.transactions.find((t) => t?.id === approveTxId);
});
@ -55,12 +56,14 @@ export const ApproveNotification = ({
intent={intent}
testId="approve-default"
message={t(
'Before you can make a deposit of your chosen asset, %s, you need to approve its use in your Ethereum wallet',
selectedAsset?.symbol
'Before you can make a deposit of your chosen asset, {{assetSymbol}}, you need to approve its use in your Ethereum wallet',
{ assetSymbol: selectedAsset?.symbol }
)}
buttonProps={{
size: 'small',
text: t('Approve %s', selectedAsset?.symbol),
text: t('Approve {{assetSymbol}}', {
assetSymbol: selectedAsset?.symbol,
}),
action: onApprove,
dataTestId: 'approve-submit',
}}
@ -72,13 +75,14 @@ export const ApproveNotification = ({
<Notification
intent={intent}
testId="reapprove-default"
message={t(
'Approve again to deposit more than %s',
formatNumber(balances.allowance.toString())
)}
message={t('Approve again to deposit more than {{allowance}}', {
allowance: formatNumber(balances.allowance.toString()),
})}
buttonProps={{
size: 'small',
text: t('Approve %s', selectedAsset?.symbol),
text: t('Approve {{assetSymbol}}', {
assetSymbol: selectedAsset?.symbol,
}),
action: onApprove,
dataTestId: 'reapprove-submit',
}}
@ -132,6 +136,7 @@ const ApprovalTxFeedback = ({
selectedAsset: Asset;
allowance?: BigNumber;
}) => {
const t = useT();
if (!tx) return null;
const txLink = tx.txHash && (
@ -161,8 +166,8 @@ const ApprovalTxFeedback = ({
intent={Intent.Warning}
testId="approve-requested"
message={t(
'Go to your Ethereum wallet and approve the transaction to enable the use of %s',
selectedAsset?.symbol
'Go to your Ethereum wallet and approve the transaction to enable the use of {{assetSymbol}}',
{ assetSymbol: selectedAsset?.symbol }
)}
/>
</div>
@ -179,8 +184,8 @@ const ApprovalTxFeedback = ({
<>
<p>
{t(
'Your %s approval is being confirmed by the Ethereum network. When this is complete, you can continue your deposit',
selectedAsset?.symbol
'Your {{assetSymbol}} approval is being confirmed by the Ethereum network. When this is complete, you can continue your deposit',
{ assetSymbol: selectedAsset?.symbol }
)}{' '}
</p>
{txLink && <p>{txLink}</p>}
@ -209,10 +214,15 @@ const ApprovalTxFeedback = ({
message={
<>
<p>
{t('You approved deposits of up to %s %s.', [
selectedAsset?.symbol,
{t(
'You approved deposits of up to {{assetSymbol}} {{approvedAllowanceValue}}.',
[
{
assetSymbol: selectedAsset?.symbol,
approvedAllowanceValue,
])}
},
]
)}
</p>
{txLink && <p>{txLink}</p>}
</>

View File

@ -10,7 +10,6 @@ import {
isAssetTypeERC20,
formatNumber,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import {
TradingFormGroup,
@ -43,6 +42,7 @@ import { FaucetNotification } from './faucet-notification';
import { ApproveNotification } from './approve-notification';
import { usePersistentDeposit } from './use-persistent-deposit';
import { AssetBalance } from './asset-balance';
import { useT } from './use-t';
interface FormFields {
asset: string;
@ -84,6 +84,7 @@ export const DepositForm = ({
approveTxId,
isFaucetable,
}: DepositFormProps) => {
const t = useT();
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
const openDialog = useWeb3ConnectStore((store) => store.open);
const { isActive, account } = useWeb3React();
@ -284,7 +285,7 @@ export const DepositForm = ({
)}
{isActive && isFaucetable && selectedAsset && (
<UseButton onClick={submitFaucet}>
{t(`Get ${selectedAsset.symbol}`)}
{t('Get {{assetSymbol}}', { assetSymbol: selectedAsset.symbol })}
</UseButton>
)}
{!errors.asset?.message && selectedAsset && (
@ -325,11 +326,11 @@ export const DepositForm = ({
const allowance = new BigNumber(balances?.allowance || 0);
if (value.isGreaterThan(allowance)) {
return t(
"You can't deposit more than your approved deposit amount, %s %s",
[
formatNumber(allowance.toString()),
selectedAsset?.symbol || ' ',
]
"You can't deposit more than your approved deposit amount, {{amount}} {{assetSymbol}}",
{
amount: formatNumber(allowance.toString()),
assetSymbol: selectedAsset?.symbol || ' ',
}
);
}
return true;
@ -347,11 +348,11 @@ export const DepositForm = ({
if (value.isGreaterThan(lifetimeLimit)) {
return t(
"You can't deposit more than your remaining deposit allowance, %s %s",
[
formatNumber(lifetimeLimit.toString()),
selectedAsset?.symbol || ' ',
]
"You can't deposit more than your remaining deposit allowance, {{amount}} {{assetSymbol}}",
{
amount: formatNumber(lifetimeLimit.toString()),
assetSymbol: selectedAsset?.symbol || ' ',
}
);
}
return true;
@ -361,8 +362,11 @@ export const DepositForm = ({
const balance = new BigNumber(balances?.balance || 0);
if (value.isGreaterThan(balance)) {
return t(
"You can't deposit more than you have in your Ethereum wallet, %s %s",
[formatNumber(balance), selectedAsset?.symbol || ' ']
"You can't deposit more than you have in your Ethereum wallet, {{amount}} {{assetSymbol}}",
{
amount: formatNumber(balance),
assetSymbol: selectedAsset?.symbol || ' ',
}
);
}
return true;
@ -419,6 +423,7 @@ interface FormButtonProps {
}
const FormButton = ({ approved, selectedAsset }: FormButtonProps) => {
const t = useT();
const { isActive, chainId } = useWeb3React();
const desiredChainId = useWeb3ConnectStore((store) => store.desiredChainId);
const invalidChain = isActive && chainId !== desiredChainId;
@ -429,9 +434,9 @@ const FormButton = ({ approved, selectedAsset }: FormButtonProps) => {
<Notification
intent={Intent.Danger}
testId="chain-error"
message={t(
`This app only works on ${getChainName(desiredChainId)}.`
)}
message={t('This app only works on {{chainId}}.', {
chainId: getChainName(desiredChainId),
})}
/>
</div>
)}
@ -464,6 +469,7 @@ const DisconnectEthereumButton = ({
}: {
onDisconnect: () => void;
}) => {
const t = useT();
const { connector } = useWeb3React();
const [, , removeEagerConnector] = useLocalStorage(ETHEREUM_EAGER_CONNECT);
const disconnect = useWeb3Disconnect(connector);
@ -495,6 +501,7 @@ export const AddressField = ({
input,
onChange,
}: AddressInputProps) => {
const t = useT();
const [isInput, setIsInput] = useState(() => {
if (pubKeys && pubKeys.length <= 1) {
return true;

View File

@ -1,5 +1,4 @@
import type { Asset } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/i18n';
import { CompactNumber } from '@vegaprotocol/react-helpers';
import {
KeyValueTable,
@ -8,6 +7,7 @@ import {
} from '@vegaprotocol/ui-toolkit';
import type BigNumber from 'bignumber.js';
import { formatNumber, quantumDecimalPlaces } from '@vegaprotocol/utils';
import { useT } from './use-t';
// Note: all of the values here are with correct asset's decimals
// See `libs/deposits/src/lib/use-deposit-balances.ts`
@ -29,6 +29,7 @@ export const DepositLimits = ({
allowance,
exempt,
}: DepositLimitsProps) => {
const t = useT();
const limits = [
{
key: 'BALANCE_AVAILABLE',
@ -51,19 +52,23 @@ export const DepositLimits = ({
<>
<p>
{t(
'VEGA has a lifetime deposit limit of %s %s per address. This can be changed through governance',
[formatNumber(max.toString()), asset.symbol]
'VEGA has a lifetime deposit limit of {{amount}} {{assetSymbol}} per address. This can be changed through governance',
{
amount: formatNumber(max.toString()),
assetSymbol: asset.symbol,
}
)}
</p>
<p>
{t(
'To date, %s %s has been deposited from this Ethereum address, so you can deposit up to %s %s more.',
[
formatNumber(deposited.toString()),
asset.symbol,
formatNumber(max.minus(deposited).toString()),
asset.symbol,
]
'To date, {{currentDeposit}} {{assetSymbol}} has been deposited from this Ethereum address, so you can deposit up to {{remainingDeposit}} {{assetSymbol}} more.',
{
currentDeposit: formatNumber(deposited.toString()),
assetSymbol: asset.symbol,
remainingDeposit: formatNumber(
max.minus(deposited).toString()
),
}
)}
</p>
</>
@ -89,8 +94,8 @@ export const DepositLimits = ({
description={
<p>
{t(
'The deposit cap is set when you approve an asset for use with this app. To increase this cap, approve %s again and choose a higher cap. Check the documentation for your Ethereum wallet app for details.',
asset.symbol
'The deposit cap is set when you approve an asset for use with this app. To increase this cap, approve {{assetSymbol}} again and choose a higher cap. Check the documentation for your Ethereum wallet app for details.',
{ assetSymbol: asset.symbol }
)}
</p>
}

View File

@ -1,11 +1,10 @@
import type { Asset } from '@vegaprotocol/assets';
import { EtherscanLink } from '@vegaprotocol/environment';
import { t } from '@vegaprotocol/i18n';
import { Intent, Notification } from '@vegaprotocol/ui-toolkit';
import { EthTxStatus, useEthTransactionStore } from '@vegaprotocol/web3';
import { getFaucetError } from './get-faucet-error';
interface FaucetNotificationProps {
import { useGetFaucetError } from './get-faucet-error';
import { useT } from './use-t';
export interface FaucetNotificationProps {
isActive: boolean;
selectedAsset?: Asset;
faucetTxId: number | null;
@ -14,14 +13,20 @@ interface FaucetNotificationProps {
/**
* Render a notification for the faucet transaction
*/
export const FaucetNotification = ({
isActive,
selectedAsset,
faucetTxId,
}: FaucetNotificationProps) => {
const t = useT();
const tx = useEthTransactionStore((state) => {
return state.transactions.find((t) => t?.id === faucetTxId);
});
const errorMessage = useGetFaucetError(
tx?.status === EthTxStatus.Error ? tx.error : null,
selectedAsset?.symbol
);
if (!isActive) {
return null;
@ -34,9 +39,7 @@ export const FaucetNotification = ({
if (!tx) {
return null;
}
if (tx.status === EthTxStatus.Error) {
const errorMessage = getFaucetError(tx.error, selectedAsset.symbol);
if (errorMessage) {
return (
<div className="mb-4">
<Notification
@ -55,7 +58,8 @@ export const FaucetNotification = ({
intent={Intent.Warning}
testId="faucet-requested"
message={t(
`Confirm the transaction in your Ethereum wallet to use the ${selectedAsset?.symbol} faucet`
'Confirm the transaction in your Ethereum wallet to use the {{assetSymbol}} faucet',
{ assetSymbol: selectedAsset?.symbol }
)}
/>
</div>
@ -72,8 +76,8 @@ export const FaucetNotification = ({
<>
<p className="mb-2">
{t(
'Your request for funds from the %s faucet is being confirmed by the Ethereum network',
selectedAsset.symbol
'Your request for funds from the {{assetSymbol}} faucet is being confirmed by the Ethereum network',
{ assetSymbol: selectedAsset.symbol }
)}{' '}
</p>
{tx.txHash && (
@ -100,8 +104,10 @@ export const FaucetNotification = ({
<>
<p className="mb-2">
{t(
'%s has been deposited in your Ethereum wallet',
selectedAsset.symbol
'{{assetSymbol}} has been deposited in your Ethereum wallet',
{
assetSymbol: selectedAsset.symbol,
}
)}{' '}
</p>
{tx.txHash && (

View File

@ -1,13 +1,14 @@
import { t } from '@vegaprotocol/i18n';
import type { TxError } from '@vegaprotocol/web3';
import { useT } from './use-t';
export const getFaucetError = (error: TxError | null, symbol: string) => {
export const useGetFaucetError = (error: TxError | null, symbol?: string) => {
const t = useT();
const reasonMap: {
[reason: string]: string;
} = {
'faucet not enabled': t(
'The %s faucet is not available at this time',
symbol
'The {{symbol}} faucet is not available at this time',
{ symbol: symbol || '' }
),
'must wait faucetCallLimit between faucet calls': t(
'You have exceeded the maximum number of faucet attempts allowed'
@ -20,5 +21,5 @@ export const getFaucetError = (error: TxError | null, symbol: string) => {
// to a non generic error message
return error && 'reason' in error && reasonMap[error.reason]
? reasonMap[error.reason]
: t('Faucet of %s failed', symbol);
: t('Faucet of {{symbol}} failed', { symbol: symbol || '' });
};

View File

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

View File

@ -0,0 +1,15 @@
export const useTranslation = () => ({
t: (label: string, replacements?: Record<string, string>) => {
const replace =
replacements?.replace && typeof replacements === 'object'
? replacements?.replace
: replacements;
let translatedLabel = replacements?.defaultValue || label;
if (typeof replace === 'object' && replace !== null) {
Object.keys(replace).forEach((key) => {
translatedLabel = translatedLabel.replace(`{{${key}}}`, replace[key]);
});
}
return translatedLabel;
},
});

View File

@ -1,7 +1,7 @@
import { t } from '@vegaprotocol/i18n';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import type { ComponentProps } from 'react';
import { ETHERSCAN_ADDRESS, ETHERSCAN_TX, useEtherscanLink } from '../hooks';
import { useT } from '../use-t';
export const EtherscanLink = ({
address,
@ -12,6 +12,7 @@ export const EtherscanLink = ({
address?: string;
tx?: string;
} & ComponentProps<typeof ExternalLink>) => {
const t = useT();
const etherscanLink = useEtherscanLink();
let href = '';

View File

@ -1,13 +1,7 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { t } from '@vegaprotocol/i18n';
import { useEnvironment } from '../../hooks/use-environment';
import {
NetworkSwitcher,
envNameMapping,
envTriggerMapping,
envDescriptionMapping,
} from './';
import { NetworkSwitcher } from './';
import { Networks } from '../../';
jest.mock('../../hooks/use-environment');
@ -33,10 +27,10 @@ describe('Network switcher', () => {
it.each`
network | label
${Networks.CUSTOM} | ${envTriggerMapping[Networks.CUSTOM]}
${Networks.DEVNET} | ${envTriggerMapping[Networks.DEVNET]}
${Networks.TESTNET} | ${envTriggerMapping[Networks.TESTNET]}
${Networks.MAINNET} | ${envTriggerMapping[Networks.MAINNET]}
${Networks.CUSTOM} | ${'Custom'}
${Networks.DEVNET} | ${'Devnet'}
${Networks.TESTNET} | ${'Fairground'}
${Networks.MAINNET} | ${'Mainnet'}
`(
'displays the correct selection label for $network',
({ network, label }) => {
@ -69,13 +63,13 @@ describe('Network switcher', () => {
await userEvent.click(screen.getByRole('button'));
let links = screen.getAllByRole('link');
expect(links[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
expect(links[1]).toHaveTextContent(envNameMapping[Networks.TESTNET]);
expect(links[0]).not.toHaveTextContent(t('current'));
expect(links[1]).not.toHaveTextContent(t('current'));
expect(links[0]).not.toHaveTextContent(t('not available'));
expect(links[1]).not.toHaveTextContent(t('not available'));
expect(links[2]).toHaveTextContent(t('Propose a network parameter change'));
expect(links[0]).toHaveTextContent('Mainnet');
expect(links[1]).toHaveTextContent('Fairground testnet');
expect(links[0]).not.toHaveTextContent('current');
expect(links[1]).not.toHaveTextContent('current');
expect(links[0]).not.toHaveTextContent('not available');
expect(links[1]).not.toHaveTextContent('not available');
expect(links[2]).toHaveTextContent('Propose a network parameter change');
const menuitems = screen.getAllByRole('menuitem');
@ -110,8 +104,8 @@ describe('Network switcher', () => {
const links = screen.getAllByRole('link');
expect(links[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
expect(links[0]).toHaveTextContent(t('current'));
expect(links[0]).toHaveTextContent('Mainnet');
expect(links[0]).toHaveTextContent('current');
});
it('displays the correct selected network on the default dropdown view when it does not have an associated url', async () => {
@ -131,8 +125,8 @@ describe('Network switcher', () => {
const links = screen.getAllByRole('link');
expect(links[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
expect(links[0]).toHaveTextContent(t('current'));
expect(links[0]).toHaveTextContent('Mainnet');
expect(links[0]).toHaveTextContent('current');
});
it('displays the correct state for a network without url on the default dropdown view', async () => {
@ -151,13 +145,17 @@ describe('Network switcher', () => {
await userEvent.click(screen.getByRole('button'));
const links = screen.getAllByRole('link');
expect(links[0]).toHaveTextContent(envNameMapping[Networks.MAINNET]);
expect(links[0]).toHaveTextContent(t('not available'));
expect(links[0]).toHaveTextContent('Mainnet');
expect(links[0]).toHaveTextContent('not available');
});
it.each([Networks.MAINNET, Networks.TESTNET, Networks.DEVNET])(
it.each([
{ network: Networks.MAINNET, name: 'Mainnet' },
{ network: Networks.TESTNET, name: 'Fairground testnet' },
{ network: Networks.DEVNET, name: 'Devnet' },
])(
'displays the advanced view in the correct state',
async (network) => {
async ({ network, name }) => {
const VEGA_NETWORKS: Record<Networks, string | undefined> = {
[Networks.CUSTOM]: undefined,
[Networks.MAINNET]: 'https://main.net',
@ -178,25 +176,14 @@ describe('Network switcher', () => {
await userEvent.click(screen.getByTestId('network-switcher'));
expect(
await screen.findByRole('menuitem', { name: t('Advanced') })
await screen.findByRole('menuitem', { name: 'Advanced' })
).toBeInTheDocument();
await userEvent.click(
screen.getByRole('menuitem', { name: t('Advanced') })
);
expect(
await screen.findByText(envDescriptionMapping[network])
).toBeInTheDocument();
expect(
screen.getByRole('link', {
name: new RegExp(`^${envNameMapping[network]}`),
})
).toBeInTheDocument();
await userEvent.click(screen.getByRole('menuitem', { name: 'Advanced' }));
await userEvent.click(
screen.getByRole('link', {
name: new RegExp(`^${envNameMapping[network]}`),
name: new RegExp(`^${name}`),
})
);
@ -224,15 +211,13 @@ describe('Network switcher', () => {
render(<NetworkSwitcher />);
await userEvent.click(screen.getByRole('button'));
await userEvent.click(
screen.getByRole('menuitem', { name: t('Advanced') })
);
await userEvent.click(screen.getByRole('menuitem', { name: 'Advanced' }));
const label = screen.getByText(`(${t('current')})`);
const label = screen.getByText('(current)');
expect(label).toBeInTheDocument();
expect(label.parentNode?.parentNode?.firstElementChild).toHaveTextContent(
envNameMapping[selectedNetwork]
'Devnet'
);
});
@ -262,7 +247,7 @@ describe('Network switcher', () => {
expect(label).toBeInTheDocument();
expect(label.parentNode?.parentNode?.firstElementChild).toHaveTextContent(
envNameMapping[Networks.MAINNET]
'Mainnet'
);
});
});

View File

@ -1,5 +1,4 @@
import { useState, useCallback } from 'react';
import { t } from '@vegaprotocol/i18n';
import {
DropdownMenu,
DropdownMenuContent,
@ -13,23 +12,34 @@ import { useEnvironment } from '../../hooks/use-environment';
import { Networks } from '../../types';
import { DApp, TOKEN_NEW_NETWORK_PARAM_PROPOSAL, useLinks } from '../../hooks';
import classNames from 'classnames';
import { useT } from '../../use-t';
export const envNameMapping: Record<Networks, string> = {
[Networks.VALIDATOR_TESTNET]: t('VALIDATOR_TESTNET'),
export const useEnvNameMapping: () => Record<Networks, string> = () => {
const t = useT();
return {
[Networks.VALIDATOR_TESTNET]: t('VALIDATOR_TESTNET', {
contextSeparator: '|',
}),
[Networks.MAINNET_MIRROR]: t('Mainnet-mirror'),
[Networks.CUSTOM]: t('Custom'),
[Networks.DEVNET]: t('Devnet'),
[Networks.STAGNET1]: t('Stagnet'),
[Networks.TESTNET]: t('Fairground testnet'),
[Networks.MAINNET]: t('Mainnet'),
};
};
export const envTriggerMapping: Record<Networks, string> = {
...envNameMapping,
export const useEnvTriggerMapping: () => Record<Networks, string> = () => {
const t = useT();
return {
...useEnvNameMapping(),
[Networks.TESTNET]: t('Fairground'),
};
};
export const envDescriptionMapping: Record<Networks, string> = {
export const useEnvDescriptionMapping: () => Record<Networks, string> = () => {
const t = useT();
return {
[Networks.CUSTOM]: '',
[Networks.VALIDATOR_TESTNET]: t('The validator deployed testnet'),
[Networks.MAINNET_MIRROR]: t('The mainnet-mirror network'),
@ -39,6 +49,7 @@ export const envDescriptionMapping: Record<Networks, string> = {
'Public testnet run by the Vega team, often used for incentives'
),
[Networks.MAINNET]: t('The vega mainnet'),
};
};
const standardNetworkKeys = [Networks.MAINNET, Networks.TESTNET];
@ -53,10 +64,11 @@ type NetworkLabelProps = {
isAvailable: boolean;
};
const getLabelText = ({
const useLabelText = ({
isCurrent = false,
isAvailable = false,
}: NetworkLabelProps) => {
const t = useT();
if (isCurrent) {
return ` (${t('current')})`;
}
@ -71,7 +83,7 @@ const NetworkLabel = ({
isAvailable = false,
}: NetworkLabelProps) => (
<span className="text-vega-dark-300 dark:text-vega-light-300">
{getLabelText({ isCurrent, isAvailable })}
{useLabelText({ isCurrent, isAvailable })}
</span>
);
@ -86,6 +98,7 @@ export const NetworkSwitcher = ({
currentNetwork,
className,
}: NetworkSwitcherProps) => {
const t = useT();
const { VEGA_ENV, VEGA_NETWORKS } = useEnvironment();
const tokenLink = useLinks(DApp.Governance);
const [isOpen, setOpen] = useState(false);
@ -102,6 +115,9 @@ export const NetworkSwitcher = ({
);
const current = currentNetwork || VEGA_ENV;
const envTriggerMapping = useEnvTriggerMapping();
const envNameMapping = useEnvNameMapping();
const envDescriptionMapping = useEnvDescriptionMapping();
return (
<DropdownMenu

View File

@ -1,6 +1,6 @@
import { t } from '@vegaprotocol/i18n';
import { Button, Splash } from '@vegaprotocol/ui-toolkit';
import { useNodeSwitcherStore } from '../../hooks/use-node-switcher-store';
import { useT } from '../../use-t';
export const NodeFailure = ({
title,
@ -10,6 +10,7 @@ export const NodeFailure = ({
error?: string | null;
}) => {
const setNodeSwitcher = useNodeSwitcherStore((store) => store.setDialogOpen);
const t = useT();
return (
<Splash>
<div className="text-center">

View File

@ -1,6 +1,6 @@
import type { ReactNode } from 'react';
import classnames from 'classnames';
import { t } from '@vegaprotocol/i18n';
import { useT } from '../../use-t';
type LayoutCellProps = {
label?: string;
@ -17,6 +17,7 @@ export const LayoutCell = ({
children,
dataTestId,
}: LayoutCellProps) => {
const t = useT();
const classes = [
'lg:text-right flex justify-between lg:block',
'my-2 lg:my-0',

View File

@ -1,6 +1,5 @@
import { useCallback, useState } from 'react';
import { isValidUrl } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
Button,
ButtonLink,
@ -15,8 +14,10 @@ import { LayoutCell } from './layout-cell';
import { LayoutRow } from './layout-row';
import { ApolloWrapper } from './apollo-wrapper';
import { RowData } from './row-data';
import { useT } from '../../use-t';
export const NodeSwitcher = ({ closeDialog }: { closeDialog: () => void }) => {
const t = useT();
const { nodes, setUrl, status, VEGA_ENV, VEGA_URL } = useEnvironment(
(store) => ({
status: store.status,
@ -73,7 +74,8 @@ export const NodeSwitcher = ({ closeDialog }: { closeDialog: () => void }) => {
<div>
<p className="mb-2 text-sm text-center">
{t(
`This app will only work on ${VEGA_ENV}. Select a node to connect to.`
'This app will only work on {{VEGA_ENV}}. Select a node to connect to.',
{ VEGA_ENV }
)}
</p>
<TradingRadioGroup
@ -153,6 +155,7 @@ const CustomRowWrapper = ({
nodeRadio,
onBlockHeight,
}: CustomRowWrapperProps) => {
const t = useT();
const [displayCustom, setDisplayCustom] = useState(false);
const [error, setError] = useState<string | null>(null);
const showInput = nodeRadio === CUSTOM_NODE_KEY || nodes.length <= 0;

View File

@ -1,5 +1,4 @@
import { isValidUrl } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { TradingRadio } from '@vegaprotocol/ui-toolkit';
import { useEffect, useState } from 'react';
import { CUSTOM_NODE_KEY } from '../../types';
@ -8,6 +7,7 @@ import {
useNodeCheckTimeUpdateSubscription,
} from '../../utils/__generated__/NodeCheck';
import { LayoutCell } from './layout-cell';
import { useT } from '../../use-t';
export const POLL_INTERVAL = 1000;
export const SUBSCRIPTION_TIMEOUT = 3000;
@ -128,6 +128,7 @@ export const RowData = ({
highestBlock,
onBlockHeight,
}: RowDataProps) => {
const t = useT();
const { status: subStatus } = useNodeSubscriptionStatus();
const { status, currentBlockHeight } = useNodeBasicStatus();
const { responseTime } = useResponseTime(url, currentBlockHeight); // measure response time (ms) every time we get data (block height)
@ -150,7 +151,7 @@ export const RowData = ({
hasError={status === Result.Failed}
dataTestId="response-time-cell"
>
{display(status, formatResponseTime(responseTime))}
{display(status, formatResponseTime(responseTime), t('n/a'))}
</LayoutCell>
<LayoutCell
label={t('Block')}
@ -169,7 +170,7 @@ export const RowData = ({
status === Result.Failed ? 'failed' : currentBlockHeight
}
>
{display(status, currentBlockHeight)}
{display(status, currentBlockHeight, t('n/a'))}
</span>
</LayoutCell>
<LayoutCell
@ -190,7 +191,7 @@ const formatResponseTime = (time: number | undefined) =>
const display = (
status: Result,
yes: string | number | undefined,
no = t('n/a')
no: string | number | undefined
) => {
switch (status) {
case Result.Successful:

View File

@ -1,6 +1,5 @@
import { parse as tomlParse } from 'toml';
import { isValidUrl, LocalStorage } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useEffect } from 'react';
import { create } from 'zustand';
import { createClient } from '@vegaprotocol/apollo-client';
@ -64,7 +63,7 @@ export const useEnvironment = create<EnvStore>()((set, get) => ({
set({ ...safeVars });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (err: any) {
const headline = t('Error processing the Vega environment');
const headline = 'Error processing the Vega environment';
set({
status: 'failed',
error: headline,
@ -108,7 +107,7 @@ export const useEnvironment = create<EnvStore>()((set, get) => ({
if (!nodes || !nodes.length) {
set({
status: 'failed',
error: t(`Failed to fetch node config from ${state.VEGA_CONFIG_URL}`),
error: `Failed to fetch node config from ${state.VEGA_CONFIG_URL}`,
});
return;
}
@ -140,9 +139,9 @@ export const useEnvironment = create<EnvStore>()((set, get) => ({
else {
set({
status: 'failed',
error: t('No node found'),
error: 'No node found',
});
console.warn(t('No suitable vega node was found'));
console.warn('No suitable vega node was found');
}
},
}));

View File

@ -56,7 +56,7 @@ function setup(
describe('useNodeHealth', () => {
it.each([
{
/*{
core: 1,
node: 1,
expectedText: 'Operational',
@ -67,7 +67,7 @@ describe('useNodeHealth', () => {
node: 5,
expectedText: 'Operational',
expectedIntent: Intent.Success,
},
},*/
{
core: 10,
node: 5,

View File

@ -4,8 +4,8 @@ import { useHeaderStore } from '@vegaprotocol/apollo-client';
import { useEnvironment } from './use-environment';
import { useNavigatorOnline } from '@vegaprotocol/react-helpers';
import { Intent } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { isTestEnv } from '@vegaprotocol/utils';
import { useT } from '../use-t';
const POLL_INTERVAL = 1000;
const BLOCK_THRESHOLD = 3;
@ -13,6 +13,7 @@ const ERROR_LATENCY = 10000;
const WARNING_LATENCY = 3000;
export const useNodeHealth = () => {
const t = useT();
const online = useNavigatorOnline();
const url = useEnvironment((store) => store.VEGA_URL);
const headerStore = useHeaderStore();
@ -50,7 +51,7 @@ export const useNodeHealth = () => {
const [text, intent] = useMemo(() => {
let intent = Intent.Success;
let text = 'Operational';
let text = t('Operational');
if (!online) {
text = t('Offline');
@ -60,23 +61,38 @@ export const useNodeHealth = () => {
text = t('Non operational');
intent = Intent.Danger;
} else if (blockUpdateMsLatency > ERROR_LATENCY) {
text = t('Erroneous latency ( >%s sec): %s sec', [
(ERROR_LATENCY / 1000).toString(),
(blockUpdateMsLatency / 1000).toFixed(2),
]);
text = t(
'Erroneous latency ( >{{errorLatency}} sec): {{blockUpdateLatency}} sec',
{
nsSeparator: '|',
replace: {
errorLatency: (ERROR_LATENCY / 1000).toString(),
blockUpdateLatency: (blockUpdateMsLatency / 1000).toFixed(2),
},
}
);
intent = Intent.Danger;
} else if (blockDiff >= BLOCK_THRESHOLD) {
text = t(`%s Blocks behind`, String(blockDiff));
text = t('blocksBehind', {
defaultValue: '{{count}} Blocks behind',
replace: { count: blockDiff },
});
intent = Intent.Warning;
} else if (blockUpdateMsLatency > WARNING_LATENCY) {
text = t('Warning delay ( >%s sec): %s sec', [
(WARNING_LATENCY / 1000).toString(),
(blockUpdateMsLatency / 1000).toFixed(2),
]);
text = t(
'Warning delay ( >{{warningLatency}} sec): {{blockUpdateLatency}} sec',
{
nsSeparator: '|',
replace: {
warningLatency: (WARNING_LATENCY / 1000).toString(),
blockUpdateLatency: (blockUpdateMsLatency / 1000).toFixed(2),
},
}
);
intent = Intent.Warning;
}
return [text, intent];
}, [online, blockDiff, blockUpdateMsLatency]);
}, [online, blockDiff, blockUpdateMsLatency, t]);
return {
datanodeBlockHeight: headers?.blockHeight,

View File

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

View File

@ -1,3 +1,4 @@
export { default as i18n_en } from './lib/i18n-en.json';
export * from './lib/fills-manager';
export * from './lib/fills-data-provider';
export * from './lib/__generated__/Fills';

View File

@ -2,7 +2,7 @@ import {
ActionsDropdown,
TradingDropdownCopyItem,
} from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { useT } from './use-t';
export const FillActionsDropdown = ({
tradeId,
@ -13,6 +13,7 @@ export const FillActionsDropdown = ({
buyOrderId: string;
sellOrderId: string;
}) => {
const t = useT();
return (
<ActionsDropdown data-testid="fill-actions-content">
<TradingDropdownCopyItem value={tradeId} text={t('Copy trade ID')} />

View File

@ -1,6 +1,5 @@
import { type AgGridReact } from 'ag-grid-react';
import { useCallback, useRef, useState } from 'react';
import { t } from '@vegaprotocol/i18n';
import { FillsTable } from './fills-table';
import { type useDataGridEvents } from '@vegaprotocol/datagrid';
import { Pagination } from '@vegaprotocol/datagrid';
@ -10,6 +9,7 @@ import {
type TradesSubscriptionFilter,
} from '@vegaprotocol/types';
import { fillsWithMarketProvider } from './fills-data-provider';
import { useT } from './use-t';
interface FillsManagerProps {
partyId: string;
@ -22,6 +22,7 @@ export const FillsManager = ({
onMarketClick,
gridProps,
}: FillsManagerProps) => {
const t = useT();
const gridRef = useRef<AgGridReact | null>(null);
const filter: TradesFilter | TradesSubscriptionFilter = {
partyIds: [partyId],

View File

@ -12,7 +12,6 @@ import {
getDateTimeFormat,
isNumeric,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import * as Schema from '@vegaprotocol/types';
import {
AgGrid,
@ -35,6 +34,7 @@ import {
} from './__generated__/Fills';
import { FillActionsDropdown } from './fill-actions-dropdown';
import { getAsset } from '@vegaprotocol/markets';
import { useT } from './use-t';
const TAKER = 'Taker';
const MAKER = 'Maker';
@ -48,6 +48,7 @@ export type Props = (AgGridReactProps | AgReactUiProps) & {
export const FillsTable = forwardRef<AgGridReact, Props>(
({ partyId, onMarketClick, ...props }, ref) => {
const t = useT();
const columnDefs = useMemo<ColDef[]>(
() => [
{
@ -144,7 +145,7 @@ export const FillsTable = forwardRef<AgGridReact, Props>(
...COL_DEFS.actions,
},
],
[onMarketClick, partyId]
[onMarketClick, partyId, t]
);
return (
<AgGrid
@ -323,6 +324,7 @@ const FeesBreakdownTooltip = ({
value: market,
partyId,
}: ITooltipParams<Trade, Trade['market']> & { partyId?: string }) => {
const t = useT();
if (!market || !data) {
return null;
}
@ -404,6 +406,7 @@ export const FeesDiscountBreakdownTooltip = ({
data,
partyId,
}: ITooltipParams<Trade, Trade['market']> & { partyId?: string }) => {
const t = useT();
if (!data || !data.market) {
return null;
}

View File

@ -0,0 +1,3 @@
{
"Size": "*Size"
}

View File

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

View File

@ -1,3 +1,4 @@
export { default as i18n_en } from './lib/i18n-en.json';
export * from './lib/funding-payments-manager';
export * from './lib/funding-payments-data-provider';
export * from './lib/__generated__/FundingPayments';

View File

@ -1,11 +1,11 @@
import { type AgGridReact } from 'ag-grid-react';
import { useCallback, useRef, useState } from 'react';
import { t } from '@vegaprotocol/i18n';
import { FundingPaymentsTable } from './funding-payments-table';
import { Pagination } from '@vegaprotocol/datagrid';
import { type useDataGridEvents } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { fundingPaymentsWithMarketProvider } from './funding-payments-data-provider';
import { useT } from './use-t';
interface FundingPaymentsManagerProps {
partyId: string;
@ -20,6 +20,7 @@ export const FundingPaymentsManager = ({
onMarketClick,
gridProps,
}: FundingPaymentsManagerProps) => {
const t = useT();
const gridRef = useRef<AgGridReact | null>(null);
const [hasDisplayedRow, setHasDisplayedRow] = useState<boolean | undefined>(
undefined

View File

@ -11,7 +11,6 @@ import {
isNumeric,
toBigNum,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
AgGrid,
@ -30,6 +29,7 @@ import type { FundingPayment } from './funding-payments-data-provider';
import { getAsset } from '@vegaprotocol/markets';
import classNames from 'classnames';
import { useT } from './use-t';
const defaultColDef = {
resizable: true,
@ -56,6 +56,7 @@ const formatAmount = ({
export const FundingPaymentsTable = forwardRef<AgGridReact, Props>(
({ onMarketClick, ...props }, ref) => {
const t = useT();
const columnDefs = useMemo<ColDef[]>(
() => [
{
@ -113,7 +114,7 @@ export const FundingPaymentsTable = forwardRef<AgGridReact, Props>(
},
},
],
[onMarketClick]
[onMarketClick, t]
);
return (
<AgGrid

View File

@ -0,0 +1,3 @@
{
"Market": "*Market"
}

View File

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

View File

@ -1 +1,9 @@
export * from './lib/i18n';
import en_accounts from './locales/en/accounts.json';
import en_governance from './locales/en/governance.json';
export const locales = {
en: {
accounts: en_accounts,
governance: en_governance,
},
};

View File

@ -0,0 +1,58 @@
{
"{{balance}} above <0>maintenance level</0>": "{{balance}} above <0>maintenance level</0>",
"Account type": "Account type",
"Amount": "Amount",
"Amount below minimum requirement set by transfer.minTransferQuantumMultiple": "Amount below minimum requirement set by transfer.minTransferQuantumMultiple",
"Amount below minimum requirements for partial transfer. Use max to bypass": "Amount below minimum requirements for partial transfer. Use max to bypass",
"Amount cannot be 0": "Amount cannot be 0",
"Amount to be transferred": "Amount to be transferred",
"Asset": "Asset",
"Asset is the collateral that is deposited into the Vega protocol.": "Asset is the collateral that is deposited into the Vega protocol.",
"Available": "Available",
"Balance": "Balance",
"balance": "balance",
"Cannot transfer to the same account type for the connected key": "Cannot transfer to the same account type for the connected key",
"Collateral not used": "Collateral not used",
"Confirm transfer": "Confirm transfer",
"Copy asset ID": "Copy asset ID",
"Current key: {{pubKey}}": "Current key: {{pubKey}}",
"Currently allocated to a market as margin or bond. Check the breakdown for details.": "Currently allocated to a market as margin or bond. Check the breakdown for details.",
"Deposit": "Deposit",
"Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.": "Deposited on the network, but not allocated to a market. Free to use for placing orders or providing liquidity.",
"Enter manually": "Enter manually",
"From account": "From account",
"Include transfer fee": "Include transfer fee",
"initial level": "initial level",
"maintenance level": "maintenance level",
"Margin health": "Margin health",
"Market": "Market",
"No accounts": "No accounts",
"None": "None",
"Please select": "Please select",
"Please select an asset": "Please select an asset",
"release level": "release level",
"search level": "search level",
"Select from wallet": "Select from wallet",
"The fee will be taken from the amount you are transferring.": "The fee will be taken from the amount you are transferring.",
"The total amount of each asset on this key. Includes used and available collateral.": "The total amount of each asset on this key. Includes used and available collateral.",
"The total amount taken from your account. The amount to be transferred plus the fee.": "The total amount taken from your account. The amount to be transferred plus the fee.",
"The total amount to be transferred (without the fee)": "The total amount to be transferred (without the fee)",
"The transfer fee is set by the network parameter transfer.fee.factor, currently set to {{feeFactor}}": "The transfer fee is set by the network parameter transfer.fee.factor, currently set to {{feeFactor}}",
"To account": "To account",
"To Vega key": "To Vega key",
"Total": "Total",
"Total amount (with fee)": "Total amount (with fee)",
"Transfer": "Transfer",
"Transfer fee": "Transfer fee",
"TRANSFER_FUNDS_TO_ANOTHER_KNOWN_VEGA_KEY": "Transfer funds to another Vega key <0>{{pubKey}}</0>. If you are at all unsure, stop and seek advice.",
"TRANSFER_FUNDS_TO_ANOTHER_VEGA_KEY": "Transfer funds to another Vega key. If you are at all unsure, stop and seek advice.",
"usage breakdown": "usage breakdown",
"Use max": "Use max",
"Used": "Used",
"View asset details": "View asset details",
"View on Etherscan": "View on Etherscan",
"View usage breakdown": "View usage breakdown",
"Withdraw": "Withdraw",
"You cannot transfer more than available": "You cannot transfer more than available",
"You have {{value}} {{symbol}} in total.": "You have {{value}} {{symbol}} in total."
}

View File

@ -0,0 +1,52 @@
{
"A Vega builtin asset": "A Vega builtin asset",
"An asset originated from an Ethereum ERC20 Token": "An asset originated from an Ethereum ERC20 Token",
"Asset can be used on the Vega network": "Asset can be used on the Vega network",
"Asset details - {{symbol}}": "Asset details - {{symbol}}",
"Asset has been proposed to the network": "Asset has been proposed to the network",
"Asset has been rejected": "Asset has been rejected",
"Asset needs to be added to the Ethereum bridge": "Asset needs to be added to the Ethereum bridge",
"Asset not found": "Asset not found",
"Builtin asset": "Builtin asset",
"Close": "Close",
"Contract address": "Contract address",
"Copy address to clipboard": "Copy address to clipboard",
"Copy id to clipboard": "Copy id to clipboard",
"Decimals": "Decimals",
"Enabled": "Enabled",
"ERC20": "ERC20",
"Fetching balance…": "Fetching balance…",
"Global reward pool account balance": "Global reward pool account balance",
"ID": "ID",
"Infrastructure fee account balance": "Infrastructure fee account balance",
"Lifetime limit": "Lifetime limit",
"Liquidity provision fee reward account balance": "Liquidity provision fee reward account balance",
"Maker paid fees account balance": "Maker paid fees account balance",
"Maker received fees account balance": "Maker received fees account balance",
"Market proposer reward account balance": "Market proposer reward account balance",
"Max faucet amount": "Max faucet amount",
"Maximum amount that can be requested by a party through the built-in asset faucet at a time": "Maximum amount that can be requested by a party through the built-in asset faucet at a time",
"Name": "Name",
"No data": "No data",
"Number of decimal / precision handled by this asset": "Number of decimal / precision handled by this asset",
"Pending listing": "Pending listing",
"Proposed": "Proposed",
"Quantum": "Quantum",
"Rejected": "Rejected",
"Status": "Status",
"Symbol": "Symbol",
"The address of the contract for the token, on the ethereum network": "The address of the contract for the token, on the ethereum network",
"The global rewards acquired in this asset": "The global rewards acquired in this asset",
"The infrastructure fee account in this asset": "The infrastructure fee account in this asset",
"The lifetime deposit limit per address. Note: this is a temporary measure that can be changed or removed through governance": "The lifetime deposit limit per address. Note: this is a temporary measure that can be changed or removed through governance",
"The minimum economically meaningful amount of the asset": "The minimum economically meaningful amount of the asset",
"The rewards acquired based on fees received for being a maker on trades": "The rewards acquired based on fees received for being a maker on trades",
"The rewards acquired based on the fees paid to makers in this asset": "The rewards acquired based on the fees paid to makers in this asset",
"The rewards acquired based on the liquidity provision fees in this asset": "The rewards acquired based on the liquidity provision fees in this asset",
"The rewards acquired based on the market proposer reward in this asset": "The rewards acquired based on the market proposer reward in this asset",
"The status of the asset in the Vega network": "The status of the asset in the Vega network",
"There is 1 unit of the settlement asset ({{assetSymbol}}) to every 1 quote unit.": "There is 1 unit of the settlement asset ({{assetSymbol}}) to every 1 quote unit.",
"Type": "Type",
"WITHDRAW_THRESHOLD_TOOLTIP_TEXT": "The maximum you can withdraw instantly. There's no limit on the size of a withdrawal, but all withdrawals over the threshold will have a delay time added to them",
"Withdrawal threshold": "Withdrawal threshold"
}

View File

@ -0,0 +1,5 @@
{
"Indicators": "Indicators",
"Interval: {{interval}}": "Interval: {{interval}}",
"No open orders": "No open orders"
}

View File

@ -0,0 +1,21 @@
{
"{{orderType}} (Iceberg)": "{{orderType}} (Iceberg)",
"{{reference}} {{side}} {{offset}} Peg limit": "{{reference}} {{side}} {{offset}} Peg limit",
"Depending on data node retention you may not be able see the full history": "Depending on data node retention you may not be able see the full history",
"End": "End",
"Liquidity provision": "Liquidity provision",
"Load more": "Load more",
"Loading...": "Loading...",
"No data": "No data",
"No rows matching selected filters": "No rows matching selected filters",
"paginationAllLoaded": "all {{count}} rows loaded",
"paginationAllLoaded_one": "all {{count}} row loaded",
"paginationAllLoaded_other": "all {{count}} rows loaded",
"paginationLoaded": "{{count}} rows loaded",
"paginationLoaded_one": "{{count}} row loaded",
"paginationLoaded_other": "{{count}} rows loaded",
"Reset": "Reset",
"Start": "Start",
"The earliest data that can be queried is {{maxSubDays}} days ago.": "The earliest data that can be queried is {{maxSubDays}} days ago.",
"The maximum time range that can be queried is {{maxDaysRange}} days.": "The maximum time range that can be queried is {{maxDaysRange}} days."
}

View File

@ -0,0 +1,132 @@
{
"\"Post only\" can not be used on \"Fill or Kill\" or \"Immediate or Cancel\" orders.": "\"Post only\" can not be used on \"Fill or Kill\" or \"Immediate or Cancel\" orders.",
"\"Post only\" will ensure the order is not filled immediately but is placed on the order book as a passive order. When the order is processed it is either stopped (if it would not be filled immediately), or placed in the order book as a passive order until the price taker matches with it.": "\"Post only\" will ensure the order is not filled immediately but is placed on the order book as a passive order. When the order is processed it is either stopped (if it would not be filled immediately), or placed in the order book as a passive order until the price taker matches with it.",
"\"Reduce only\" can be used only with non-persistent orders, such as \"Fill or Kill\" or \"Immediate or Cancel\".": "\"Reduce only\" can be used only with non-persistent orders, such as \"Fill or Kill\" or \"Immediate or Cancel\".",
"\"Reduce only\" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.": "\"Reduce only\" will ensure that this order will not increase the size of an open position. When the order is matched, it will only trade enough volume to bring your open volume towards 0 but never change the direction of your position. If applied to a limit order that is not instantly filled, the order will be stopped.",
"{{amount}} {{assetSymbol}} is currently required": "{{amount}} {{assetSymbol}} is currently required",
"{{triggerTrailingPercentOffset}}% trailing": "{{triggerTrailingPercentOffset}}% trailing",
"A release candidate for the staging environment": "A release candidate for the staging environment",
"above": "above",
"Advanced": "Advanced",
"An estimate of the most you would be expected to pay in fees, in the market's settlement asset {{assetSymbol}}. Fees estimated are \"taker\" fees and will only be payable if the order trades aggressively. Rebate equal to the maker portion will be paid to the trader if the order trades passively.": "An estimate of the most you would be expected to pay in fees, in the market's settlement asset {{assetSymbol}}. Fees estimated are \"taker\" fees and will only be payable if the order trades aggressively. Rebate equal to the maker portion will be paid to the trader if the order trades passively.",
"Any orders placed now will not trade until the auction ends": "Any orders placed now will not trade until the auction ends",
"below": "below",
"Cancel": "Cancel",
"Closed": "Closed",
"Closing on {{time}}": "Closing on {{time}}",
"Could not load market": "Could not load market",
"Current margin allocation": "Current margin allocation",
"Custom": "Custom",
"Deduction from collateral": "Deduction from collateral",
"DEDUCTION_FROM_COLLATERAL_TOOLTIP_TEXT": "To cover the required margin, this amount will be drawn from your general ({{assetSymbol}}) account.",
"Deposit {{assetSymbol}}": "Deposit {{assetSymbol}}",
"Devnet": "Devnet",
"EST_TOTAL_MARGIN_TOOLTIP_TEXT": "Estimated total margin that will cover open positions, active orders and this order.",
"Est. uncrossing price": "Est. uncrossing price",
"Est. uncrossing vol": "Est. uncrossing vol",
"Expire": "Expire",
"Expiry time/date": "Expiry time/date",
"Fairground": "Fairground",
"Fairground testnet": "Fairground testnet",
"Fees": "Fees",
"Find out more": "Find out more",
"For full details please see <0>liquidation price estimate documentation</0>.": "For full details please see <0>liquidation price estimate documentation</0>.",
"Iceberg": "Iceberg",
"ICEBERG_TOOLTIP": "Trade only a fraction of the order size at once. After the peak size of the order has traded, the size is reset. This is repeated until the order is cancelled, expires, or its full volume trades away. For example, an iceberg order with a size of 1000 and a peak size of 100 will effectively be split into 10 orders with a size of 100 each. Note that the full volume of the order is not hidden and is still reflected in the order book.",
"Infrastructure fee": "Infrastructure fee",
"Limit": "Limit",
"Liquidation": "Liquidation",
"LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT": "This is an approximation for the liquidation price for that particular contract position, assuming nothing else changes, which may affect your margin and collateral balances.",
"Liquidity fee": "Liquidity fee",
"Long": "Long",
"Mainnet": "Mainnet",
"Mainnet-mirror": "Mainnet-mirror",
"Make a deposit": "Make a deposit",
"Maker fee": "Maker fee",
"Margin required": "Margin required",
"MARGIN_ACCOUNT_TOOLTIP_TEXT": "Margin account balance.",
"MARGIN_DIFF_TOOLTIP_TEXT": "The additional margin required for your new position (taking into account volume and open orders), compared to your current margin. Measured in the market's settlement asset ({{assetSymbol}}).",
"Market": "Market",
"Minimum size": "Minimum size",
"Minimum visible size cannot be greater than the peak size ({{peakSize}})": "Minimum visible size cannot be greater than the peak size ({{peakSize}})",
"Minimum visible size cannot be lower than {{sizeStep}}": "Minimum visible size cannot be lower than {{sizeStep}}",
"No public key selected": "No public key selected",
"No trading enabled for this market.": "No trading enabled for this market.",
"Not enough liquidity to open": "Not enough liquidity to open",
"Notional": "Notional",
"NOTIONAL_SIZE_TOOLTIP_TEXT": "The notional size represents the position size in the settlement asset {{quoteName}} of the futures contract. This is calculated by multiplying the number of contracts by the prices of the contract. For example 10 contracts traded at a price of $50 has a notional size of $500.",
"OCO": "OCO",
"One cancels another": "One cancels another",
"Only limit orders are permitted when market is in auction": "Only limit orders are permitted when market is in auction",
"Peak size": "Peak size",
"Peak size cannot be greater than the size ({{size}})": "Peak size cannot be greater than the size ({{size}})",
"Peak size cannot be lower than {{stepSize}}": "Peak size cannot be lower than {{stepSize}}",
"Place limit order": "Place limit order",
"Place limit stop order": "Place limit stop order",
"Place market order": "Place market order",
"Place market stop order": "Place market stop order",
"Place OCO stop order": "Place OCO stop order",
"Post only": "Post only",
"Price": "Price",
"Price cannot be lower than {{priceStep}}": "Price cannot be lower than {{priceStep}}",
"Projected margin": "Projected margin",
"Propose a network parameter change": "Propose a network parameter change",
"Public testnet run by the Vega team, often used for incentives": "Public testnet run by the Vega team, often used for incentives",
"Reduce only": "Reduce only",
"Referral discount": "Referral discount",
"Short": "Short",
"Size": "Size",
"Size cannot be lower than {{sizeStep}}": "Size cannot be lower than {{sizeStep}}",
"sizeAtPrice-market": "market",
"Stagnet": "Stagnet",
"Stop": "Stop",
"Stop Limit": "Stop Limit",
"Stop Market": "Stop Market",
"Stop order will be triggered immediately": "Stop order will be triggered immediately",
"Strategy": "Strategy",
"Submit": "Submit",
"terminated": "terminated",
"The expiry date that you have entered appears to be in the past": "The expiry date that you have entered appears to be in the past",
"The latest Vega code auto-deployed": "The latest Vega code auto-deployed",
"The mainnet-mirror network": "The mainnet-mirror network",
"The maximum volume that can be traded at once. Must be less than the total size of the order.": "The maximum volume that can be traded at once. Must be less than the total size of the order.",
"The validator deployed testnet": "The validator deployed testnet",
"The vega mainnet": "The vega mainnet",
"There is a limit of {{maxNumberOfOrders}} active stop orders per market. Orders submitted above the limit will be immediately rejected.": "There is a limit of {{maxNumberOfOrders}} active stop orders per market. Orders submitted above the limit will be immediately rejected.",
"This is a new market in an opening auction to determine a fair mid-price before starting continuous trading.": "This is a new market in an opening auction to determine a fair mid-price before starting continuous trading.",
"This is the standard trading mode where trades are executed whenever orders are received.": "This is the standard trading mode where trades are executed whenever orders are received.",
"This market has been suspended via a governance vote and can be resumed or terminated by further votes.": "This market has been suspended via a governance vote and can be resumed or terminated by further votes.",
"This market is {{marketState}} and not accepting orders": "This market is {{marketState}} and not accepting orders",
"This market is in auction due to high price volatility.": "This market is in auction due to high price volatility.",
"This market is in auction until it reaches sufficient liquidity.": "This market is in auction until it reaches sufficient liquidity.",
"This market is in opening auction until it has reached enough liquidity to move into continuous trading.": "This market is in opening auction until it has reached enough liquidity to move into continuous trading.",
"This market may have sufficient liquidity but there are not enough priced limit orders in the order book, which are required to deploy liquidity commitment pegged orders.": "This market may have sufficient liquidity but there are not enough priced limit orders in the order book, which are required to deploy liquidity commitment pegged orders.",
"Time in force": "Time in force",
"TIME_IN_FORCE_SELECTOR_LIQUIDITY_MONITORING_AUCTION": "This market is in auction until it reaches <0>sufficient liquidity</0>. Until the auction ends, you can only place GFA, GTT, or GTC limit orders.",
"TIME_IN_FORCE_SELECTOR_PRICE_MONITORING_AUCTION": "This market is in auction due to <0>high price volatility</0>. Until the auction ends, you can only place GFA, GTT, or GTC limit orders.",
"Total fees": "Total fees",
"Total margin available": "Total margin available",
"TOTAL_MARGIN_AVAILABLE": "Total margin available = general {{assetSymbol}} balance ({{generalAccountBalance}} {{assetSymbol}}) + margin balance ({{marginAccountBalance}} {{assetSymbol}}) - maintenance level ({{marginMaintenance}} {{assetSymbol}}).",
"Trading terminated": "Trading terminated",
"Trailing percent offset cannot be higher than 99.9": "Trailing percent offset cannot be higher than 99.9",
"Trailing percent offset cannot be lower than {{trailingPercentOffsetStep}}": "Trailing percent offset cannot be lower than {{trailingPercentOffsetStep}}",
"Trailing percentage offset": "Trailing percentage offset",
"Trigger": "Trigger",
"Type": "Type",
"TYPE_SELECTOR_LIQUIDITY_MONITORING_AUCTION": "This market is in auction until it reaches <0>sufficient liquidity</0>. Only limit orders are permitted when market is in auction.",
"TYPE_SELECTOR_PRICE_MONITORING_AUCTION": "This market is in auction due to <0>high price volatility</0>. Only limit orders are permitted when market is in auction.",
"Until the auction ends, you can only place GFA, GTT, or GTC limit orders": "Until the auction ends, you can only place GFA, GTT, or GTC limit orders",
"VALIDATOR_TESTNET": "VALIDATOR_TESTNET",
"Volume discount": "Volume discount",
"When the order trades and its size falls below this threshold, it will be reset to the peak size and moved to the back of the priority order. Must be less than or equal to peak size, and greater than 0.": "When the order trades and its size falls below this threshold, it will be reset to the peak size and moved to the back of the priority order. Must be less than or equal to peak size, and greater than 0.",
"You have only {{amount}}.": "You have only {{amount}}.",
"You may not have enough margin available to open this position.": "You may not have enough margin available to open this position.",
"You need {{symbol}} in your wallet to trade in this market.": "You need {{symbol}} in your wallet to trade in this market.",
"You need provide a expiry time/date": "You need provide a expiry time/date",
"You need provide a price": "You need provide a price",
"You need provide a trailing percent offset": "You need provide a trailing percent offset",
"You need to connect your own wallet to start trading on this market": "You need to connect your own wallet to start trading on this market",
"You need to provide a minimum visible size": "You need to provide a minimum visible size",
"You need to provide a peak size": "You need to provide a peak size",
"You need to provide a size": "You need to provide a size"
}

View File

@ -0,0 +1,44 @@
{
"{{assetSymbol}} has been deposited in your Ethereum wallet": "{{assetSymbol}} has been deposited in your Ethereum wallet",
"Amount": "Amount",
"Approval failed": "Approval failed",
"Approve {{assetSymbol}}": "Approve {{assetSymbol}}",
"Approve again to deposit more than {{allowance}}": "Approve again to deposit more than {{allowance}}",
"Asset": "Asset",
"Balance available": "Balance available",
"Before you can make a deposit of your chosen asset, {{assetSymbol}}, you need to approve its use in your Ethereum wallet": "Before you can make a deposit of your chosen asset, {{assetSymbol}}, you need to approve its use in your Ethereum wallet",
"Confirm the transaction in your Ethereum wallet to use the {{assetSymbol}} faucet": "Confirm the transaction in your Ethereum wallet to use the {{assetSymbol}} faucet",
"Connect": "Connect",
"Connect Ethereum wallet": "Connect Ethereum wallet",
"Could not verify balances of account": "Could not verify balances of account",
"Deposit": "Deposit",
"Disconnect": "Disconnect",
"Enter manually": "Enter manually",
"Ethereum deposit cap": "Ethereum deposit cap",
"Exempt": "Exempt",
"Faucet of {{symbol}} failed": "Faucet of {{symbol}} failed",
"From (Ethereum address)": "From (Ethereum address)",
"Get {{assetSymbol}}": "Get {{assetSymbol}}",
"Go to your Ethereum wallet and approve the transaction to enable the use of {{assetSymbol}}": "Go to your Ethereum wallet and approve the transaction to enable the use of {{assetSymbol}}",
"Please select": "Please select",
"Please select an asset": "Please select an asset",
"Remaining deposit allowance": "Remaining deposit allowance",
"Select from wallet": "Select from wallet",
"The {{symbol}} faucet is not available at this time": "The {{symbol}} faucet is not available at this time",
"The deposit cap is set when you approve an asset for use with this app. To increase this cap, approve {{assetSymbol}} again and choose a higher cap. Check the documentation for your Ethereum wallet app for details.": "The deposit cap is set when you approve an asset for use with this app. To increase this cap, approve {{assetSymbol}} again and choose a higher cap. Check the documentation for your Ethereum wallet app for details.",
"The faucet transaction was rejected by the connected Ethereum wallet": "The faucet transaction was rejected by the connected Ethereum wallet",
"This app only works on {{chainId}}.": "This app only works on {{chainId}}.",
"To (Vega key)": "To (Vega key)",
"To date, {{currentDeposit}} {{assetSymbol}} has been deposited from this Ethereum address, so you can deposit up to {{remainingDeposit}} {{assetSymbol}} more.": "To date, {{currentDeposit}} {{assetSymbol}} has been deposited from this Ethereum address, so you can deposit up to {{remainingDeposit}} {{assetSymbol}} more.",
"Use maximum": "Use maximum",
"VEGA has a lifetime deposit limit of {{amount}} {{assetSymbol}} per address. This can be changed through governance": "VEGA has a lifetime deposit limit of {{amount}} {{assetSymbol}} per address. This can be changed through governance",
"View asset details": "View asset details",
"View on Etherscan": "View on Etherscan",
"You approved deposits of up to {{assetSymbol}} {{approvedAllowanceValue}}.": "You approved deposits of up to {{assetSymbol}} {{approvedAllowanceValue}}.",
"You can't deposit more than you have in your Ethereum wallet, {{amount}} {{assetSymbol}}": "You can't deposit more than you have in your Ethereum wallet, {{amount}} {{assetSymbol}}",
"You can't deposit more than your approved deposit amount, {{amount}} {{assetSymbol}}": "You can't deposit more than your approved deposit amount, {{amount}} {{assetSymbol}}",
"You can't deposit more than your remaining deposit allowance, {{amount}} {{assetSymbol}}": "You can't deposit more than your remaining deposit allowance, {{amount}} {{assetSymbol}}",
"You have exceeded the maximum number of faucet attempts allowed": "You have exceeded the maximum number of faucet attempts allowed",
"Your {{assetSymbol}} approval is being confirmed by the Ethereum network. When this is complete, you can continue your deposit": "Your {{assetSymbol}} approval is being confirmed by the Ethereum network. When this is complete, you can continue your deposit",
"Your request for funds from the {{assetSymbol}} faucet is being confirmed by the Ethereum network": "Your request for funds from the {{assetSymbol}} faucet is being confirmed by the Ethereum network"
}

View File

@ -0,0 +1,40 @@
{
"A release candidate for the staging environment": "A release candidate for the staging environment",
"Advanced": "Advanced",
"Block": "Block",
"blocksBehind_one": "{{count}} Block behind",
"blocksBehind_other": "{{count}} Blocks behind",
"Change node": "Change node",
"Check": "Check",
"Checking": "Checking",
"Connect to this node": "Connect to this node",
"current": "current",
"Custom": "Custom",
"Devnet": "Devnet",
"Erroneous latency ( >{{errorLatency}} sec): {{blockUpdateLatency}} sec": "Erroneous latency ( >{{errorLatency}} sec): {{blockUpdateLatency}} sec",
"Fairground": "Fairground",
"Fairground testnet": "Fairground testnet",
"Mainnet": "Mainnet",
"Mainnet-mirror": "Mainnet-mirror",
"n/a": "n/a",
"No": "No",
"Node": "Node",
"Non operational": "Non operational",
"not available": "not available",
"Offline": "Offline",
"Operational": "Operational",
"Other": "Other",
"Propose a network parameter change": "Propose a network parameter change",
"Public testnet run by the Vega team, often used for incentives": "Public testnet run by the Vega team, often used for incentives",
"Response time": "Response time",
"Stagnet": "Stagnet",
"Subscription": "Subscription",
"The latest Vega code auto-deployed": "The latest Vega code auto-deployed",
"The mainnet-mirror network": "The mainnet-mirror network",
"The validator deployed testnet": "The validator deployed testnet",
"The vega mainnet": "The vega mainnet",
"VALIDATOR_TESTNET": "VALIDATOR_TESTNET",
"View on Etherscan (opens in a new tab)": "View on Etherscan (opens in a new tab)",
"Warning delay ( >{{warningLatency}} sec): {{blockUpdateLatency}} sec": "Warning delay ( >{{warningLatency}} sec): {{blockUpdateLatency}} sec",
"Yes": "Yes"
}

View File

@ -0,0 +1,20 @@
{
"Copy buy order ID": "Copy buy order ID",
"Copy sell order ID": "Copy sell order ID",
"Copy trade ID": "Copy trade ID",
"Date": "Date",
"Fee": "Fee",
"Fee Discount": "Fee Discount",
"Fees to be paid by the taker.": "Fees to be paid by the taker.",
"If the market is active the maker will pay zero infrastructure and liquidity fees.": "If the market is active the maker will pay zero infrastructure and liquidity fees.",
"If the market is in monitoring auction, half of the infrastructure and liquidity fees will be paid.": "If the market is in monitoring auction, half of the infrastructure and liquidity fees will be paid.",
"Infrastructure Fee": "Infrastructure Fee",
"Market": "Market",
"No fills": "No fills",
"Notional": "Notional",
"Price": "Price",
"Referral Discount": "Referral Discount",
"Role": "Role",
"Size": "Size",
"Volume Discount": "Volume Discount"
}

Some files were not shown because too many files have changed in this diff Show More