動態國際化、拼接國際化較優實現

1. 背景

前端開發過程當中偶有須要動態配置的國際化,好比:您購物車中的布加迪威龍、LV和《點贊JS強迫症患者》已下架,請從新加入購物車。更有:"我的信息"欄目中的AAA,BBB,CCC不能爲空。javascript

筆者在實際開發過程當中找到了三個解決方案,最終獲得我的認爲較優的實現。前端

2. Bad Solution

最簡單也是最容易想到的方法是根據不一樣的語言來切換不一樣的字符串,弊端顯而易見:國際化充斥代碼,支持多語言難度大。具體以下:java

// 模擬國際化
const lang = {
    data: {
        zh: {},
        en: {},
        ['客家話']: {}
    },
    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=['布加迪威龍','LV','《點贊JS強迫症患者》']
    switch (language) {
        case 'zh':
            showMessage = `您購物車中的${notFound.join('、')}已下架,請從新加入購物車`;
            break
        case 'en':
            showMessage = `${notFound.join(',')} in your shopping cart have been removed, please re-add to the shopping cart`;
            break
        case '客家話':
            showMessage = `漁買給${notFound.join('、')}某A,嗯該重新買`;
            break
    }
    return showMessage
}
console.log(badSolution())
// badSolution-zh : 您購物車中的布加迪威龍、LV、《點贊JS強迫症患者》已下架,請從新加入購物車
console.log(badSolution('en'))
// badSolution-en : 布加迪威龍,LV,《點贊JS強迫症患者》 in your shopping cart have been removed, please re-add to the shopping cart
console.log(badSolution('客家話'))
// badSolution-客家話 : 漁買給布加迪威龍、LV、《點贊JS強迫症患者》某A,嗯該重新買
複製代碼

3. Solution

很差意思,上面的方案是本人初涉社會社會寫的代碼,回過頭來看,想打屎本身。做爲一個強迫症患者,確定要重構一下,這時候就想起了String的replace API,便產生了下面代碼:安全

function solution(lan) {
    lan && lang.setLang(lan)
    // 增長模擬數據,實際開發過程當中在國際化文件中配置
    lang.data = {
        zh: {
            notFound: `您購物車中的notFound已下架,請從新加入購物車`,
            join: '、'
        },
        en: {
            notFound: `notFound in your shopping cart have been removed, please re-add to the shopping cart`,
            join: ','
        },
        ['客家話']: {
            notFound: `漁買給notFound某A,嗯該重新買`,
            join: '、'
        }
    }

    const notFound = ['布加迪威龍', 'LV', '《點贊JS強迫症患者》']
    return lang.getLang('notFound').replace('notFound', notFound.join(lang.getLang('join')))
}
console.log('solution-zh : ', solution('zh'))
// solution-zh : 您購物車中的布加迪威龍、LV、《點贊JS強迫症患者》已下架,請從新加入購物車
console.log('solution-en : ', solution('en'))
// solution-en : 布加迪威龍,LV,《點贊JS強迫症患者》 in your shopping cart have been removed, please re-add to the shopping cart
console.log('solution-客家話 : ', solution('客家話'))
// solution-客家話 : 漁買給布加迪威龍、LV、《點贊JS強迫症患者》某A,嗯該重新買
複製代碼

上面代碼看起來順眼多了。可是重構代碼裏國際化的時候發現有些國際化要動態配置兩個甚至更多值,這時候弊端體現出來了:多個變量的替換繁瑣,佔位符命名難(要避免和國際化衝突)服務器

function solutions(lan) {
    lan && lang.setLang(lan)
    // 增長模擬數據,實際開發過程當中在國際化文件中配置
    lang.data = {
        zh: {
            SectionEmpty: `Section欄目中的cantEmpty不能爲空`,
            join: '、'
        },
        en: {
            SectionEmpty: `cantEmpty in the Section column cannot be empty`,
            join: ','
        },
        ['客家話']: {
            SectionEmpty: `Section欄目中給cantEmpty嗯扣以空`,
            join: '、'
        }
    }

    const notFound = ['AAA', 'BBB', 'CCC']
    const section = '我的信息'
    return lang.getLang('SectionEmpty')
        .replace('cantEmpty', notFound.join(lang.getLang('join')))
        .replace('Section', section)
}

console.log('solutions-zh : ', solutions('zh'))
// solutions-zh : 我的信息欄目中的AAA、BBB、CCC不能爲空
console.log('solutions-en : ', solutions('en'))
// solutions-en : AAA,BBB,CCC in the 我的信息 column cannot be empty
console.log('solutions-客家話 : ', solutions('客家話'))
// solutions-客家話 : 我的信息欄目中給AAA、BBB、CCC嗯扣以空
複製代碼

4. Good Solution

上面方案差強人意,動態國際化少且變量少的時候能夠採用,做爲一個強迫症患者,孰能忍。工具

這時候Node服務器的template技術給了我靈感:能夠創建string template,經過傳入上下文,調用方法獲得對應國際化。因爲引入三方插件成本高,公司安全策略要求高,不能隨便引入插件,因此就沒有搜索相關的插件,想一想功能也不復雜,就動手實踐了一個。我採用的是工具類的形式,而不是侵入式的污染String的prototype,如嫌棄調用複雜且項目組容許往prototype上添加方法能夠適當改造(改造部分類比能夠實現),工具類的實現以下:ui

function isUndefined(val) {
    return Object.prototype.toString.call(val).includes('Undefined')
}

/** * 傳入'a.b'獲得context.a.b * @param {string} link * @param {object} context */
function getValueByKeyLink(link = '', context = {}) {
    const keys = link.split('.')
    let nextContext = JSON.parse(JSON.stringify(context)) // 爲了避免影響外部參數,簡單深拷貝
    let isFound = true
    keys.forEach(key => {
        if (!isUndefined(nextContext[key])) {
            nextContext = nextContext[key] // 此處有bug
        } else {
            isFound = false
        }
    })
    return isFound ? nextContext : undefined
}

/** * 字符串模板根據上下文替換 * 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 字符串模板 * @param {object} context 上下文 */
function replaceByContext(str = '', context = {}) {
    const reg = /{\s*([A-Za-z0-9\\.\\_]+)\s*}/g
    // 去重 匹配 去空格
    const matchs = [...new Set(str.match(reg).map(item => item.replace(/\ /g, '')))]
    // [ '{util}', '{name}', '{c.cc}', '{notFound}' ]
    console.log(matchs)

    let replaceTime = matchs.length // 去重後找到4個合法的上下文,要替換4次
    while (replaceTime > 0) {
        replaceTime--
        reg.test(str)
        const keyStr = RegExp.$1
        const contextValue = getValueByKeyLink(keyStr, context)
        if (!isUndefined(contextValue)) { // 有值的時候才替換
            // /{name}/g 'you'
            str = str.replace(new RegExp(`{\\s*${keyStr}\\s*}`, 'g'), contextValue)
        }
    }
    return str
}
複製代碼

終於寫完,激動時刻this

function goodSolution(lan) {
    lan && lang.setLang(lan)
    // 增長模擬數據,實際開發過程當中在國際化文件中配置
    lang.data = {
        zh: {
            SectionEmpty: `{ Section}欄目中的{notFounds}不能爲空`,
            join: '、'
        },
        en: {
            SectionEmpty: `{notFounds} in the {Section} column cannot be empty`,
            join: ','
        },
        ['客家話']: {
            SectionEmpty: `{Section}欄目中給{notFounds}嗯扣以空`,
            join: '、'
        }
    }

    const notFounds = ['AAA', 'BBB', 'CCC']
    const context = {
        Section: '我的信息',
        notFounds: notFounds.join(lang.getLang('join')),
    }
    return replaceByContext(lang.getLang('SectionEmpty'), context)
}

console.log('goodSolution-zh : ', goodSolution('zh'))
// goodSolution-zh : 我的信息欄目中的AAA、BBB、CCC不能爲空
console.log('goodSolution-en : ', goodSolution('en'))
// goodSolution-en : AAA,BBB,CCC in the 我的信息 column cannot be empty
console.log('goodSolution-客家話 : ', goodSolution('客家話'))
// goodSolution-客家話 : 我的信息欄目中給AAA、BBB、CCC嗯扣以空
複製代碼

5. 總結

相關文章
相關標籤/搜索