最近搞了國際化,有點感想。
把一些想法和實現(xiàn)寫出來。
期望如果可以通過自動化的腳本做到一鍵全局翻譯,并且以后的迭代,如果寫了中文。eslint會報錯,配合husky不允許提交,這樣的話就可以讓項目更好的使用國際化。
web實現(xiàn)國際化現(xiàn)在是一個很簡單的事情,i18n的插件也很多,大多都是安裝一下,配置一下就可以很快的實現(xiàn)國際化。我們使用了vue-i18n。
其實難點不在國際化的安裝,而在國際化json文件的配置,大家需要把所有文件review一遍,把所有的中文抽出來,換成變量比如$t、{{ t() }}。在不同的文件里面還需要有不同的注意事項。比如vue template 在這個里面需要使用{{ t() }} 或者 :t() 。setup 語法里面 需要導入一些插件,并且使用它 可能是$t的方式。還有jsx、tsx 里面配置還會不一樣 比如 $bei.t()。 $bei是我們掛載的全局變量。
出于這樣的想法去思考的話,期望有一個腳本是實現(xiàn)這樣的事情。我去開發(fā)了一個nodejs的腳本,可以走三方翻譯, 找到文件里面的中文,抽出來放到一個json文件,而且做replace。我是實現(xiàn)了比較簡單的功能。我只替換template里面的中文并且都是替換為{{ t() }} ,這個對于提效來講很一般,大家都還需要在很多東西,屬性的需要替換為: setup和tsx需要格外去改。
我們?nèi)it上了找到了一個插件@ifreeovo/i18n-extract-cli
https://github.com/IFreeOvO/i18n-cli
貼個地址還是比較好用的,通過一些簡單的配置,可以實現(xiàn)全局自動化i18n。
它的實現(xiàn)邏輯也是比較有趣。它通過glob遍歷所有的文件,利用nodejs讀取文件內(nèi)容,使用babel解析為抽象語法樹。
解析非vue文件只用做字符串替換就可以。
vue文件需要注意的東西多一點,把vue 文件抽離為html css js 。 css文件直接跳過, js 依然使用babel處理。 html的使用htmlparser2 解析為html去處理這個很有趣,內(nèi)容會轉成child 屬性會轉成props。這樣可以很簡單的實現(xiàn)屬性和內(nèi)容的區(qū)分。找到中文之后,去調(diào)用三方翻譯,實現(xiàn)翻譯功能,保存到文件中。最后使用pritter實現(xiàn)格式化即可。
再說后半部分
使用@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",
// 優(yōu)先級低于 parse 的語法解析配置
parserOptions: {
parser: "@typescript-eslint/parser",
ecmaVersion: 2020,
sourceType: "module",
jsxPragma: "React",
ecmaFeatures: {
jsx: true
}
},
// 繼承某些已有的規(guī)則
extends: [
"plugin:vue/vue3-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"plugin:@intlify/vue-i18n/recommended-legacy"
],
plugins: ["i18n"],
/**
* "off" 或 0 ==> 關閉規(guī)則
* "warn" 或 1 ==> 打開的規(guī)則作為警告(不影響代碼執(zhí)行)
* "error" 或 2 ==> 規(guī)則作為一個錯誤(代碼不能執(zhí)行,界面報錯)
*/
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", // 禁止在 函數(shù)/類/變量 定義之前使用它們
// typeScript (https://typescript-eslint.io/rules)
"@typescript-eslint/no-unused-vars": ["error", { ignoreRestSiblings: true }], // 禁止定義未使用的變量 忽略解構對象中的一些用不到的變量
"@typescript-eslint/no-empty-function": "error", // 禁止空函數(shù)
"@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() 函數(shù)導入模塊
"@typescript-eslint/no-non-null-assertion": "off", // 不允許使用后綴運算符的非空斷言(!)
// vue (https://eslint.vuejs.org/rules)
"vue/script-setup-uses-vars": "error", // 防止<script setup>使用的變量<template>被標記為未使用,此規(guī)則僅在啟用該 no-unused-vars 規(guī)則時有效
"vue/v-slot-style": "error", // 強制執(zhí)行 v-slot 指令樣式
"vue/no-mutating-props": "error", // 不允許改變組件 prop
"vue/custom-event-name-casing": "error", // 為自定義事件名稱強制使用特定大小寫
"vue/html-closing-bracket-newline": "error", // 在標簽的右括號之前要求或禁止換行
"vue/attribute-hyphenation": "error", // 對模板中的自定義組件強制執(zhí)行屬性命名樣式:my-prop="prop"
"vue/attributes-order": "off", // vue api使用順序,強制執(zhí)行屬性順序
"vue/no-v-html": "off", // 禁止使用 v-html
"vue/require-default-prop": "off", // 此規(guī)則要求為每個 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"
}
}
};