Internationalization of react project: realizing automatic assembly scheme

This scheme provides a plug-in front-end project internationalization implementation scheme, which can support internationalization without supporting internationalization at the beginning for some reasons, and then support internationalization without modifying the original business code. Make use of construction tools to achieve the internationalization scheme of business development without feeling.

In the process of international development, the general process is: when the front-end development engineer encounters Chinese, he needs to design a code first. Generally, in order to avoid code duplication, he also needs to comply with certain rules and become more and more lengthy with the business iteration; Then import the international multilingual tool function and call the international multilingual function; Then translate and maintain international configuration data; If the internationalized data is placed in the database and supports online dynamic configuration, it also needs to be sent to the back end for unified maintenance in the system. The whole process is lengthy and requires the cooperation of different personnel, which is very prone to problems.

If you use react Intl universal to support Internationalization:

import intl from 'react-intl-universal';

// When the initialization code is in the entry file of the whole system.

intl.get('SIMPLE').d('simple');

Suppose a front-end translation tool is developed. When encountering the Chinese in the code, it will automatically import the international tool function, automatically generate the code according to certain rules, replace the original Chinese code with the call of the international function, and then collect all the international language data after the compilation of the whole project, so as to directly generate the configuration file of the international language, Or generate some structured data for inserting into the database.

According to this idea, we can realize a scheme of automatic assembly internationalization for the project. In this scheme, the front-end development engineer does not need to pay attention to internationalization when developing, and obtains the same development experience as the projects that do not need internationalization support, so he can focus more on business development. Similarly, the program is a basic support in the form of mount, which can quickly support a project that does not support internationalization at first, but later needs to be international oriented due to development.

Similarly, this scheme focuses on how to automatically generate the calling code of international multilingual functions, and there are no restrictions on the use of an international framework. You can choose any internationalization framework according to the actual needs, and then code convert its use.

This scheme only aims at simple internationalization requirements. For some complex requirements, such as amount, date, etc., you still need to manually use some api of internationalization framework. However, in a project, the most important thing is to support the internationalization of some simple display texts.

From the perspective of scheme design, it is mainly divided into two parts:

  • Analysis code: when Chinese is encountered, it is translated into international function call statements.
  • Collect information: collect the information of conversion statements in the process of analyzing code and use it to generate configuration data.

The two parts are handled with two tools respectively.

Code analysis tool

The analysis code can implement a babel plug-in to handle Chinese internationalization when translating js code.

Chinese text is mainly string or in template string, so you only need to parse and convert these two statements, that is, you need to process StringLiteral and TemplateLiteral statements in babel plug-in.

The main structure of the plug-in is:

module.exports = (babel) => {
  visitor: {
      StringLiteral(path, state) {
      },
      TemplateLiteral: {
        enter(_path, state) {
        },
      },
  },
};

TemplateLiteral is complex to handle, so take StringLiteral as an example to illustrate the key logic. In the StringLiteral statement, analyze whether the string contains Chinese, and judge with regularity:

StringLiteral(path, state) {
    const { node } = path;
    const text = node.value;
    if (str.search(/[^\x00-\xff]/) === -1) {
        return;
    }
},

If it does not contain Chinese, it will be returned directly without processing. If it contains Chinese, it will be converted to the international import function (using the react Intl Universal Library):

const intlMember = t.memberExpression(
  t.identifier('intl'),
  t.identifier('get'),
  false, false,
);
// Code generation, where Chinese is directly used as the code. If you are afraid of garbled code and other problems,
// md5 codes can be used or codes can be generated according to actual rules and file paths
const codeText = text;
const codeTextNode = t.stringLiteral(codeText);
// solve
codeTextNode.extra = {
  rawValue: codeText,
  raw: `'${codeText.split("'").join('\\\'').split('\n').join('\\\n')}'`,
};
const intlCall = t.callExpression(intlMember, [codeTextNode]);
const memberExpression = t.memberExpression(intlCall, t.identifier('d'), false, false);
let fnNode = t.callExpression(memberExpression, [node]);
const parentNode = _.get(path, 'parentPath.node');
if (t.isJSXAttribute(parentNode)) {
  fnNode = t.jsxExpressionContainer(fnNode);
}
path.replaceWith(fnNode);

So for text

const text = 'Chinese Chinese';

Will be converted to:

const test = intl.get('chinese').d('chinese');

In the above, intl is hard coded and used directly. You need to rely on the entry file to put the intl function into the global object:

import intl from 'react-intl-universal';

window.intl = intl;

However, for higher extensibility, you can use the code to import automatically. Before converting the code, import the international multilingual function first:

const node = addDefault(path, 'react-intl-universal', { nameHint: 'intl' });
const intlLibName = node.name;
const intlMember = t.memberExpression(
  intlLibName,
  t.identifier('get'),
  false, false,
);

The addDefault function in the babel tool library @ babel / helper module imports can generate a default statement to import the component library without naming conflicts with other variables.
If you use other libraries, you can modify the corresponding method of generating import statements.
This will change the text above to:

import intl from 'react-intl-universal';

const test = intl.get('chinese').d('chinese');

If the current file has been manually imported, such as:

import intl from 'react-intl-universal';
const test = intl.get('code').d('Existing text');

const text = 'chinese';

Will be converted to:

import intl from 'react-intl-universal';
const test = intl.get('code').d('Existing text');

const text = intl.get('chinese').d('chinese');;

In this way, the core code has been implemented to save the processed information for subsequent collection:

module.exports = (babel) => {
  const records = new Map();
  visitor: {
      Program: {
        enter(_1, state) {
          records.clear();
        },
        exit(_1, state) {
          const { filename: filePath } = state;
          const _records = Array.from(records);
          // Save data
          records.clear();
        },
      },
      StringLiteral(path, state) {
        // Conversion code
        records.set(codeText, text);
      },
  },
};

Saving data requires special processing, because webpack 4 or 5 is usually built in a multi process way, so it can not be simply put in memory, but in the file system. It also needs to solve the problem of locking files operated by multiple processes. The information of each js file that can be parsed is placed in a separate file, or the information of the same process is placed in a file to avoid lock competition.

For the processing of TemplateLiteral, because the template may be extremely complex, multiple text variable intervals, and other strings or template languages may be embedded, special processing is required. For example, for template statements:

const hasChineseTemplate = `chinese ${someVars}Chinese Chinese ${1 + 1 + 'Hello'}Ha ha ha`;

Two treatment methods are recommended:

  • Replace the full template with internationalization function:

    const hasChineseTemplate = intl.get('code').d(`chinese ${someVars}Chinese Chinese ${1+1 + 'Hello'}Ha ha ha`);
  • Separate items in the template are treated as internationalization functions:

    const hasChineseTemplate = `${intl.get('code1').d('chinese')${someVars}}${intl.get('code2').d('Chinese Chinese')${1+1 + intl.get('code3').d('Hello')}${intl.get('code4').d('Ha ha ha')}`

In the actual implementation process, we also need to deal with the problem of repeated parsing. Since the architecture of babel and the plug-in will basically be executed first, when the subsequent plug-ins are converted, it may trigger the plug-in to be re enabled, which will repeatedly parse the same statement in the actual sense, and a mechanism is needed to mark after processing.

I have implemented a tool library babel-plugin-i18n-chinese . This tool has been running online for a year, solving some common problems and providing more scalability as possible.

Information collection tools

The tool for collecting code can use a webpack plug-in that takes effect after compilation.

The functions of the information collection tool are relatively simple. According to the way the code analysis tool stores international data, obtain the data, and then generate the data file according to the actual needs.

The only thing to note is that the webpack plug-in needs to take effect after compilation, that is, the plug-in needs to be registered in this way:

module.exports = class AutoI18NWebpackPlugin {
  apply(_compiler) {
    const compiler = _compiler;
    compiler.hooks.done.tapPromise(this.constructor.name, async () => {
      // Get the data generated by the analysis tool
      // Output data file
    });
  }, 
}

The corresponding plug-in has been implemented by the team webpack-plugin-i18n-chinese.

Keywords: Front-end React Webpack Babel i18n

Added by lancey10 on Tue, 15 Feb 2022 08:27:00 +0200