numberparrot

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

example.ts
const text = (1234567.891).toLocaleString("fr-FR"); // "1 234 567,891"
parseFloat(text); // 1
Number(text);     // NaN

Why 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:

example.ts
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

example.ts
import { getLocaleInfo } from "numberparrot";

getLocaleInfo("de-CH");
// { locale: "de-CH", decimal: ".", group: "'", minusSign: "-",
//   percentSign: "%", exponent: "E", digits: "0123456789", numberingSystem: "latn" }

Parse many values fast

example.ts
import { createParser } from "numberparrot";

const sv = createParser("sv-SE");
rows.map((row) => sv.parse(row.amount));

Install

Terminal
npm install numberparrot

Zero dependencies, ESM and CommonJS, TypeScript types included. Works in Node.js 18+, Deno, Bun and browsers. Try it in the playground.

More guides