I'm working on a banking app front-end written in TypeScript.
From the back-end, I receive:
"32012012012012312.09"
"USD"
)I'd like to format the amount with thousand separators, a currency symbol, and NBSP/NNBSP handling, all with respect to provided locale. What's the easiest way?
Are there potential solutions to partial problems? E.g. 1. formatting the number and 2. formatting with currency symbol.
What I tried:
Intl.NumberFormat
- I like it, but it takes a number | bigint
(although it doesn't crash with strings), but it stops being precise at large decimal numbers - e.g. it formats 32_012_012_012_012_312.09
as 32,012,012,012,012,310.00
- I believe this is a JavaScript limitation regarding numbers, that's why I'm looking for something handling stringsaccounting-js
- formatMoney
function with both string and number - same result and missing functionality of passing the locale inUsing Šimon Kocúrek's advice to format the BigInt part separately and bringing it a step further to still utilize the currency formatting of Intl.NumberFormat
in a safer way:
const locale = 'de-DE'
const currency = 'EUR'
const amount = "321321321321321321.357" // parseFloat(c) gives 321321321321321340
// in the comments I also give an example for '321321321321321321.998' because of rounding issue
const [mainString, decimalString] = amount.split(".") // ['321321321321321321', '.357' | '998']
const decimalFormat = new Intl.NumberFormat(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
const decimalFullString = `0.${decimalString}` // '0.357' | '0.998'
const decimalFullNumber = Number.parseFloat(decimalFullString) // 0.357 | 0.998
const decimalFullFinal = decimalFormat.format(decimalFullNumber) // '0,36' | '1,00'
const decimalFinal = decimalFullFinal.slice(1) // ',36' | ',00'
const mainFormat = new Intl.NumberFormat(locale, { minimumFractionDigits: 0 })
let mainBigInt = BigInt(mainString) // 321321321321321321n
if (decimalFullFinal[0] === "1") mainBigInt += BigInt(1) // 321321321321321321n | 321321321321321322n
const mainFinal = mainFormat.format(mainBigInt) // '321.321.321.321.321.321' | '321.321.321.321.321.322'
const amountFinal = `${mainFinal}${decimalFinal}` // '321.321.321.321.321.321,36' | '321.321.321.321.321.322,00'
const currencyFormat = new Intl.NumberFormat(locale, { style: "currency", currency, maximumFractionDigits: 0 })
const template = currencyFormat.format(0) // '€0'
const result = template.replace("0", amountFinal) // '€321.321.321.321.321.321,36' | '€321.321.321.321.321.322,00'
Running with:
locale = 'fr'
produces '321 321 321 321 321 321,36 €'
(with NNBSP between trios)locale = 'de'
produces '321.321.321.321.321.321,36 €'
locale = 'en'
produces '€321,321,321,321,321,321.36'
This also handles string-numbers with more than 2 decimal digits.