I've been working on something like this lately, although I don't need SSR for my project. I found that pairing dynamic import syntax with React's Suspense component achieves the desired result. Here's a rough overview of what I found to work, at least in my case, which doesn't include SSR:
// 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 a hook like useState, should kick off re-render and load new message bundle when locale changes (but I haven't tested this yet)
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; definitely more than would be achieved via CopyWebpackPlugin:
(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");
/***/ })
}]);
Hopefully, this is a good starting point and either works with SSR or can be modified to work with SSR. Please report back with your findings on that subject. 🙂