Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - translations per component

I'm using create-react-app for my React application and I'm currently trying to figure out translations.

Right now I have one big JSON file per locale and each locale file includes translations for all components - which is not ideal and I would like to have separate translation files for each component.

So the structure of locales will look like this: ComponentA - locales - en.json - sk.json

ComponentB - locales - en.json - sk.json

What is the best way to achieve this? Which modules should I use to achieve this?

I do not want to include these locale files into build because we will support 10 languages so the bundle size will be too big.

like image 666
Palo Delinčák Avatar asked Jan 02 '23 15:01

Palo Delinčák


1 Answers

I'm going to answer my own question. After research I figured out that my use-case is rare and I decided to do some locale pre-processing to achieve desired functionality.

I'm running default config of create-react-app together with react-i18next and my file structure looks like this:

src
  - Component A
    - locales
      - en.json
      - sk.json
  - Component B
    - locales
      - en.json

Here are settings for the react-i18next module:

i18n
  .use(Backend)
  .use('en')
  .use(reactI18nextModule)
  .init({
    fallbackLng: 'en',
    debug: false,
    backend: {
      loadPath: function() {
        return `${config.BASE_URL}locales/{{lng}}.json`;
      }
    },
    react: {
      wait: true
    }
  });

Since I'm using default config of create-react-app then the locales are served from public folder in the app and build itself.

And now it gets tricky. Since the locale files are located in the component tree (and not in public folder) then we need to do some pre-processing of locales - basically we need to concat all JSON files and move them to the public folder.

Files for public folder are created on the fly using node task (prepare-locales.js) below:

const jsonConcat = require('json-concat');
const minifyJson = require('minify-json');
const glob = require('glob');
const availableLocales = ['en', 'sk'];
const path = require('path');

module.exports.development = function() {
  availableLocales.forEach(locale => {
    glob(`./src/**/locales/${locale}.json`, {}, (err, globFiles) => {
      if (globFiles.length) {
        const file = `./public/locales/${locale}.json`;

        jsonConcat(
          {
            src: globFiles,
            dest: file
          },
          json => {
            minifyJson(file);
          }
        );
      }
    });
  });
};

module.exports.production = function() {
  glob('./build/static/js/main.*.js', {}, (err, globFiles) => {
    const files = require('source-map-explorer')(globFiles[0]).files;

    let localeFolders = [];
    Object.keys(files).forEach(fileName => {
      localeFolders.push(path.dirname(fileName).replace('./', ''));
    });
    localeFolders = [...new Set(localeFolders)];

    availableLocales.forEach(locale => {
      const localeFiles = localeFolders.map(
        lf => `./src/${lf}/locales/${locale}.json`
      );

      if (localeFiles.length) {
        const file = `./build/locales/${locale}.json`;

        jsonConcat(
          {
            src: localeFiles,
            dest: file
          },
          json => {
            minifyJson(file);
          }
        );
      }
    });
  });
};

For the development version I'm concating all JSON files which are found in the tree.

For the production version I'm using source-map-explorer and I'm concating only those locales which are used in the build.

Here is my simplified package.json with tasks required for this to work:

{
  "devDependencies": {    
    "json-concat": "0.0.1",
    "minify-json": "^1.0.0",
    "npm-watch": "^0.4.0",
    "path": "^0.12.7",
    "react-scripts": "2.1.1",
    "source-map-explorer": "^1.6.0"
  },
  "scripts": {    
    "start": "react-scripts start & npm run prepare-locales:development:watch",
    "build:production": "react-scripts build && npm run prepare-locales:production",    
    "prepare-locales:development:watch": "npm-watch prepare-locales:development",
    "prepare-locales:development": "node -e 'require(\"./prepare-locales.js\").development()'",
    "prepare-locales:production": "node -e 'require(\"./prepare-locales.js\").production()'"
  },
  "watch": {
    "prepare-locales:development": {
      "patterns": "src/**/locales/",
      "extensions": "json"
    }
  }  
}

Maybe it's not the best solution but it works for my use-case and hopefully it will help someone with similar issues.

like image 55
Palo Delinčák Avatar answered Jan 05 '23 06:01

Palo Delinčák