23

For formatting a number according to locale, there is a standard JavaScript API: Intl.NumberFormat

But for the reverse action, parsing a string to a number I cannot find any standard API supporting locales:

Is there really no JavaScript standard API to parse a string to a number according to a locale?

And if not: are there any market established, open source libraries to do so?

Min-Soo Pipefeet
  • 2,208
  • 4
  • 12
  • 31
  • afaik, no. what else beside converting a comma decimal point to a period would need done? – dandavis Mar 26 '19 at 19:37
  • 1
    Not standard, but you might look at https://github.com/Brightspace/intl – p.s.w.g Mar 26 '19 at 19:40
  • 2
    @dandavis Group separator, e.g. Maybe other things, I don't know because I don't know every locale on earth. That's why I'd like to have a standardized and properly maintained localization functionality. – Min-Soo Pipefeet Mar 26 '19 at 19:55
  • well JS has never been able to parse grouped digits or leading char like `$`, which is why i asked... – dandavis Mar 26 '19 at 20:24
  • See [this related post](https://stackoverflow.com/q/25645163/8583692). – Mahozad Dec 24 '21 at 15:20

4 Answers4

17

The NPM package d2l-intl provides a locale-sensitive parser. It has since been superseded by @brightspace-ui/intl (essentially version 3), so some info below might or might not apply in its newer incantation.

const { NumberFormat, NumberParse } = require('d2l-intl');
const formatter = new NumberFormat('es');
const parser = new NumberParse('es');
const number = 1234.5;
console.log(formatter.format(number));                 // 1.234,5
console.log(parser.parse(formatter.format(1234.5)));   // 1234.5

Unfortunately, that library only comes with support for a handful of locales out of the box. It also uses parseInt which only supports Western Arabic numerals, so for locales that use different numeral systems, you're going to have to get more clever. Here's one solution I found by Mike Bostock. I don't want to take credit for it, but I've reproduced it here for posterity (with some slight tweaks based on my own preferences):

class NumberParser {
  constructor(locale) {
    const format = new Intl.NumberFormat(locale);
    const parts = format.formatToParts(12345.6);
    const numerals = Array.from({ length: 10 }).map((_, i) => format.format(i));
    const index = new Map(numerals.map((d, i) => [d, i]));
    this._group = new RegExp(`[${parts.find(d => d.type === "group").value}]`, "g");
    this._decimal = new RegExp(`[${parts.find(d => d.type === "decimal").value}]`);
    this._numeral = new RegExp(`[${numerals.join("")}]`, "g");
    this._index = d => index.get(d);
  }
  parse(string) {
    return (string = string.trim()
      .replace(this._group, "")
      .replace(this._decimal, ".")
      .replace(this._numeral, this._index)) ? +string : NaN;
  }
}

const formatter = new Intl.NumberFormat('ar-EG');
const parser = new NumberParser('ar-EG');
console.log(formatter.format(1234.5));               // ١٬٢٣٤٫٥
console.log(parser.parse(formatter.format(1234.5))); // 1234.5
oligofren
  • 20,744
  • 16
  • 93
  • 180
p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
  • d2l-intl's support of locales is very sparsely, indeed; there is even no de locale. – Min-Soo Pipefeet Mar 27 '19 at 07:30
  • 1
    @Min-SooPipefeet It seems like the newer versions rely a lot more on the browser includes? https://www.npmjs.com/package/@brightspace-ui/intl – oligofren Oct 16 '22 at 21:24
  • 2
    In order to deal correctly with negative numbers, we need to use negative number in `format.formatToParts(-12345.6)`, store the minus sign (that might be a different char than the regular `-`), and replace it later with regular minus sign. Working [slightly extended example here](https://stackoverflow.com/a/74352853/1761692) – Evgeni Dikerman Nov 07 '22 at 21:04
3

"A JavaScript library for internationalization and localization that leverage the official Unicode CLDR JSON data. The library works both for the browser and as a Node.js module."

https://github.com/globalizejs/globalize

Andrea
  • 137
  • 2
  • 11
2

Try Mike Bostock's coercion of the Intl.NumberFormat tool into a parser.

class NumberParser {
  constructor(locale) {
    const parts = new Intl.NumberFormat(locale).formatToParts(12345.6);
    const numerals = [...new Intl.NumberFormat(locale, {useGrouping: false}).format(9876543210)].reverse();
    const index = new Map(numerals.map((d, i) => [d, i]));
    this._group = new RegExp(`[${parts.find(d => d.type === "group").value}]`, "g");
    this._decimal = new RegExp(`[${parts.find(d => d.type === "decimal").value}]`);
    this._numeral = new RegExp(`[${numerals.join("")}]`, "g");
    this._index = d => index.get(d);
  }
  parse(string) {
    return (string = string.trim()
      .replace(this._group, "")
      .replace(this._decimal, ".")
      .replace(this._numeral, this._index)) ? +string : NaN;
  }
}
oligofren
  • 20,744
  • 16
  • 93
  • 180
Jim Morrison
  • 2,077
  • 1
  • 19
  • 27
0

This library tries to handle all locales. It retrieves string which contains a number and it tries to "guess" which culture is the number from and converts it to number.

https://www.npmjs.com/package/number-parsing

Use it in a following way:

var parser = require("number-parsing");
var a = parser("123'123.99USD"); // will return 123123.99
var b = parser("1234"); // will return 1234
var c = parser("123 123,777") // will return 123123.777
// and so on
michal.jakubeczy
  • 8,221
  • 1
  • 59
  • 63