Search code examples
javascriptreactjsreact-nativepolyfillszulip

How to polyfill the JavaScript `Intl` API in React Native?


Closed: This issue is because of a bug on my side. See the answer for more information.


I am testing a custom JavaScript runtime on android, with a sample React Native application (specifically: Zulip Mobile). The runtime supports most modern JavaScript features, but it lacks support for Intl.

However, the RN app uses Intl via react-intl's IntlProvider type (see here). To continue testing with this app, I would need to polyfill the API myself, but none of my tries succeeded in putting the polyfilled Intl into global variables.

import { IntlProvider, IntlContext } from 'react-intl';

// ...

export default function TranslationProvider(props: Props): Node {
  const { children } = props;
  const language = useGlobalSelector(state => getGlobalSettings(state).language);

  return (
    <IntlProvider locale={language} textComponent={Text} messages={messages[language]}>
      <TranslationContextTranslator>{children}</TranslationContextTranslator>
    </IntlProvider>
  );
}

The error stacktrace was:

ReferenceError: 'Intl' is not defined

Component Stack:

    in IntlProvider
    in Unknown
    in RCTView
    in Unknown
    in L
    in Connect(L)
    in Unknown
    in RNCSafeAreaProvider
    in Unknown
    in Unknown
    in n
    in Unknown
    in b
    in O
    in Unknown
    in RCTView
    in Unknown
    in RCTView
    in Unknown
    in b

Call Stack:

    at <anonymous> (index.android.bundle)
    at <anonymous> (index.android.bundle)
    at <anonymous> (index.android.bundle)
    at o (index.android.bundle)
    at jt (index.android.bundle)
    at ul (index.android.bundle)
    at ql (index.android.bundle)
    at Fa (index.android.bundle)
    at Ua (index.android.bundle)
    at La (index.android.bundle)
    at Ea (index.android.bundle)
    at ft (index.android.bundle)
    at Te (index.android.bundle)
    at Pe (index.android.bundle)
    at notify (index.android.bundle)
    at notifyNestedSubs (index.android.bundle)
    at f (index.android.bundle)
    at b (index.android.bundle)
    at <anonymous> (index.android.bundle)
    at <anonymous> (index.android.bundle)
    at <anonymous> (index.android.bundle)
    at <anonymous> (index.android.bundle)
    at call (native)
    at v (index.android.bundle)
    at <anonymous> (index.android.bundle)
    at <anonymous> (index.android.bundle)
    at call (native)
    at v (index.android.bundle)
    at o (index.android.bundle)
    at <anonymous> (index.android.bundle)
    at u (index.android.bundle)
    at <anonymous> (index.android.bundle)
    at apply (native)
    at <anonymous> (index.android.bundle)
    at k (index.android.bundle)
    at w (index.android.bundle)
    at callReactNativeMicrotasks (index.android.bundle)
    at value (index.android.bundle)
    at <anonymous> (index.android.bundle)
    at value (index.android.bundle)
    at value (index.android.bundle)
    at value (index.android.bundle)

I tried polyfilling the Intl API using both intl and formatjs packages from NPM. Neither succeeded, and the error was the same as above.

I do not have any previous experience writing React Native apps (I'm testing the ability of the runtime). My method of polyfilling could be completely wrong -- if that's the case I would like to be kindly pointed out.

// index.js

/* @flow strict-local */
// React Navigation requires this react-native-gesture-handler import,
// as the very first import of this entry-point file.  See our #5373.
import 'react-native-gesture-handler';
import IntlPolyfill from 'intl'; // <-
import { AppRegistry } from 'react-native';
import ZulipMobile from './src/ZulipMobile';

if (!globalThis.Intl) {
    globalThis.Intl = IntlPolyfill;
}

AppRegistry.registerComponent('ZulipMobile', () => ZulipMobile);
// index.js

/* @flow strict-local */
// React Navigation requires this react-native-gesture-handler import,
// as the very first import of this entry-point file.  See our #5373.
import 'react-native-gesture-handler';
import { AppRegistry } from 'react-native';
import ZulipMobile from './src/ZulipMobile';

// polyfills
import '@formatjs/intl-getcanonicallocales/polyfill';
import '@formatjs/intl-locale/polyfill';
import '@formatjs/intl-displaynames/polyfill';
import '@formatjs/intl-displaynames/locale-data/en';

if (!global.Intl) {
  throw new Error('Intl is missing???');
}

AppRegistry.registerComponent('ZulipMobile', () => ZulipMobile);

I have also reviewed and tried the answers under these questions, but also didn't succeed:


Solution

  • It was a bug on my side.

    Specifically, it was the script not updating at all when I was editing, for some reason.

    In order to build and profile the app with Android Studio, I had to bundle the script manually, and then put it in the android app assets. Although the build script would run the bundler again while building for release, the result cannot be recognized by the android app assets, and I needed to build it manually.

    npx react-native bundle \
      --platform android \
      --dev false \
      --entry-file index.js \
      --bundle-output android/app/src/main/assets/index.android.bundle \
      --assets-dest test-assets
    

    However, I totally forgot the fact that the script was there while I was trying to fix the bug. Having found that the bundled script was not updated, I just need to manually run the bundler on each build.


    As of polyfilling Intl, this is the piece of code (and packages) I used:

    // index.js
    
    import '@formatjs/intl-getcanonicallocales/polyfill';
    import '@formatjs/intl-locale/polyfill';
    import '@formatjs/intl-numberformat/polyfill';
    import '@formatjs/intl-numberformat/locale-data/en';
    import '@formatjs/intl-datetimeformat/polyfill';
    import '@formatjs/intl-datetimeformat/locale-data/en';
    import '@formatjs/intl-pluralrules/polyfill';
    import '@formatjs/intl-pluralrules/locale-data/en';
    import '@formatjs/intl-relativetimeformat/polyfill';
    import '@formatjs/intl-relativetimeformat/locale-data/en';
    import '@formatjs/intl-displaynames/polyfill';