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

效果展示

查看效果

插件功能介绍

  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

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值