国际化自动化脚本&eslint配置

最近搞了国际化,有点感想。
把一些想法和实现写出来。

期望如果可以通过自动化的脚本做到一键全局翻译,并且以后的迭代,如果写了中文。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"
    }
  }
};
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容