Using .toLocaleString() in Node.js

Just in case someone else stumbles upon this, here's how I formatted a number into a valid US dollar string while in a Node.js environment.

Number.prototype.toMoney = function() {
  var integer = this.toString().split('.')[0];
  var decimal = this.getDecimal();

  integer = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ",");

  if( !decimal || !decimal.length ) {
    decimal = "00";
  } else if ( decimal.length === 1) {
    decimal += '0';
  } else if ( decimal.length > 2 ) {
    decimal = decimal.substr(0, 2);
  }

  return '$' + integer + '.' + decimal;
};

Number.prototype.getDecimal = function() {
  var n = Math.abs(this);
  var dec = n - Math.floor(n);
  dec = ( Math.round( dec * 100 ) / 100 ).toString();

  if( dec.split('.').length ) {
    return dec.split('.')[1];
  } else return "";
};

There are a few boo-boo's here, namely extending the native Number prototype. You will want to avoid this is 90% of the time; this is more specific to my particular implementation.

I blatantly stole the regex for formatting the commas from this question. and hacked together the decimal support of my own volition. Mileage may vary.


The results depend on the ICU data used by Node. From site.icu-project.org:

ICU is a mature, widely used set of – – libraries providing Unicode and Globalization support for software applications. ICU is widely portable and gives applications the same results on all platforms – –.

Node 13+

Starting from version 13.0.0, Node comes with full ICU support by default. This means that formatting numbers should automatically work the same way in Node (from version 13 onwards) as it does in browsers.

From v13 changelog:

Node.js releases are now built with default full-icu support. This means that all locales supported by ICU are now included and Intl-related APIs may return different values than before (Richard Lau) #29887.

Issue #19214 has also relevant discussion about this.

Note: v13 is not an LTS (long-time support) version, but v14 is, so v14 is a better choice. See Node.js Releases.

Node 12 and earlier

You need to install and enable full ICU data manually. Here's how:

  1. Run npm install full-icu --save.

  2. Run also npm install cross-env --save to support Windows users (optional but recommended).

  3. Update the scripts section of package.json to set the environment variable NODE_ICU_DATA. For example:

    {
      "scripts": {
        // Before
        "start": "react-scripts start",
        "test": "react-scripts test",
    
        // After (omit "cross-env" if you didn't install the package in step two)
        "start": "react-scripts start",
        "test": "cross-env NODE_ICU_DATA=node_modules/full-icu react-scripts test"
      }
    }
    

This way, when you run npm test, Node will load the full ICU data from node_modules/full-icu, and you should get consistent results between browser and server environments.

You could also modify the start script, but it might be unnecessary; depends on what the script does. In the example above, the script opens a browser, so modifying it would be unnecessary.

Disclaimer: I haven't tested whether this affects performance. In my case, I did this to fix Jest tests of an app that runs in the browser, so a small performance hit when running the tests would have been acceptable.

For further details, see "Internationalization support" in Node (v12) docs.

Kudos to Rndmax's answer here: Date toLocaleDateString in node


Based on this issue it appears that it was decided that shipping node.js with internationalization would make it too large. You can npm install intl and require that, and it will replace toLocaleString with a version that works.


So to update this for anyone facing the same issue...

We had used intl for our localization solution when server side rendering, but we recently had a requirement to add {timeZoneName: 'short'} to our .toLocaleString() options and this field is not supported.

Source Code from intl.js:

case 'timeZoneName':
  fv = ''; // ###TODO
  break;

Additionally, there was a breaking change in the latest patch release which forced us to lock down our version to 1.2.4.

Ultimately, we dropped usage of intl in favor of full-icu. Simply adding it to our yarn.lock solved all our Node.js server side date localization issues. Haven't verified currency localization, but so far so good. I recommend giving it a try.