I18n and webpack dev server. Generate missing translation keys

24 August 2021
#i18n#webpack#dev server#translation#react

Let's cover how to generate missing translation keys with i18n and webpack dev server and not to think about it in progress of development.

Motivation

  1. When we’re working on different components with translations we want to have all focus on the code.
  2. When we’ve completed work on them we need to add all translations to JSON file and sometimes it’s hard to go through the code and get all translations keys.
  3. We don’t need to run a separate node js server for that (because we already run dev server).
  4. With auto-creating files of missing keys, we don’t need to think about it and it’ll automatically create all missing keys inside this file. Then we just move it to the main translation file and add them.

How it works

For example we have Sidebar component where we need to add new item with translation, let's say it's contact-new item (in real cases you'll have more challenging tasks).

import React from 'react';
import { useTranslation } from 'react-i18next';

const Sidebar: React.FC = () => {
  const { t } = useTranslation('Sidebar');

  return (
    <ul className="sidebar">
      <li>{t('home')}</li>
      <li>{t('posts')}</li>
      <li>{t('about')}</li>
      <li>{t('news')}</li>
      <li>{t('contact')}</li>
      <li>{t('contact-new')}</li>    </ul>
  );
};

export default Sidebar;

Now we just save our file and refresh the page (Sidebar component should render on the page to create missing file). And we can see Sidebar.missing.json in translations folder.

Missing file

Setup

I've created the project via CRA (create react app) and run eject.

You need to install next packages - npm install i18next react-i18next i18next-http-backend i18next-http-middleware i18next-fs-backend --save

In src folder let's create new folder with name locales. Inside create two files: i18n.ts and namespaces.json.

// i18n.ts

import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import Backend from 'i18next-http-backend';

const isDevelopmentEnv = process.env.NODE_ENV === 'development';

i18n
  // load translation using http -> see /public/translations
  .use(Backend)
  // detect user language
  .use(LanguageDetector)
  // pass the i18n instance to react-i18next.
  .use(initReactI18next)
  // init i18next
  .init({
    backend: {
      loadPath: '/translations/{{lng}}/{{ns}}.json', // path where resources get loaded from
      addPath: '/translations/add/{{lng}}/{{ns}}' // path to post missing resources
    },

    saveMissing: isDevelopmentEnv, // save missing keys just when node env. is development
    saveMissingTo: 'all',

    fallbackLng: 'en',
    supportedLngs: ['en', 'ru'],
    debug: isDevelopmentEnv, // debug just for development env.
    load: 'currentOnly',

    defaultNS: 'Common',
    fallbackNS: 'Common',
    ns: [],

    interpolation: {
      escapeValue: false // not needed for react as it escapes by default
    }
  });

export default i18n;
// namespaces.json
[
  "Common",
  "Content",
  "Sidebar"
]

In config of webpack dev server we need to add next:

// config/webpackDevServer.config.js

const middleware = require('i18next-http-middleware');
const i18next = require('i18next');
const FsBackend = require('i18next-fs-backend');
const i18nextNamespaces = require(paths.appSrc + '/locales/namespaces.json');

i18next.use(FsBackend).init({
  backend: {
    loadPath: path.join(paths.appPublic, '/translations/{{lng}}/{{ns}}.json'),
    addPath: path.join(paths.appPublic, '/translations/{{lng}}/{{ns}}.missing.json')
  },
  crossDomain: false,
  defaultNS: 'Common',
  fallbackNS: 'Common',
  fallbackLng: ['en'],
  ns: i18nextNamespaces,
  debug: true
});

and

// config/webpackDevServer.config.js

setup(app, server) {
  app.use(bodyParser.urlencoded({ extended: false }));
  app.use(bodyParser.json());
  // our front will call this method and missingKeyHandler will check and create missing files with missing keys
  app.post('/translations/add/:lng/:ns', middleware.missingKeyHandler(i18next));
},

And that's all. Have a good coding ;)


You can find demo project on GitHub.