andyearnshaw / Intl.js

Compatibility implementation of the ECMAScript Internationalization API (ECMA-402) for JavaScript -- UNMAINTAINED
Other
1.7k stars 215 forks source link

Bug when adding NumberFormat Polyfill #315

Open DavidLangva opened 6 years ago

DavidLangva commented 6 years ago

Replication of Issue: https://repl.it/repls/GrouchyBruisedMigration Bug: https://github.com/andyearnshaw/Intl.js/blob/v1.2.4/src/11.numberformat.js#L47-L56

Following the recommendations on the README, we are reassigning NumberFormat in case areIntlLocalesSupported returns false:

if (global.Intl) {
    if (!areIntlLocalesSupported(localesMyAppSupports)) {
        var IntlPolyfill    = require('intl');
        Intl.NumberFormat   = IntlPolyfill.NumberFormat;
        Intl.DateTimeFormat = IntlPolyfill.DateTimeFormat;
    }
}

However, because we reassign the Global Intl.NumberFormat to the polyfilled polyfillIntl.NumberFormat, we have the following problem when calling Intl.NumberFormat: !this || this === Intl always evaluate to false:

if (!this || this === Intl){
  return new Intl.NumberFormat(locales, options); 
}
return InitializeNumberFormat(toObject(this), locales, options);}

Because NumberFormat is a function called on the global Intl Object the this keyword refers to the global Intl object. Thus the if statement will never be satisfied (this will always be defined and the global Intl object will never === the local/polyfilled Intl object ) thus InitializeNumberFormat will be called every time.

Also, the first time InitializeNumberFormat is called, it is passed the global Intl object (via the this keyword) and not a NumberFormat Object InitializeNumberFormat(toObject(this), locales, options)

Then

Intl.NumberFormat('en-US');
Intl.NumberFormat('fr-FR');

This raises a type error TypeError: 'this' object has already been initialized as an Intl object

The workaround is to use it as a function rather than a method so that the this keyword is not bound to the global Intl Object:

const intlNumberFormat = Intl.NumberFormat; 
intlNumberFormat('en-US');
intlNumberFormat('fr-FR');

OR to completely replace Intl in the initialization step:

if (global.Intl) {
    if (!areIntlLocalesSupported(localesMyAppSupports)) {
        var IntlPolyfill    = require('intl');
        Intl = IntlPolyfill;
    }
}
serhiipalash commented 4 years ago

We have the same problem with using moment-duration-format library in our React Native app on Android. In Android JS Engine global.Intl is available, but NumberFormat is missing in it, and moment-duration-format library checks if Intl.NumberFormat is avaialbe on load, so our app just crashes on start. The only solution is to downgrade to previous versions of moment-duration-format, in which there is no call of Intl.NumberFormat on load module.

@DavidLangva where this code lies in this repository? I didn't find it.

if (!this || this === Intl){
  return new Intl.NumberFormat(locales, options); 
}