Handwritten automated Prompter and Backfill Tool to achieve front-end internationalization (language switch)

The function requirements of front-end internationalization are as follows: select a language from the drop-down list, and then switch the language accordingly. Chinese and English are supported at first. (Currently, there are 10+ projects, which should be handled gradually)

Technical tools :(the project is vue project) vue-i18n, all Chinese needs to be put out and backfill

Difficulties: The project is old and large, manual lifting is too stupid and inefficient (and there are 10+ projects), so you need a tool that can automatically complete lifting and backfilling

Implementation outline:

  1. The usage of VUE-I18N
  2. Develop automated prompter and backfill tool analysis
  3. Difficulties encountered in promptings
  4. Backfill encountered difficulties
  5. Add a tool platform to make it easy for team members to use

1. Introduction to vuE-I18N

// import main.js
import i18n from './i18n'.new Vue({
  i18n,
  ...  
})
Copy the code
// i18n.js
import Vue from 'vue'
import iView from 'view-design'
import VueI18n from 'vue-i18n'
import zhView from 'view-design/dist/locale/zh-CN'
import enView from 'view-design/dist/locale/en-US'
import zh from './langs/zh.json'
import en from './langs/en.json'

Vue.use(VueI18n)
Vue.locale = () = > {}

const messages = {
  en: Object.assign(enView, en), // Combine your Own English package with iView
  zh: Object.assign(zhView, zh) // Combine your Own Chinese package with iView
}

let locale = localStorage.getItem('lan')
if(! locale) {if (navigator.language === 'zh-CN') { // Get the language of the browser
    locale = 'zh'
  } else {
    locale = 'en'}}const i18n = new VueI18n({
  locale: locale, // Set the language, if local storage is used, use local, not default 'en'
  messages
})

Vue.use(iView, {
  i18n: (key, value) = > i18n.t(key, value)
})

export default i18n
Copy the code
// The logic of the corresponding language switch button
<Select @on-change="languageChange" placeholder="Language" size="small">
  <Option value="zh">Simplified Chinese</Option>
  <Option value="en">English</Option>
</Select>

languageChange(lang) {
  localStorage.setItem('lan', lang)   
  location.reload()
}
Copy the code

Vue-i18n is now configured, and also requires resources zh.json, en.json, and backfill

  • After vue-i18n is registered, there is a global $T that handles the language switch
// json {" confirm ", "confirm "," cancel ", "cancel ",... } / / en. Json {" sure ", "confirm", "cancel", "cancel",... } // xx.vue backfill, vue-i18n after registration, there will be a global $t, handle the language switch<template>Originally written:<button>determine</button>After the backfill:<button>{{$t (' sure ')}}</button>
</template>
<script>
    export default {
        data() {
            return {
                msg: 'cancel'.// The original way of writing
                msg: this.$t('cancel') / / after backfill}}}</script>
Copy the code

2. Developed automated prompter and backfill tool analysis

We also need resources zh.json, en.json, and backfill

The goal is to write a script, execute a command, such as LAN/SRC /views, to get zh.json, en.json in/SRC /views, and backfill it

Decoupling development consists of three steps:

  1. There is a front-end tool platform (internal front-end tool platform setup) that can execute commands such as terminal input: eslint or webpack to execute the corresponding tool library

    • The purpose is that the team members can use it easily, for examplenpm i -g feToolsAnd thenfe lan /src/viewsYou can execute the script. Instead of using copy source files (copy is also bad for version management)
  2. The flash

    Use the re match, get all the Chinese, and generate zh.json and en.json as follows

    // zh.json
    {
        "Sure"."Sure"."Cancel"."Cancel". }// en.json is then sent to the product for translation, or translated by itself, and the result of translation is written to the corresponding value
    {
        "Sure".""."Cancel"."". }Copy the code
  3. backfill

    $(“); $(“); $(“)

    • Such as:<span>{{$t(' query ')}}</span>
    /* Handles the template part of vue */
    letVueStr = (source code).replace(re,word= > {
        if (zhJson[word.trim()]) {
            return "$t('" + word.trim() + "')"
        }
        return word
    })
    Copy the code

3. Difficulties encountered in promptings

The regular expressions that match Chinese: /[\u4e00-\u9fa5]{1,}/g, but in fact many of them are not simple Chinese

Things are:

<span> HTTP content-type is in JSON format. The recommended format is </span> 2. <span> </span> 3. '< SPAN > Enterprise {{obj.name}} atlas </span>' <span> Enterprise {{obj.name}} atlas </span> 4. Filter comments (HTML comments, JS comments) // comments <! - comments -- >Copy the code

Analysis problem:

<span> HTTP content-type is in JSON format. The recommended format is </span> 2. <span> </span> 3. <span> enterprise {{obj. Name}} atlas </span> (consider the subsequent backfill) < span > HTTP {{$t (' ')}} the content-type {{$t (' adopt ')}} json {{$t (' format ')}}, {{$t (' suggestion using the format of the ')}} < / span > points more paragraphs are too ugly right example: <span>{{$t(' HTTP content-type: json ')}}</span> 4. Filter comment (HTML comment, JS comment) // comment /* comment * comment comment comment */ <! -- Comment --> <! - comments -- >Copy the code

The regular expression results: [\ u4e00 – \ u9fa5 | | | \ \ w s,,,. / / / * : : = () ()!!?? \ \ -] {1}

Regular expression analysis: Chinese + English digits + Spaces or newlines + some punctuation marks (<> and {} are filtered out, so you can’t write \S directly)

  • The key point is: don’t assume that a single re will solve all the problems, because it matches English, so all English matches will be matched, you need to write a re to filter the results
Regular character string: < SPAN > Enterprise {{obj.name}} atlas </ SPAN > < SPAN > HTTP content-type is in JSON format. The recommended format is </ SPAN > // Comment <! - comments -- > regular expression: [\ u4e00 - \ u9fa5 | | | \ \ w s,,,. / / / * : : = () ()!!?? \ \ -] {1} matching results: a total of find 10 matches: Span Enterprise obj.name Atlas/SPAN SPAN The HTTP content-type is in json format. The recommended format is/SPAN // Comments! - comments - the source code: the function getChineseList (STR) {/ / get all the Chinese English mix Spaces and punctuation const re = / [\ u4e00 - \ u9fa5 | | | \ \ w s,,,. / / / * : : = () ()!! \? -] {1} / g return STR. The match (re). The map (e = > {e = e. rim () / / and English annotation to filter out the if (/ [\ u4E00 - \ u9FA5] /. The test (e) &&! /\/\/|\/\*--/.test(e)) return e }).filter(e => e) }Copy the code

Final summary:

  1. When you’re thinking about a problem, it’s easy to end up in a dead end thinking that one regular rule will solve all the problems. You can do more regex or filter the result of the regex
  2. The scene will be complex, and there will still be some overextraction (e.g. <> Angle brackets in comments), but not underextraction. It’s hard to be 100% accurate, at least not less

4. Difficulties encountered in backfilling

There are too many cases to handle in one session

Things are:

  1. Elements within the template > tag a replacement: < / span > < span > query replace = > < span > {{$t (‘ query ‘)}} < / span >

    Solution:

    // There are some complications:< < span > query/span> / query  XXX </< a > information/span> / query {{obj.name}} information </span> // Additional re processing is required here
    
    // Actual processing
    const getZh = / [\ u4e00 - \ u9fa5 | | | \ \ w s,,,. / / / * : : = () ()!!? \? -] {1} / g // Match Both Chinese and English with some punctuation
    
    letVueStr = (source code).replace(getZh,word= > {
        if (zhJson[word.trim()]) { // zhJson is the map taken by the prompter function
            return "$t('" + word.trim() + "')"
        }
        return word
    })
    const textRe = / >. *? <|\n\$.*? \n/g //. Will not match "\n", add a question mark, non-greedy mode
    vueStr = vueStr.replace(textRe, e= > {
        if (e.slice(0.2) = = ='> $') {
            if (/ / {{.test(e)) { $t(' query '){{obj. Name}}$t(' info ')<
                e = e.replace(/\$t\(.*? \)/g.res= > {
                    return '{{' + res + '}} '})}else {
                return ` > {{${e.slice(1, e.length - 1)}}} < `}}else if (e[0= = ='\n') {
            return `\n{{${e.slice(1, e.length - 1)}}}\n`
        }
        return e
    })
    Copy the code
  2. Now, placeholder=” task ID” has become placeholder=”$t(‘ task ID’)”

    Solution:

    const vueRe = /[a-zA-Z|="'\>\$t\(]{1,}/g
    / / processing within the props, coupled with a colon Such as: placeholder = "$t (' task ID)" = > : placeholder = "$t (' task ID ')"
    let propsVue = vueStr.match(vueRe).map(e= > {
      if (e.includes('="$t(')) return e
    }).filter(e= > e)
    
    const map = new Map(a)/ / to heavy
    propsVue = propsVue.map(e= > { // Remove the weight, avoid extra colons
      const val = e.split('=') [0]
      if(! map.get(val)) { map.set(val,1)
        return val
      }
    }).filter(e= > e)
    
    propsVue.forEach(e= > {
      vueStr = vueStr.replace(new RegExp(e, 'g'), word= > {
        if (word.includes(':')) {
          return word
        } else {
          return ':' + word
        }
      })
    })
    Copy the code

    Abnormal conditions:

    • It is difficult to clean up some exceptions in the regular process. It is much easier to clean up exceptions in the regular process
    1.:label-width='70'
    2.In some ternary expressions, it appears: xx?'$t('Open the') ' : '$t('Shut down') 'You need to get rid of the quotes'$t('Open the') ' => $t('open')
    
    / /!!!!!!!!!! Wash some of the dirt off
    vueStr = vueStr.replace(new RegExp(/ : : /.'g'), word= > ':')
    $t(' open ') => $t(' open ')
    vueStr = vueStr.replace(new RegExp(/'\$t\('/.'g'), word= > "$t('")
    vueStr = vueStr.replace(new RegExp(/ '\]'.'g'), word= > "')")
    Copy the code
  3. Substitution in <script> :(and possibly template string concatenation)

    / * js: * the columns: [{(old) title: 'task name, (new) title: this. $t (" task name "), the key: 'error ${res.message}' '${this.$t(' error ')}${res.message}' */Copy the code

    Solution:

    function escapeRegExp (str) { // The function of escapeRegExp is: for example, if STR is' before change (', then new RegExp(STR) will report an error, so it needs to escape to: 'before change \\('
      return str.replace(/[\(\)]/g.'\ \ $&')}// Process non-template strings
    let jsStr = data[1]
    const zhJsonNew = {}
    Object.keys(zhJson).forEach(e= > {
      zhJsonNew[` '${e}'`] = e // Here take Chinese + double quotes string for example: "message"
      zhJsonNew[`"${e}"`] = e // Here take Chinese + single quote string for example: 'message'
    })
    
    Object.keys(zhJsonNew).forEach(e= > {
      // The function of escapeRegExp is as follows: for example, e is' before change (', in this case, new RegExp(e) will report an error, which needs to be escaped to: 'before change \\('
      jsStr = jsStr.replace(new RegExp(escapeRegExp(e), 'g'), word= > {
          return 'this.$t(' + word + ') '})})// Process template strings
    const zhJsonTpl = {}
    Object.keys(zhJson).forEach(e= > {
      zhJsonTpl[e] = e // Only Chinese strings are taken here (to match template strings) e.g. message
    })
    
    jsStr = jsStr.replace(/`.*`/g.word= > {
      Object.keys(zhJsonTpl).forEach(e= > {
        // The function of escapeRegExp is as follows: for example, e is' before change (', in this case, new RegExp(e) will report an error, which needs to be escaped to: 'before change \\('
        word = word.replace(new RegExp(escapeRegExp(e), 'g'), zh= > {
          return "${this.$t('" + zh + "')}"})})return word
    })
    Copy the code

5. Join a tool platform that team members can easily use

Add tool platform: build internal front-end tool platform

After the tool platform is installed :(actual usage 🙂

  • Terminal input: FE LAN parameter 1 [parameter 2:replace or R, parameter 3: noOutput or n]]

    Parameter 1 (mandatory) : path to the cue

    • Directories are supported as well as files
    • Recommend absolute path, can use editor copy out. Relative paths work, but make sure you input them correctly

    Parameter 2 (optional) : whether to backfill, can be abbreviated as r

    • If you do, you can only fill in replace or R.
    • Parameter 3 (optional. Parameter 2 is required to take effect): Function: Backfill only. Json file is not output

Code word is not easy, little praise ~