Splitting Frontend i18n Files
Static texts (titles, dialog messages, etc.) need i18n. As content grows, a single file becomes unwieldy. Split by feature into multiple JSON/JS files to improve maintainability.
We want the split JSON files to look like this:
How do we achieve this? Keep reading.
Environment
Here’s the environment I used:
- React
v16.4.2
- React-intl
v^2.7.2
- Webpack
v4.17.1
Install merge-jsons-webpack-plugin
yarn add merge-jsons-webpack-plugin --dev
Webpack config
const MergeJsonWebpackPlugin = require('merge-jsons-webpack-plugin');
new MergeJsonWebpackPlugin({
output: {
groupBy: [
{pattern: "./src/main/webapp/i18n/en/*.json", fileName: "./i18n/en.json"}
// jhipster-needle-i18n-language-webpack - JHipster will add/remove languages in this array
]
}
})
App loading
The following is the React loading code; Angular, Vue, etc. follow the same pattern.
export const getLocale = lang => axios.get(`/i18n/${lang}.json`);
setupAxiosInterceptors(() => (location.href = '/#/login'));
const rootEl = document.getElementById('root');
const messages = {};
const locale = 'en';
const render = () =>
ReactDOM.render(
<AppContainer>
<IntlProvider locale={locale} messages={messages[locale]}>
{
// @ts-ignore
<Provider store={store}>
<AppComponent />
</Provider>
}
</IntlProvider>
</AppContainer>,
rootEl
);
getLocale(locale).then(res => {
messages[locale] = res.data;
render();
});
How it works
The concept is simple, but let’s walk through it anyway: Webpack, as the build tool, merges N JSON files into one and moves it to a specific path. The web app requests the merged file, so externally it’s still a single file, while authors can maintain multiple files. Webpack handles that middle step.
Caching of merged files
We all know the frontend caching story: for static assets such as images, videos, or i18n files, the browser may stick with the cached version. If we add translations but users keep seeing the cached file, what should we do? We need a way to make the browser skip the cache and fetch the fresh file.
Solution
Add a timestamp parameter to the request. Changing the URL means the browser naturally bypasses the cache. In effect, whenever the frontend ships a new build, users pick up the new i18n file; otherwise they keep using the cached one.
export const getLocale = lang =>
axios.get(`/i18n/${lang}.json`, {
params: {
buildTimestamp: process.env.BUILD_TIMESTAMP
}
});
What’s the best approach?
That works, but let’s go a step further.
Strictly speaking, “build time” correlates with i18n changes but isn’t a sufficient condition. The perfect solution is: if the i18n content changes, request the new file; otherwise stay with the cached one.
Can we make that happen? Definitely—it’s all just code. MergeJsonWebpackPlugin would need to add a hash fingerprint during the merge (just like JS bundling), and the request would target the hashed filename. The plugin doesn’t support it yet, so it’s on us to implement.
Final Thoughts
That’s it for now—no new pitfalls uncovered yet. A small problem, but a foundational one. Bye!