64

Updated 2022: With hermes enabled you should be good now.

I'm using .toLocaleString() on react-native for my number output. All work on IOS but seems not working on Android. This is normal or? Do I need to use a function for the decimal?

enter image description here

EQuimper
  • 5,811
  • 8
  • 29
  • 42
  • Possible duplicate of [Number().toLocaleString() has different format in different browsers](http://stackoverflow.com/questions/29942231/number-tolocalestring-has-different-format-in-different-browsers) – Andreyco Dec 31 '16 at 15:22
  • I understand in browser that can be different but this is for react-native. So that why I got stuck. – EQuimper Dec 31 '16 at 20:36
  • 1
    RN uses Javascript engine integrated in OS. JS engine is very likely one from browser used on particular platform and as you can see, they have differences. I suggest to use lib to get same results across different platforms. – Andreyco Jan 01 '17 at 18:43
  • 1
    This is a known issue in RN Android: https://github.com/facebook/react-native/issues/16867 – Jeremy Jan 11 '18 at 21:51
  • @EQuimper did you find any solution? – jake oliver Jul 13 '18 at 10:35
  • In 2022, this may be fixed by using the latest version of RN and using the hermes js engine, which is now recommended for everyone (I think). – Chrispher Dec 15 '22 at 13:42
  • 1
    Yes hermes fix it – EQuimper Dec 15 '22 at 15:04

16 Answers16

59

rather than using a polyfill or an external dependency, change the JSC your android app builds with. For the newer versions of react-native add or override the following line in app/build.gradle

def jscFlavor = 'org.webkit:android-jsc-intl:+'
Taylor Johnson
  • 1,845
  • 1
  • 18
  • 31
  • 5
    This is also the most complete and flexible answer IMHO, since it adds support for Internationalization to JavaScript Core. Do remember this runtime is slightly bigger that its counterpart (~6MB). – santamanno Mar 02 '20 at 17:45
  • Yes this should be marked as the correct answer. The one above isn't considering the device locale at all... – Dion Dec 18 '20 at 11:23
  • This worked for me. I would just add that it only worked for me after doing `yarn add jsc-android`. – rcorrie Apr 16 '21 at 16:41
17

On newer versions of RN >0.62 you can change the JSC (JavaScriptCore) build variant to support/include ICU i18n library and necessary data allowing to use e.g. Date.toLocaleString and String.localeCompare

Replace this line in your android/app/build.gradle file

def jscFlavor = 'org.webkit:android-jsc:+'

with this line

def jscFlavor = 'org.webkit:android-jsc-intl:+'

Clean build and react-native run android

Note

This variant is about 6MiB larger per architecture than default. So, expect your APK size to increase by about 4MB for each APK architecture build if using def enableSeparateBuildPerCPUArchitecture = true and a more bigger APK if separate build per architecture is disabled

16

You can use

number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
  • 77
    This is shameful. – iuliu.net Dec 13 '17 at 08:42
  • 10
    wont be helpful when you are using localisation and if you are trying to format currency amount. – Bajju Mar 14 '18 at 05:40
  • 1
    love this solution! – Itamar Nov 06 '18 at 12:46
  • This restricts the number formatting to western decimal system. It is not the required locale formatting – Dani Akash Apr 26 '19 at 06:27
  • 3
    this seems to add commas after decimal points, which is not desirable. `0.0123` becomes `0.0,123` – Zack Aug 15 '19 at 16:59
  • 1
    I am writing an app that uses Expo which doesn't allow modifying `app/build.gradle` so I used this solution instead. To work around the issue pointed out by @Zack the method checks for a decimal point, `number.toString().indexOf('.') > 1`, and if one exists it does a split `const numberParts = number.toString().split(/\./);`. Then the regex is applied to `numberParts[0]`, then `'.' + numberParts[1]` is appended. If there is no decimal then the regex is applied directly to `number`. – knot22 Jun 15 '20 at 01:37
  • Based. Only solution which works between environments. – wilmol Aug 19 '20 at 04:24
  • This deoesn't work if you have decimal places – ßiansor Å. Ålmerol Jul 23 '21 at 13:29
  • This is not the correct solution. Localization is hard. In some locales, 123456.789 should be formatted as ١٢٣٬٤٥٦٫٧٨٩ (SO doesn't show those separators visually differently). Try (123456.789).toLocaleString('ar-EG') on a modern browser. A correct solution would give the same behaviour as a modern browser, which this solution does not. In particular, users in Egypt using the 'arabic' locale should see Eastern Arabic numerals. – Chrispher Dec 15 '22 at 13:50
15

This is an issue with Javascript core used to run react native in Android and not with react native itself. To overcome this, you'll have to integrate latest javascript core into your android build or upgrade react native to 0.59.

The details are documented in JSC Android Buildscripts repo.

Now for people who would like to do the locale string formatting without needing to integrate the entire javascript core, Javascript has Internationalization API which lets you format numbers to language sensitive format. Documentation available at MDN

This API is not available in android and needs to be polyfilled using Intl

In your project root, install the Intl library

yarn add intl

And then in your project's index file (index.js) add the following code at the top of the file:

if(Platform.OS === 'android') { // only android needs polyfill
  require('intl'); // import intl object
  require('intl/locale-data/jsonp/en-IN'); // load the required locale details
}

After doing the above two steps, you can now get locale string anywhere in your project using

new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR' }).format(10000000);

In case you need to format number for another locale code, all the locale code details are available under the intl/locale-data/jsonp/ directory. Simply require the ones you need in your index.js file.

Dani Akash
  • 6,828
  • 3
  • 35
  • 47
6

The reason for this is very old version of JavaScriptCore used by react-native. iOS embeds own version which is why it is working fine there.

Issue still exists (some reading about where it's heading https://github.com/facebook/react-native/issues/19737)

And more info about this from Airbnb devs https://medium.com/airbnb-engineering/react-native-at-airbnb-the-technology-dafd0b43838 (search for "JavaScriptCore inconsistencies")

3
(value) => {
    if (typeof value === 'number') {
      const [currency, cents] = (value / 100).toFixed(2).toString().split('.');

      return `${currency.replace(/\B(?=(\d{3})+(?!\d))/g, '.')},${cents}`;
    }

    return '0,00';
  }
  • Some explanation in plain english may improve your answer. – de. Sep 15 '20 at 20:49
  • This seems like a typescript function that returns the number behind the decimal point. The original accepted answer only does the replace with regex pattern without returning any numbers behind decimal points. This will probably return just 2 fractionals behind the decimal point as per the toFixed(2). – Alen Saqe Nov 02 '21 at 21:34
  • The only pitfall is that if the provided value is not a number but a string number it will return 0,00 and not try to parse this as an Int. – Alen Saqe Nov 02 '21 at 21:36
2

it's more recent and lightweight, please check

  1. First install:

yarn add @formatjs/intl-getcanonicallocales @formatjs/intl-locale @formatjs/intl-pluralrules @formatjs/intl-numberformat

  1. Check if need polyfill

import {shouldPolyfill} from '@formatjs/intl-numberformat/should-polyfill'

if (shouldPolyfill()) {
    require('@formatjs/intl-getcanonicallocales/polyfill');
    require('@formatjs/intl-locale/polyfill');
    require('@formatjs/intl-pluralrules/polyfill');
    require('@formatjs/intl-numberformat/polyfill');
    require('@formatjs/intl-numberformat/locale-data/en-US');
}

see source: https://formatjs.io/docs/polyfills/intl-numberformat/

Mike Vargas
  • 186
  • 2
  • 6
1

A very easy and straight forward way is to use a polyfill: First it needs to be installed:

npm i number-to-locale-string-polyfill

This has to be added in your code, best just outside the class/function where you want to use .toLocaleString().

require('number-to-locale-string-polyfill');
Anna Ira Hurnaus
  • 1,680
  • 3
  • 19
  • 29
1

I solved this using a custom function

function numberToMoney(amount, simbol = '$', decimalCount = 2, decimal 
   = ".", thousands = ",") {
   decimalCount = Math.abs(decimalCount)
   decimalCount = isNaN(decimalCount) ? 2 : decimalCount

   const negativeSign = amount < 0 ? "-" : ""

   const i = parseInt(amount = Math.abs(Number(amount) || 
             0).toFixed(decimalCount)).toString()
   const j = (i.length > 3) ? i.length % 3 : 0

   return simbol + negativeSign + (j ? i.substr(0, j) + thousands : '') + 
   i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + thousands) + (decimalCount ? 
   decimal + Math.abs(amount - i).toFixed(decimalCount).slice(2) : "")
};

No need to install extra packages

Maicon Gilton
  • 581
  • 1
  • 7
  • 6
1

Displaying currency values in React Native

A zero dependencies solution:

const parseCurr = (value) =>
   Platform.OS === 'android'  
      ?  '$' + price.toFixed(2)  
      :  price.toLocaleString('en-US', { style: 'currency', currency:'USD' });


parseCurr(25.75) // => $25.75

A real life example (money values are multiplied by 100 for better cents precision) and converting the value to Brazilian Reais (R$)

export const getBRPrice = (price: number) => {
   const parsedPrice = 
      ( price / 100 ).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' });

    return Platform.OS === 'android'
        ? `R$${ ( price / 100 ).toFixed(2) }`
        : parsedPrice;
};


// getBRPrice(450) => R$4,50
Felipe Chernicharo
  • 3,619
  • 2
  • 24
  • 32
1

Solution: 1

Go to your android/app/build.gradle

Replace this line def jscFlavor = 'org.webkit:android-jsc:+'

with this

def jscFlavor = 'org.webkit:android-jsc-intl:+'

Stop the metro and rebuild your app.

Solution: 2

Otherwise, you can use this package https://www.npmjs.com/package/luxon

import import {DateTime} from 'luxon';

const date = DateTime.fromISO(new Date().toISOString());

const formatted = date.toLocaleString(DateTime.DATETIME_MED);

console.log(formatted);

0

Merging some responses from this thread, you can use this code where it is possible to customize the formatted response

const defaultOptions = {
  significantDigits: 2,
  thousandsSeparator: ',',
  decimalSeparator: '.',
  symbol: '$'
}

const currencyFormatter = (value, options) => {
  if (typeof value !== 'number') value = 0.0
  options = { ...defaultOptions, ...options }
  value = value.toFixed(options.significantDigits)

  const [currency, decimal] = value.split('.')
  return `${options.symbol} ${currency.replace(
    /\B(?=(\d{3})+(?!\d))/g,
    options.thousandsSeparator
  )}${options.decimalSeparator}${decimal}`
}
Gabriel Brito
  • 1,003
  • 2
  • 16
  • 26
0
function numberWithCommas(x) {
   return x.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ",");
}

This will remove commas after decimal point

0

If you need two digits after the decimal and always want to round down you can use below code.

Math.floor(1233.31231231 * 100) / 100).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")

To round differently check out this resource

0

If these solutions don't work for you... In my case, I was using React Native with the expo web simulator and wanted to format minutes with 2 characters ie. 00, 01, ... 10, 11, etc. My solution was to check if minutes contained one character, if so, prepend a "0".

... + (date.getMinutes().toString().length == 1 ? "0" : "") + date.getMinutes().toString()
Enrique Avina
  • 973
  • 7
  • 20
0

Use this, it worked for me:

{Platform.OS == 'android' ? '$' +  amount.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ",") : amount.toLocaleString('en-US', {style: 'currency', currency: 'USD'})}
Joel
  • 327
  • 4
  • 3