Parse a Localized Number in JavaScript (Any Locale, Using Intl)
toLocaleString() and Intl.NumberFormat can write a number in any locale. Nothing built in can read it back.
The broken round trip
const text = (1234567.891).toLocaleString("fr-FR"); // "1 234 567,891"
parseFloat(text); // 1
Number(text); // NaNWhy a quick regex does not survive real locales
A popular workaround asks Intl.NumberFormat for the group and decimal characters with formatToParts and replaces them. It is the right idea, but the details bite:
- Invisible characters. fr-FR groups with a narrow no-break space (U+202F), ru-RU and sv-SE with a no-break space (U+00A0). Users type a normal space. A regex built from the Intl character will not match what people type.
- Apostrophes. de-CH groups with an apostrophe, printed as
'or as a right single quotation mark depending on the ICU version. - Indian grouping. en-IN and hi-IN write
12,34,567.89: groups of two after the first three digits. - Other digits. ar-EG prints Arabic-Indic digits with
٫and٬separators, fa-IR Persian digits, and Intl inserts invisible bidi marks around the minus sign. - Signs. sv-SE prints the minus sign U+2212, not a hyphen. Accounting formats use parentheses.
The fix
numberparrot does the formatToParts work once per locale, caches it, and handles every case above:
import { parse } from "numberparrot";
const locales = ["fr-FR", "de-CH", "en-IN", "ar-EG", "sv-SE", "ja-JP"];
for (const locale of locales) {
const text = new Intl.NumberFormat(locale).format(-1234567.891);
console.log(locale, text, parse(text, locale)); // always -1234567.891
}The test suite runs this round trip for 17 locales and more than 80 values in decimal, scientific, percent, currency and accounting styles.
Look at the locale data
import { getLocaleInfo } from "numberparrot";
getLocaleInfo("de-CH");
// { locale: "de-CH", decimal: ".", group: "'", minusSign: "-",
// percentSign: "%", exponent: "E", digits: "0123456789", numberingSystem: "latn" }Parse many values fast
import { createParser } from "numberparrot";
const sv = createParser("sv-SE");
rows.map((row) => sv.parse(row.amount));Install
npm install numberparrotZero dependencies, ESM and CommonJS, TypeScript types included. Works in Node.js 18+, Deno, Bun and browsers. Try it in the playground.