开发过程中,因为国际化太麻烦,我写了这款vscode国际化插件。

6,878 阅读8分钟

效果展示

完整流程

由于掘金gif图有大小限制,这里录了两个。

01

01.gif

02

02.gif

删除某个中文和检测本地文件是否已经存在

03.gif

  1. 这里如果不想对某个中文做国际化,可以手动给删掉。
  2. 如果本地已经对当前中文做过国际化,就不会再去翻译了,而是直接把对应的key取出来,直接替换代码。

增加国际化语言类型

04.gif 这里新增了一个国际化类型,翻译的时候多了一个繁体中文,保存的时候,也会检测本地文件是否存在如果不存在,会自动创建该类型文件。添加国际化语言时冒号前面是百度翻译支持的类型,后面是文件名,如果一样可以只写一个。

下面是百度翻译常用语种和对应代码。

名称代码名称代码
自动检测auto中文zh
英语en粤语yue
文言文wyw日语jp
韩语kor法语fra
西班牙语spa泰语th
阿拉伯语ara俄语ru
葡萄牙语pt德语de
意大利语it希腊语el
荷兰语nl波兰语pl
保加利亚语bul爱沙尼亚语est
丹麦语dan芬兰语fin
捷克语cs罗马尼亚语rom
斯洛文尼亚语slo瑞典语swe
匈牙利语hu繁体中文cht
越南语vie

react代码中出现中文的几种方式

05.gif 利用抽象语法树,能够精准的获取代码中的中文。

插件功能介绍

  1. 到vscode插件市场搜索easy-i18n-helper然后安装,作者是dbfu321,别安装错了。也可以访问这个在线地址去安装。
  2. 首先在百度翻译平台开通通用文本服务,得到APP ID和密钥,然后在vscode配置中设置easy-i18n-helper.Baidu App Id和easy-i18n-helper.Baidu App Token后,就能使用该插件了。开通百度翻译服务教程在下面。
  3. 在react代码文件中右键,然后点击翻译当前页面,会自动获取当前页面的中文,然后展示出来,这里如果你不想对某个中文做国际化,你可以给删除。
  4. 点击名称,可以跳到对应编辑器中并选中当前中文,可以让你快速知道当前中文在代码中的位置,然后决定是否删除。
  5. 点击翻译会调百度翻译公共接口去翻译,这里会检测本地是否已经存在要做国际化的中文,如果存在,则不会去调翻译接口,直接用本地的。
  6. 点击翻译按钮过后,会在翻译结果页面看到哪些是新增的,哪些是直接取本地的。
  7. 点击保存后,会自动检测当前文件是否引入了国际化方法,如果没有引入,会自动引入,这个导入代码可以在配置里自定义。
  8. 把新增的国际化内容保存到本地文件中,如果本地文件不存在,则自动创建。
  9. 用国际化方法覆盖掉代码中的中文
  10. 自定义语种。到vscode配置中搜索easy-i18n-helper.languages,然后添加一个语种,百度翻译支持的语种上面已经发过了。
  11. 自定义导入国际化方法语句。到vscode配置中搜索easy-i18n-helper.Import Codes,然后改成自己想要的。
  12. 自定义存放本地国际化文件的文件夹地址。到vscode配置中搜索easy-i18n-helper.Locales Path,然后改成自己的。
  13. 自定义国际化文件后缀。有的项目可能没有用ts,所以这里支持去修改成js。到vscode配置中搜索easy-i18n-helper.File Type,然后改成其他的。
  14. 自定义国际化方法名。有的项目可能不叫t,到vscode配置中搜索easy-i18n-helper.Method Name,然后改成其他的。
  15. mac快捷键:command+shift+t
  16. win快捷键:ctrl+shift+t

开通百度翻译翻译服务和获取app id和密钥

开通这个服务还是很简单的,整个流程大概只需要几分钟。百度翻译每个月有100w字符的免费(良心企业啊),如果个人使用,基本可以放心的使用。这里提醒一下,如果超出100w字符,是会扣费的,所以大家要保护好自己的app id和密钥。

进入百度翻译网站,登录百度账号。

点击下面的通用文本翻译的查看详情

image.png

点击立即使用

image.png

这里选个人开发者,填写一下信息

image.png

这里实名认证一下,可以开通高级版

image.png

开通文本翻译服务

image.png

选通用文本翻译

image.png

开通高级版

image.png

写个应用名,这个可以随便写一个,其他信息可以不填,提交申请,然后秒通过。

image.png

到总览的下方,可以得到app id和密钥,配置到vscode中,就能使用当前插件了。

image.png

插件代码实现思路

  1. 获取并检查用户配置,并把获取到的用户配置存到全局。
  2. 获取当前内容ast
  3. 通过ast获取所有中文
  4. 使用vscode webview显示中文列表页面,并在页面中实现删除功能。
  5. 监听webview消息事件
  6. 当监听到webview传输open类型的事件时:使用vscode api实现选中当前中文。
  7. 使用ast检测用户当前本地项目中有没有对应的中文
  8. 根据用户配置的语言列表,把本地没有做过国际化的中文,借助百度翻译api翻译成对应的语言。
  9. 使用webiew展示翻译结果
  10. 保存,把新增的国际化存到本地,然后使用国际化生成的key和封装的国际化方法替换掉当前文本。

插件中用到的主要技术,

抽象语法树

项目里主要使用了babel把文件内容转换成抽象语法树,然后通过遍历语法树获取中文。

@babel/parser 使用这个库把代码转换成抽象语法树

@babel/traverse 使用这个库遍历语法树,获取代码中的中文

@babel/generator 使用这个库把处理过的ast转换成代码

 private getAst(): ParseResult<t.File> | undefined {
    try {
      this.ast = parse(this.fileContent, {
        sourceType: "module",
        plugins: [
          "jsx",
          "flow",
          ["decorators", { "decoratorsBeforeExport": true }],
        ],
      });
      return this.ast;
    } catch (error) {
      console.log(error);
    }
  }
private getChineseWordsByAst(ast: ParseResult<t.File>) {
    const words: Words[] = [];
    traverse(ast, {
      ["StringLiteral"]: (path: any) => {
        if (/[\u4e00-\u9fa5]/.test(path.node.value)) {
          words.push({
            value: path.node.value,
            loc: path.node.loc,
            isJsxAttr: path.parent.type === "JSXAttribute",
            id: getId(),
          });
        }
      },
      ["JSXText"]: (path: any) => {
        if (/[\u4e00-\u9fa5]/.test(path.node.value)) {
          const val = path.node.value.replace(/\n/g, '').trim();
          words.push({
            id: getId(),
            value: val,
            loc: path.node.loc,
            isJsxAttr: true,
            isJsxText: true,
            rawValue: path.node.value,
          });
        }
      },
      ["TemplateElement"]: (path: any) => {
        if (/[\u4e00-\u9fa5]/.test(path.node.value.raw)) {
          const val = path.node.value.raw.replace(/\n/g, '').trim();
          words.push({
            id: getId(),
            value: val,
            loc: path.node.loc,
            isTemplate: true,
          });
        }
      }
    });
    return words;
  }
    const ast = parse(fileContent, { sourceType: "module" });

    const visitor: any = {
      // eslint-disable-next-line @typescript-eslint/naming-convention
      ObjectExpression(nodePath: any) {
        const { node } = nodePath;

        node.properties.push(
          ...newWords.map((word) => {
            return t.objectProperty(
              t.stringLiteral(word.key),
              t.stringLiteral(word.value),
            );
          })
        );
      },
    };

    traverse(ast, visitor);

    const newContent = generator(ast, {
      jsescOption: { minimal: true },
    }).code;

这里只简单的介绍一下,后面会开单篇详细介绍babel。

vscode插件开发

webview和vscode通信

vscode中创建webview后,可以使用onDidReceiveMessage这个方法监听webview中js发送过来的消息。

this.webviewPanel = window.createWebviewPanel(
  'translate',
  "中文列表",
  columnToShowIn || ViewColumn.Active,
  {
    retainContextWhenHidden: true,
    enableScripts: true
  }
);
this.webviewPanel.webview.onDidReceiveMessage((e) => this.didReceiveMessageHandle(e));

webview script中可以通过acquireVsCodeApi这个方法得到vscodeAPI对象,然后使用postMessage给vscode发送消息。

<script>
 const vscode = acquireVsCodeApi();
 vscode.postMessage({
   data: window.words,
   type: "save",
 });
</script>

webview中获取vscode主题

为了让用户体验更好,插件可以根据用户主题自动改变颜色。

在css中可以使用body.vscode-lightbody.vscode-dark来定制webview中的主题。

body.vscode-light {
   color: #333;
}

body.vscode-dark {
  color: #eee;
}

使用--vscode-editor-font-size变量获取编辑器设置的字体大小

body,
table {
  font-size: var(--vscode-editor-font-size);
  word-break: break-word;
}

小结

后面也会开单篇介绍如何从零开发一个vscode插件

ejs html模版引擎

官网地址

这个我用的也不多,因为要根据变量生成一些dom元素,不想用原生api一个个创建dom元素,就使用了这个市面上比较常用的html模版引擎。 用了一下,感觉还挺方便。就遇到了一个问题,怎么把变量注入到window对象上。从网上搜了一些资料,他们给的方案是有点问题的。

网上的方案:

<script>
   window.words = <%= JSON.stringify(words) %>;
 </script>

这样写在运行的时候会报错,因为<%= 会把内容转换成html,导致json中{会被转译掉,然后JSON.stringify就报错了。

正确用法:

<script>
   window.words = <%- JSON.stringify(words) %>;
 </script>

使用<%-就行了。

  • <% '脚本' 标签,用于流程控制,无输出。
  • <%_ 删除其前面的空格符
  • <%= 输出数据到模板(输出是转义 HTML 标签)
  • <%- 输出非转义的数据到模板
  • <%# 注释标签,不执行、不输出内容
  • <%% 输出字符串 '<%'
  • %> 一般结束标签
  • -%> 删除紧随其后的换行符
  • _%> 将结束标签后面的空格符删除

总结

相信很多同学都不愿意做国际化,但是公司要求必须要做,只能很痛苦的去做。但是有了这款插件,开发的时候就不用关注国际化了,可以在开发完一个功能后,一键国际化。

我不知道别人的系统国际化是怎么做的,看了一些开源的框架,都是在本地文件中做,其实这样做有个不好的地方,很容易导致代码冲突,所以我们把本地存储迁移到了数据库。迁移到线上还有一个好处,如果业务对机翻的结果不满意,可以在线修改。

在公司内部我还写了一个插件,和这个区别是调系统接口把自动翻译的结果存储到数据库,而不是存储到本地。

因为我对vue不熟,所以现在插件只支持react,哪位兄弟有时间提个pr,把vue也支持一下吧。代码中我写了个抽象类,写vue插件的时候继承一下这个类,只要实现获取中文,替换文本和导入国际化这三个抽象方法就行了。代码文件可以加在这个目录下。

image.png

组件仓库地址:github.com/dbfu/easy-i… 如果插件对你有用,麻烦给个star吧。

下篇预告

因为我觉得在项目里使用icon太麻烦,于是我写了一个vscode插件。