preface

In a large project (with internationalization requirements), internationalization support is essential; That how to land will have to specific problems specific analysis, here I have met and landing a transformation plan;

Let’s talk about the background of the project. It was an iterative production-research project for many years (the whole system was developed around the React ecosystem), with a lot of historical burdens. Multiple third-party libraries exist, as well as iframe scenarios and plugins (modern sandbox isolation).

Scheme for reference only, ha!

plan

Basic Information (Technology Stack)

  • Build toolstream: Gulp 4 + Webpack 4
  • Third-party Libraries (Lib)
    • moment
    • dayjs
    • gantt
    • ckeditor
    • .
  • React Standard family bucket

Focal point

Integration of all I18N resources, centralized packaging, pre-loading (page header -C end rendering); And we don’t consider IE here, focusing on modern browsers ~

From the following aspects of language package coverage

  • At the business level, i18Next is used as the field copy maintenance.
    • All non-third-party libraries, by themselves, can be considered business level
  • Component libraries provide language package-side field mapping objects
  • Third party slightly magic change
    • No multilingual support or version too old to upgrade
      • Initialization timing tamper with the prototype chain
    • For those that support switching, such as moment, DayJS, ck
      • Build the corresponding language object and leave it to them to initialize!

Language resources must be centrally maintained! (So it took us some time to unify the whole system.)

Language switching timing

  • Blocking the loading language pack during page loading, and then continuing with the initialization logic
  • The language switch adopts reload scheme

Why overload? Because it will be more thorough and correct; As mentioned above, this is a project combining old and new technologies, not pure!

Overloading has two very big benefits

  • The language identifier is issued from the interface layer, and the data can be pulled to the correct response data (response of different languages) when entering the user interface.
  • Second, language resources can be loaded on demand (and initialized very correctly)

The flow chart

gulp

Why gulp? Gulp is useful in some scenarios (such as static resource conversion, migration, etc.); Dropping webpacks all at once can actually cause a lot of build overhead;

So the language files are monitored in real time with gulp Watch, and the product is typed to a specific location.

The language resource here is maintained as an NPM module, as shown in the figure

Locale: watch the entire directory. For example, this task builds the language artifacts and then merges the export into gulp Stream! (For information only)

import { resolve } from 'path';
import { src, dest, parallel, watch } from 'gulp';
import { accessSync, constants, statSync, readdirSync } from 'fs';
import gulpEsbuild from 'gulp-esbuild';
import { getDevModeAndParams } from '.. /.. /utils';

function checkDirExist(checkPath) {
  try {
    accessSync(checkPath, constants.R_OK | constants.W_OK);
    console.log(`${checkPath}The gulp path can read and write);
  } catch (err) {
    console.error(`${checkPath}Unable to attempt access, please check for existence of 'first, err);
    process.exit(1); }}function getLocaleDirName(path) {
  if(! path)throw new Error('path no exist');
  try {
    const localeDirName = [];
    const localeGulpTaskName = [];
    const readList = readdirSync(path);
    for (const item of readList) {
      const fullPath = resolve(path, item);
      const stats = statSync(fullPath);
      if (stats.isDirectory()) {
        localeDirName.push(item);
        localeGulpTaskName.push(`${item}_build_locale_task`); }}return {
      localeDirName,
      localeGulpTaskName,
    };
  } catch (error) {
    console.log(
      '%c 🍇 error: '.'font-size:20px; background-color: #7F2B82; color:#fff; '.'Language file not found', error ); }}function localeBuild(srcPath, DestDirPath, outputName) {
  return () = > {
    const inputFile = resolve(srcPath, 'index.js');
    const isRelease = getDevModeAndParams('release'.true);
    const esbuildPipe = () = > {
      return gulpEsbuild({
        incremental: !isRelease,
        outfile: `${outputName}.js`.bundle: true.charset: 'utf8'.format: 'iife'.minify: !isRelease,
        sourcemap: false.platform: 'browser'.loader: {
          '.js': 'js',}}); };return src(inputFile).pipe(esbuildPipe()).pipe(dest(DestDirPath));
  };
}

export function langBuild() {
  const SrcDirPath = resolve(
    process.cwd(),
    'node_modules'.'@ones-ai'.'lang/locale'
  );
  const DestDirPath = resolve(process.cwd(), 'dest/locale');
  checkDirExist(SrcDirPath);
  const { localeDirName } = getLocaleDirName(SrcDirPath);
  const tasksFunction = (srcPath, destPath) = >
    localeDirName.map((localeKey) = >
      localeBuild(resolve(srcPath, localeKey), destPath, localeKey)
    );

  const watchLocaleBuild = (cb) = > {
    watch(
      [`${SrcDirPath}/**/*.js`], parallel(... tasksFunction(SrcDirPath, DestDirPath)) ); cb(); };const isDevWatch = getDevModeAndParams('release'.true)? [] : [watchLocaleBuild];const taskQueue = [...tasksFunction(SrcDirPath, DestDirPath), ...isDevWatch];
  returnparallel(... taskQueue); }Copy the code

webpack

Webpack in this process, more is the linkage between gulp and Webpack and pages; Including injecting some variables, packaging product structure adjustment and so on ~ ~

Of course, gulp startup, webpack startup are manual intervention is not reasonable; So in the encapsulated CLI already through!

engineering

Index. TPL may not be very clear, I will assist a pseudo code screenshot, it will be very clear

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>Document</title>
    <script>
      // Here are the variables injected by the HTML-webpack-plugin plugin
      window.htmlInjectTplParams =<%= htmlInjectTplParams %>
    </script>
    <! Get the relevant language id dynamically
    <script
      src="locale-resource-loader/index.js"
      id="locale-source-loader"
    ></script>
    <script>
      // Follow the document flow with document.write to initialize standard scripts (synchronous blocking!)
      document.write(
        '<script src="' + window.I18N_LOCALE_SOURCE_URL + '"><\/script>'
      );
    </script>
  </head>

  <body>
	<! React {react} react {react}
  </body>
</html>

Copy the code

Cookie -> localStorage -> Navigator. language ->defaultLang

function getCookie(name) {
  const cookie = `; The ${document.cookie}`;
  const parts = cookie.split(`; ${name}= `);
  if (parts.length === 2) return parts.pop().split('; ').shift();
  return;
}

const isValidLang = (lang) = > {
  const validLang = ['zh'.'en'.'ja'];
  if(! lang) {return false;
  }
  return validLang.includes(lang);
};


// Gets the current locale language identifier
const getLocaleKey = () = > {
  let lang = 'zh';
  const getValidLang = [
    getCookie('language'),
    localStorage.getItem('language'),
    navigator.language.slice(0.2)];for (const iterator of getValidLang) {
    if (isValidLang(iterator)) {
      lang = iterator;
      returnlang; }}};const getLocaleUrl = (lang, isAbsolute = false) = > {
  const {
    htmlInjectTplParams: { isRelease, commit },
  } = window;
  return `${isAbsolute ? '/' : ' '}locale/${lang}.js? version=${
    isRelease ? commit : new Date().getTime()
  }`;
};
const localeUrl = getLocaleUrl(getLocaleKey());
window.I18N_LOCALE_SOURCE_URL = localeUrl;

Copy the code

Caching strategies

No doubt some people will think of a resource cache problem (static resources can be queried by disk cache). This is not feasible without a cache policy, otherwise a brand new resource will be pulled every time (also an additional network overhead).

That’s it

And a fixed logo (that cannot change with the standard is not reasonable), because of the subsequent iterations of the new copy and so on!! This problem is actually easy to solve because most of our code workflows now revolve around Git!

Git commit hash (This is an identifier that is guaranteed to change with the code)

Build (Development mode)

  • In development mode, the timestamp used by Query is pulled completely when overloaded

Product (mode of production)

  • Git commit hash is used here

So how do you follow the product? Here we use the htML-webpack-plugin to dynamically inject variables; At build time, inject the current code’s Git commit hash into env, and then write it into code!

Why did you put it in there? The advantage of writing in this hash is not only the identification of the cache policy, but more importantly, you can quickly use this hash to locate the customer and check the version of the work order!!

The advantages and disadvantages

advantages

  • Because it’s Reload, switching languages is pretty thorough
    • From the interface to the page, the link is retraced, clean
  • Because language resources are mounted on Windows, they can be sent to others by some means
    • Microfront-end system
    • iframe

To improve the

  • Development mode
    • I didn’t let it reload automatically after gulp Watch
      • Because field changes are not high-frequency operations!
      • Changes in the business itself will also start webpack hot update, and some scenes will automatically reload the page
  • Production mode
    • Resource pack size problem, is currently the full field entered, volume is acceptable
      • The compressed volume of more than 10,000 fields in a single language is about a little over 1m
        • A means of reducing the volume of a field to a certain extent
          • You can choose the byte compression encoding scheme, time for space, initialization process and assembly
  • The cache policy relies on Git commit hash, and for non-Git maintained scenarios, you need to specifically design a set of unique identifiers to follow code standardization

rendering

Early renderings

conclusion

There is no perfect program, the design of the program should be combined with the status quo to do adjustment, balance; There may be a lot of transitional measures in the middle, but it will be unified step by step as time goes by.

Only this article to draw a sentence this year, I wish you a happy New Year in advance, all the best, tiger tiger live! Have wrong place please leave a message, thank you for reading!