最近搞了国际化,有点感想。
把一些想法和实现写出来。
期望如果可以通过自动化的脚本做到一键全局翻译,并且以后的迭代,如果写了中文。eslint会报错,配合husky不允许提交,这样的话就可以让项目更好的使用国际化。
web实现国际化现在是一个很简单的事情,i18n的插件也很多,大多都是安装一下,配置一下就可以很快的实现国际化。我们使用了vue-i18n。
其实难点不在国际化的安装,而在国际化json文件的配置,大家需要把所有文件review一遍,把所有的中文抽出来,换成变量比如$t、{{ t() }}。在不同的文件里面还需要有不同的注意事项。比如vue template 在这个里面需要使用{{ t() }} 或者 :t() 。setup 语法里面 需要导入一些插件,并且使用它 可能是$t的方式。还有jsx、tsx 里面配置还会不一样 比如 $bei.t()。 $bei是我们挂载的全局变量。
出于这样的想法去思考的话,期望有一个脚本是实现这样的事情。我去开发了一个nodejs的脚本,可以走三方翻译, 找到文件里面的中文,抽出来放到一个json文件,而且做replace。我是实现了比较简单的功能。我只替换template里面的中文并且都是替换为{{ t() }} ,这个对于提效来讲很一般,大家都还需要在很多东西,属性的需要替换为: setup和tsx需要格外去改。
我们去git上了找到了一个插件@ifreeovo/i18n-extract-cli
https://github.com/IFreeOvO/i18n-cli
贴个地址还是比较好用的,通过一些简单的配置,可以实现全局自动化i18n。
它的实现逻辑也是比较有趣。它通过glob遍历所有的文件,利用nodejs读取文件内容,使用babel解析为抽象语法树。
解析非vue文件只用做字符串替换就可以。
vue文件需要注意的东西多一点,把vue 文件抽离为html css js 。 css文件直接跳过, js 依然使用babel处理。 html的使用htmlparser2 解析为html去处理这个很有趣,内容会转成child 属性会转成props。这样可以很简单的实现属性和内容的区分。找到中文之后,去调用三方翻译,实现翻译功能,保存到文件中。最后使用pritter实现格式化即可。
再说后半部分
使用@intlify/eslint-plugin-vue-i18n 去做vue的eslint校验,
使用eslint-plugin-i18n 去做jsx和tsx的eslint校验。
贴一个简单的eslint配置文件
// @see: http://eslint.cn
module.exports = {
root: true,
env: {
browser: true,
node: true,
es6: true
},
// 指定如何解析语法
parser: "vue-eslint-parser",
// 优先级低于 parse 的语法解析配置
parserOptions: {
parser: "@typescript-eslint/parser",
ecmaVersion: 2020,
sourceType: "module",
jsxPragma: "React",
ecmaFeatures: {
jsx: true
}
},
// 继承某些已有的规则
extends: [
"plugin:vue/vue3-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"plugin:@intlify/vue-i18n/recommended-legacy"
],
plugins: ["i18n"],
/**
* "off" 或 0 ==> 关闭规则
* "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行)
* "error" 或 2 ==> 规则作为一个错误(代码不能执行,界面报错)
*/
rules: {
// eslint (http://eslint.cn/docs/rules)
"no-var": "error", // 要求使用 let 或 const 而不是 var
"no-multiple-empty-lines": ["error", { max: 1 }], // 不允许多个空行
"prefer-const": "off", // 使用 let 关键字声明但在初始分配后从未重新分配的变量,要求使用 const
"no-use-before-define": "off", // 禁止在 函数/类/变量 定义之前使用它们
// typeScript (https://typescript-eslint.io/rules)
"@typescript-eslint/no-unused-vars": ["error", { ignoreRestSiblings: true }], // 禁止定义未使用的变量 忽略解构对象中的一些用不到的变量
"@typescript-eslint/no-empty-function": "error", // 禁止空函数
"@typescript-eslint/prefer-ts-expect-error": "error", // 禁止使用 @ts-ignore
"@typescript-eslint/ban-ts-comment": "error", // 禁止 @ts-<directive> 使用注释或要求在指令后进行描述
"@typescript-eslint/no-inferrable-types": "off", // 可以轻松推断的显式类型可能会增加不必要的冗长
"@typescript-eslint/no-namespace": "off", // 禁止使用自定义 TypeScript 模块和命名空间
"@typescript-eslint/no-explicit-any": "off", // 禁止使用 any 类型
"@typescript-eslint/ban-types": "off", // 禁止使用特定类型
"@typescript-eslint/no-var-requires": "off", // 允许使用 require() 函数导入模块
"@typescript-eslint/no-non-null-assertion": "off", // 不允许使用后缀运算符的非空断言(!)
// vue (https://eslint.vuejs.org/rules)
"vue/script-setup-uses-vars": "error", // 防止<script setup>使用的变量<template>被标记为未使用,此规则仅在启用该 no-unused-vars 规则时有效
"vue/v-slot-style": "error", // 强制执行 v-slot 指令样式
"vue/no-mutating-props": "error", // 不允许改变组件 prop
"vue/custom-event-name-casing": "error", // 为自定义事件名称强制使用特定大小写
"vue/html-closing-bracket-newline": "error", // 在标签的右括号之前要求或禁止换行
"vue/attribute-hyphenation": "error", // 对模板中的自定义组件强制执行属性命名样式:my-prop="prop"
"vue/attributes-order": "off", // vue api使用顺序,强制执行属性顺序
"vue/no-v-html": "off", // 禁止使用 v-html
"vue/require-default-prop": "off", // 此规则要求为每个 prop 为必填时,必须提供默认值
"vue/multi-word-component-names": "off", // 要求组件名称始终为 “-” 链接的单词
"vue/no-setup-props-destructure": "off", // 禁止解构 props 传递给 setup
// Optional.
"@intlify/vue-i18n/no-dynamic-keys": "error",
"@intlify/vue-i18n/no-raw-text": [
"error",
{
ignoreText: [
"-",
"[",
"]",
",",
"!",
"?"
], // 忽略某些字符
attributes: {
"/.+/": [
"content",
"title",
"aria-label",
"aria-placeholder",
"aria-roledescription",
"aria-valuetext",
"placeholder",
"label",
"value",
"description"
]
}
}
],
"@intlify/vue-i18n/no-deprecated-i18n-component": "error",
"@intlify/vue-i18n/no-deprecated-i18n-place-attr": "error",
"@intlify/vue-i18n/no-deprecated-i18n-places-prop": "error",
"@intlify/vue-i18n/no-duplicate-keys-in-locale": "error",
"@intlify/vue-i18n/no-unknown-locale": "error",
"@intlify/vue-i18n/no-deprecated-modulo-syntax": "error",
"@intlify/vue-i18n/no-html-messages": "error",
"@intlify/vue-i18n/no-deprecated-v-t": "error",
"@intlify/vue-i18n/prefer-sfc-lang-attr": "error",
"i18n/no-chinese-character": [
"error",
{
includeIdentifier: true,
excludeModuleImports: true,
excludeArgsForFunctions: ["$t", "t", "$bei.t"]
}
]
},
settings: {
"vue-i18n": {
localeDir: "./src/languages/modules/*.{js,json,json5,yaml,yml}", // extension is glob formatting!
messageSyntaxVersion: "^9.10.1"
}
}
};