monetochka
Version:
Simple, fast and zero-dependency currency formatting library
285 lines (278 loc) • 8.43 kB
JavaScript
const symbols = {
BTC: '₿',
LTC: null,
ETH: null,
USDT: 'USD₮',
JPYT: 'JPY₮',
EURT: 'EUR₮',
BCH: null,
DASH: 'Dash',
AED: 'د.إ.',
ARS: '$',
AUD: 'A$',
BRL: 'R$',
CAD: 'CA$',
CHF: 'CHF',
CLP: '$',
CNY: '¥',
COP: '$',
CZK: 'Kč',
DKK: 'kr',
DOP: 'RD$',
EGP: '£',
EUR: '€',
GBP: '£',
HKD: 'HK$',
HRK: 'kn',
HUF: 'Ft',
IDR: 'Rp',
INR: '₹',
IRR: '﷼',
JPY: '¥',
KES: 'KSh',
KRW: '₩',
KZT: '₸',
MAD: '.د.م',
MXN: 'MX$',
MYR: 'RM',
NGN: '₦',
NOK: 'kr',
NZD: 'NZ$',
PEN: 'S/.',
PHP: '₱',
PKR: '₨',
PLN: 'zł',
RON: 'lei',
RUB: '₽',
SAR: '.ر.س',
SEK: 'kr',
SGD: 'S$',
THB: '฿',
TRY: '₺',
TZS: 'TSh',
UAH: '₴',
USD: '$',
VEF: 'Bs',
VND: '₫',
ZAR: 'R',
};
const defaultLocales = {
BTC: 'crypto',
LTC: 'crypto',
ETH: 'crypto',
USDT: 'crypto',
JPYT: 'crypto',
EURT: 'crypto',
BCH: 'crypto',
DASH: 'crypto',
AED: 'ar-AE',
ARS: 'es-AR',
AUD: 'en',
BRL: 'pt-BR',
CAD: 'en-CA',
CHF: 'de-CH',
CLP: 'es-CL',
CNY: 'zh-Hans-CN',
COP: 'es-CO',
CZK: 'cs',
DKK: 'da-DK',
DOP: 'es-DO',
EGP: 'ar-EG',
EUR: 'en',
GBP: 'en',
HKD: 'zh-Hans-HK',
HRK: 'hr-HR',
HUF: 'hu-HU',
IDR: 'id',
INR: 'en-IN',
IRR: 'fa-IR',
JPY: 'ja',
KES: 'en-KE',
KRW: 'ko-KR',
KZT: 'kk',
MAD: 'ar',
MXN: 'es-MX',
MYR: 'ms-MY',
NGN: 'en-NG',
NOK: 'nb-NO',
NZD: 'en-NZ',
PEN: 'es-PE',
PHP: 'fil',
PKR: 'ur-PK',
PLN: 'pl',
RON: 'ro',
RUB: 'ru',
SAR: 'ar-SA',
SEK: 'sv-SE',
SGD: 'en-SG',
THB: 'th',
TRY: 'tr-TR',
TZS: 'sw-TZ',
UAH: 'uk',
USD: 'en-US',
VEF: 'es-VE',
VND: 'vi',
ZAR: 'zu',
};
const locales = {
'crypto': { g: ',', d: '.', fMax: 8, t: '@ !' },
'ar': { g: ',', d: '.', f: 2, t: '! @' },
'ar-AE': { h: 'ar' },
'ar-EG': { h: 'ar' },
'ar-SA': { h: 'ar' },
'cs': { g: ' ', d: ',', f: 2, t: '@ !' },
'da': { g: '.', d: ',', f: 2, t: '@ !' },
'da-DK': { h: 'da' },
'de-CH': { g: '’', d: '.', f: 2, t: '! @' },
'en': { g: ',', d: '.', f: 2, t: '!@' },
'en-CA': { h: 'en' },
'en-IN': { g: ',', d: '.', f: 2, t: '! @' },
'en-KE': { h: 'en' },
'en-NG': { h: 'en' },
'en-NZ': { h: 'en' },
'en-SG': { h: 'en' },
'en-US': { h: 'en' },
'es': { g: '.', d: ',', f: 2, t: '@ !' },
'es-AR': { g: '.', d: ',', f: 2, t: '! @' },
'es-CL': { g: '.', d: ',', f: 0, t: '!@' },
'es-CO': { g: '.', d: ',', f: 2, t: '! @' },
'es-DO': { h: 'es' },
'es-MX': { h: 'es' },
'es-PE': { h: 'es' },
'es-VE': { g: '.', d: ',', f: 2, t: '!@' },
'fa': { g: ',', d: '.', f: 2, t: '!@' },
'fa-IR': { h: 'fa' },
'fil': { g: ',', d: '.', f: 2, t: '!@' },
'hr': { g: '.', d: ',', f: 2, t: '@ !' },
'hr-HR': { h: 'hr' },
'hu': { g: ' ', d: ',', f: 2, t: '@ !' },
'hu-HU': { h: 'hu' },
'id': { g: '.', d: ',', f: 2, t: '!@' },
'ja': { g: ',', d: '.', f: 0, t: '!@' },
'kk': { g: ' ', d: ',', f: 2, t: '@ !' },
'ko': { g: ',', d: '.', f: 0, t: '!@' },
'ko-KR': { h: 'ko' },
'ms': { g: ',', d: '.', f: 2, t: '!@' },
'ms-MY': { h: 'ms' },
'nb': { g: ' ', d: ',', f: 2, t: '! @' },
'nb-NO': { h: 'nb' },
'pl': { g: ' ', d: ',', f: 2, t: '@ !' },
'pt': { g: '.', d: ',', f: 2, t: '!@' },
'pt-BR': { h: 'pt' },
'ro': { g: '.', d: ',', f: 2, t: '@ !' },
'ru': { g: ' ', d: ',', f: 2, t: '@ !' },
'sv': { g: ' ', d: ',', f: 2, t: '@ !' },
'sv-SE': { h: 'sv' },
'sw': { g: ',', d: '.', f: 2, t: '!@' },
'sw-TZ': { h: 'sw' },
'th': { g: ',', d: '.', f: 2, t: '!@' },
'tr': { g: '.', d: ',', f: 2, t: '@ !' },
'tr-TR': { h: 'tr' },
'uk': { g: ' ', d: ',', f: 2, t: '@ !' },
'ur': { g: ',', d: '.', f: 2, t: '! @' },
'ur-PK': { h: 'ur' },
'vi': { g: '.', d: ',', f: 0, t: '@ !' },
'zh': { g: ',', d: '.', f: 2, t: '!@' },
'zh-Hans-CN': { h: 'zh' },
'zh-Hans-HK': { h: 'zh' },
'zu': { g: ',', d: '.', f: 2, t: '!@' },
};
const siRound = (value, options) => {
if (value < 1000 || !options.siRounding)
return [value, ''];
const units = options.siUnits;
let decimal;
for (let i = units.length - 1; i >= 0; i--) {
decimal = Math.pow(1000, i + 1);
if (value <= -decimal || value >= decimal) {
return [value / decimal, units[i]];
}
}
};
const numberFormat = (siAmount, options, localeSettings) => {
const [amount, siPostfix] = siAmount;
let minimumFractionDigits = typeof localeSettings.f !== 'undefined'
? localeSettings.f
: 0;
if (typeof options.fractionDigits !== 'undefined' || typeof options.maximumFractionDigits !== 'undefined') {
minimumFractionDigits = typeof options.fractionDigits === 'undefined'
? options.maximumFractionDigits
: options.fractionDigits;
}
let maximumFractionDigits = typeof localeSettings.fMax !== 'undefined'
? localeSettings.fMax
: typeof localeSettings.f !== 'undefined'
? localeSettings.f
: options.fractionDigits;
if (options.siRounding && amount < 1000) {
minimumFractionDigits = options.siPrecision;
maximumFractionDigits = options.siPrecision;
}
const localeString = amount.toLocaleString(`${options.locale}-u-nu-latn`, {
minimumFractionDigits,
maximumFractionDigits,
});
return `${localeString}${siPostfix}`;
};
const defaultSiUnits = ['K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
const defaultOptions = {
isCrypto: false,
siPrecision: 0,
siRounding: false,
siUnits: defaultSiUnits,
throwOnError: false,
errorValue: '—',
};
const throwErr = (message, options, e) => {
if (options.throwOnError) {
throw (e || new Error(message));
}
return options.errorValue;
};
const format = (amount, currency, options = {}) => {
const mergedOptions = Object.assign({}, defaultOptions, options);
if (Number.isNaN(amount) || !Number.isFinite(amount)) {
return throwErr('The amount is not countable (NaN or Infinity)', mergedOptions);
}
if (typeof amount !== 'number') {
return throwErr(`Wrong amount type (${typeof amount})`, mergedOptions);
}
if (typeof currency !== 'string') {
return throwErr(`Wrong currency type (${typeof amount})`, mergedOptions);
}
const currencyLocale = defaultLocales[currency] ? defaultLocales[currency] : 'en';
if (!mergedOptions.locale)
mergedOptions.locale = currencyLocale;
const currencySymbol = symbols[currency] && currencyLocale !== 'crypto' ? symbols[currency] : currency;
const formatRules = locales[currencyLocale].h
? locales[locales[currencyLocale].h]
: locales[currencyLocale];
let amountWithSI;
let formattedAmount;
try {
amountWithSI = siRound(amount, mergedOptions);
formattedAmount = numberFormat(amountWithSI, mergedOptions, formatRules);
}
catch (e) {
return throwErr(`Can't parse a value`, mergedOptions, e);
}
return formatRules.t
.replace('!', currencySymbol)
.replace('@', formattedAmount);
};
const parse = (string, currency) => {
const currencyLocale = defaultLocales[currency] ? defaultLocales[currency] : 'en';
const formatRules = locales[currencyLocale].h
? locales[locales[currencyLocale].h]
: locales[currencyLocale];
let cleanedString = string;
if (symbols[currency])
cleanedString = cleanedString.replace(symbols[currency], '');
cleanedString = cleanedString.replace(currency, '');
cleanedString = cleanedString.trim();
cleanedString = cleanedString.split(formatRules.g).join('');
cleanedString = cleanedString.replace(formatRules.d, '.');
return parseFloat(cleanedString);
};
var index = { format, parse };
export default index;