1. The background

In the process of front-end development, there are occasionally internationalization that requires dynamic configuration. For example, Bugatti Veydragon, LV and “Like JS Obsessive Compulsive Disorder” in your shopping cart have been removed, please add them to the shopping cart again. More: “personal information” column AAA, BBB, CCC can not be empty.

The author found three solutions in the actual development process, and finally got the better implementation.

2. Bad Solution

The simplest and easiest way to think about it is to switch different strings for different languages, but the drawbacks are obvious: internationalization is rife with code and supporting multiple languages is difficult. Details are as follows:

// Simulate internationalization
const lang = {
    data: {
        zh: {},
        en: {},'Hakka dialect']: {}
    },
    current: 'zh',
    getLang(key) {
        const {
            data,
            current
        } = this
        return data[current][key] || key
    },
    setLang(language = 'zh') {
        this.current = language
    },
    getCurrent() {
        return this.current
    }
}

function badSolution(lan) {
    lan && lang.setLang(lan)
    const language = lang.getCurrent()
    let showMessage = ' '
    const notFound=['Bugatti Veyron'.'LV'.'Like JS obsessive-compulsive']
    switch (language) {
        case 'zh':
            showMessage = 'In your shopping cart${notFound.join(', ')}Removed, please add to cart again;
            break
        case 'en':
            showMessage = `${notFound.join(', ')} in your shopping cart have been removed, please re-add to the shopping cart`;
            break
        case 'Hakka dialect':
            showMessage = ` fish to buy${notFound.join(', ')}A, uh, should buy A new one;
            break
    }
    return showMessage
}
console.log(badSolution())
// badSolution-zh: Bugatti Veyron, LV and JS obsessive compulsive have been removed from your shopping cart, please add them to your cart again
console.log(badSolution('en'))
// BadSolution-en: Bugatti Veyron, Louis Vuitton, in your shopping Cart Have been removed, please re-add to the shopping cart
console.log(badSolution('Hakka dialect'))
// badSolution- Hakka dialect: Buy fish for Bugatti Veyron, LV, "Praise JS obsessive-compulsive patient" x, HMM, it should be bought again
Copy the code

3. Solution

Sorry, the above scheme is my first involvement in the social society to write the code, looking back, want to hit shit yourself. As an OBSessive-compulsive, you’d have to refactor a bit, and remember the String replace API, which generates the following code:

function solution(lan) {
    lan && lang.setLang(lan)
    // Add simulation data, which is configured in the internationalization file during actual development
    lang.data = {
        zh: {
            notFound: 'notFound in your shopping cart has been removed, please add it to your shopping cart..join: ', '
        },
        en: {
            notFound: `notFound in your shopping cart have been removed, please re-add to the shopping cart`.join: ', '},'Hakka dialect'] : {notFound: 'I bought it for notFound A.join: ', '}}const notFound = ['Bugatti Veyron'.'LV'.'Like JS obsessive-compulsive']
    return lang.getLang('notFound').replace('notFound', notFound.join(lang.getLang('join')))}console.log('solution-zh : ', solution('zh'))
// solution-zh: Bugatti Veyron, LV and "Like JS obsessive compulsive" in your shopping cart have been removed, please add them to your shopping cart again
console.log('solution-en : ', solution('en'))
// Solution-en: Bugatti Veyron,LV, "In your shopping cart have been removed, please re-add to the shopping cart
console.log('Solution - Hakka Dialect:', solution('Hakka dialect'))
// Solution - Hakka dialect: Buy fish for Bugatti Veyron, LV, "Praise JS obsessive-compulsive patient" x, HMM, it should be bought again
Copy the code

This code looks a lot nicer. However, when reconstructing the code for internationalization, it was found that some internationalized values need to be dynamically configured with two or more values. At this time, disadvantages were reflected: cumbersome replacement of multiple variables and difficult naming of placeholders (to avoid conflicts with internationalization).

function solutions(lan) {
    lan && lang.setLang(lan)
    // Add simulation data, which is configured in the internationalization file during actual development
    lang.data = {
        zh: {
            SectionEmpty: CantEmpty in the Section column cannot be empty.join: ', '
        },
        en: {
            SectionEmpty: `cantEmpty in the Section column cannot be empty`.join: ', '},'Hakka dialect'] : {SectionEmpty: Empty 'Section' for cantEmpty.join: ', '}}const notFound = ['AAA'.'BBB'.'CCC']
    const section = 'Personal Information'
    return lang.getLang('SectionEmpty')
        .replace('cantEmpty', notFound.join(lang.getLang('join')))
        .replace('Section', section)
}

console.log('solutions-zh : ', solutions('zh'))
// solutions-zh: AAA, BBB, and CCC in the personal information column cannot be empty
console.log('solutions-en : ', solutions('en'))
// solutions-en: AAA,BBB,CCC in the column cannot be empty
console.log('Solutions - Hakka:', solutions('Hakka dialect'))
// Solutions - Hakka: blank the personal information column for AAA, BBB and CCC
Copy the code

4. Good Solution

The above scheme is not satisfactory, dynamic internationalization and less variables can be used, as a patient with obsessive-compulsive disorder, which can endure.

I was inspired by the Node server template technology: you can create a string template, pass in the context, and call the method to get the corresponding internationalization. Due to the high cost of introducing tripartite plug-ins and the high requirements of the company’s security policy, plug-ins cannot be introduced randomly. Therefore, we did not search for relevant plug-ins and put one into practice considering that its functions are not complicated. I use the form of a utility class, rather than an intrusive contamination of String prototype. If the call is complex and the project team allows methods to be added to Prototype, it can be modified as follows:

function isUndefined(val) {
    return Object.prototype.toString.call(val).includes('Undefined')}/** * Pass 'a.b' to get context.a.b * @param {string} link * @param {object} context */
function getValueByKeyLink(link = ' ', context = {}) {
    const keys = link.split('. ')
    let nextContext = JSON.parse(JSON.stringify(context)) // In order not to affect external parameters, simple deep copy
    let isFound = true
    keys.forEach(key= > {
        if(! isUndefined(nextContext[key])) { nextContext = nextContext[key]// There is a bug here
        } else {
            isFound = false}})return isFound ? nextContext : undefined
}

/** * string template replaces * demo:; (() => { const str = ` { util } is helpful, { name} can try it. {util} is wanderful, {name} must try it! It also can replace c.cc to { c.cc }. If no match,It would't replace {notFound} or { c. cc} ` const context = { util: 'replaceByContext', name: 'you', c: { cc: 'CCC' } } console.log(replaceByContext(str, context)) // replaceByContext is helpful, you can try it. // replaceByContext is wanderful, you must try it! // It also can replace c.cc to CCC. // If no match,It would't replace {notFound} or { c. cc} })(); * @param {string} STR String template * @param {object} context */
function replaceByContext(str = ' ', context = {}) {
    const reg = /{\s*([A-Za-z0-9\\.\\_]+)\s*}/g
    // unmatch unspace
    const matchs = [...new Set(str.match(reg).map(item= > item.replace(/\ /g.' '))))// [ '{util}', '{name}', '{c.cc}', '{notFound}' ]
    console.log(matchs)

    let replaceTime = matchs.length // Find 4 valid contexts after deduplicate, replace 4 times
    while (replaceTime > 0) {
        replaceTime--
        reg.test(str)
        const keyStr = RegExp. $1
        const contextValue = getValueByKeyLink(keyStr, context)
        if(! isUndefined(contextValue)) {// Replace only when there is a value
            // /{name}/g 'you'
            str = str.replace(new RegExp(`{\\s*${keyStr}\\s*}`.'g'), contextValue)
        }
    }
    return str
}
Copy the code

Finally finished, the exciting moment:

function goodSolution(lan) {
    lan && lang.setLang(lan)
    // Add simulation data, which is configured in the internationalization file during actual development
    lang.data = {
        zh: {
            SectionEmpty: {notFounds} in the '{Section} column cannot be null'.join: ', '
        },
        en: {
            SectionEmpty: `{notFounds} in the {Section} column cannot be empty`.join: ', '},'Hakka dialect'] : {SectionEmpty: Leave {notFounds} blank in the '{Section} column.join: ', '}}const notFounds = ['AAA'.'BBB'.'CCC']
    const context = {
        Section: 'Personal Information'.notFounds: notFounds.join(lang.getLang('join'})),return replaceByContext(lang.getLang('SectionEmpty'), context)
}

console.log('goodSolution-zh : ', goodSolution('zh'))
// goodSolution-zh: AAA, BBB, and CCC in the personal information column cannot be empty
console.log('goodSolution-en : ', goodSolution('en'))
// goodSolution-en: AAA,BBB,CCC in the column cannot be empty
console.log('goodSolution- Hakka: ', goodSolution('Hakka dialect'))
// goodSolution- Hakka: Leave AAA, BBB, CCC blank in the personal information column
Copy the code

5. To summarize