chore: updating numberFormat for other currencies and languages (#7239)

* adding currency settings option

* moving menu item to shared component

* adding supported currencies

* currency menu items

* currency url params

* currency selector e2e tests

* fixing tests

* currency icons

* removing eslint

* removing another eslint disable

* renaming to local currency

* more name changes

* design updates

* adding currencies

* changing formatter rules to formatter options

* renaming file

* fixing lint

* custom currency symbol in number formatting

* refactoring input to number formatter

* locale selection

* updating all format numbers with currency and locale

* updating portfolio

* better formatting symbols

* removing adding locale and currency

* upgrading constants to be translatable

* refactoring

* adding tests

* updating comment

* removing 0 from hardcoded input and comment
This commit is contained in:
Jack Short 2023-09-05 12:59:43 -04:00 committed by GitHub
parent cd520a9e2c
commit 60acc689ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 621 additions and 223 deletions

@ -279,16 +279,20 @@ export default function AuthenticatedHeader({ account, openSettings }: { account
{totalBalance !== undefined ? (
<FadeInColumn gap="xs">
<ThemedText.HeadlineLarge fontWeight={535} data-testid="portfolio-total-balance">
{formatNumber(totalBalance, NumberType.PortfolioBalance)}
{formatNumber({
input: totalBalance,
type: NumberType.PortfolioBalance,
})}
</ThemedText.HeadlineLarge>
<AutoRow marginBottom="20px">
{absoluteChange !== 0 && percentChange && (
<>
<PortfolioArrow change={absoluteChange as number} />
<ThemedText.BodySecondary>
{`${formatNumber(Math.abs(absoluteChange as number), NumberType.PortfolioBalance)} (${formatDelta(
percentChange
)})`}
{`${formatNumber({
input: Math.abs(absoluteChange as number),
type: NumberType.PortfolioBalance,
})} (${formatDelta(percentChange)})`}
</ThemedText.BodySecondary>
</>
)}

@ -167,18 +167,21 @@ function PositionListItem({ positionInfo }: { positionInfo: PositionInfo }) {
placement="left"
text={
<div style={{ padding: '4px 0px' }}>
<ThemedText.BodySmall>{`${formatNumber(
liquidityValue,
NumberType.PortfolioBalance
)} (liquidity) + ${formatNumber(
feeValue,
NumberType.PortfolioBalance
)} (fees)`}</ThemedText.BodySmall>
<ThemedText.BodySmall>{`${formatNumber({
input: liquidityValue,
type: NumberType.PortfolioBalance,
})} (liquidity) + ${formatNumber({
input: feeValue,
type: NumberType.PortfolioBalance,
})} (fees)`}</ThemedText.BodySmall>
</div>
}
>
<ThemedText.SubHeader>
{formatNumber((liquidityValue ?? 0) + (feeValue ?? 0), NumberType.PortfolioBalance)}
{formatNumber({
input: (liquidityValue ?? 0) + (feeValue ?? 0),
type: NumberType.PortfolioBalance,
})}
</ThemedText.SubHeader>
</MouseoverTooltip>

@ -101,7 +101,7 @@ function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: Tok
title={<TokenNameText>{token?.name}</TokenNameText>}
descriptor={
<TokenBalanceText>
{formatNumber(quantity, NumberType.TokenNonTx)} {token?.symbol}
{formatNumber({ input: quantity, type: NumberType.TokenNonTx })} {token?.symbol}
</TokenBalanceText>
}
onClick={navigateToTokenDetails}
@ -109,7 +109,10 @@ function TokenRow({ token, quantity, denominatedValue, tokenProjectMarket }: Tok
denominatedValue && (
<>
<ThemedText.SubHeader>
{formatNumber(denominatedValue?.value, NumberType.PortfolioBalance)}
{formatNumber({
input: denominatedValue?.value,
type: NumberType.PortfolioBalance,
})}
</ThemedText.SubHeader>
<Row justify="flex-end">
<PortfolioArrow change={percentChange} size={20} strokeWidth={1.75} />

@ -39,7 +39,10 @@ export function FiatValue({
<Row gap="sm">
<ThemedText.BodySmall color="neutral2">
{fiatValue.data ? (
formatNumber(fiatValue.data, NumberType.FiatTokenPrice)
formatNumber({
input: fiatValue.data,
type: NumberType.FiatTokenPrice,
})
) : (
<MouseoverTooltip text={<Trans>Not enough liquidity to show accurate USD value.</Trans>}>-</MouseoverTooltip>
)}

@ -62,7 +62,12 @@ function Stat({
return (
<StatWrapper data-cy={`${dataCy}`}>
<MouseoverTooltip text={description}>{title}</MouseoverTooltip>
<StatPrice>{formatNumber(value, NumberType.FiatTokenStats)}</StatPrice>
<StatPrice>
{formatNumber({
input: value,
type: NumberType.FiatTokenStats,
})}
</StatPrice>
</StatWrapper>
)
}

@ -503,11 +503,19 @@ export const LoadedRow = forwardRef((props: LoadedRowProps, ref: ForwardedRef<HT
}
tvl={
<ClickableContent>
{formatNumber(token.market?.totalValueLocked?.value, NumberType.FiatTokenStats)}
{formatNumber({
input: token.market?.totalValueLocked?.value,
type: NumberType.FiatTokenStats,
})}
</ClickableContent>
}
volume={
<ClickableContent>{formatNumber(token.market?.volume?.value, NumberType.FiatTokenStats)}</ClickableContent>
<ClickableContent>
{formatNumber({
input: token.market?.volume?.value,
type: NumberType.FiatTokenStats,
})}
</ClickableContent>
}
sparkLine={
<SparkLine>

@ -73,10 +73,10 @@ export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }:
>
<TextWithLoadingPlaceholder syncing={syncing} width={50}>
<ThemedText.BodySmall>
{`${trade.totalGasUseEstimateUSD ? '~' : ''}${formatNumber(
trade.totalGasUseEstimateUSD,
NumberType.FiatGasPrice
)}`}
{`${trade.totalGasUseEstimateUSD ? '~' : ''}${formatNumber({
input: trade.totalGasUseEstimateUSD,
type: NumberType.FiatGasPrice,
})}`}
</ThemedText.BodySmall>
</TextWithLoadingPlaceholder>
</MouseoverTooltip>

@ -39,7 +39,11 @@ const GasCostItem = ({
<Row justify="space-between">
<ThemedText.SubHeaderSmall>{title}</ThemedText.SubHeaderSmall>
<ThemedText.SubHeaderSmall color="neutral1">
{itemValue ?? formatNumber(amount, NumberType.FiatGasPrice)}
{itemValue ??
formatNumber({
input: amount,
type: NumberType.FiatGasPrice,
})}
</ThemedText.SubHeaderSmall>
</Row>
)

@ -47,10 +47,20 @@ export default function GasEstimateTooltip({ trade, loading }: { trade?: Interfa
{isUniswapXTrade(trade) ? <UniswapXRouterIcon testId="gas-estimate-uniswapx-icon" /> : <StyledGasIcon />}
<ThemedText.BodySmall color="neutral2">
<Row gap="xs">
<div>{formatNumber(trade.totalGasUseEstimateUSD, NumberType.FiatGasPrice)}</div>
<div>
{formatNumber({
input: trade.totalGasUseEstimateUSD,
type: NumberType.FiatGasPrice,
})}
</div>
{isUniswapXTrade(trade) && (
<div>
<s>{formatNumber(trade.classicGasUseEstimateUSD, NumberType.FiatGasPrice)}</s>
<s>
{formatNumber({
input: trade.classicGasUseEstimateUSD,
type: NumberType.FiatGasPrice,
})}
</s>
</div>
)}
</Row>

@ -109,7 +109,12 @@ export default function SwapModalFooter({
</Label>
</MouseoverTooltip>
<MouseoverTooltip placement="right" size={TooltipSize.Small} text={<GasBreakdownTooltip trade={trade} />}>
<DetailRowValue>{formatNumber(trade.totalGasUseEstimateUSD, NumberType.FiatGasPrice)}</DetailRowValue>
<DetailRowValue>
{formatNumber({
input: trade.totalGasUseEstimateUSD,
type: NumberType.FiatGasPrice,
})}
</DetailRowValue>
</MouseoverTooltip>
</Row>
</ThemedText.BodySmall>

@ -59,7 +59,10 @@ export function SwapModalHeaderAmount({ tooltipText, label, amount, usdAmount, f
</ResponsiveHeadline>
{usdAmount && (
<ThemedText.BodySmall color="neutral2">
{formatNumber(usdAmount, NumberType.FiatTokenQuantity)}
{formatNumber({
input: usdAmount,
type: NumberType.FiatTokenQuantity,
})}
</ThemedText.BodySmall>
)}
</Column>

@ -58,7 +58,14 @@ export default function TradePrice({ price }: TradePriceProps) {
<ThemedText.BodySmall>{text}</ThemedText.BodySmall>{' '}
{usdPrice && (
<ThemedText.BodySmall color="neutral2">
<Trans>({formatNumber(usdPrice, NumberType.FiatTokenPrice)})</Trans>
<Trans>
(
{formatNumber({
input: usdPrice,
type: NumberType.FiatTokenPrice,
})}
)
</Trans>
</ThemedText.BodySmall>
)}
</StyledPriceContainer>

@ -46,6 +46,31 @@ export type SupportedLocalCurrency = (typeof SUPPORTED_LOCAL_CURRENCIES)[number]
export const DEFAULT_LOCAL_CURRENCY: SupportedLocalCurrency = 'USD'
// some currencies need to be forced to use the narrow symbol and others need to be forced to use symbol
// for example: when CAD is set to narrowSymbol it is displayed as $ which offers no differentiation from USD
// but when set to symbol it is displayed as CA$ which is correct
// On the other hand when TBH is set to symbol it is displayed as THB, but when set to narrowSymbol it is ฿ which is correct
export const LOCAL_CURRENCY_SYMBOL_DISPLAY_TYPE: Record<SupportedLocalCurrency, 'narrowSymbol' | 'symbol'> = {
USD: 'narrowSymbol',
EUR: 'narrowSymbol',
RUB: 'narrowSymbol',
INR: 'narrowSymbol',
GBP: 'narrowSymbol',
JPY: 'narrowSymbol',
VND: 'narrowSymbol',
SGD: 'symbol',
BRL: 'symbol',
HKD: 'symbol',
CAD: 'symbol',
IDR: 'narrowSymbol',
TRY: 'narrowSymbol',
NGN: 'narrowSymbol',
UAH: 'narrowSymbol',
PKR: 'narrowSymbol',
AUD: 'symbol',
THB: 'narrowSymbol',
}
export function getLocalCurrencyIcon(localCurrency: SupportedLocalCurrency, size = 20): ReactNode {
switch (localCurrency) {
case 'USD':

@ -14,122 +14,357 @@ import {
} from './formatNumbers'
it('formats token reference numbers correctly', () => {
expect(formatNumber(1234567000000000, NumberType.TokenNonTx)).toBe('>999T')
expect(formatNumber(1002345, NumberType.TokenNonTx)).toBe('1.00M')
expect(formatNumber(1234, NumberType.TokenNonTx)).toBe('1,234.00')
expect(formatNumber(0.00909, NumberType.TokenNonTx)).toBe('0.009')
expect(formatNumber(0.09001, NumberType.TokenNonTx)).toBe('0.090')
expect(formatNumber(0.00099, NumberType.TokenNonTx)).toBe('<0.001')
expect(formatNumber(0, NumberType.TokenNonTx)).toBe('0')
expect(formatNumber({ input: 1234567000000000, type: NumberType.TokenNonTx })).toBe('>999T')
expect(formatNumber({ input: 1234567000000000, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('>999\xa0Bio.')
expect(formatNumber({ input: 1002345, type: NumberType.TokenNonTx })).toBe('1.00M')
expect(formatNumber({ input: 1002345, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('1,00\xa0Mio.')
expect(formatNumber({ input: 1234, type: NumberType.TokenNonTx })).toBe('1,234.00')
expect(formatNumber({ input: 1234, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('1.234,00')
expect(formatNumber({ input: 0.00909, type: NumberType.TokenNonTx })).toBe('0.009')
expect(formatNumber({ input: 0.00909, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('0,009')
expect(formatNumber({ input: 0.09001, type: NumberType.TokenNonTx })).toBe('0.090')
expect(formatNumber({ input: 0.09001, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('0,090')
expect(formatNumber({ input: 0.00099, type: NumberType.TokenNonTx })).toBe('<0.001')
expect(formatNumber({ input: 0.00099, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('<0,001')
expect(formatNumber({ input: 0, type: NumberType.TokenNonTx })).toBe('0')
expect(formatNumber({ input: 0, type: NumberType.TokenNonTx, locale: 'de-DE' })).toBe('0')
})
it('formats token transaction numbers correctly', () => {
expect(formatNumber(1234567.8901, NumberType.TokenTx)).toBe('1,234,567.89')
expect(formatNumber(765432.1, NumberType.TokenTx)).toBe('765,432.10')
expect(formatNumber({ input: 1234567.8901, type: NumberType.TokenTx })).toBe('1,234,567.89')
expect(formatNumber({ input: 1234567.8901, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('1\xa0234\xa0567,89')
expect(formatNumber({ input: 765432.1, type: NumberType.TokenTx })).toBe('765,432.10')
expect(formatNumber({ input: 765432.1, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('765\xa0432,10')
expect(formatNumber(7654.321, NumberType.TokenTx)).toBe('7,654.32')
expect(formatNumber(765.4321, NumberType.TokenTx)).toBe('765.432')
expect(formatNumber(76.54321, NumberType.TokenTx)).toBe('76.5432')
expect(formatNumber(7.654321, NumberType.TokenTx)).toBe('7.65432')
expect(formatNumber(7.60000054321, NumberType.TokenTx)).toBe('7.60')
expect(formatNumber(7.6, NumberType.TokenTx)).toBe('7.60')
expect(formatNumber(7, NumberType.TokenTx)).toBe('7.00')
expect(formatNumber({ input: 7654.321, type: NumberType.TokenTx })).toBe('7,654.32')
expect(formatNumber({ input: 7654.321, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('7\xa0654,32')
expect(formatNumber({ input: 765.4321, type: NumberType.TokenTx })).toBe('765.432')
expect(formatNumber({ input: 765.4321, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('765,432')
expect(formatNumber({ input: 76.54321, type: NumberType.TokenTx })).toBe('76.5432')
expect(formatNumber({ input: 76.54321, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('76,5432')
expect(formatNumber({ input: 7.654321, type: NumberType.TokenTx })).toBe('7.65432')
expect(formatNumber({ input: 7.654321, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('7,65432')
expect(formatNumber({ input: 7.60000054321, type: NumberType.TokenTx })).toBe('7.60')
expect(formatNumber({ input: 7.60000054321, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('7,60')
expect(formatNumber({ input: 7.6, type: NumberType.TokenTx })).toBe('7.60')
expect(formatNumber({ input: 7.6, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('7,60')
expect(formatNumber({ input: 7, type: NumberType.TokenTx })).toBe('7.00')
expect(formatNumber({ input: 7, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('7,00')
expect(formatNumber(0.987654321, NumberType.TokenTx)).toBe('0.98765')
expect(formatNumber(0.9, NumberType.TokenTx)).toBe('0.90')
expect(formatNumber(0.901000123, NumberType.TokenTx)).toBe('0.901')
expect(formatNumber(0.000000001, NumberType.TokenTx)).toBe('<0.00001')
expect(formatNumber(0, NumberType.TokenTx)).toBe('0')
expect(formatNumber({ input: 0.987654321, type: NumberType.TokenTx })).toBe('0.98765')
expect(formatNumber({ input: 0.987654321, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('0,98765')
expect(formatNumber({ input: 0.9, type: NumberType.TokenTx })).toBe('0.90')
expect(formatNumber({ input: 0.9, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('0,90')
expect(formatNumber({ input: 0.901000123, type: NumberType.TokenTx })).toBe('0.901')
expect(formatNumber({ input: 0.901000123, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('0,901')
expect(formatNumber({ input: 0.000000001, type: NumberType.TokenTx })).toBe('<0.00001')
expect(formatNumber({ input: 0.000000001, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('<0,00001')
expect(formatNumber({ input: 0, type: NumberType.TokenTx })).toBe('0')
expect(formatNumber({ input: 0, type: NumberType.TokenTx, locale: 'ru-RU' })).toBe('0')
})
it('formats fiat estimates on token details pages correctly', () => {
expect(formatNumber(1234567.891, NumberType.FiatTokenDetails)).toBe('$1.23M')
expect(formatNumber(1234.5678, NumberType.FiatTokenDetails)).toBe('$1,234.57')
expect(formatNumber(1.048942, NumberType.FiatTokenDetails)).toBe('$1.049')
expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenDetails })).toBe('$1.23M')
expect(
formatNumber({ input: 1234567.891, type: NumberType.FiatTokenDetails, locale: 'fr-FR', localCurrency: 'EUR' })
).toBe('1,23\xa0M\xa0€')
expect(formatNumber({ input: 1234.5678, type: NumberType.FiatTokenDetails })).toBe('$1,234.57')
expect(
formatNumber({ input: 1234.5678, type: NumberType.FiatTokenDetails, locale: 'fr-FR', localCurrency: 'EUR' })
).toBe('1\u202f234,57\xa0€')
expect(formatNumber({ input: 1.048942, type: NumberType.FiatTokenDetails })).toBe('$1.049')
expect(
formatNumber({ input: 1.048942, type: NumberType.FiatTokenDetails, locale: 'fr-FR', localCurrency: 'EUR' })
).toBe('1,049\xa0€')
expect(formatNumber(0.001231, NumberType.FiatTokenDetails)).toBe('$0.00123')
expect(formatNumber(0.00001231, NumberType.FiatTokenDetails)).toBe('$0.0000123')
expect(formatNumber({ input: 0.001231, type: NumberType.FiatTokenDetails })).toBe('$0.00123')
expect(
formatNumber({ input: 0.001231, type: NumberType.FiatTokenDetails, locale: 'fr-FR', localCurrency: 'EUR' })
).toBe('0,00123\xa0€')
expect(formatNumber({ input: 0.00001231, type: NumberType.FiatTokenDetails })).toBe('$0.0000123')
expect(
formatNumber({ input: 0.00001231, type: NumberType.FiatTokenDetails, locale: 'fr-FR', localCurrency: 'EUR' })
).toBe('0,0000123\xa0€')
expect(formatNumber(0.0000001234, NumberType.FiatTokenDetails)).toBe('$0.000000123')
expect(formatNumber(0.000000009876, NumberType.FiatTokenDetails)).toBe('<$0.00000001')
expect(formatNumber({ input: 0.0000001234, type: NumberType.FiatTokenDetails })).toBe('$0.000000123')
expect(
formatNumber({ input: 0.0000001234, type: NumberType.FiatTokenDetails, locale: 'fr-FR', localCurrency: 'EUR' })
).toBe('0,000000123\xa0€')
expect(formatNumber({ input: 0.000000009876, type: NumberType.FiatTokenDetails })).toBe('<$0.00000001')
expect(
formatNumber({ input: 0.000000009876, type: NumberType.FiatTokenDetails, locale: 'fr-FR', localCurrency: 'EUR' })
).toBe('<0,00000001\xa0€')
})
it('formats fiat estimates for tokens correctly', () => {
expect(formatNumber(1234567.891, NumberType.FiatTokenPrice)).toBe('$1.23M')
expect(formatNumber(1234.5678, NumberType.FiatTokenPrice)).toBe('$1,234.57')
expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenPrice })).toBe('$1.23M')
expect(
formatNumber({ input: 1234567.891, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: 'JPY' })
).toBe('1,23\xa0M¥')
expect(formatNumber({ input: 1234.5678, type: NumberType.FiatTokenPrice })).toBe('$1,234.57')
expect(
formatNumber({ input: 1234.5678, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: 'JPY' })
).toBe('1234,57\xa0¥')
expect(
formatNumber({ input: 12345.678, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: 'JPY' })
).toBe('12.345,68\xa0¥')
expect(formatNumber(0.010235, NumberType.FiatTokenPrice)).toBe('$0.0102')
expect(formatNumber(0.001231, NumberType.FiatTokenPrice)).toBe('$0.00123')
expect(formatNumber(0.00001231, NumberType.FiatTokenPrice)).toBe('$0.0000123')
expect(formatNumber({ input: 0.010235, type: NumberType.FiatTokenPrice })).toBe('$0.0102')
expect(
formatNumber({ input: 0.010235, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: 'JPY' })
).toBe('0,0102\xa0¥')
expect(formatNumber({ input: 0.001231, type: NumberType.FiatTokenPrice })).toBe('$0.00123')
expect(
formatNumber({ input: 0.001231, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: 'JPY' })
).toBe('0,00123\xa0¥')
expect(formatNumber({ input: 0.00001231, type: NumberType.FiatTokenPrice })).toBe('$0.0000123')
expect(
formatNumber({ input: 0.00001231, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: 'JPY' })
).toBe('0,0000123\xa0¥')
expect(formatNumber(0.0000001234, NumberType.FiatTokenPrice)).toBe('$0.000000123')
expect(formatNumber(0.000000009876, NumberType.FiatTokenPrice)).toBe('<$0.00000001')
expect(formatNumber(10000000000000000000000000000000, NumberType.FiatTokenPrice)).toBe('$1.000000E31')
expect(formatNumber({ input: 0.0000001234, type: NumberType.FiatTokenPrice })).toBe('$0.000000123')
expect(
formatNumber({ input: 0.0000001234, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: 'JPY' })
).toBe('0,000000123\xa0¥')
expect(formatNumber({ input: 0.000000009876, type: NumberType.FiatTokenPrice })).toBe('<$0.00000001')
expect(
formatNumber({ input: 0.000000009876, type: NumberType.FiatTokenPrice, locale: 'es-ES', localCurrency: 'JPY' })
).toBe('<0,00000001\xa0¥')
expect(formatNumber({ input: 10000000000000000000000000000000, type: NumberType.FiatTokenPrice })).toBe(
'$1.000000E31'
)
expect(
formatNumber({
input: 10000000000000000000000000000000,
type: NumberType.FiatTokenPrice,
locale: 'es-ES',
localCurrency: 'JPY',
})
).toBe('1,000000E31\xa0¥')
})
it('formats fiat estimates for token stats correctly', () => {
expect(formatNumber(1234576, NumberType.FiatTokenStats)).toBe('$1.2M')
expect(formatNumber(234567, NumberType.FiatTokenStats)).toBe('$234.6K')
expect(formatNumber(123.456, NumberType.FiatTokenStats)).toBe('$123.46')
expect(formatNumber(1.23, NumberType.FiatTokenStats)).toBe('$1.23')
expect(formatNumber(0.123, NumberType.FiatTokenStats)).toBe('$0.12')
expect(formatNumber(0.00123, NumberType.FiatTokenStats)).toBe('<$0.01')
expect(formatNumber(0, NumberType.FiatTokenStats)).toBe('-')
expect(formatNumber({ input: 1234576, type: NumberType.FiatTokenStats })).toBe('$1.2M')
expect(formatNumber({ input: 1234576, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: 'CAD' })).toBe(
'CA$123.5万'
)
expect(formatNumber({ input: 234567, type: NumberType.FiatTokenStats })).toBe('$234.6K')
expect(formatNumber({ input: 234567, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: 'CAD' })).toBe(
'CA$23.5万'
)
expect(formatNumber({ input: 123.456, type: NumberType.FiatTokenStats })).toBe('$123.46')
expect(formatNumber({ input: 123.456, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: 'CAD' })).toBe(
'CA$123.46'
)
expect(formatNumber({ input: 1.23, type: NumberType.FiatTokenStats })).toBe('$1.23')
expect(formatNumber({ input: 1.23, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: 'CAD' })).toBe(
'CA$1.23'
)
expect(formatNumber({ input: 0.123, type: NumberType.FiatTokenStats })).toBe('$0.12')
expect(formatNumber({ input: 0.123, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: 'CAD' })).toBe(
'CA$0.12'
)
expect(formatNumber({ input: 0.00123, type: NumberType.FiatTokenStats })).toBe('<$0.01')
expect(formatNumber({ input: 0.00123, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: 'CAD' })).toBe(
'<CA$0.01'
)
expect(formatNumber({ input: 0, type: NumberType.FiatTokenStats })).toBe('-')
expect(formatNumber({ input: 0, type: NumberType.FiatTokenStats, locale: 'ja-JP', localCurrency: 'CAD' })).toBe('-')
})
it('formats gas USD prices correctly', () => {
expect(formatNumber(1234567.891, NumberType.FiatGasPrice)).toBe('$1.23M')
expect(formatNumber(18.448, NumberType.FiatGasPrice)).toBe('$18.45')
expect(formatNumber(0.0099, NumberType.FiatGasPrice)).toBe('<$0.01')
expect(formatNumber(0, NumberType.FiatGasPrice)).toBe('$0.00')
expect(formatNumber({ input: 1234567.891, type: NumberType.FiatGasPrice })).toBe('$1.23M')
expect(
formatNumber({ input: 1234567.891, type: NumberType.FiatGasPrice, locale: 'pt-PR', localCurrency: 'THB' })
).toBe('฿\xa01,23\xa0mi')
expect(formatNumber({ input: 18.448, type: NumberType.FiatGasPrice })).toBe('$18.45')
expect(formatNumber({ input: 18.448, type: NumberType.FiatGasPrice, locale: 'pt-PR', localCurrency: 'THB' })).toBe(
'฿\xa018,45'
)
expect(formatNumber({ input: 0.0099, type: NumberType.FiatGasPrice })).toBe('<$0.01')
expect(formatNumber({ input: 0.0099, type: NumberType.FiatGasPrice, locale: 'pt-PR', localCurrency: 'THB' })).toBe(
'<฿\xa00,01'
)
expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice })).toBe('$0.00')
expect(formatNumber({ input: 0, type: NumberType.FiatGasPrice, locale: 'pt-PR', localCurrency: 'THB' })).toBe(
'฿\xa00,00'
)
})
it('formats USD token quantities prices correctly', () => {
expect(formatNumber(1234567.891, NumberType.FiatTokenQuantity)).toBe('$1.23M')
expect(formatNumber(18.448, NumberType.FiatTokenQuantity)).toBe('$18.45')
expect(formatNumber(0.0099, NumberType.FiatTokenQuantity)).toBe('<$0.01')
expect(formatNumber(0, NumberType.FiatTokenQuantity)).toBe('$0.00')
expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenQuantity })).toBe('$1.23M')
expect(formatNumber({ input: 1234567.891, type: NumberType.FiatTokenQuantity, localCurrency: 'NGN' })).toBe('₦1.23M')
expect(formatNumber({ input: 18.448, type: NumberType.FiatTokenQuantity })).toBe('$18.45')
expect(formatNumber({ input: 18.448, type: NumberType.FiatTokenQuantity, localCurrency: 'NGN' })).toBe('₦18.45')
expect(formatNumber({ input: 0.0099, type: NumberType.FiatTokenQuantity })).toBe('<$0.01')
expect(formatNumber({ input: 0.0099, type: NumberType.FiatTokenQuantity, localCurrency: 'NGN' })).toBe('<₦0.01')
expect(formatNumber({ input: 0, type: NumberType.FiatTokenQuantity })).toBe('$0.00')
expect(formatNumber({ input: 0, type: NumberType.FiatTokenQuantity, localCurrency: 'NGN' })).toBe('₦0.00')
})
it('formats Swap text input/output numbers correctly', () => {
expect(formatNumber(1234567.8901, NumberType.SwapTradeAmount)).toBe('1234570')
expect(formatNumber(765432.1, NumberType.SwapTradeAmount)).toBe('765432')
expect(formatNumber({ input: 1234567.8901, type: NumberType.SwapTradeAmount })).toBe('1234570')
expect(formatNumber({ input: 1234567.8901, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('1234570')
expect(formatNumber({ input: 765432.1, type: NumberType.SwapTradeAmount })).toBe('765432')
expect(formatNumber({ input: 765432.1, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('765432')
expect(formatNumber(7654.321, NumberType.SwapTradeAmount)).toBe('7654.32')
expect(formatNumber(765.4321, NumberType.SwapTradeAmount)).toBe('765.432')
expect(formatNumber(76.54321, NumberType.SwapTradeAmount)).toBe('76.5432')
expect(formatNumber(7.654321, NumberType.SwapTradeAmount)).toBe('7.65432')
expect(formatNumber(7.60000054321, NumberType.SwapTradeAmount)).toBe('7.60')
expect(formatNumber(7.6, NumberType.SwapTradeAmount)).toBe('7.60')
expect(formatNumber(7, NumberType.SwapTradeAmount)).toBe('7.00')
expect(formatNumber({ input: 7654.321, type: NumberType.SwapTradeAmount })).toBe('7654.32')
expect(formatNumber({ input: 7654.321, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('7654.32')
expect(formatNumber({ input: 765.4321, type: NumberType.SwapTradeAmount })).toBe('765.432')
expect(formatNumber({ input: 765.4321, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('765.432')
expect(formatNumber({ input: 76.54321, type: NumberType.SwapTradeAmount })).toBe('76.5432')
expect(formatNumber({ input: 76.54321, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('76.5432')
expect(formatNumber({ input: 7.654321, type: NumberType.SwapTradeAmount })).toBe('7.65432')
expect(formatNumber({ input: 7.654321, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('7.65432')
expect(formatNumber({ input: 7.60000054321, type: NumberType.SwapTradeAmount })).toBe('7.60')
expect(formatNumber({ input: 7.60000054321, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('7.60')
expect(formatNumber({ input: 7.6, type: NumberType.SwapTradeAmount })).toBe('7.60')
expect(formatNumber({ input: 7.6, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('7.60')
expect(formatNumber({ input: 7, type: NumberType.SwapTradeAmount })).toBe('7.00')
expect(formatNumber({ input: 7, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('7.00')
expect(formatNumber(0.987654321, NumberType.SwapTradeAmount)).toBe('0.98765')
expect(formatNumber(0.9, NumberType.SwapTradeAmount)).toBe('0.90')
expect(formatNumber(0.901000123, NumberType.SwapTradeAmount)).toBe('0.901')
expect(formatNumber(0.000000001, NumberType.SwapTradeAmount)).toBe('0.000000001')
expect(formatNumber(0, NumberType.SwapTradeAmount)).toBe('0')
expect(formatNumber({ input: 0.987654321, type: NumberType.SwapTradeAmount })).toBe('0.98765')
expect(formatNumber({ input: 0.987654321, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('0.98765')
expect(formatNumber({ input: 0.9, type: NumberType.SwapTradeAmount })).toBe('0.90')
expect(formatNumber({ input: 0.9, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('0.90')
expect(formatNumber({ input: 0.901000123, type: NumberType.SwapTradeAmount })).toBe('0.901')
expect(formatNumber({ input: 0.901000123, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('0.901')
expect(formatNumber({ input: 0.000000001, type: NumberType.SwapTradeAmount })).toBe('0.000000001')
expect(formatNumber({ input: 0.000000001, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('0.000000001')
expect(formatNumber({ input: 0, type: NumberType.SwapTradeAmount })).toBe('0')
expect(formatNumber({ input: 0, type: NumberType.SwapTradeAmount, locale: 'ko-KR' })).toBe('0')
})
it('formats NFT numbers correctly', () => {
expect(formatNumber(1234567000000000, NumberType.NFTTokenFloorPrice)).toBe('>999T')
expect(formatNumber(1002345, NumberType.NFTTokenFloorPrice)).toBe('1M')
expect(formatNumber(1234, NumberType.NFTTokenFloorPrice)).toBe('1.23K')
expect(formatNumber(12.34467, NumberType.NFTTokenFloorPrice)).toBe('12.34')
expect(formatNumber(12.1, NumberType.NFTTokenFloorPrice)).toBe('12.1')
expect(formatNumber(0.00909, NumberType.NFTTokenFloorPrice)).toBe('0.009')
expect(formatNumber(0.09001, NumberType.NFTTokenFloorPrice)).toBe('0.09')
expect(formatNumber(0.00099, NumberType.NFTTokenFloorPrice)).toBe('<0.001')
expect(formatNumber(0, NumberType.NFTTokenFloorPrice)).toBe('0')
expect(formatNumber({ input: 1234567000000000, type: NumberType.NFTTokenFloorPrice })).toBe('>999T')
expect(
formatNumber({
input: 1234567000000000,
type: NumberType.NFTTokenFloorPrice,
locale: 'pt-BR',
localCurrency: 'BRL',
})
).toBe('>999\xa0tri')
expect(formatNumber({ input: 1002345, type: NumberType.NFTTokenFloorPrice })).toBe('1M')
expect(
formatNumber({
input: 1002345,
type: NumberType.NFTTokenFloorPrice,
locale: 'pt-BR',
localCurrency: 'BRL',
})
).toBe('1\xa0mi')
expect(formatNumber({ input: 1234, type: NumberType.NFTTokenFloorPrice })).toBe('1.23K')
expect(
formatNumber({
input: 1234,
type: NumberType.NFTTokenFloorPrice,
locale: 'pt-BR',
localCurrency: 'BRL',
})
).toBe('1,23\xa0mil')
expect(formatNumber({ input: 12.34467, type: NumberType.NFTTokenFloorPrice })).toBe('12.34')
expect(
formatNumber({
input: 12.34467,
type: NumberType.NFTTokenFloorPrice,
locale: 'pt-BR',
localCurrency: 'BRL',
})
).toBe('12,34')
expect(formatNumber({ input: 12.1, type: NumberType.NFTTokenFloorPrice })).toBe('12.1')
expect(
formatNumber({
input: 12.1,
type: NumberType.NFTTokenFloorPrice,
locale: 'pt-BR',
localCurrency: 'BRL',
})
).toBe('12,1')
expect(formatNumber({ input: 0.00909, type: NumberType.NFTTokenFloorPrice })).toBe('0.009')
expect(
formatNumber({
input: 0.00909,
type: NumberType.NFTTokenFloorPrice,
locale: 'pt-BR',
localCurrency: 'BRL',
})
).toBe('0,009')
expect(formatNumber({ input: 0.09001, type: NumberType.NFTTokenFloorPrice })).toBe('0.09')
expect(
formatNumber({
input: 0.09001,
type: NumberType.NFTTokenFloorPrice,
locale: 'pt-BR',
localCurrency: 'BRL',
})
).toBe('0,09')
expect(formatNumber({ input: 0.00099, type: NumberType.NFTTokenFloorPrice })).toBe('<0.001')
expect(
formatNumber({
input: 0.00099,
type: NumberType.NFTTokenFloorPrice,
locale: 'pt-BR',
localCurrency: 'BRL',
})
).toBe('<0,001')
expect(formatNumber({ input: 0, type: NumberType.NFTTokenFloorPrice })).toBe('0')
expect(
formatNumber({
input: 0,
type: NumberType.NFTTokenFloorPrice,
locale: 'pt-BR',
localCurrency: 'BRL',
})
).toBe('0')
expect(formatNumber(12.1, NumberType.NFTTokenFloorPriceTrailingZeros)).toBe('12.10')
expect(formatNumber(0.09001, NumberType.NFTTokenFloorPriceTrailingZeros)).toBe('0.090')
expect(formatNumber({ input: 12.1, type: NumberType.NFTTokenFloorPriceTrailingZeros })).toBe('12.10')
expect(
formatNumber({
input: 12.1,
type: NumberType.NFTTokenFloorPriceTrailingZeros,
locale: 'pt-BR',
localCurrency: 'BRL',
})
).toBe('12,10')
expect(formatNumber({ input: 0.09001, type: NumberType.NFTTokenFloorPriceTrailingZeros })).toBe('0.090')
expect(
formatNumber({
input: 0.09001,
type: NumberType.NFTTokenFloorPriceTrailingZeros,
locale: 'pt-BR',
localCurrency: 'BRL',
})
).toBe('0,090')
expect(formatNumber(0.987654321, NumberType.NFTCollectionStats)).toBe('1')
expect(formatNumber(0.9, NumberType.NFTCollectionStats)).toBe('1')
expect(formatNumber(76543.21, NumberType.NFTCollectionStats)).toBe('76.5K')
expect(formatNumber(7.60000054321, NumberType.NFTCollectionStats)).toBe('8')
expect(formatNumber(1234567890, NumberType.NFTCollectionStats)).toBe('1.2B')
expect(formatNumber(1234567000000000, NumberType.NFTCollectionStats)).toBe('1234.6T')
expect(formatNumber({ input: 0.987654321, type: NumberType.NFTCollectionStats })).toBe('1')
expect(
formatNumber({ input: 0.987654321, type: NumberType.NFTCollectionStats, locale: 'pt-BR', localCurrency: 'BRL' })
).toBe('1')
expect(formatNumber({ input: 0.9, type: NumberType.NFTCollectionStats })).toBe('1')
expect(formatNumber({ input: 0.9, type: NumberType.NFTCollectionStats, locale: 'pt-BR', localCurrency: 'BRL' })).toBe(
'1'
)
expect(formatNumber({ input: 76543.21, type: NumberType.NFTCollectionStats })).toBe('76.5K')
expect(
formatNumber({ input: 76543.21, type: NumberType.NFTCollectionStats, locale: 'pt-BR', localCurrency: 'BRL' })
).toBe('76,5\xa0mil')
expect(formatNumber({ input: 7.60000054321, type: NumberType.NFTCollectionStats })).toBe('8')
expect(
formatNumber({ input: 7.60000054321, type: NumberType.NFTCollectionStats, locale: 'pt-BR', localCurrency: 'BRL' })
).toBe('8')
expect(formatNumber({ input: 1234567890, type: NumberType.NFTCollectionStats })).toBe('1.2B')
expect(
formatNumber({ input: 1234567890, type: NumberType.NFTCollectionStats, locale: 'pt-BR', localCurrency: 'BRL' })
).toBe('1,2\xa0bi')
expect(formatNumber({ input: 1234567000000000, type: NumberType.NFTCollectionStats })).toBe('1234.6T')
expect(
formatNumber({
input: 1234567000000000,
type: NumberType.NFTCollectionStats,
locale: 'pt-BR',
localCurrency: 'BRL',
})
).toBe('1234,6\xa0tri')
})
describe('formatUSDPrice', () => {

@ -1,244 +1,302 @@
import { Currency, CurrencyAmount, Percent, Price } from '@uniswap/sdk-core'
import { DEFAULT_LOCALE } from 'constants/locales'
import {
DEFAULT_LOCAL_CURRENCY,
LOCAL_CURRENCY_SYMBOL_DISPLAY_TYPE,
SupportedLocalCurrency,
} from 'constants/localCurrencies'
import { DEFAULT_LOCALE, SupportedLocale } from 'constants/locales'
type Nullish<T> = T | null | undefined
type NumberFormatOptions = Intl.NumberFormatOptions
// Number formatting follows the standards laid out in this spec:
// https://www.notion.so/uniswaplabs/Number-standards-fbb9f533f10e4e22820722c2f66d23c0
const FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN = new Intl.NumberFormat('en-US', {
const FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN: NumberFormatOptions = {
notation: 'standard',
maximumFractionDigits: 5,
minimumFractionDigits: 2,
})
}
const FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN_NO_COMMAS = new Intl.NumberFormat('en-US', {
const FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN_NO_COMMAS: NumberFormatOptions = {
notation: 'standard',
maximumFractionDigits: 5,
minimumFractionDigits: 2,
useGrouping: false,
})
}
const NO_DECIMALS = new Intl.NumberFormat('en-US', {
const NO_DECIMALS: NumberFormatOptions = {
notation: 'standard',
maximumFractionDigits: 0,
minimumFractionDigits: 0,
})
}
const THREE_DECIMALS_NO_TRAILING_ZEROS = new Intl.NumberFormat('en-US', {
const THREE_DECIMALS_NO_TRAILING_ZEROS: NumberFormatOptions = {
notation: 'standard',
maximumFractionDigits: 3,
minimumFractionDigits: 0,
})
}
const THREE_DECIMALS = new Intl.NumberFormat('en-US', {
const THREE_DECIMALS: NumberFormatOptions = {
notation: 'standard',
maximumFractionDigits: 3,
minimumFractionDigits: 3,
})
}
const THREE_DECIMALS_USD = new Intl.NumberFormat('en-US', {
const THREE_DECIMALS_CURRENCY: NumberFormatOptions = {
notation: 'standard',
maximumFractionDigits: 3,
minimumFractionDigits: 3,
currency: 'USD',
style: 'currency',
})
}
const TWO_DECIMALS_NO_TRAILING_ZEROS = new Intl.NumberFormat('en-US', {
const TWO_DECIMALS_NO_TRAILING_ZEROS: NumberFormatOptions = {
notation: 'standard',
maximumFractionDigits: 2,
})
}
const TWO_DECIMALS = new Intl.NumberFormat('en-US', {
const TWO_DECIMALS: NumberFormatOptions = {
notation: 'standard',
maximumFractionDigits: 2,
minimumFractionDigits: 2,
})
}
const TWO_DECIMALS_USD = new Intl.NumberFormat('en-US', {
const TWO_DECIMALS_CURRENCY: NumberFormatOptions = {
notation: 'standard',
maximumFractionDigits: 2,
minimumFractionDigits: 2,
currency: 'USD',
style: 'currency',
})
}
const SHORTHAND_TWO_DECIMALS = new Intl.NumberFormat('en-US', {
const SHORTHAND_TWO_DECIMALS: NumberFormatOptions = {
notation: 'compact',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})
}
const SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS = new Intl.NumberFormat('en-US', {
const SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS: NumberFormatOptions = {
notation: 'compact',
maximumFractionDigits: 2,
})
}
const SHORTHAND_ONE_DECIMAL = new Intl.NumberFormat('en-US', {
const SHORTHAND_ONE_DECIMAL: NumberFormatOptions = {
notation: 'compact',
minimumFractionDigits: 1,
maximumFractionDigits: 1,
})
}
const SHORTHAND_USD_TWO_DECIMALS = new Intl.NumberFormat('en-US', {
const SHORTHAND_CURRENCY_TWO_DECIMALS: NumberFormatOptions = {
notation: 'compact',
minimumFractionDigits: 2,
maximumFractionDigits: 2,
currency: 'USD',
style: 'currency',
})
}
const SHORTHAND_USD_ONE_DECIMAL = new Intl.NumberFormat('en-US', {
const SHORTHAND_CURRENCY_ONE_DECIMAL: NumberFormatOptions = {
notation: 'compact',
minimumFractionDigits: 1,
maximumFractionDigits: 1,
currency: 'USD',
style: 'currency',
})
}
const SIX_SIG_FIGS_TWO_DECIMALS = new Intl.NumberFormat('en-US', {
const SIX_SIG_FIGS_TWO_DECIMALS: NumberFormatOptions = {
notation: 'standard',
maximumSignificantDigits: 6,
minimumSignificantDigits: 3,
maximumFractionDigits: 2,
minimumFractionDigits: 2,
})
}
const SIX_SIG_FIGS_NO_COMMAS = new Intl.NumberFormat('en-US', {
const SIX_SIG_FIGS_NO_COMMAS: NumberFormatOptions = {
notation: 'standard',
maximumSignificantDigits: 6,
useGrouping: false,
})
}
const SIX_SIG_FIGS_TWO_DECIMALS_NO_COMMAS = new Intl.NumberFormat('en-US', {
const SIX_SIG_FIGS_TWO_DECIMALS_NO_COMMAS: NumberFormatOptions = {
notation: 'standard',
maximumSignificantDigits: 6,
minimumSignificantDigits: 3,
maximumFractionDigits: 2,
minimumFractionDigits: 2,
useGrouping: false,
})
}
const THREE_SIG_FIGS_USD = new Intl.NumberFormat('en-US', {
const ONE_SIG_FIG_CURRENCY: NumberFormatOptions = {
notation: 'standard',
minimumSignificantDigits: 1,
maximumSignificantDigits: 1,
currency: 'USD',
style: 'currency',
}
const THREE_SIG_FIGS_CURRENCY: NumberFormatOptions = {
notation: 'standard',
minimumSignificantDigits: 3,
maximumSignificantDigits: 3,
currency: 'USD',
style: 'currency',
})
}
const SEVEN_SIG_FIGS__SCI_NOTATION_USD = new Intl.NumberFormat('en-US', {
const SEVEN_SIG_FIGS__SCI_NOTATION_CURRENCY: NumberFormatOptions = {
notation: 'scientific',
minimumSignificantDigits: 7,
maximumSignificantDigits: 7,
currency: 'USD',
style: 'currency',
})
type Format = Intl.NumberFormat | string
}
// each rule must contain either an `upperBound` or an `exact` value.
// upperBound => number will use that formatter as long as it is < upperBound
// exact => number will use that formatter if it is === exact
type FormatterRule =
| { upperBound?: undefined; exact: number; formatter: Format }
| { upperBound: number; exact?: undefined; formatter: Format }
// if hardcodedinput is supplied it will override the input value or use the hardcoded output
type HardCodedInputFormat =
| {
input: number
prefix?: string
hardcodedOutput?: undefined
}
| {
input?: undefined
prefix?: undefined
hardcodedOutput: string
}
type FormatterBaseRule = { formatterOptions: NumberFormatOptions }
type FormatterExactRule = { upperBound?: undefined; exact: number } & FormatterBaseRule
type FormatterUpperBoundRule = { upperBound: number; exact?: undefined } & FormatterBaseRule
type FormatterRule = (FormatterExactRule | FormatterUpperBoundRule) & { hardCodedInput?: HardCodedInputFormat }
// these formatter objects dictate which formatter rule to use based on the interval that
// the number falls into. for example, based on the rule set below, if your number
// falls between 1 and 1e6, you'd use TWO_DECIMALS as the formatter.
const tokenNonTxFormatter: FormatterRule[] = [
{ exact: 0, formatter: '0' },
{ upperBound: 0.001, formatter: '<0.001' },
{ upperBound: 1, formatter: THREE_DECIMALS },
{ upperBound: 1e6, formatter: TWO_DECIMALS },
{ upperBound: 1e15, formatter: SHORTHAND_TWO_DECIMALS },
{ upperBound: Infinity, formatter: '>999T' },
{ exact: 0, formatterOptions: NO_DECIMALS },
{ upperBound: 0.001, hardCodedInput: { input: 0.001, prefix: '<' }, formatterOptions: THREE_DECIMALS },
{ upperBound: 1, formatterOptions: THREE_DECIMALS },
{ upperBound: 1e6, formatterOptions: TWO_DECIMALS },
{ upperBound: 1e15, formatterOptions: SHORTHAND_TWO_DECIMALS },
{
upperBound: Infinity,
hardCodedInput: { input: 999_000_000_000_000, prefix: '>' },
formatterOptions: SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS,
},
]
const tokenTxFormatter: FormatterRule[] = [
{ exact: 0, formatter: '0' },
{ upperBound: 0.00001, formatter: '<0.00001' },
{ upperBound: 1, formatter: FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN },
{ upperBound: 10000, formatter: SIX_SIG_FIGS_TWO_DECIMALS },
{ upperBound: Infinity, formatter: TWO_DECIMALS },
{ exact: 0, formatterOptions: NO_DECIMALS },
{
upperBound: 0.00001,
hardCodedInput: { input: 0.00001, prefix: '<' },
formatterOptions: FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN,
},
{ upperBound: 1, formatterOptions: FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN },
{ upperBound: 10000, formatterOptions: SIX_SIG_FIGS_TWO_DECIMALS },
{ upperBound: Infinity, formatterOptions: TWO_DECIMALS },
]
const swapTradeAmountFormatter: FormatterRule[] = [
{ exact: 0, formatter: '0' },
{ upperBound: 0.1, formatter: SIX_SIG_FIGS_NO_COMMAS },
{ upperBound: 1, formatter: FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN_NO_COMMAS },
{ upperBound: Infinity, formatter: SIX_SIG_FIGS_TWO_DECIMALS_NO_COMMAS },
{ exact: 0, formatterOptions: NO_DECIMALS },
{ upperBound: 0.1, formatterOptions: SIX_SIG_FIGS_NO_COMMAS },
{ upperBound: 1, formatterOptions: FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN_NO_COMMAS },
{ upperBound: Infinity, formatterOptions: SIX_SIG_FIGS_TWO_DECIMALS_NO_COMMAS },
]
const swapPriceFormatter: FormatterRule[] = [
{ exact: 0, formatter: '0' },
{ upperBound: 0.00001, formatter: '<0.00001' },
{ exact: 0, formatterOptions: NO_DECIMALS },
{
upperBound: 0.00001,
hardCodedInput: { input: 0.00001, prefix: '<' },
formatterOptions: FIVE_DECIMALS_MAX_TWO_DECIMALS_MIN,
},
...swapTradeAmountFormatter,
]
const fiatTokenDetailsFormatter: FormatterRule[] = [
{ exact: 0, formatter: '$0.00' },
{ upperBound: 0.00000001, formatter: '<$0.00000001' },
{ upperBound: 0.1, formatter: THREE_SIG_FIGS_USD },
{ upperBound: 1.05, formatter: THREE_DECIMALS_USD },
{ upperBound: 1e6, formatter: TWO_DECIMALS_USD },
{ upperBound: Infinity, formatter: SHORTHAND_USD_TWO_DECIMALS },
{ exact: 0, formatterOptions: TWO_DECIMALS_CURRENCY },
{
upperBound: 0.00000001,
hardCodedInput: { input: 0.00000001, prefix: '<' },
formatterOptions: ONE_SIG_FIG_CURRENCY,
},
{ upperBound: 0.1, formatterOptions: THREE_SIG_FIGS_CURRENCY },
{ upperBound: 1.05, formatterOptions: THREE_DECIMALS_CURRENCY },
{ upperBound: 1e6, formatterOptions: TWO_DECIMALS_CURRENCY },
{ upperBound: Infinity, formatterOptions: SHORTHAND_CURRENCY_TWO_DECIMALS },
]
const fiatTokenPricesFormatter: FormatterRule[] = [
{ exact: 0, formatter: '$0.00' },
{ upperBound: 0.00000001, formatter: '<$0.00000001' },
{ upperBound: 1, formatter: THREE_SIG_FIGS_USD },
{ upperBound: 1e6, formatter: TWO_DECIMALS_USD },
{ upperBound: 1e16, formatter: SHORTHAND_USD_TWO_DECIMALS },
{ upperBound: Infinity, formatter: SEVEN_SIG_FIGS__SCI_NOTATION_USD },
{ exact: 0, formatterOptions: TWO_DECIMALS_CURRENCY },
{
upperBound: 0.00000001,
hardCodedInput: { input: 0.00000001, prefix: '<' },
formatterOptions: ONE_SIG_FIG_CURRENCY,
},
{ upperBound: 1, formatterOptions: THREE_SIG_FIGS_CURRENCY },
{ upperBound: 1e6, formatterOptions: TWO_DECIMALS_CURRENCY },
{ upperBound: 1e16, formatterOptions: SHORTHAND_CURRENCY_TWO_DECIMALS },
{ upperBound: Infinity, formatterOptions: SEVEN_SIG_FIGS__SCI_NOTATION_CURRENCY },
]
const fiatTokenStatsFormatter: FormatterRule[] = [
// if token stat value is 0, we probably don't have the data for it, so show '-' as a placeholder
{ exact: 0, formatter: '-' },
{ upperBound: 0.01, formatter: '<$0.01' },
{ upperBound: 1000, formatter: TWO_DECIMALS_USD },
{ upperBound: Infinity, formatter: SHORTHAND_USD_ONE_DECIMAL },
{ exact: 0, hardCodedInput: { hardcodedOutput: '-' }, formatterOptions: ONE_SIG_FIG_CURRENCY },
{ upperBound: 0.01, hardCodedInput: { input: 0.01, prefix: '<' }, formatterOptions: TWO_DECIMALS_CURRENCY },
{ upperBound: 1000, formatterOptions: TWO_DECIMALS_CURRENCY },
{ upperBound: Infinity, formatterOptions: SHORTHAND_CURRENCY_ONE_DECIMAL },
]
const fiatGasPriceFormatter: FormatterRule[] = [
{ exact: 0, formatter: '$0.00' },
{ upperBound: 0.01, formatter: '<$0.01' },
{ upperBound: 1e6, formatter: TWO_DECIMALS_USD },
{ upperBound: Infinity, formatter: SHORTHAND_USD_TWO_DECIMALS },
{ exact: 0, formatterOptions: TWO_DECIMALS_CURRENCY },
{ upperBound: 0.01, hardCodedInput: { input: 0.01, prefix: '<' }, formatterOptions: TWO_DECIMALS_CURRENCY },
{ upperBound: 1e6, formatterOptions: TWO_DECIMALS_CURRENCY },
{ upperBound: Infinity, formatterOptions: SHORTHAND_CURRENCY_TWO_DECIMALS },
]
const fiatTokenQuantityFormatter = [{ exact: 0, formatter: '$0.00' }, ...fiatGasPriceFormatter]
const fiatTokenQuantityFormatter: FormatterRule[] = [
{ exact: 0, formatterOptions: TWO_DECIMALS_CURRENCY },
...fiatGasPriceFormatter,
]
const portfolioBalanceFormatter: FormatterRule[] = [
{ exact: 0, formatter: '$0.00' },
{ upperBound: Infinity, formatter: TWO_DECIMALS_USD },
{ exact: 0, formatterOptions: TWO_DECIMALS_CURRENCY },
{ upperBound: Infinity, formatterOptions: TWO_DECIMALS_CURRENCY },
]
const ntfTokenFloorPriceFormatterTrailingZeros: FormatterRule[] = [
{ exact: 0, formatter: '0' },
{ upperBound: 0.001, formatter: '<0.001' },
{ upperBound: 1, formatter: THREE_DECIMALS },
{ upperBound: 1000, formatter: TWO_DECIMALS },
{ upperBound: 1e15, formatter: SHORTHAND_TWO_DECIMALS },
{ upperBound: Infinity, formatter: '>999T' },
{ exact: 0, formatterOptions: NO_DECIMALS },
{ upperBound: 0.001, hardCodedInput: { input: 0.001, prefix: '<' }, formatterOptions: THREE_DECIMALS },
{ upperBound: 1, formatterOptions: THREE_DECIMALS },
{ upperBound: 1000, formatterOptions: TWO_DECIMALS },
{ upperBound: 1e15, formatterOptions: SHORTHAND_TWO_DECIMALS },
{
upperBound: Infinity,
hardCodedInput: { input: 999_000_000_000_000, prefix: '>' },
formatterOptions: SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS,
},
]
const ntfTokenFloorPriceFormatter: FormatterRule[] = [
{ exact: 0, formatter: '0' },
{ upperBound: 0.001, formatter: '<0.001' },
{ upperBound: 1, formatter: THREE_DECIMALS_NO_TRAILING_ZEROS },
{ upperBound: 1000, formatter: TWO_DECIMALS_NO_TRAILING_ZEROS },
{ upperBound: 1e15, formatter: SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS },
{ upperBound: Infinity, formatter: '>999T' },
{ exact: 0, formatterOptions: NO_DECIMALS },
{ upperBound: 0.001, hardCodedInput: { input: 0.001, prefix: '<' }, formatterOptions: THREE_DECIMALS },
{ upperBound: 1, formatterOptions: THREE_DECIMALS_NO_TRAILING_ZEROS },
{ upperBound: 1000, formatterOptions: TWO_DECIMALS_NO_TRAILING_ZEROS },
{ upperBound: 1e15, formatterOptions: SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS },
{
upperBound: Infinity,
hardCodedInput: { input: 999_000_000_000_000, prefix: '>' },
formatterOptions: SHORTHAND_TWO_DECIMALS_NO_TRAILING_ZEROS,
},
]
const ntfCollectionStatsFormatter: FormatterRule[] = [
{ upperBound: 1000, formatter: NO_DECIMALS },
{ upperBound: Infinity, formatter: SHORTHAND_ONE_DECIMAL },
{ upperBound: 1000, formatterOptions: NO_DECIMALS },
{ upperBound: Infinity, formatterOptions: SHORTHAND_ONE_DECIMAL },
]
export enum NumberType {
@ -300,32 +358,57 @@ const TYPE_TO_FORMATTER_RULES = {
[NumberType.NFTCollectionStats]: ntfCollectionStatsFormatter,
}
function getFormatterRule(input: number, type: NumberType): Format {
function getFormatterRule(input: number, type: NumberType): FormatterRule {
const rules = TYPE_TO_FORMATTER_RULES[type]
for (const rule of rules) {
if (
(rule.exact !== undefined && input === rule.exact) ||
(rule.upperBound !== undefined && input < rule.upperBound)
) {
return rule.formatter
return rule
}
}
throw new Error(`formatter for type ${type} not configured correctly`)
}
export function formatNumber(
input: Nullish<number>,
type: NumberType = NumberType.TokenNonTx,
placeholder = '-'
): string {
interface FormatNumberOptions {
input: Nullish<number>
type?: NumberType
placeholder?: string
locale?: SupportedLocale
localCurrency?: SupportedLocalCurrency
}
export function formatNumber({
input,
type = NumberType.TokenNonTx,
placeholder = '-',
locale = DEFAULT_LOCALE,
localCurrency = DEFAULT_LOCAL_CURRENCY,
}: FormatNumberOptions): string {
if (input === null || input === undefined) {
return placeholder
}
const formatter = getFormatterRule(input, type)
if (typeof formatter === 'string') return formatter
return formatter.format(input)
const { hardCodedInput, formatterOptions } = getFormatterRule(input, type)
if (formatterOptions.currency) {
formatterOptions.currency = localCurrency
formatterOptions.currencyDisplay = LOCAL_CURRENCY_SYMBOL_DISPLAY_TYPE[localCurrency]
}
if (!hardCodedInput) {
return new Intl.NumberFormat(locale, formatterOptions).format(input)
}
if (hardCodedInput.hardcodedOutput) {
return hardCodedInput.hardcodedOutput
}
const { input: hardCodedInputValue, prefix } = hardCodedInput
if (hardCodedInputValue === undefined) return placeholder
return (prefix ?? '') + new Intl.NumberFormat(locale, formatterOptions).format(hardCodedInputValue)
}
export function formatCurrencyAmount(
@ -333,7 +416,7 @@ export function formatCurrencyAmount(
type: NumberType = NumberType.TokenNonTx,
placeholder?: string
): string {
return formatNumber(amount ? parseFloat(amount.toSignificant()) : undefined, type, placeholder)
return formatNumber({ input: amount ? parseFloat(amount.toSignificant()) : undefined, type, placeholder })
}
export function formatPriceImpact(priceImpact: Percent | undefined): string {
@ -356,13 +439,13 @@ export function formatPrice(
return '-'
}
return formatNumber(parseFloat(price.toSignificant()), type)
return formatNumber({ input: parseFloat(price.toSignificant()), type })
}
export function formatNumberOrString(price: Nullish<number | string>, type: NumberType): string {
if (price === null || price === undefined) return '-'
if (typeof price === 'string') return formatNumber(parseFloat(price), type)
return formatNumber(price, type)
if (typeof price === 'string') return formatNumber({ input: parseFloat(price), type })
return formatNumber({ input: price, type })
}
export function formatUSDPrice(price: Nullish<number | string>, type: NumberType = NumberType.FiatTokenPrice): string {