DI18n

Front-end common internationalization solution

background

The front-end technology changes rapidly and the technology stack is numerous. React, Vue, Angular, etc., with webpack, gulp, Browserify, fis and other build tools to meet the daily development work. Meanwhile, in daily work, different projects use different technology stacks. When part of a project needs to be internationalized, you need to find an internationalized plug-in tool that matches the technology stack used in the current project due to the technology stack. Such as:

  • vue + vue-i18n
  • angular + angular-translate
  • react + react-intl
  • jquery + jquery.i18n.property

And so on, while there may be pages that don’t use frameworks, or are static front-end pages that aren’t engineered at all.

In order to reduce the cost of learning related internationalization plug-ins caused by different technology stacks and the internationalization pit that may be encountered in the development process, AFTER trying to analyze the main problems faced by front-end internationalization and related solutions, I think it is possible to use more general technical solutions to complete the internationalization work.

The problems of internationalization

1. Language translation

  • Static copy translation (front-end static template copy)
  • Dynamic Copy translation (serverDynamic data delivered by the end)

Styles 2.

  • Copy length is not the same in different languages caused by the style disorder
  • Image replacement

3. The map table maintenance

4. Third-party services

  • SDK

5. The localization

  • Monetary unit
  • currency
  • Time format

6. Package solutions

  • The runtime
  • The compiled

The solution

In the daily development process, most of the scenarios that need to be internationalized are: language translation, style,map table maintenance and packaging scheme. The next steps are to illustrate a common solution for internationalization in combination with the daily development process.

Let’s start with the possible technology stacks for the current development environment:

1. Build tools are used

  • webpack
  • gulp
  • fis
  • browserify
  • .

Based on these build tools, use:

  • Vue
  • Angular
  • React
  • Backbone
  • .
  • No use of anyframework

2. No build tools are used

  • Using thejqueryorzeptoSuch as the class library
  • nativejs

In the first development process, there are more options available for internationalization tools:

At the framework level, each framework has its own internationalized plug-ins, such as Vue-i18n, Angular-Translate, react-Intl, etc. These plug-ins can be seamlessly integrated into the current development process. The advantage is that these framework level internationalization plug-ins are flexible to use and can be used for static copy translation and dynamic copy translation. The disadvantage is that different frameworks need to learn corresponding plug-ins in the development process, there is a certain learning cost, and there may be different language package judgment logic in the business code.

From the perspective of building tools, webpack has corresponding i18n-webpack-plugin, and gulp has corresponding plug-ins such as gulp-static-i18n. The general pattern of these plug-ins is that you customize the map language mapping table, use the code format defined by the plug-in to be compiled, and then replace the static copy with string matching during the code compilation phase. These plug-ins only solve static copywriting problems, such as some styles, image substitutions,class attributes, and translation of dynamic copywriting. In fact, these plug-ins are very easy to do in the compilation process for style, image replacement, class attributes and so on, while dynamic text translation will not use these plug-ins because of the lack of context. Instead, it makes more sense to put the translation of dynamic copywriting into runtime.

But on the other hand, in addition to the frameworks based on these build tools, build tool-level internationalization plug-ins can help smooth out the differences between using different frameworks by moving the internationalization process from runtime to compile time, where most of the internationalization tasks are done. Reduce the cost of learning the corresponding internationalization plug-in, while enabling customization in the build package. However, there are some disadvantages, that is, these internationalization plug-ins at the construction tool level can only complete some basic static copy translation, because of the lack of context, it can not be a good way to complete the translation work of dynamic copy, it is more suitable for some purely static, exhibition-oriented web pages.

In the second development process, there are fewer internationalization tools available, most of which are combined with libraries like jquery and their corresponding plug-ins like jquery.i18n or i18Next.

Combining different build tools, development frameworks, and libraries, it seems possible to find a common solution to internationalization for different development environments.

The general idea of this scheme is: through the construction tool to complete the style, image replacement, class attribute replacement work, in the business code will not appear too much because of the internationalization of the variable name, at the same time use a universal translation function to complete the static copy and dynamic copy translation work. Instead of using the corresponding internationalization plug-ins provided by different frameworks. To put it simply:

  • Depending on what you’re usingBuild tools+ a generic oneTranslation functionTo complete front-end internationalization

First, this universal language translation function: DI18N-Translate. It provides static and dynamic copy translation, independent of development frameworks and build tools.

  npm install di18n-translateCopy the code
// const LOCALE = 'en' const DI18n = require('di18n-translate') const DI18n = new DI18n({LOCALE: Messages: {// Language mapping table en: {Hello: 'Hello, {person}'}, zh: {hello: 'hello, {person}}}}) di18n inheritance in a translation class, provides two methods ` $t ` ` $HTML ` : di18n. $t (' hello' {person: 'xl'}) / / output: $HTML (htmlTemp) // Pass the string concatenated DOM, return the matched string, // External chain <script SRC ="./lib/di18n-translate/index.js"></script> <script> const LOCALE = 'en' const di18n = new DI18n({locale: locale, isReplace: false, messages: {// language package}}) </script>Copy the code

At this point you just need to integrate the generic translation function into your development framework in a proper way.

Next, the corresponding solutions will be explained in combination with specific scenarios:

useMVVMOf the classframework

When using the MVVM class framework, you can use the framework to render the view layer for you, so you can easily control the content of the class through the code, and the image replacement work in different languages.

For example, vue (1):

Js file: window.locale = 'en'Copy the code
App. vue file: <template> <p class="desc" :class="locale" // locale this variable to control the content of the class :style="{backgroundImage: </p> <img: SRC ="imgSrc"> // imgSrc To control the image path </template> <script> export default  { name: 'page', data () { return { locale: LOCALE, imgSrc: require(`./${LOCALE}/img/demo.png`), bgImg: require(`./${LOCALE}/img/demo.png`) } } } </script>Copy the code

Now add a reference to the translation function di18n-translate in main.js:

The main. Js file: import Vue from 'vue' window.LOCALE = 'en' const DI18n = require('di18n-translate') const di18n = new DI18n({ locale: Messages: {// Language mapping table en: {Hello: 'Hello, {person}'}, zh: {person}'}}) Vue. Prototype. D18n = di18nCopy the code

Basic use of translation functions, of course, you can also use other ways to integrate into your development environment:

App. vue file: <template> <p class="desc" :class="locale" // locale this variable to control the content of the class :style="{backgroundImage: 'url (' + bgImg +') '} "/ / bgImg to control the path of background image > < / p > < img: SRC =" imgSrc "> / / imgSrc to control the image path < p > {{title}} < / p > < / template > <script> export default { name: 'page', data () { return { locale: LOCALE, imgSrc: require(`./${LOCALE}/img/demo.png`), bgImg: require(`./${LOCALE}/img/demo.png`), title: This.di18n.$t(' hello ')}}} </script>Copy the code

Use MVVM Framework for internationalization, the above way should be more appropriate, mainly with the framework to help you complete the view layer rendering work, and then introduce a translation function to complete some dynamic copy translation work

This internationalized approach is run time processing and requires only a single piece of code to develop and finally go live.

Of course, in the case of using the MVVM Framework, we can also complete this part of the view layer without the help of the framework, and complete it through the building tool. The routine of this part can be seen in the afternoon example 3

Don’t usemvvmFrameworks that use build tools (e.gwebpack/gulp/browserify/fis)

Front-end templates are used

The internationalization approach is the same as using the MVVM framework mentioned above, because there is a template engine that does the view layer rendering for you. Therefore, the processing of style, picture and class attributes can be consistent with the above methods, and translation functions should be introduced for dynamic copy translation.

This international approach is also run time processing, with only one piece of code required to develop and finally go live.

No front-end templates are used

Because no front-end templates are used, there is less processing for the View layer. At this point, your DOM structure may be defined from the beginning of the HTML file, or it may be with webPack, which allows you to develop using modularity, dynamically inserting the DOM through JS.

Let’s start with the DOM structure as a dead project written in HTML files without building tools like WebPack that allow you to do modular development. In this case you lose the ability to render the view layer. So in this case there are two ways to deal with this situation.

The first is to add run-time code to your own code. The general idea is to add attributes at the DOM level, along with keys for the map table you want to translate:

Example (2) :

HTML file:

<div class="wrapper" i18n-class="${locale}"> <img i18n-img="/images/${locale}/test.png"> <input i18n-placeholder=" howdo "> <p i18n-content=" hello "></p> </div>Copy the code

Runtime:

<script src="[PATH]/di18-translate/index.js"></script> <script> const LOCALE = 'en' const di18n = new DI18n({ locale: LOCALE, isReplace: true, // Enable runtime messages: {en: {Hello: 'Hello'}, zh: {Hello: 'Hello'}}}) </script>Copy the code

Finally, the HTML will be converted to:

  <div class="wrapper en">
    <img src="/images/en/test.png">
    <input placeholder="Hello">
    <p>Hello</p>
  </div>Copy the code

The second way is to use the build tool to complete the work of internationalization in the process of code compilation. Take WebPack as an example:

Example (3) :

HTML file:

< div class = "wrapper ${locale}" > < img SRC = "/ images / ${locale} / test. The PNG" > < p > $t (' hello ') < / p > < / div >Copy the code

A Webpack preloader is used: Locale-path-loader, which uses webpack to configure the locale before compiling. There won’t be much in your business code about locale variables and how to replace images with background CSS images at runtime. Please refer to the locale-path-loader documentation

Usage:

  npm install locale-path-loaderCopy the code

Webpack 1.x configuration:

  module.exports = {
    ....
    preLoaders: [
      {
        test: /\.*$/,
        exclude: /node_modules/,
        loaders: [
          'eslint',
          'locale-path?outputDir=./src/common&locale=en&inline=true'
        ]
      } 
    ]
    ....
  }Copy the code

Webpack 2 configuration:

  module.exports = {
    ....
    module: {
      rules: [{
        test: /\.*$/,
        enforce: 'pre',
        exclude: /node_modules/,
        use: [{
          loader: 'locale-path-loader',
          options: {
            locale: 'en',
            outputDir: './src/common',
            inline: true
          }
        }]
      }]
    }
    ....
  }Copy the code

After being processed by webpack’s Preloader, the DOM inserted into the page ends up as:

  <div class="wrapper en">
    <img src="/images/en/test.png">
    <p>Hello</p>
  </div>Copy the code

However, this solution needs to be processed in the final packaging process, because through the processing of preloader, the page has been translated into the corresponding language version, so it needs to output different language version files by building tools and changing the parameters of Preloader. Of course webPack is not the only build tool, but the idea is the same. This approach is compile-time processing, in which only one copy of the code is maintained at the time of development, but the final output will be the code of different language packages. Of course, this scheme also needs the support of the server, according to different locale request, return the corresponding entry file. See vue-demo for more information about subcontracting using webpack and locale-path-loader:

|--deploy
  |   |
  |   |---en
  |   |    |--app.js
  |   |    |--vendor.js
  |   |    |--index.html
  |   |---zh
  |   |    |--app.js
  |   |    |--vendor.js
  |   |    |--index.html
  |   |---jp
  |   |    |--app.js
  |   |    |--vendor.js
  |   |    |--index.html
  |   |----lang.jsonCopy the code

Moving on to projects that are modularized with build tools, these projects may end up with the DOM on the page being dynamically inserted into the page using JS. So, obviously, static text translation, styles, image substitution, class attributes, etc. can be done as soon as the DOM is inserted in front of the page.

Example (4): HTML file:

< div class = "wrapper ${locale}" > < img SRC = "/ images / ${locale} / test. The PNG" > < p > $t (' hello ') < / p > < / div >Copy the code

Js file:

let tpl = require('html! ./index.html') let wrapper = document.querySelector('.box-wrapper') // di18n.$HTML replaces the HTML string you loaded with the corresponding language version wrapper.innerHTML = di18n.$html(tpl)Copy the code

The DOM inserted into the last page is:

  <div class="wrapper en">
    <img src="/images/en/test.png">
    <p>Hello</p>
  </div>Copy the code

At this time dynamic translation again with the help of the introduced di18n $t method

Di18n. $t (' hello ')Copy the code

This approach to development is also run time, with only one piece of code to maintain once it is developed and live.

Without using anyframeworkandBuild toolsPure static, partial display of web pages

Internationalization of such web pages can be done with the basic internationalization mentioned above by injecting the runtime into the code, as described in example (2) and the HTML-demo folder in the repository.

Language package map table maintenance

You are advised to create a separate file to maintain the language package and obtain the language package through asynchronous loading.

Project address (if you think the article is good, please don’t be stingy with your star~~)

Please stamp me

Finally, I would like to thank @KenBerkeley, with whom I have discussed internationalization several times before. Meanwhile, his article (please poke me) also gave me some good ideas about compile time.