阅读本文,可以得到以下内容:
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
中,通过 js
向 html
注入结构代码
所以源码 => 发布代码的这个过程是会把源码拿来编译的,而编译时可以拿得到整个源码树的 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
的插件,注册相应的命令:
告诉 vue-cli-serve 引擎,我在 plugin/i18n/build-en.js 中,我写的插件逻辑
"vuePlugins": {
"service": ["./plugins/i18n/build-en.js"]
}
这里都新增了一个删除 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"
}
新建 /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