首页 > 其他 > 详细

前端国际化的思考

时间:2020-02-24 22:01:23      阅读:91      评论:0      收藏:0      [点我收藏+]

写在前面

阅读本文,可以得到以下内容:

  1. 前端国际化常见的做法及其优劣
  2. 工程化角度上如何解决国际化问题
  3. 一个 vue-cli3 版本的国际化思路

转载请保留出处

常见做法

我们平时看到的国际化站点,大多都是使用一个公共函数,以需要翻译的文案为参数调用,然后在模板引擎渲染时,或者在 js 执行时依据一个目标语言的短语来确定语言包,然后以参数为 key 去做语言包的查找,最终返回相应的语言版本文案,比如以下伪代码:

<!--模板引擎-->
<h2>{{ '这是需要翻译的文案' | translate }}</h2>

<script>
    // 语言 flag
    const LANG_ENV = 'en'
    // 语言包
    const langMap = {
        en : {
            '这是需要翻译的文案' : 'Need to be translated msg.'
        }
    }
    // 注册模板引擎翻译插件
    TemplateEngine.registerPlugin(
        'translate',
        (val) => {
            return langMap[LANG_ENV][val] || val
        }
    )
</script>
// js 执行时翻译

// 语言 flag
const LANG_ENV = 'en'
// 语言包
const langMap = {
    en : {
        '这是需要翻译的文案' : 'Need to be translated msg.'
    }
}

// 翻译函数
const trans = (val, en = LANG_ENV) => {
    return langMap[en][val] || val
}

// 使用
$
    .get('/a.json')
    .then(res => trans(res.msg))

这种做法的好处就是比较容易理解,前置操作少,也比较好实施

当然也存在坏处,就是改动太多,想要加这样一个功能,需要将所有文案的地方全部包裹上相应的函数,前置的操作可能只是新建一些语言包文件,把插件引入,后续就是加公共函数,如果是老项目或者很大的项目的话,工作量更是不可估量

工程化角度来看国际化问题

其实我们结合现在的前端代码和工程化来看这个问题,现在编写好源码之后,通过打包工具打包之后,生成一个几乎是空(带一小部分 head 信息)的 html 模板,其他的逻辑都在 js 中,通过 jshtml 注入结构代码

所以源码 => 发布代码的这个过程是会把源码拿来编译的,而编译时可以拿得到整个源码树的 AST, 每个单元都会有其不同的 type,
我们完全可以在这个过程中做文章,这就要用到 babel

babel 的主要过程为解析,转换,和输出

技术分享图片

我们可以在转换的那一步,做国际化的实现。

比如拿出来一段代码:

console.log('这段文案需要翻译')
'这段文案需要翻译'

我们可以将上面那些代码的AST取出来:

{
    "type": "ExpressionStatement",
    "start": 24,
    "end": 34,
    "loc": {
        "start": {
        "line": 2,
        "column": 0
        },
        "end": {
        "line": 2,
        "column": 10
        }
    },
    "expression": {
        "type": "Literal",
        "start": 24,
        "end": 34,
        "loc": {
        "start": {
            "line": 2,
            "column": 0
        },
        "end": {
            "line": 2,
            "column": 10
        }
    },
    "value": "这段文案需要翻译",
    "rawValue": "这段文案需要翻译",
    "raw": "'这段文案需要翻译'"
}

因为 babel 的存在,我们可以在编译时获取这段 AST, 并在转换时,替换掉 path.node.value 的值:

const langMap = {
    en : {
        // xxx...
    }
}

module.exports = function () {
    return {
        // 访问者模式
        visitor : {
            // 文本节点的 type 统一为 Literal
            Literal (path, state) {
              const value = path.node.value
              // 文本节点里有中文,则拿去语言包里找
              if (/[\u4e00-\u9fa5]/.test(value)) {
                  path.node.value = langMap['en'][path.node.value] || path.node.value
              }
            }
        }
    }
}

这样我们就可以在不改动代码,只添加语言包的情况下完成两者的转换

当然我们不能完全弃用以前的i18n方式,因为我们在编译期而非运行时去解决这个问题,是有弊端的

比如请求一个接口,返回数据为:

{
    "code" : 500,
    "data" : {
        "msg" : "这里是需要翻译的文案"
    }
}

这种我们是没有办法的,还是得规规矩矩的再给语言方案一个公共的方法,上文也给出了伪代码

明确一点,我们是想要侵入性最小,而不是没有侵入性,所以运行时的,还是得使用老的办法2333

一个 vue-cli3 国际化的实现

因为部门的技术栈为 vue 全家桶,所以这里也提供一个 vue-cli3 的方法

这需要我们编写一个 vue-cli3 的插件,注册相应的命令:

1. package.json 中添加 serve 插件地址

告诉 vue-cli-serve 引擎,我在 plugin/i18n/build-en.js 中,我写的插件逻辑

"vuePlugins": {
    "service": ["./plugins/i18n/build-en.js"]
}
2. 修改 scripts 中的 build 命令, 新增 build:en 命令

这里都新增了一个删除 node_modules/.cache 的行为,因为 vue-loader 默认添加 cache-loader,在我们编译的时候有可能会引起问题

"scripts": {
    "build": "rm -rf node_modules/.cache && vue-cli-service build",
    "build:en" : "rm -rf node_modules/.cache && vue-cli-service build:en"
}
3. 添加插件

新建 /plugins/i18n/build-en.js 文件,写入:

let path = require('path')

module.exports = function i18nPlugin (api, projectOptions) {
    // 注册命令
    api.registerCommand('build:en', {}, args => {
        const config = api.resolveWebpackConfig()
        // 设置产出目录为 dist/en
        projectOptions.outputDir = `${config.output.path}/en/`

        global.BUILD_LANGUAGE = 'en'

        // webpack babel-loader 配置中新增 babel 插件
        api.configureWebpack(webpackConfig => {
            const rules = webpackConfig.module.rules

            // 处理 /\.m?jsx?$/ 时
            const configBabelLoader = rules[rules.length - 2]
            // 给 babel loader 添加插件
            configBabelLoader.use[configBabelLoader.use.length - 1].options = {
                plugins : [
                    path.resolve(__dirname, './babel-parser.js')
                ]
            }
        })

        // 新配置执行 build 命令
        api.service.run('build')
    })
}

// 设置新注册插件模式为 production
module.exports.defaultModes = {
    'build:en' : 'production'
}

其实写到这一步,我都以为大功告成了,然而出了问题:

<template>
    <h2>这是需要翻译的文案1</h2>
    <p>{{ msg }}</p>
</template>

<script>
    module.exports = {
        data () {
            return {
                msg : '这是需要翻译的文案2'
            }
        }
    }
</script>

script 中的文案被顺利转换,但是在 template 中的文案没有

因为 .vue 的文件由 vue-loader 解析,其中 template 部分并没有过 babel 解析器,所以我们需要修改 vue-loader 上的内容

node_modules/vue-loader/lib/loaders/templateLoaders.js 修改一下,module.exports 函数的 const { code } = compiled 之前,添加如下代码:

const path = require('path')
const PARSE_DIR = path.resolve(__dirname, '../../../../plugins', 'i18n/babel-parser.js')

const _code = compiled.code
compiled.code = require('@babel/core')
    .transformSync(
        _code,
        {
            plugins : [PARSE_DIR]
        }
    )
    .code

因为涉及到改 node_modules 中的东西,可以 npm install 之后手动修改,或者可以把 vue-loader 部署到私有库中从私有库中拉取,两种方式都阔以

此时执行编译:

npm run build
npm run build:en

等待 build 完成后,我们发现 dist 的目录结构为:

dist 目录
├── css                                     ----- 中文版样式
│?? ├── app.6346a619.css
│?? └── chunk-vendors.fcfb82ac.css
├── en                                      ----- 英文版主目录
│?? ├── css                                 ----- 英文版样式
│?? │?? ├── app.6346a619.css
│?? │?? └── chunk-vendors.fcfb82ac.css
│?? ├── favicon.ico
│?? ├── index.html                          ----- 英文版入口
│?? └── js                                  ----- 英文版脚本
│??     ├── app.e084e180.js
│??     ├── app.e084e180.js.map
│??     ├── chunk-vendors.6dfdaecd.js
│??     └── chunk-vendors.6dfdaecd.js.map
├── favicon.ico
├── index.html                              ----- 中文版入口
└── js                                      ----- 中文版脚本
    ├── app.89fdbe11.js
    ├── app.89fdbe11.js.map
    ├── chunk-vendors.66512729.js
    └── chunk-vendors.66512729.js.map

再用 ng 将请求转发到我们的 dist 目录,比如

location /a {
    alias /source/vue-cli-test/dist;
    index index.html;
    ....
}

则我们访问 /a 为中文版站点,/a/en 为英文版站点,大功告成

结语

vue-cli3 的解决方案中有些细节不便列出,文中也只是提供一个简易的思路

本文所写均为自己的理解,如有错误还请斧正

前端国际化的思考

原文:https://www.cnblogs.com/amnhhh/p/12358890.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!