@coingecko/cryptoformat
Advanced tools
+9
-10
| { | ||
| "name": "@coingecko/cryptoformat", | ||
| "version": "0.8.2", | ||
| "type": "module", | ||
| "version": "0.8.3", | ||
| "description": "Javascript library to format and display cryptocurrencies and fiat", | ||
@@ -27,12 +28,10 @@ "main": "lib/cryptoformat.cjs.js", | ||
| "@babel/preset-env": "^7.6.2", | ||
| "babel-jest": "^24.9.0", | ||
| "jest": "^24.9.0", | ||
| "regenerator-runtime": "^0.13.3", | ||
| "rollup": "^1.22.0", | ||
| "babel-jest": "^30.1.1", | ||
| "jest": "^30.1.1", | ||
| "regenerator-runtime": "^0.14.1", | ||
| "rollup": "^4.48.1", | ||
| "rollup-plugin-commonjs": "^10.1.0", | ||
| "rollup-plugin-copy": "^3.1.0", | ||
| "rollup-plugin-copy": "^3.5.0", | ||
| "rollup-plugin-node-resolve": "^5.2.0" | ||
| }, | ||
| "types": "lib/index.d.ts", | ||
| "dependencies": {}, | ||
| }, | ||
| "files": [ | ||
@@ -42,4 +41,4 @@ "lib" | ||
| "engines": { | ||
| "node": ">=10" | ||
| "node": ">=18" | ||
| } | ||
| } |
| 'use strict'; | ||
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| // A map of supported currency codes to currency symbols. | ||
| const supportedCurrencySymbols = { | ||
| BTC: "₿", | ||
| ETH: "Ξ", | ||
| USD: "$", | ||
| CAD: "C$", | ||
| GBP: "£", | ||
| EUR: "€", | ||
| CHF: "Fr.", | ||
| SEK: "kr", | ||
| JPY: "¥", | ||
| CNY: "¥", | ||
| INR: "₹", | ||
| RUB: "₽", | ||
| AUD: "A$", | ||
| HKD: "HK$", | ||
| SGD: "S$", | ||
| TWD: "NT$", | ||
| BRL: "R$", | ||
| KRW: "₩", | ||
| ZAR: "R", | ||
| MYR: "RM", | ||
| IDR: "Rp", | ||
| NZD: "NZ$", | ||
| MXN: "MX$", | ||
| PHP: "₱", | ||
| DKK: "kr.", | ||
| PLN: "zł", | ||
| AED: "DH", | ||
| ARS: "$", | ||
| CLP: "CLP", | ||
| CZK: "Kč", | ||
| HUF: "Ft", | ||
| ILS: "₪", | ||
| KWD: "KD", | ||
| LKR: "Rs", | ||
| NOK: "kr", | ||
| PKR: "₨", | ||
| SAR: "SR", | ||
| THB: "฿", | ||
| TRY: "₺", | ||
| XAU: "XAU", | ||
| XAG: "XAG", | ||
| XDR: "XDR", | ||
| }; | ||
| // A map of override objects to apply. | ||
| // Format: | ||
| // { location: { start: Boolean, end: Boolean }, forLocales: { <locale>: Boolean } } | ||
| const symbolOverrides = { | ||
| MYR: { location: { start: true }, forLocales: { en: true } }, | ||
| SGD: { location: { start: true }, forLocales: { en: true } }, | ||
| PHP: { location: { start: true }, forLocales: { en: true } }, | ||
| BTC: { location: { start: true }, forLocales: { en: true } }, | ||
| ETH: { location: { start: true }, forLocales: { en: true } }, | ||
| }; | ||
| // Feature detection for Intl.NumberFormat | ||
| function IntlNumberFormatSupported() { | ||
| return !!( | ||
| typeof Intl == "object" && | ||
| Intl && | ||
| typeof Intl.NumberFormat == "function" | ||
| ); | ||
| } | ||
| function isBTCETH(isoCode) { | ||
| return isoCode === "BTC" || isoCode === "ETH"; | ||
| } | ||
| function isCrypto(isoCode) { | ||
| return isBTCETH(isoCode) || supportedCurrencySymbols[isoCode] == null; | ||
| } | ||
| // Function to transform decimal trailing zeroes to exponent | ||
| function decimalTrailingZeroesToExponent(formattedCurrency, maximumDecimalTrailingZeroes) { | ||
| const decimalTrailingZeroesPattern = new RegExp(`(\\.|,)(0{${maximumDecimalTrailingZeroes + 1},})(?=[1-9]?)`); | ||
| return formattedCurrency.replace( | ||
| decimalTrailingZeroesPattern, | ||
| (_match, separator, decimalTrailingZeroes) => `${separator}0<sub title=\"${formattedCurrency}\">${decimalTrailingZeroes.length}</sub>`, | ||
| ) | ||
| } | ||
| // Function to transform the output from Intl.NumberFormat#format | ||
| function formatCurrencyOverride(formattedCurrency, locale = "en", maximumDecimalTrailingZeroes) { | ||
| if (typeof maximumDecimalTrailingZeroes !== "undefined") { | ||
| formattedCurrency = decimalTrailingZeroesToExponent(formattedCurrency, maximumDecimalTrailingZeroes); | ||
| } | ||
| // If currency code remains in front | ||
| const currencyCodeFrontMatch = formattedCurrency.match(/^[A-Z]{3}\s?/); | ||
| if (currencyCodeFrontMatch != null) { | ||
| const code = currencyCodeFrontMatch[0].trim(); // trim possible trailing space | ||
| // Replace currency code with symbol if whitelisted. | ||
| const overrideObj = symbolOverrides[code]; | ||
| if ( | ||
| overrideObj && | ||
| overrideObj.location.start && | ||
| overrideObj.forLocales[locale] | ||
| ) { | ||
| return formattedCurrency.replace( | ||
| currencyCodeFrontMatch[0], | ||
| supportedCurrencySymbols[code] | ||
| ); | ||
| } else { | ||
| return formattedCurrency; | ||
| } | ||
| } | ||
| // If currency code is at the back | ||
| const currencyCodeBackMatch = formattedCurrency.match(/[A-Z]{3}$/); | ||
| if (currencyCodeBackMatch != null) { | ||
| const code = currencyCodeBackMatch[0]; | ||
| // Replace currency code with symbol if whitelisted. | ||
| const overrideObj = symbolOverrides[code]; | ||
| if ( | ||
| overrideObj && | ||
| overrideObj.location.end && | ||
| overrideObj.forLocales[locale] | ||
| ) { | ||
| return formattedCurrency.replace(code, supportedCurrencySymbols[code]); | ||
| } else { | ||
| return formattedCurrency; | ||
| } | ||
| } | ||
| return formattedCurrency; | ||
| } | ||
| // Generates a formatter from Intl.NumberFormat | ||
| function generateIntlNumberFormatter(isoCode, locale, numDecimals, numSigFig) { | ||
| try { | ||
| const params = { | ||
| style: "currency", | ||
| currency: isoCode, | ||
| currencyDisplay: "symbol", | ||
| }; | ||
| if (numDecimals !== undefined) { | ||
| params.minimumFractionDigits = numDecimals; | ||
| params.maximumFractionDigits = numDecimals; | ||
| } else if (numSigFig !== undefined) { | ||
| params.maximumSignificantDigits = numSigFig; | ||
| } | ||
| return new Intl.NumberFormat(locale, params); | ||
| } catch (e) { | ||
| // Unsupported currency, etc. | ||
| // Use primitive fallback | ||
| return generateFallbackFormatter(isoCode, locale, numDecimals, numSigFig); | ||
| } | ||
| } | ||
| // Generates a primitive fallback formatter with no symbol support. | ||
| function generateFallbackFormatter(isoCode, locale, numDecimals = 2, maximumSignificantDigits = 4) { | ||
| isoCode = isoCode.toUpperCase(); | ||
| if (numDecimals > 2) { | ||
| return { | ||
| format: (value) => { | ||
| return isCrypto(isoCode) | ||
| ? `${value.toFixed(numDecimals)} ${isoCode}` | ||
| : `${isoCode} ${value.toFixed(numDecimals)}`; | ||
| }, | ||
| }; | ||
| } else { | ||
| return { | ||
| format: (value) => { | ||
| let formattedValue = value; | ||
| // try using Intl.NumberFormat when possible to support max significant digits | ||
| try { | ||
| formattedValue = new Intl.NumberFormat(locale, { maximumSignificantDigits }).format(value); | ||
| } catch (e) {} | ||
| return isCrypto(isoCode) | ||
| ? `${formattedValue.toLocaleString(locale)} ${isoCode}` | ||
| : `${isoCode} ${formattedValue.toLocaleString(locale)}`; | ||
| }, | ||
| }; | ||
| } | ||
| } | ||
| function generateAbbreviatedFormatter(isoCode, locale) { | ||
| // Show regular numbers if no Intl.NumberFormat support. | ||
| if (!IntlNumberFormatSupported()) { | ||
| return generateFallbackFormatter(isoCode, locale, 0); | ||
| } | ||
| let numberFormatOptions = { style: "decimal", notation: "compact", minimumFractionDigits: 0, maximumFractionDigits: 3 }; | ||
| // Currency symbol is supported if currency is Fiat/BTC/ETH. | ||
| if (!isCrypto(isoCode) || isBTCETH(isoCode)) { | ||
| numberFormatOptions.style = "currency"; | ||
| numberFormatOptions.currency = isoCode; | ||
| } | ||
| return new Intl.NumberFormat(locale, numberFormatOptions); | ||
| } | ||
| function generateFormatter(isoCode, locale, numDecimals, numSigFig) { | ||
| const isNumberFormatSupported = IntlNumberFormatSupported(); | ||
| const useIntlNumberFormatter = | ||
| isNumberFormatSupported && (!isCrypto(isoCode) || isBTCETH(isoCode)); | ||
| return useIntlNumberFormatter | ||
| ? generateIntlNumberFormatter(isoCode, locale, numDecimals, numSigFig) | ||
| : generateFallbackFormatter(isoCode, locale, numDecimals, numSigFig); | ||
| } | ||
| // State variables | ||
| let currentISOCode; | ||
| let currentLocale; | ||
| let currencyFormatterNormal; | ||
| let currencyFormatterNoDecimal; | ||
| let currencyFormatterMedium; | ||
| let currencyFormatterTwoDecimal; | ||
| let currencyFormatterSmall; | ||
| let currencyFormatterVerySmall; | ||
| let currencyFormatterVeryVerySmall; | ||
| let currencyFormatter15DP; | ||
| let currencyFormatter18DP; | ||
| let currencyFormatterAbbreviated; | ||
| // If a page has to display multiple currencies, formatters would have to be created for each of them | ||
| // To save some effort, we save formatters for reuse | ||
| let formattersCache = {}; | ||
| function clearCache() { | ||
| formattersCache = {}; | ||
| } | ||
| function initializeFormatters(isoCode, locale) { | ||
| const cacheKey = `${isoCode}-${locale}`; | ||
| const cachedFormatter = formattersCache[cacheKey]; | ||
| currencyFormatterNormal = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterNormal | ||
| : generateFormatter(isoCode, locale); | ||
| currencyFormatterNoDecimal = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterNoDecimal | ||
| : generateFormatter(isoCode, locale, 0); | ||
| currencyFormatterMedium = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterMedium | ||
| : generateFormatter(isoCode, locale, 3); | ||
| currencyFormatterTwoDecimal = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterTwoDecimal | ||
| : generateFormatter(isoCode, locale, 2); | ||
| currencyFormatterSmall = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterSmall | ||
| : generateFormatter(isoCode, locale, 6); | ||
| currencyFormatterVerySmall = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterVerySmall | ||
| : generateFormatter(isoCode, locale, 8); | ||
| currencyFormatterVeryVerySmall = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterVeryVerySmall | ||
| : generateFormatter(isoCode, locale, 12); | ||
| currencyFormatter15DP = cachedFormatter | ||
| ? cachedFormatter.currencyFormatter15DP | ||
| : generateFormatter(isoCode, locale, 15); | ||
| currencyFormatter18DP = cachedFormatter | ||
| ? cachedFormatter.currencyFormatter18DP | ||
| : generateFormatter(isoCode, locale, 18); | ||
| currencyFormatterAbbreviated = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterAbbreviated | ||
| : generateAbbreviatedFormatter(isoCode, locale); | ||
| // Save in cache | ||
| if (cachedFormatter == null) { | ||
| formattersCache[cacheKey] = {}; | ||
| formattersCache[cacheKey].currencyFormatterNormal = currencyFormatterNormal; | ||
| formattersCache[cacheKey].currencyFormatterNoDecimal = currencyFormatterNoDecimal; | ||
| formattersCache[cacheKey].currencyFormatterMedium = currencyFormatterMedium; | ||
| formattersCache[cacheKey].currencyFormatterTwoDecimal = currencyFormatterTwoDecimal; | ||
| formattersCache[cacheKey].currencyFormatterSmall = currencyFormatterSmall; | ||
| formattersCache[cacheKey].currencyFormatterVerySmall = currencyFormatterVerySmall; | ||
| formattersCache[cacheKey].currencyFormatterVeryVerySmall = currencyFormatterVeryVerySmall; | ||
| formattersCache[cacheKey].currencyFormatter15DP = currencyFormatter15DP; | ||
| formattersCache[cacheKey].currencyFormatter18DP = currencyFormatter18DP; | ||
| formattersCache[cacheKey].currencyFormatterAbbreviated = currencyFormatterAbbreviated; | ||
| } | ||
| } | ||
| // Moderate crypto amount threshold | ||
| const MEDIUM_CRYPTO_THRESHOLD = 50; | ||
| // Large crypto amount threshold | ||
| const LARGE_CRYPTO_THRESHOLD = 1000; | ||
| // No decimal threshold for large amounts | ||
| const NO_DECIMAL_THRESHOLD = 100000; | ||
| function formatCurrency( | ||
| amount, | ||
| isoCode, | ||
| locale = "en", | ||
| raw = false, | ||
| noDecimal = false, | ||
| abbreviated = false, | ||
| ) { | ||
| isoCode = isoCode.toUpperCase(); | ||
| let maximumDecimalTrailingZeroes = undefined; | ||
| if (currentISOCode !== isoCode || currentLocale != locale) { | ||
| currentISOCode = isoCode; | ||
| currentLocale = locale; | ||
| // Formatters are tied to currency code, we try to initialize as infrequently as possible. | ||
| initializeFormatters(isoCode, locale); | ||
| } | ||
| if (abbreviated) { | ||
| let formattedAbbreviatedCurrency = currencyFormatterAbbreviated.format(amount); | ||
| // Manually add currency code to the back for non-BTC/ETH crypto currencies. | ||
| if (isCrypto(isoCode) && !isBTCETH(isoCode)) { | ||
| formattedAbbreviatedCurrency = `${formattedAbbreviatedCurrency} ${isoCode}`; | ||
| } | ||
| return formatCurrencyOverride(formattedAbbreviatedCurrency, locale); | ||
| } | ||
| if (noDecimal === true && amount > 100.0) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNoDecimal.format(amount), | ||
| locale | ||
| ); | ||
| } else if (typeof noDecimal === "object" && noDecimal !== null) { | ||
| if (raw) { | ||
| // Limit to max n decimal places if applicable | ||
| let raw_amount = noDecimal.hasOwnProperty("decimalPlaces") | ||
| ? amount.toFixed(noDecimal.decimalPlaces) | ||
| : amount; | ||
| // Round off to number of significant figures without trailing 0's | ||
| return `${parseFloat(raw_amount).toPrecision(noDecimal.significantFigures) / 1}`; | ||
| } | ||
| if (noDecimal.hasOwnProperty("maximumDecimalTrailingZeroes")) { | ||
| maximumDecimalTrailingZeroes = noDecimal.maximumDecimalTrailingZeroes; | ||
| } | ||
| if (noDecimal.hasOwnProperty("decimalPlaces") && noDecimal.hasOwnProperty("significantFigures")) { | ||
| // Show specified number of significant digits with cutoff of specified fraction digits | ||
| const currencyFormatterCustom = generateFormatter( | ||
| isoCode, | ||
| locale, | ||
| undefined, | ||
| noDecimal.significantFigures | ||
| ); | ||
| return formatCurrencyOverride( | ||
| currencyFormatterCustom.format( | ||
| Number.parseFloat(amount.toFixed(noDecimal.decimalPlaces)) | ||
| ), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (noDecimal.hasOwnProperty("decimalPlaces") || noDecimal.hasOwnProperty("significantFigures")) { | ||
| const currencyFormatterCustom = generateFormatter( | ||
| isoCode, | ||
| locale, | ||
| noDecimal.decimalPlaces, | ||
| noDecimal.significantFigures | ||
| ); | ||
| return formatCurrencyOverride( | ||
| currencyFormatterCustom.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } | ||
| } | ||
| if (isCrypto(isoCode)) { | ||
| let price = parseFloat(amount); | ||
| if (raw) { | ||
| if (amount === 0.0) { | ||
| return price.toFixed(2); | ||
| } | ||
| return price.toPrecision(8); | ||
| } | ||
| if (amount === 0.0) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNormal.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= LARGE_CRYPTO_THRESHOLD) { | ||
| // Large crypto amount, show no decimal value | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNoDecimal.format(amount), | ||
| locale | ||
| ); | ||
| } else if ( | ||
| price >= MEDIUM_CRYPTO_THRESHOLD && | ||
| price < LARGE_CRYPTO_THRESHOLD | ||
| ) { | ||
| // Medium crypto amount, show 3 fraction digits | ||
| return formatCurrencyOverride( | ||
| currencyFormatterMedium.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= 1.0 && price < MEDIUM_CRYPTO_THRESHOLD) { | ||
| // crypto amount, show 6 fraction digits | ||
| return formatCurrencyOverride( | ||
| currencyFormatterSmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= 0.000001 && price < 1.0) { | ||
| // crypto amount, show 8 fraction digits | ||
| return formatCurrencyOverride( | ||
| currencyFormatterVerySmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= 10**-9 && price < 10**-6) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterVeryVerySmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= 10**-12 && price < 10**-9) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatter15DP.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price < 10**-12) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatter18DP.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } | ||
| } else { | ||
| const unsigned_amount = Math.abs(amount); | ||
| if (raw) { | ||
| if (unsigned_amount < 10**-12) { | ||
| return amount.toFixed(18); | ||
| } else if (unsigned_amount < 10**-9) { | ||
| return amount.toFixed(15); | ||
| } else if (unsigned_amount < 10**-6) { | ||
| return amount.toFixed(12); | ||
| } else if (unsigned_amount < 10**-3) { | ||
| return amount.toFixed(8); | ||
| } else if (unsigned_amount < 1.0) { | ||
| return amount.toFixed(6); | ||
| } else { | ||
| return amount.toFixed(2); | ||
| } | ||
| } | ||
| if (unsigned_amount === 0.0) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNormal.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 10**-12) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatter18DP.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 10**-9) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatter15DP.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 10**-6) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterVeryVerySmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 0.05) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterVerySmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 1.0) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterSmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (isoCode === "JPY" && unsigned_amount < 100) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterTwoDecimal.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount > NO_DECIMAL_THRESHOLD) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNoDecimal.format(amount), | ||
| locale | ||
| ); | ||
| } else { | ||
| // Let the formatter do what it seems best. In particular, we should not set any fraction amount for Japanese Yen | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNormal.format(amount), | ||
| locale | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| exports.clearCache = clearCache; | ||
| exports.formatCurrency = formatCurrency; | ||
| exports.isCrypto = isCrypto; |
| // A map of supported currency codes to currency symbols. | ||
| const supportedCurrencySymbols = { | ||
| BTC: "₿", | ||
| ETH: "Ξ", | ||
| USD: "$", | ||
| CAD: "C$", | ||
| GBP: "£", | ||
| EUR: "€", | ||
| CHF: "Fr.", | ||
| SEK: "kr", | ||
| JPY: "¥", | ||
| CNY: "¥", | ||
| INR: "₹", | ||
| RUB: "₽", | ||
| AUD: "A$", | ||
| HKD: "HK$", | ||
| SGD: "S$", | ||
| TWD: "NT$", | ||
| BRL: "R$", | ||
| KRW: "₩", | ||
| ZAR: "R", | ||
| MYR: "RM", | ||
| IDR: "Rp", | ||
| NZD: "NZ$", | ||
| MXN: "MX$", | ||
| PHP: "₱", | ||
| DKK: "kr.", | ||
| PLN: "zł", | ||
| AED: "DH", | ||
| ARS: "$", | ||
| CLP: "CLP", | ||
| CZK: "Kč", | ||
| HUF: "Ft", | ||
| ILS: "₪", | ||
| KWD: "KD", | ||
| LKR: "Rs", | ||
| NOK: "kr", | ||
| PKR: "₨", | ||
| SAR: "SR", | ||
| THB: "฿", | ||
| TRY: "₺", | ||
| XAU: "XAU", | ||
| XAG: "XAG", | ||
| XDR: "XDR", | ||
| }; | ||
| // A map of override objects to apply. | ||
| // Format: | ||
| // { location: { start: Boolean, end: Boolean }, forLocales: { <locale>: Boolean } } | ||
| const symbolOverrides = { | ||
| MYR: { location: { start: true }, forLocales: { en: true } }, | ||
| SGD: { location: { start: true }, forLocales: { en: true } }, | ||
| PHP: { location: { start: true }, forLocales: { en: true } }, | ||
| BTC: { location: { start: true }, forLocales: { en: true } }, | ||
| ETH: { location: { start: true }, forLocales: { en: true } }, | ||
| }; | ||
| // Feature detection for Intl.NumberFormat | ||
| function IntlNumberFormatSupported() { | ||
| return !!( | ||
| typeof Intl == "object" && | ||
| Intl && | ||
| typeof Intl.NumberFormat == "function" | ||
| ); | ||
| } | ||
| function isBTCETH(isoCode) { | ||
| return isoCode === "BTC" || isoCode === "ETH"; | ||
| } | ||
| function isCrypto(isoCode) { | ||
| return isBTCETH(isoCode) || supportedCurrencySymbols[isoCode] == null; | ||
| } | ||
| // Function to transform decimal trailing zeroes to exponent | ||
| function decimalTrailingZeroesToExponent(formattedCurrency, maximumDecimalTrailingZeroes) { | ||
| const decimalTrailingZeroesPattern = new RegExp(`(\\.|,)(0{${maximumDecimalTrailingZeroes + 1},})(?=[1-9]?)`); | ||
| return formattedCurrency.replace( | ||
| decimalTrailingZeroesPattern, | ||
| (_match, separator, decimalTrailingZeroes) => `${separator}0<sub title=\"${formattedCurrency}\">${decimalTrailingZeroes.length}</sub>`, | ||
| ) | ||
| } | ||
| // Function to transform the output from Intl.NumberFormat#format | ||
| function formatCurrencyOverride(formattedCurrency, locale = "en", maximumDecimalTrailingZeroes) { | ||
| if (typeof maximumDecimalTrailingZeroes !== "undefined") { | ||
| formattedCurrency = decimalTrailingZeroesToExponent(formattedCurrency, maximumDecimalTrailingZeroes); | ||
| } | ||
| // If currency code remains in front | ||
| const currencyCodeFrontMatch = formattedCurrency.match(/^[A-Z]{3}\s?/); | ||
| if (currencyCodeFrontMatch != null) { | ||
| const code = currencyCodeFrontMatch[0].trim(); // trim possible trailing space | ||
| // Replace currency code with symbol if whitelisted. | ||
| const overrideObj = symbolOverrides[code]; | ||
| if ( | ||
| overrideObj && | ||
| overrideObj.location.start && | ||
| overrideObj.forLocales[locale] | ||
| ) { | ||
| return formattedCurrency.replace( | ||
| currencyCodeFrontMatch[0], | ||
| supportedCurrencySymbols[code] | ||
| ); | ||
| } else { | ||
| return formattedCurrency; | ||
| } | ||
| } | ||
| // If currency code is at the back | ||
| const currencyCodeBackMatch = formattedCurrency.match(/[A-Z]{3}$/); | ||
| if (currencyCodeBackMatch != null) { | ||
| const code = currencyCodeBackMatch[0]; | ||
| // Replace currency code with symbol if whitelisted. | ||
| const overrideObj = symbolOverrides[code]; | ||
| if ( | ||
| overrideObj && | ||
| overrideObj.location.end && | ||
| overrideObj.forLocales[locale] | ||
| ) { | ||
| return formattedCurrency.replace(code, supportedCurrencySymbols[code]); | ||
| } else { | ||
| return formattedCurrency; | ||
| } | ||
| } | ||
| return formattedCurrency; | ||
| } | ||
| // Generates a formatter from Intl.NumberFormat | ||
| function generateIntlNumberFormatter(isoCode, locale, numDecimals, numSigFig) { | ||
| try { | ||
| const params = { | ||
| style: "currency", | ||
| currency: isoCode, | ||
| currencyDisplay: "symbol", | ||
| }; | ||
| if (numDecimals !== undefined) { | ||
| params.minimumFractionDigits = numDecimals; | ||
| params.maximumFractionDigits = numDecimals; | ||
| } else if (numSigFig !== undefined) { | ||
| params.maximumSignificantDigits = numSigFig; | ||
| } | ||
| return new Intl.NumberFormat(locale, params); | ||
| } catch (e) { | ||
| // Unsupported currency, etc. | ||
| // Use primitive fallback | ||
| return generateFallbackFormatter(isoCode, locale, numDecimals, numSigFig); | ||
| } | ||
| } | ||
| // Generates a primitive fallback formatter with no symbol support. | ||
| function generateFallbackFormatter(isoCode, locale, numDecimals = 2, maximumSignificantDigits = 4) { | ||
| isoCode = isoCode.toUpperCase(); | ||
| if (numDecimals > 2) { | ||
| return { | ||
| format: (value) => { | ||
| return isCrypto(isoCode) | ||
| ? `${value.toFixed(numDecimals)} ${isoCode}` | ||
| : `${isoCode} ${value.toFixed(numDecimals)}`; | ||
| }, | ||
| }; | ||
| } else { | ||
| return { | ||
| format: (value) => { | ||
| let formattedValue = value; | ||
| // try using Intl.NumberFormat when possible to support max significant digits | ||
| try { | ||
| formattedValue = new Intl.NumberFormat(locale, { maximumSignificantDigits }).format(value); | ||
| } catch (e) {} | ||
| return isCrypto(isoCode) | ||
| ? `${formattedValue.toLocaleString(locale)} ${isoCode}` | ||
| : `${isoCode} ${formattedValue.toLocaleString(locale)}`; | ||
| }, | ||
| }; | ||
| } | ||
| } | ||
| function generateAbbreviatedFormatter(isoCode, locale) { | ||
| // Show regular numbers if no Intl.NumberFormat support. | ||
| if (!IntlNumberFormatSupported()) { | ||
| return generateFallbackFormatter(isoCode, locale, 0); | ||
| } | ||
| let numberFormatOptions = { style: "decimal", notation: "compact", minimumFractionDigits: 0, maximumFractionDigits: 3 }; | ||
| // Currency symbol is supported if currency is Fiat/BTC/ETH. | ||
| if (!isCrypto(isoCode) || isBTCETH(isoCode)) { | ||
| numberFormatOptions.style = "currency"; | ||
| numberFormatOptions.currency = isoCode; | ||
| } | ||
| return new Intl.NumberFormat(locale, numberFormatOptions); | ||
| } | ||
| function generateFormatter(isoCode, locale, numDecimals, numSigFig) { | ||
| const isNumberFormatSupported = IntlNumberFormatSupported(); | ||
| const useIntlNumberFormatter = | ||
| isNumberFormatSupported && (!isCrypto(isoCode) || isBTCETH(isoCode)); | ||
| return useIntlNumberFormatter | ||
| ? generateIntlNumberFormatter(isoCode, locale, numDecimals, numSigFig) | ||
| : generateFallbackFormatter(isoCode, locale, numDecimals, numSigFig); | ||
| } | ||
| // State variables | ||
| let currentISOCode; | ||
| let currentLocale; | ||
| let currencyFormatterNormal; | ||
| let currencyFormatterNoDecimal; | ||
| let currencyFormatterMedium; | ||
| let currencyFormatterTwoDecimal; | ||
| let currencyFormatterSmall; | ||
| let currencyFormatterVerySmall; | ||
| let currencyFormatterVeryVerySmall; | ||
| let currencyFormatter15DP; | ||
| let currencyFormatter18DP; | ||
| let currencyFormatterAbbreviated; | ||
| // If a page has to display multiple currencies, formatters would have to be created for each of them | ||
| // To save some effort, we save formatters for reuse | ||
| let formattersCache = {}; | ||
| function clearCache() { | ||
| formattersCache = {}; | ||
| } | ||
| function initializeFormatters(isoCode, locale) { | ||
| const cacheKey = `${isoCode}-${locale}`; | ||
| const cachedFormatter = formattersCache[cacheKey]; | ||
| currencyFormatterNormal = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterNormal | ||
| : generateFormatter(isoCode, locale); | ||
| currencyFormatterNoDecimal = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterNoDecimal | ||
| : generateFormatter(isoCode, locale, 0); | ||
| currencyFormatterMedium = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterMedium | ||
| : generateFormatter(isoCode, locale, 3); | ||
| currencyFormatterTwoDecimal = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterTwoDecimal | ||
| : generateFormatter(isoCode, locale, 2); | ||
| currencyFormatterSmall = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterSmall | ||
| : generateFormatter(isoCode, locale, 6); | ||
| currencyFormatterVerySmall = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterVerySmall | ||
| : generateFormatter(isoCode, locale, 8); | ||
| currencyFormatterVeryVerySmall = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterVeryVerySmall | ||
| : generateFormatter(isoCode, locale, 12); | ||
| currencyFormatter15DP = cachedFormatter | ||
| ? cachedFormatter.currencyFormatter15DP | ||
| : generateFormatter(isoCode, locale, 15); | ||
| currencyFormatter18DP = cachedFormatter | ||
| ? cachedFormatter.currencyFormatter18DP | ||
| : generateFormatter(isoCode, locale, 18); | ||
| currencyFormatterAbbreviated = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterAbbreviated | ||
| : generateAbbreviatedFormatter(isoCode, locale); | ||
| // Save in cache | ||
| if (cachedFormatter == null) { | ||
| formattersCache[cacheKey] = {}; | ||
| formattersCache[cacheKey].currencyFormatterNormal = currencyFormatterNormal; | ||
| formattersCache[cacheKey].currencyFormatterNoDecimal = currencyFormatterNoDecimal; | ||
| formattersCache[cacheKey].currencyFormatterMedium = currencyFormatterMedium; | ||
| formattersCache[cacheKey].currencyFormatterTwoDecimal = currencyFormatterTwoDecimal; | ||
| formattersCache[cacheKey].currencyFormatterSmall = currencyFormatterSmall; | ||
| formattersCache[cacheKey].currencyFormatterVerySmall = currencyFormatterVerySmall; | ||
| formattersCache[cacheKey].currencyFormatterVeryVerySmall = currencyFormatterVeryVerySmall; | ||
| formattersCache[cacheKey].currencyFormatter15DP = currencyFormatter15DP; | ||
| formattersCache[cacheKey].currencyFormatter18DP = currencyFormatter18DP; | ||
| formattersCache[cacheKey].currencyFormatterAbbreviated = currencyFormatterAbbreviated; | ||
| } | ||
| } | ||
| // Moderate crypto amount threshold | ||
| const MEDIUM_CRYPTO_THRESHOLD = 50; | ||
| // Large crypto amount threshold | ||
| const LARGE_CRYPTO_THRESHOLD = 1000; | ||
| // No decimal threshold for large amounts | ||
| const NO_DECIMAL_THRESHOLD = 100000; | ||
| function formatCurrency( | ||
| amount, | ||
| isoCode, | ||
| locale = "en", | ||
| raw = false, | ||
| noDecimal = false, | ||
| abbreviated = false, | ||
| ) { | ||
| isoCode = isoCode.toUpperCase(); | ||
| let maximumDecimalTrailingZeroes = undefined; | ||
| if (currentISOCode !== isoCode || currentLocale != locale) { | ||
| currentISOCode = isoCode; | ||
| currentLocale = locale; | ||
| // Formatters are tied to currency code, we try to initialize as infrequently as possible. | ||
| initializeFormatters(isoCode, locale); | ||
| } | ||
| if (abbreviated) { | ||
| let formattedAbbreviatedCurrency = currencyFormatterAbbreviated.format(amount); | ||
| // Manually add currency code to the back for non-BTC/ETH crypto currencies. | ||
| if (isCrypto(isoCode) && !isBTCETH(isoCode)) { | ||
| formattedAbbreviatedCurrency = `${formattedAbbreviatedCurrency} ${isoCode}`; | ||
| } | ||
| return formatCurrencyOverride(formattedAbbreviatedCurrency, locale); | ||
| } | ||
| if (noDecimal === true && amount > 100.0) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNoDecimal.format(amount), | ||
| locale | ||
| ); | ||
| } else if (typeof noDecimal === "object" && noDecimal !== null) { | ||
| if (raw) { | ||
| // Limit to max n decimal places if applicable | ||
| let raw_amount = noDecimal.hasOwnProperty("decimalPlaces") | ||
| ? amount.toFixed(noDecimal.decimalPlaces) | ||
| : amount; | ||
| // Round off to number of significant figures without trailing 0's | ||
| return `${parseFloat(raw_amount).toPrecision(noDecimal.significantFigures) / 1}`; | ||
| } | ||
| if (noDecimal.hasOwnProperty("maximumDecimalTrailingZeroes")) { | ||
| maximumDecimalTrailingZeroes = noDecimal.maximumDecimalTrailingZeroes; | ||
| } | ||
| if (noDecimal.hasOwnProperty("decimalPlaces") && noDecimal.hasOwnProperty("significantFigures")) { | ||
| // Show specified number of significant digits with cutoff of specified fraction digits | ||
| const currencyFormatterCustom = generateFormatter( | ||
| isoCode, | ||
| locale, | ||
| undefined, | ||
| noDecimal.significantFigures | ||
| ); | ||
| return formatCurrencyOverride( | ||
| currencyFormatterCustom.format( | ||
| Number.parseFloat(amount.toFixed(noDecimal.decimalPlaces)) | ||
| ), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (noDecimal.hasOwnProperty("decimalPlaces") || noDecimal.hasOwnProperty("significantFigures")) { | ||
| const currencyFormatterCustom = generateFormatter( | ||
| isoCode, | ||
| locale, | ||
| noDecimal.decimalPlaces, | ||
| noDecimal.significantFigures | ||
| ); | ||
| return formatCurrencyOverride( | ||
| currencyFormatterCustom.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } | ||
| } | ||
| if (isCrypto(isoCode)) { | ||
| let price = parseFloat(amount); | ||
| if (raw) { | ||
| if (amount === 0.0) { | ||
| return price.toFixed(2); | ||
| } | ||
| return price.toPrecision(8); | ||
| } | ||
| if (amount === 0.0) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNormal.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= LARGE_CRYPTO_THRESHOLD) { | ||
| // Large crypto amount, show no decimal value | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNoDecimal.format(amount), | ||
| locale | ||
| ); | ||
| } else if ( | ||
| price >= MEDIUM_CRYPTO_THRESHOLD && | ||
| price < LARGE_CRYPTO_THRESHOLD | ||
| ) { | ||
| // Medium crypto amount, show 3 fraction digits | ||
| return formatCurrencyOverride( | ||
| currencyFormatterMedium.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= 1.0 && price < MEDIUM_CRYPTO_THRESHOLD) { | ||
| // crypto amount, show 6 fraction digits | ||
| return formatCurrencyOverride( | ||
| currencyFormatterSmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= 0.000001 && price < 1.0) { | ||
| // crypto amount, show 8 fraction digits | ||
| return formatCurrencyOverride( | ||
| currencyFormatterVerySmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= 10**-9 && price < 10**-6) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterVeryVerySmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= 10**-12 && price < 10**-9) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatter15DP.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price < 10**-12) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatter18DP.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } | ||
| } else { | ||
| const unsigned_amount = Math.abs(amount); | ||
| if (raw) { | ||
| if (unsigned_amount < 10**-12) { | ||
| return amount.toFixed(18); | ||
| } else if (unsigned_amount < 10**-9) { | ||
| return amount.toFixed(15); | ||
| } else if (unsigned_amount < 10**-6) { | ||
| return amount.toFixed(12); | ||
| } else if (unsigned_amount < 10**-3) { | ||
| return amount.toFixed(8); | ||
| } else if (unsigned_amount < 1.0) { | ||
| return amount.toFixed(6); | ||
| } else { | ||
| return amount.toFixed(2); | ||
| } | ||
| } | ||
| if (unsigned_amount === 0.0) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNormal.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 10**-12) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatter18DP.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 10**-9) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatter15DP.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 10**-6) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterVeryVerySmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 0.05) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterVerySmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 1.0) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterSmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (isoCode === "JPY" && unsigned_amount < 100) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterTwoDecimal.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount > NO_DECIMAL_THRESHOLD) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNoDecimal.format(amount), | ||
| locale | ||
| ); | ||
| } else { | ||
| // Let the formatter do what it seems best. In particular, we should not set any fraction amount for Japanese Yen | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNormal.format(amount), | ||
| locale | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| export { clearCache, formatCurrency, isCrypto }; |
| (function (global, factory) { | ||
| typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
| typeof define === 'function' && define.amd ? define(['exports'], factory) : | ||
| (global = global || self, factory(global.cryptoformat = {})); | ||
| }(this, function (exports) { 'use strict'; | ||
| // A map of supported currency codes to currency symbols. | ||
| const supportedCurrencySymbols = { | ||
| BTC: "₿", | ||
| ETH: "Ξ", | ||
| USD: "$", | ||
| CAD: "C$", | ||
| GBP: "£", | ||
| EUR: "€", | ||
| CHF: "Fr.", | ||
| SEK: "kr", | ||
| JPY: "¥", | ||
| CNY: "¥", | ||
| INR: "₹", | ||
| RUB: "₽", | ||
| AUD: "A$", | ||
| HKD: "HK$", | ||
| SGD: "S$", | ||
| TWD: "NT$", | ||
| BRL: "R$", | ||
| KRW: "₩", | ||
| ZAR: "R", | ||
| MYR: "RM", | ||
| IDR: "Rp", | ||
| NZD: "NZ$", | ||
| MXN: "MX$", | ||
| PHP: "₱", | ||
| DKK: "kr.", | ||
| PLN: "zł", | ||
| AED: "DH", | ||
| ARS: "$", | ||
| CLP: "CLP", | ||
| CZK: "Kč", | ||
| HUF: "Ft", | ||
| ILS: "₪", | ||
| KWD: "KD", | ||
| LKR: "Rs", | ||
| NOK: "kr", | ||
| PKR: "₨", | ||
| SAR: "SR", | ||
| THB: "฿", | ||
| TRY: "₺", | ||
| XAU: "XAU", | ||
| XAG: "XAG", | ||
| XDR: "XDR", | ||
| }; | ||
| // A map of override objects to apply. | ||
| // Format: | ||
| // { location: { start: Boolean, end: Boolean }, forLocales: { <locale>: Boolean } } | ||
| const symbolOverrides = { | ||
| MYR: { location: { start: true }, forLocales: { en: true } }, | ||
| SGD: { location: { start: true }, forLocales: { en: true } }, | ||
| PHP: { location: { start: true }, forLocales: { en: true } }, | ||
| BTC: { location: { start: true }, forLocales: { en: true } }, | ||
| ETH: { location: { start: true }, forLocales: { en: true } }, | ||
| }; | ||
| // Feature detection for Intl.NumberFormat | ||
| function IntlNumberFormatSupported() { | ||
| return !!( | ||
| typeof Intl == "object" && | ||
| Intl && | ||
| typeof Intl.NumberFormat == "function" | ||
| ); | ||
| } | ||
| function isBTCETH(isoCode) { | ||
| return isoCode === "BTC" || isoCode === "ETH"; | ||
| } | ||
| function isCrypto(isoCode) { | ||
| return isBTCETH(isoCode) || supportedCurrencySymbols[isoCode] == null; | ||
| } | ||
| // Function to transform decimal trailing zeroes to exponent | ||
| function decimalTrailingZeroesToExponent(formattedCurrency, maximumDecimalTrailingZeroes) { | ||
| const decimalTrailingZeroesPattern = new RegExp(`(\\.|,)(0{${maximumDecimalTrailingZeroes + 1},})(?=[1-9]?)`); | ||
| return formattedCurrency.replace( | ||
| decimalTrailingZeroesPattern, | ||
| (_match, separator, decimalTrailingZeroes) => `${separator}0<sub title=\"${formattedCurrency}\">${decimalTrailingZeroes.length}</sub>`, | ||
| ) | ||
| } | ||
| // Function to transform the output from Intl.NumberFormat#format | ||
| function formatCurrencyOverride(formattedCurrency, locale = "en", maximumDecimalTrailingZeroes) { | ||
| if (typeof maximumDecimalTrailingZeroes !== "undefined") { | ||
| formattedCurrency = decimalTrailingZeroesToExponent(formattedCurrency, maximumDecimalTrailingZeroes); | ||
| } | ||
| // If currency code remains in front | ||
| const currencyCodeFrontMatch = formattedCurrency.match(/^[A-Z]{3}\s?/); | ||
| if (currencyCodeFrontMatch != null) { | ||
| const code = currencyCodeFrontMatch[0].trim(); // trim possible trailing space | ||
| // Replace currency code with symbol if whitelisted. | ||
| const overrideObj = symbolOverrides[code]; | ||
| if ( | ||
| overrideObj && | ||
| overrideObj.location.start && | ||
| overrideObj.forLocales[locale] | ||
| ) { | ||
| return formattedCurrency.replace( | ||
| currencyCodeFrontMatch[0], | ||
| supportedCurrencySymbols[code] | ||
| ); | ||
| } else { | ||
| return formattedCurrency; | ||
| } | ||
| } | ||
| // If currency code is at the back | ||
| const currencyCodeBackMatch = formattedCurrency.match(/[A-Z]{3}$/); | ||
| if (currencyCodeBackMatch != null) { | ||
| const code = currencyCodeBackMatch[0]; | ||
| // Replace currency code with symbol if whitelisted. | ||
| const overrideObj = symbolOverrides[code]; | ||
| if ( | ||
| overrideObj && | ||
| overrideObj.location.end && | ||
| overrideObj.forLocales[locale] | ||
| ) { | ||
| return formattedCurrency.replace(code, supportedCurrencySymbols[code]); | ||
| } else { | ||
| return formattedCurrency; | ||
| } | ||
| } | ||
| return formattedCurrency; | ||
| } | ||
| // Generates a formatter from Intl.NumberFormat | ||
| function generateIntlNumberFormatter(isoCode, locale, numDecimals, numSigFig) { | ||
| try { | ||
| const params = { | ||
| style: "currency", | ||
| currency: isoCode, | ||
| currencyDisplay: "symbol", | ||
| }; | ||
| if (numDecimals !== undefined) { | ||
| params.minimumFractionDigits = numDecimals; | ||
| params.maximumFractionDigits = numDecimals; | ||
| } else if (numSigFig !== undefined) { | ||
| params.maximumSignificantDigits = numSigFig; | ||
| } | ||
| return new Intl.NumberFormat(locale, params); | ||
| } catch (e) { | ||
| // Unsupported currency, etc. | ||
| // Use primitive fallback | ||
| return generateFallbackFormatter(isoCode, locale, numDecimals, numSigFig); | ||
| } | ||
| } | ||
| // Generates a primitive fallback formatter with no symbol support. | ||
| function generateFallbackFormatter(isoCode, locale, numDecimals = 2, maximumSignificantDigits = 4) { | ||
| isoCode = isoCode.toUpperCase(); | ||
| if (numDecimals > 2) { | ||
| return { | ||
| format: (value) => { | ||
| return isCrypto(isoCode) | ||
| ? `${value.toFixed(numDecimals)} ${isoCode}` | ||
| : `${isoCode} ${value.toFixed(numDecimals)}`; | ||
| }, | ||
| }; | ||
| } else { | ||
| return { | ||
| format: (value) => { | ||
| let formattedValue = value; | ||
| // try using Intl.NumberFormat when possible to support max significant digits | ||
| try { | ||
| formattedValue = new Intl.NumberFormat(locale, { maximumSignificantDigits }).format(value); | ||
| } catch (e) {} | ||
| return isCrypto(isoCode) | ||
| ? `${formattedValue.toLocaleString(locale)} ${isoCode}` | ||
| : `${isoCode} ${formattedValue.toLocaleString(locale)}`; | ||
| }, | ||
| }; | ||
| } | ||
| } | ||
| function generateAbbreviatedFormatter(isoCode, locale) { | ||
| // Show regular numbers if no Intl.NumberFormat support. | ||
| if (!IntlNumberFormatSupported()) { | ||
| return generateFallbackFormatter(isoCode, locale, 0); | ||
| } | ||
| let numberFormatOptions = { style: "decimal", notation: "compact", minimumFractionDigits: 0, maximumFractionDigits: 3 }; | ||
| // Currency symbol is supported if currency is Fiat/BTC/ETH. | ||
| if (!isCrypto(isoCode) || isBTCETH(isoCode)) { | ||
| numberFormatOptions.style = "currency"; | ||
| numberFormatOptions.currency = isoCode; | ||
| } | ||
| return new Intl.NumberFormat(locale, numberFormatOptions); | ||
| } | ||
| function generateFormatter(isoCode, locale, numDecimals, numSigFig) { | ||
| const isNumberFormatSupported = IntlNumberFormatSupported(); | ||
| const useIntlNumberFormatter = | ||
| isNumberFormatSupported && (!isCrypto(isoCode) || isBTCETH(isoCode)); | ||
| return useIntlNumberFormatter | ||
| ? generateIntlNumberFormatter(isoCode, locale, numDecimals, numSigFig) | ||
| : generateFallbackFormatter(isoCode, locale, numDecimals, numSigFig); | ||
| } | ||
| // State variables | ||
| let currentISOCode; | ||
| let currentLocale; | ||
| let currencyFormatterNormal; | ||
| let currencyFormatterNoDecimal; | ||
| let currencyFormatterMedium; | ||
| let currencyFormatterTwoDecimal; | ||
| let currencyFormatterSmall; | ||
| let currencyFormatterVerySmall; | ||
| let currencyFormatterVeryVerySmall; | ||
| let currencyFormatter15DP; | ||
| let currencyFormatter18DP; | ||
| let currencyFormatterAbbreviated; | ||
| // If a page has to display multiple currencies, formatters would have to be created for each of them | ||
| // To save some effort, we save formatters for reuse | ||
| let formattersCache = {}; | ||
| function clearCache() { | ||
| formattersCache = {}; | ||
| } | ||
| function initializeFormatters(isoCode, locale) { | ||
| const cacheKey = `${isoCode}-${locale}`; | ||
| const cachedFormatter = formattersCache[cacheKey]; | ||
| currencyFormatterNormal = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterNormal | ||
| : generateFormatter(isoCode, locale); | ||
| currencyFormatterNoDecimal = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterNoDecimal | ||
| : generateFormatter(isoCode, locale, 0); | ||
| currencyFormatterMedium = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterMedium | ||
| : generateFormatter(isoCode, locale, 3); | ||
| currencyFormatterTwoDecimal = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterTwoDecimal | ||
| : generateFormatter(isoCode, locale, 2); | ||
| currencyFormatterSmall = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterSmall | ||
| : generateFormatter(isoCode, locale, 6); | ||
| currencyFormatterVerySmall = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterVerySmall | ||
| : generateFormatter(isoCode, locale, 8); | ||
| currencyFormatterVeryVerySmall = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterVeryVerySmall | ||
| : generateFormatter(isoCode, locale, 12); | ||
| currencyFormatter15DP = cachedFormatter | ||
| ? cachedFormatter.currencyFormatter15DP | ||
| : generateFormatter(isoCode, locale, 15); | ||
| currencyFormatter18DP = cachedFormatter | ||
| ? cachedFormatter.currencyFormatter18DP | ||
| : generateFormatter(isoCode, locale, 18); | ||
| currencyFormatterAbbreviated = cachedFormatter | ||
| ? cachedFormatter.currencyFormatterAbbreviated | ||
| : generateAbbreviatedFormatter(isoCode, locale); | ||
| // Save in cache | ||
| if (cachedFormatter == null) { | ||
| formattersCache[cacheKey] = {}; | ||
| formattersCache[cacheKey].currencyFormatterNormal = currencyFormatterNormal; | ||
| formattersCache[cacheKey].currencyFormatterNoDecimal = currencyFormatterNoDecimal; | ||
| formattersCache[cacheKey].currencyFormatterMedium = currencyFormatterMedium; | ||
| formattersCache[cacheKey].currencyFormatterTwoDecimal = currencyFormatterTwoDecimal; | ||
| formattersCache[cacheKey].currencyFormatterSmall = currencyFormatterSmall; | ||
| formattersCache[cacheKey].currencyFormatterVerySmall = currencyFormatterVerySmall; | ||
| formattersCache[cacheKey].currencyFormatterVeryVerySmall = currencyFormatterVeryVerySmall; | ||
| formattersCache[cacheKey].currencyFormatter15DP = currencyFormatter15DP; | ||
| formattersCache[cacheKey].currencyFormatter18DP = currencyFormatter18DP; | ||
| formattersCache[cacheKey].currencyFormatterAbbreviated = currencyFormatterAbbreviated; | ||
| } | ||
| } | ||
| // Moderate crypto amount threshold | ||
| const MEDIUM_CRYPTO_THRESHOLD = 50; | ||
| // Large crypto amount threshold | ||
| const LARGE_CRYPTO_THRESHOLD = 1000; | ||
| // No decimal threshold for large amounts | ||
| const NO_DECIMAL_THRESHOLD = 100000; | ||
| function formatCurrency( | ||
| amount, | ||
| isoCode, | ||
| locale = "en", | ||
| raw = false, | ||
| noDecimal = false, | ||
| abbreviated = false, | ||
| ) { | ||
| isoCode = isoCode.toUpperCase(); | ||
| let maximumDecimalTrailingZeroes = undefined; | ||
| if (currentISOCode !== isoCode || currentLocale != locale) { | ||
| currentISOCode = isoCode; | ||
| currentLocale = locale; | ||
| // Formatters are tied to currency code, we try to initialize as infrequently as possible. | ||
| initializeFormatters(isoCode, locale); | ||
| } | ||
| if (abbreviated) { | ||
| let formattedAbbreviatedCurrency = currencyFormatterAbbreviated.format(amount); | ||
| // Manually add currency code to the back for non-BTC/ETH crypto currencies. | ||
| if (isCrypto(isoCode) && !isBTCETH(isoCode)) { | ||
| formattedAbbreviatedCurrency = `${formattedAbbreviatedCurrency} ${isoCode}`; | ||
| } | ||
| return formatCurrencyOverride(formattedAbbreviatedCurrency, locale); | ||
| } | ||
| if (noDecimal === true && amount > 100.0) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNoDecimal.format(amount), | ||
| locale | ||
| ); | ||
| } else if (typeof noDecimal === "object" && noDecimal !== null) { | ||
| if (raw) { | ||
| // Limit to max n decimal places if applicable | ||
| let raw_amount = noDecimal.hasOwnProperty("decimalPlaces") | ||
| ? amount.toFixed(noDecimal.decimalPlaces) | ||
| : amount; | ||
| // Round off to number of significant figures without trailing 0's | ||
| return `${parseFloat(raw_amount).toPrecision(noDecimal.significantFigures) / 1}`; | ||
| } | ||
| if (noDecimal.hasOwnProperty("maximumDecimalTrailingZeroes")) { | ||
| maximumDecimalTrailingZeroes = noDecimal.maximumDecimalTrailingZeroes; | ||
| } | ||
| if (noDecimal.hasOwnProperty("decimalPlaces") && noDecimal.hasOwnProperty("significantFigures")) { | ||
| // Show specified number of significant digits with cutoff of specified fraction digits | ||
| const currencyFormatterCustom = generateFormatter( | ||
| isoCode, | ||
| locale, | ||
| undefined, | ||
| noDecimal.significantFigures | ||
| ); | ||
| return formatCurrencyOverride( | ||
| currencyFormatterCustom.format( | ||
| Number.parseFloat(amount.toFixed(noDecimal.decimalPlaces)) | ||
| ), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (noDecimal.hasOwnProperty("decimalPlaces") || noDecimal.hasOwnProperty("significantFigures")) { | ||
| const currencyFormatterCustom = generateFormatter( | ||
| isoCode, | ||
| locale, | ||
| noDecimal.decimalPlaces, | ||
| noDecimal.significantFigures | ||
| ); | ||
| return formatCurrencyOverride( | ||
| currencyFormatterCustom.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } | ||
| } | ||
| if (isCrypto(isoCode)) { | ||
| let price = parseFloat(amount); | ||
| if (raw) { | ||
| if (amount === 0.0) { | ||
| return price.toFixed(2); | ||
| } | ||
| return price.toPrecision(8); | ||
| } | ||
| if (amount === 0.0) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNormal.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= LARGE_CRYPTO_THRESHOLD) { | ||
| // Large crypto amount, show no decimal value | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNoDecimal.format(amount), | ||
| locale | ||
| ); | ||
| } else if ( | ||
| price >= MEDIUM_CRYPTO_THRESHOLD && | ||
| price < LARGE_CRYPTO_THRESHOLD | ||
| ) { | ||
| // Medium crypto amount, show 3 fraction digits | ||
| return formatCurrencyOverride( | ||
| currencyFormatterMedium.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= 1.0 && price < MEDIUM_CRYPTO_THRESHOLD) { | ||
| // crypto amount, show 6 fraction digits | ||
| return formatCurrencyOverride( | ||
| currencyFormatterSmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= 0.000001 && price < 1.0) { | ||
| // crypto amount, show 8 fraction digits | ||
| return formatCurrencyOverride( | ||
| currencyFormatterVerySmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= 10**-9 && price < 10**-6) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterVeryVerySmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price >= 10**-12 && price < 10**-9) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatter15DP.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (price < 10**-12) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatter18DP.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } | ||
| } else { | ||
| const unsigned_amount = Math.abs(amount); | ||
| if (raw) { | ||
| if (unsigned_amount < 10**-12) { | ||
| return amount.toFixed(18); | ||
| } else if (unsigned_amount < 10**-9) { | ||
| return amount.toFixed(15); | ||
| } else if (unsigned_amount < 10**-6) { | ||
| return amount.toFixed(12); | ||
| } else if (unsigned_amount < 10**-3) { | ||
| return amount.toFixed(8); | ||
| } else if (unsigned_amount < 1.0) { | ||
| return amount.toFixed(6); | ||
| } else { | ||
| return amount.toFixed(2); | ||
| } | ||
| } | ||
| if (unsigned_amount === 0.0) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNormal.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 10**-12) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatter18DP.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 10**-9) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatter15DP.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 10**-6) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterVeryVerySmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 0.05) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterVerySmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount < 1.0) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterSmall.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (isoCode === "JPY" && unsigned_amount < 100) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterTwoDecimal.format(amount), | ||
| locale, | ||
| maximumDecimalTrailingZeroes | ||
| ); | ||
| } else if (unsigned_amount > NO_DECIMAL_THRESHOLD) { | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNoDecimal.format(amount), | ||
| locale | ||
| ); | ||
| } else { | ||
| // Let the formatter do what it seems best. In particular, we should not set any fraction amount for Japanese Yen | ||
| return formatCurrencyOverride( | ||
| currencyFormatterNormal.format(amount), | ||
| locale | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| exports.clearCache = clearCache; | ||
| exports.formatCurrency = formatCurrency; | ||
| exports.isCrypto = isCrypto; | ||
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| })); |
| export function IntlNumberFormatSupported(): boolean; | ||
| export function isCrypto(isoCode: string): boolean; | ||
| export function clearCache(): void; | ||
| // format currency | ||
| export function formatCurrency(amount: number, isoCode: string): string; | ||
| export function formatCurrency( | ||
| amount: number, | ||
| isoCode: string, | ||
| locale: string | ||
| ): string; | ||
| export function formatCurrency( | ||
| amount: number, | ||
| isoCode: string, | ||
| locale: string, | ||
| raw: boolean | ||
| ): string; | ||
| export function formatCurrency( | ||
| amount: number, | ||
| isoCode: string, | ||
| locale: string, | ||
| raw: boolean, | ||
| noDecimal: boolean | decimalConfig | ||
| ): string; | ||
| export function formatCurrency( | ||
| amount: number, | ||
| isoCode: string, | ||
| locale: string, | ||
| raw: boolean, | ||
| noDecimal: boolean | decimalConfig, | ||
| abbreviated: boolean, | ||
| ): string; | ||
| interface decimalConfig { | ||
| decimalPlaces?: number, | ||
| significantFigures?: number, | ||
| maximumDecimalTrailingZeroes?: number | ||
| } |
Empty package
Supply chain riskPackage does not contain any code. It may be removed, is name squatting, or the result of a faulty package publish.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
0
-100%Yes
NaN5118
-91.02%3
-57.14%0
-100%2
100%