Internationalization

RelativeCI - In-depth bundle stats analysis and monitoring - Interview with Viorel Cojocaru

Internationalization (i18n) is a big topic by itself. The broadest definition has to do with translating your user interface to other languages. Localization (l10n) is a more specific term, and it describes how to adapt your application to a particular locale or market. Different locales can have the same language, but they still have their customs, like date formatting or measures.

The problem could be solved by pushing the translations behind an endpoint and loading them dynamically to decouple the issue from webpack. Doing this would also allow you to implement a translation interface within your application to enable your translators, or even users, to translate the application. The downside of this approach is that then you have a translation backend to maintain.

Another approach is to let webpack generate static builds, each per language. The problem is that you have to update your application each time your translations change.

See the Intl JavaScript API to find out what utilities the browsers provide to help with the problem.

i18n with webpack#

The basic idea of i18n with webpack is often the same. You have a translation definition that is then mapped to the application through replacements. The result contains a translated version of the application.

You can use po-loader to map GNU gettext PO files to multiple formats, including raw JSON and Jed.

Another way is to use webpack's import() syntax and Dynamic Loading to set up a small system of your own. That's what we'll do next.

Setting up translations#

Set up initial translations as below:

translations/en.json

{ "hello": "Hello world" }

translations/fi.json

{ "hello": "Terve maailma" }

Setting up webpack#

If you've implemented webpack configuration in the book so far, you can reuse most of that. The configuration below works standalone and provides a build for React:

webpack.i18n.js

const path = require("path");
const {
  MiniHtmlWebpackPlugin,
} = require("mini-html-webpack-plugin");
const APP_SOURCE = path.join(__dirname, "src");

module.exports = {
  mode: "production",
  entry: { index: path.join(APP_SOURCE, "i18n.js") },
  module: {
    rules: [
      {
        test: /\.js$/,
        include: APP_SOURCE,
        use: "babel-loader",
      },
    ],
  },
  plugins: [new MiniHtmlWebpackPlugin()],
};

The Babel configuration required looks like this:

{
  "presets": [
    ["@babel/preset-env", { "modules": false }],
    "@babel/preset-react"
  ]
}

To make it convenient to generate the demo application, set up a shortcut:

package.json

{
  "scripts": {
"build:i18n": "wp --config webpack.i18n.js",
} }

Setting up application#

The last step is to create a small application to load the translations using React and import():

src/i18n.js

import "regenerator-runtime/runtime";
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";

const App = () => {
  const [language, setLanguage] = useState("en");
  const [hello, setHello] = useState("");

  const changeLanguage = () =>
    setLanguage(language === "en" ? "fi" : "en");

  useEffect(() => {
    translate(language, "hello")
      .then(setHello)
      .catch(console.error);
  }, [language]);

  return (
    <div>
      <button onClick={changeLanguage}>Change language</button>
      <div>{hello}</div>
    </div>
  );
};

function translate(locale, text) {
  return getLocaleData(locale).then((messages) => messages[text]);
}

async function getLocaleData(locale) {
  return import(`../translations/${locale}.json`);
}

const root = document.createElement("div");

root.setAttribute("id", "app");
document.body.appendChild(root);

ReactDOM.render(<App />, root);

If you build (npm run build:i18n) and run (npx serve dist) the application, you should see that it's loading the translation dynamically and as you click the button, it's changing the translation.

To eliminate that regenerator-runtime/runtime import, use Babel's useBuiltIns option. It's explained in more detail at the Loading JavaScript chapter.

Conclusion#

An internationalization and localization approach can be built on top of webpack. Specific loaders can help in the task as you can push tasks like processing gettext PO files to them.

To recap:

  • Webpack supports multiple approaches to i18n and l10n. As a starting point, you can develop a small setup on top of webpack's import() syntax.
  • A part of the logic can be pushed to loaders for processing PO files for example.

The next chapter covers various testing setups and tools that work with webpack.

Previous chapter
Web Workers
Next chapter
Testing

This book is available through Leanpub (digital), Amazon (paperback), and Kindle (digital). By purchasing the book you support the development of further content. A part of profit (~30%) goes to Tobias Koppers, the author of webpack.

Need help?