最近的夜莺开发过程中虽然提前被告知过需要做好i18n的准备,但是开发过程很紧张,难点一个又一个,i18n就被低优考虑了,等快上线前补充这个功能的时候。已经有133 个文件,577 处需要被translation函数包裹,而且每个文件前都需要引入依赖,就变成一个纯粹的体力活。每个文件都需要如下的改动
1 | import React from 'react'; |
我的第一个想法是拿nodejs脚本通过正则的手段来匹配dom中的中文,属性中的中文,模板字符串中的中文等进行对应的包裹,插入和替换。团队中的另一个小伙伴提出可以使用AST来做,我们就开始了babel的探索。
babel以及AST的介绍我就不再赘述,以下链接都讲的非常清楚和深入。
下边的链接都是精华
https://lihautan.com/step-by-step-guide-for-writing-a-babel-transformation/
https://lihautan.com/manipulating-ast-with-javascript/#creating-a-node
上边那么多的链(血)接(泪)告诉我们几个事实。
- AST的Node类型很多,很复杂,变幻多端;但是可以对不同的类型进行精准操作很方便。
- 看起来用字符串处理能轻松搞定的事,AST费死老劲
- AST一定要用最新的包,看最新的文档。babel-types,babel-traverse等等都是过时的包,最新的包都在@babel的目录下
举个简单的例子吧,我们写一个最简单的代码const habit = t("running");
如果用AST来构造是什么样子的呢?
1 | const t = require('@babel/types'); |
天壤之别。所以根据 https://lihautan.com/manipulating-ast-with-javascript/#creating-a-node 的推荐,使用@babel/parse将需要的结构直接解析。
1 | const babelParser = require('@babel/parser'); |
回到正题,我们想要的是什么?
- 插入依赖
- 调用useTranslation hook
- 包裹字符串
第一个插入依赖,使用上边提到的方法找到对应的节点插入即可。
第二个插入hook,粗暴的在所有返回JSX 函数组件顶层添加一个useTranslation 调用即可。
第三个使用t方法包裹字符串则比较麻烦,需要根据dom,属性,以及模板字符串中的分别记性处理。
普通字符串处理比较简单,直接在traverse中调用path.replaceWithSourceString('t("'+node.value+'")')
就好。但是要处理好所有的场景需要分别对StringLiteral,JSXText,TemplateLiteral,ReturnStatement,Program等类型分别进行处理。
假设我们要处理的原文件如下:
source javascript
1 | import React from 'react'; |
完整的babel脚本
1 | let fs = require('fs'); |
target javascript
1 | import { useTranslation } from 'react-i18next'; |
这次的i18n的过程中学到了很多,第一次手写一个babel的visitor方法,不断的调试中感受到尤大能把vue转成JavaScript的loader是真的不易。