Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there any JavaScript standard API to parse to number according to locale?

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:

  • Number does not support any locale arguments.
  • parseFloat and parseInt don't support any locale arguments either.

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?

like image 632
Min-Soo Pipefeet Avatar asked Mar 26 '19 19:03

Min-Soo Pipefeet


People also ask

How do I localize a number in JavaScript?

In JavaScript, toLocaleString() is a Number method that is used to convert a number into a locale-specific numeric representation of the number (rounding the result where necessary) and return its value as a string.

How do you convert a string to a number in JavaScript?

How to convert a string to a number in JavaScript using the parseInt() function. Another way to convert a string into a number is to use the parseInt() function. This function takes in a string and an optional radix. A radix is a number between 2 and 36 which represents the base in a numeral system.

What does the toLocaleString () method do in JS?

The toLocaleString() method returns a string with a language-sensitive representation of this date. In implementations with Intl. DateTimeFormat API support, this method simply calls Intl. DateTimeFormat .

What is locale JavaScript?

Locale (Runtime - JavaScript) A Locale object represents a specific geographical, political, or cultural region. An operation that requires a Locale to perform its task is called locale-sensitive and uses the Locale to tailor information for the user.


2 Answers

The NPM package d2l-intl provides a locale-sensitive parser.

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
like image 105
p.s.w.g Avatar answered Sep 28 '22 19:09

p.s.w.g


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

Credit: https://observablehq.com/@mbostock/localized-number-parsing

ES6:

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;
  }
}

ES5:

var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();

    function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }

    function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

    var NumberParser = (function () {
        function NumberParser(locale) {
            _classCallCheck(this, NumberParser);

            var parts = new Intl.NumberFormat(locale).formatToParts(12345.6);
            var numerals = [].concat(_toConsumableArray(new Intl.NumberFormat(locale, { useGrouping: false }).format(9876543210))).reverse();
            var index = new Map(numerals.map(function (d, i) {
            return [d, i];
            }));
            this._group = new RegExp("[" + parts.find(function (d) {
            return d.type === "group";
            }).value + "]", "g");
            this._decimal = new RegExp("[" + parts.find(function (d) {
            return d.type === "decimal";
            }).value + "]");
            this._numeral = new RegExp("[" + numerals.join("") + "]", "g");
            this._index = function (d) {
            return index.get(d);
            };
        }

        _createClass(NumberParser, [{
            key: "parse",
            value: function parse(string) {
            return (string = string.trim().replace(this._group, "").replace(this._decimal, ".").replace(this._numeral, this._index)) ? +string : NaN;
            }
        }]);

        return NumberParser;
    })();
like image 23
Jim Morrison Avatar answered Sep 28 '22 19:09

Jim Morrison