locale/browserTag
This commit is contained in:
@@ -1,19 +0,0 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { getSeparator } from '@/lib/numbers';
|
||||
|
||||
export const useLocaleSeparators = () => {
|
||||
const [locale, setLocale] = useState(navigator.language || 'en-US');
|
||||
|
||||
useEffect(() => {
|
||||
const handler = () => setLocale(navigator.language || 'en-US');
|
||||
globalThis.addEventListener('languagechange', handler);
|
||||
return () => globalThis.removeEventListener('languagechange', handler);
|
||||
}, []);
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
group: getSeparator({ locale, separatorType: 'group' }),
|
||||
decimal: getSeparator({ locale, separatorType: 'decimal' }),
|
||||
};
|
||||
}, [locale]);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { SUPPORTED_BASE_TAGS_LOCALE_MAPPING } from '@/constants/localization';
|
||||
|
||||
import { getSelectedLocale } from '@/state/localizationSelectors';
|
||||
|
||||
import abacusStateManager from '@/lib/abacus';
|
||||
import { getSeparator } from '@/lib/numbers';
|
||||
|
||||
type LocaleContextType = ReturnType<typeof useLocaleContext>;
|
||||
const LocaleContext = createContext<LocaleContextType>({} as LocaleContextType);
|
||||
LocaleContext.displayName = 'Locale';
|
||||
|
||||
export const LocaleProvider = ({ ...props }) => (
|
||||
<LocaleContext.Provider value={useLocaleContext()} {...props} />
|
||||
);
|
||||
|
||||
const useLocaleContext = () => {
|
||||
const selectedLocale = useSelector(getSelectedLocale);
|
||||
const [browserLanguage, setBrowserLanguage] = useState(navigator.language || 'en-US');
|
||||
|
||||
useEffect(() => {
|
||||
const handler = () => setBrowserLanguage(navigator.language || 'en-US');
|
||||
globalThis.addEventListener('languagechange', handler);
|
||||
return () => globalThis.removeEventListener('languagechange', handler);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedLocale) {
|
||||
const updatedBrowserLanguage = Object.entries(SUPPORTED_BASE_TAGS_LOCALE_MAPPING).find(
|
||||
([, value]) => value === selectedLocale
|
||||
);
|
||||
|
||||
if (updatedBrowserLanguage) {
|
||||
setBrowserLanguage(selectedLocale);
|
||||
}
|
||||
}
|
||||
}, [selectedLocale]);
|
||||
|
||||
const separators = useMemo(() => {
|
||||
return {
|
||||
group: getSeparator({ browserLanguage, separatorType: 'group' }),
|
||||
decimal: getSeparator({ browserLanguage, separatorType: 'decimal' }),
|
||||
};
|
||||
}, [browserLanguage]);
|
||||
|
||||
useEffect(() => {
|
||||
abacusStateManager.setLocaleSeparators(separators);
|
||||
}, [separators]);
|
||||
|
||||
return separators;
|
||||
};
|
||||
|
||||
export const useLocaleSeparators = () => useContext(LocaleContext);
|
||||
@@ -1,12 +1,25 @@
|
||||
import type { AbacusFormatterProtocol } from '@/constants/abacus';
|
||||
|
||||
import { type LocaleSeparators, MustBigNumber, getFractionDigits } from '../numbers';
|
||||
|
||||
class AbacusFormatter implements AbacusFormatterProtocol {
|
||||
localeSeparators: LocaleSeparators;
|
||||
|
||||
constructor() {
|
||||
this.localeSeparators = { group: ',', decimal: '.' };
|
||||
}
|
||||
|
||||
setLocaleSeparators({ group, decimal }: LocaleSeparators) {
|
||||
this.localeSeparators = { group, decimal };
|
||||
}
|
||||
|
||||
percent(value: number, digits: number): string {
|
||||
return value.toString();
|
||||
return MustBigNumber(value).toFixed(digits);
|
||||
}
|
||||
|
||||
dollar(value: number, tickSize: string): string {
|
||||
return value.toString();
|
||||
const tickSizeDecimals = getFractionDigits(tickSize);
|
||||
return MustBigNumber(value).toFixed(tickSizeDecimals);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import AbacusStateNotifier from './stateNotification';
|
||||
import AbacusLocalizer from './localizer';
|
||||
import AbacusFormatter from './formatter';
|
||||
import AbacusThreading from './threading';
|
||||
import { LocaleSeparators } from '../numbers';
|
||||
|
||||
class AbacusStateManager {
|
||||
private store: RootStore | undefined;
|
||||
@@ -42,12 +43,14 @@ class AbacusStateManager {
|
||||
stateManager: InstanceType<typeof AsyncAbacusStateManager>;
|
||||
websocket: AbacusWebsocket;
|
||||
stateNotifier: AbacusStateNotifier;
|
||||
abacusFormatter: AbacusFormatter;
|
||||
|
||||
constructor() {
|
||||
this.store = undefined;
|
||||
this.currentMarket = undefined;
|
||||
this.stateNotifier = new AbacusStateNotifier();
|
||||
this.websocket = new AbacusWebsocket();
|
||||
this.abacusFormatter = new AbacusFormatter();
|
||||
|
||||
const ioImplementations = new IOImplementations(
|
||||
// @ts-ignore
|
||||
@@ -63,7 +66,7 @@ class AbacusStateManager {
|
||||
const uiImplementations = new UIImplementations(
|
||||
// @ts-ignore
|
||||
new AbacusLocalizer(),
|
||||
new AbacusFormatter()
|
||||
this.abacusFormatter
|
||||
);
|
||||
|
||||
this.stateManager = new AsyncAbacusStateManager(
|
||||
@@ -179,6 +182,10 @@ class AbacusStateManager {
|
||||
this.stateManager.closePosition(value, field);
|
||||
};
|
||||
|
||||
setLocaleSeparators = ({ group, decimal }: LocaleSeparators) => {
|
||||
this.abacusFormatter.setLocaleSeparators({ group, decimal });
|
||||
};
|
||||
|
||||
// ------ Utils ------ //
|
||||
placeOrderPayload = (): Nullable<HumanReadablePlaceOrderPayload> =>
|
||||
this.stateManager.placeOrderPayload();
|
||||
|
||||
+4
-3
@@ -1,6 +1,7 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
|
||||
export type BigNumberish = BigNumber | string | number;
|
||||
export type LocaleSeparators = { group?: string; decimal?: string };
|
||||
|
||||
export const BIG_NUMBERS = {
|
||||
ZERO: new BigNumber(0),
|
||||
@@ -56,12 +57,12 @@ export const shorternNumberForDisplay = (num?: number) =>
|
||||
* @returns separator for the given locale and separator type
|
||||
*/
|
||||
export const getSeparator = ({
|
||||
locale = navigator.language || 'en-US',
|
||||
browserLanguage = navigator.language || 'en-US',
|
||||
separatorType,
|
||||
}: {
|
||||
locale?: string;
|
||||
browserLanguage?: string;
|
||||
separatorType: Intl.NumberFormatPartTypes;
|
||||
}) =>
|
||||
Intl.NumberFormat(locale)
|
||||
Intl.NumberFormat(browserLanguage)
|
||||
.formatToParts(1000.1)
|
||||
.find?.((part) => part.type === separatorType)?.value;
|
||||
|
||||
+18
-3
@@ -13,7 +13,7 @@ import {
|
||||
} from '@/constants/abacus';
|
||||
|
||||
import { AlertType } from '@/constants/alerts';
|
||||
import { PERCENT_DECIMALS } from '@/constants/numbers';
|
||||
import { PERCENT_DECIMALS, USD_DECIMALS } from '@/constants/numbers';
|
||||
import { TRADE_ROUTE } from '@/constants/routes';
|
||||
import { PositionSide, TradeTypes } from '@/constants/trade';
|
||||
|
||||
@@ -65,9 +65,13 @@ export const hasPositionSideChanged = ({
|
||||
const formatErrorParam = ({
|
||||
value,
|
||||
format,
|
||||
stepSizeDecimals,
|
||||
tickSizeDecimals,
|
||||
}: {
|
||||
value: Nullable<string>;
|
||||
format?: Nullable<string>;
|
||||
stepSizeDecimals: Nullable<number>;
|
||||
tickSizeDecimals: Nullable<number>;
|
||||
}) => {
|
||||
switch (format) {
|
||||
case 'percent': {
|
||||
@@ -76,7 +80,11 @@ const formatErrorParam = ({
|
||||
}
|
||||
case 'size': {
|
||||
const sizeBN = MustBigNumber(value);
|
||||
return sizeBN.toFixed(0);
|
||||
return sizeBN.toFixed(stepSizeDecimals ?? 0);
|
||||
}
|
||||
case 'price': {
|
||||
const dollarBN = MustBigNumber(value);
|
||||
return `$${dollarBN.toFixed(tickSizeDecimals ?? USD_DECIMALS)}`;
|
||||
}
|
||||
default: {
|
||||
return value || '';
|
||||
@@ -90,9 +98,13 @@ const formatErrorParam = ({
|
||||
export const getTradeInputAlert = ({
|
||||
abacusInputErrors,
|
||||
stringGetter,
|
||||
stepSizeDecimals,
|
||||
tickSizeDecimals,
|
||||
}: {
|
||||
abacusInputErrors: ValidationError[];
|
||||
stringGetter: StringGetterFunction;
|
||||
stepSizeDecimals: Nullable<number>;
|
||||
tickSizeDecimals: Nullable<number>;
|
||||
}) => {
|
||||
const inputAlerts = abacusInputErrors.map(({ action: errorAction, resources, type }) => {
|
||||
const { action, text } = resources || {};
|
||||
@@ -104,7 +116,10 @@ export const getTradeInputAlert = ({
|
||||
Object.fromEntries(
|
||||
stringParams
|
||||
.toArray()
|
||||
.map(({ key, value, format }) => [key, formatErrorParam({ value, format })])
|
||||
.map(({ key, value, format }) => [
|
||||
key,
|
||||
formatErrorParam({ value, format, stepSizeDecimals, tickSizeDecimals }),
|
||||
])
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
+4
-1
@@ -11,12 +11,15 @@ import { ErrorBoundary } from './components/ErrorBoundary';
|
||||
import './index.css';
|
||||
|
||||
import App from './App';
|
||||
import { LocaleProvider } from './hooks/useLocaleSeparators';
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<ErrorBoundary>
|
||||
<StrictMode>
|
||||
<Provider store={store}>
|
||||
<HashRouter children={<App />} />
|
||||
<LocaleProvider>
|
||||
<HashRouter children={<App />} />
|
||||
</LocaleProvider>
|
||||
</Provider>
|
||||
</StrictMode>
|
||||
</ErrorBoundary>
|
||||
|
||||
@@ -99,7 +99,8 @@ export const TradeForm = ({
|
||||
const { limitPrice, triggerPrice, trailingPercent } = price || {};
|
||||
const hasUncommittedOrders = useSelector(calculateHasUncommittedOrders);
|
||||
const currentInput = useSelector(getCurrentInput);
|
||||
const { tickSizeDecimals } = useSelector(getCurrentMarketConfig, shallowEqual) || {};
|
||||
const { tickSizeDecimals, stepSizeDecimals } =
|
||||
useSelector(getCurrentMarketConfig, shallowEqual) || {};
|
||||
|
||||
const needsAdvancedOptions =
|
||||
needsGoodUntil || timeInForceOptions || executionOptions || needsPostOnly || needsReduceOnly;
|
||||
@@ -120,6 +121,8 @@ export const TradeForm = ({
|
||||
const inputAlert = getTradeInputAlert({
|
||||
abacusInputErrors: tradeErrors ?? [],
|
||||
stringGetter,
|
||||
stepSizeDecimals,
|
||||
tickSizeDecimals,
|
||||
});
|
||||
|
||||
if (placeOrderError) {
|
||||
|
||||
Reference in New Issue
Block a user