2

I'm making a multi language universal app using React and I'm having a hard time to find out the best way to deal with locale data. The app is going to be available in 16 languages and the amount of translated messages are quite big, so I can't load all the messages in one big json (as it is used in most react-intl examples) and I can't import those messages in the webpack generated bundle, I just need to load the user language messages on demand. I was able to do it when the app is just running on the client side, but I also need it to be working on the server side too. I'm using express for server side rendering and webpack for bundling. Could anyone help to find out the best way to deal with this?

matthias
  • 2,255
  • 1
  • 23
  • 28
Coluccini
  • 688
  • 1
  • 7
  • 18

1 Answers1

4

I've been working on something like this lately, although I don't have SSR in my project. I found that pairing dynamic import syntax with React's Suspense component seems to achieve the desired result in my case. Your milage may vary since you need SSR as well, but here's a rough overview of what I found to work for me:

// wrap this around your JSX in App.js:
<React.Suspense fallback={<SomeLoadingComponent />}>
  <AsyncIntlProvider>
    {/* app child components go here */}
  </AsyncIntlProvider>
</React.Suspense>

// the rest is in support of this
// can be placed in another file
// simply import AsyncIntlProvider in App.js

const messagesCache = {};

const AsyncIntlProvider = ({ children }) => {
  // replace with your app's locale getting logic
  // if based on something like useState, should kick off re-render and load new message bundle when locale changes
  const locale = getLocale();

  const messages = getMessages(locale);
  return (
    <IntlProvider locale={locale} messages={messages}>
      {children}
    </IntlProvider>
  );
};

function getMessages(locale) {
  if (messagesCache[locale]) {
    return messagesCache[locale];
  }
  // Suspense is based on ErrorBoundary
  // throwing a promise will cause <SomeLoadingComponent /> to render until the promise resolves
  throw loadMessages(locale);
}

async function loadMessages(locale) {
  // dynamic import syntax tells webpack to split this module into its own chunk
  const messages = await import('./path/to/${locale}.json`);
  messagesCache[locale] = messages;
  return messages;
}

Webpack should split each locale JSON file into its own chunk. If it doesn't, something is likely transpiling the dynamic import syntax to a different module system (require, etc) before it reaches webpack. For example: if using Typescript, tsconfig needs "module": "esnext" to preserve import() syntax. If using Babel, it may try to do module transpilation too.

The chunk output for a single locale will look something like this:

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0],{

/***/ "./path/to/en-US.json":
/*!*************************************!*\
  !*** ./path/to/en-US.json ***!
  \*************************************/
/*! exports provided: message.id, default */
/***/ (function(module) {

eval("module.exports = JSON.parse(\"{\\\"message.id\\\":\\\"Localized message text\\\"}\");//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvbG9jYWxpemF0aW9uL2VuLVVTLmpzb24uanMiLCJzb3VyY2VzIjpbXSwibWFwcGluZ3MiOiIiLCJzb3VyY2VSb290IjoiIn0=\n//# sourceURL=webpack-internal:///./path/to/en-US.json\n");

/***/ })

}]);

Hope this helps. Best of luck internationalizing your project!

nickiaconis
  • 1,418
  • 12
  • 24
  • Hi @nickiaconis! Thanks a lot :) Your answer looks pretty good, but my issue was so much time ago (3 years xD) and I've moved to a such a different project that I'm not able to judge it So I would let the community to do it. Thanks a lot! – Coluccini Oct 30 '19 at 08:43