Splitting Frontend i18n Files

· 3 min read · 438 Words · -Views -Comments

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:

image

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!

Authors
Developer, digital product enthusiast, tinkerer, sharer, open source lover