我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。
本文作者:霜序
前言
在阅读本文之前,需要读者有一些 babel 的基础知识,babel 的架构图如下:
确定中文范围
先需要明确项目中可能存在中文的情况有哪些?- const a = '霜序';
- const b = `霜序`;
- const c = `${isBoolean} ? "霜序" : "FBB"`;
- const obj = { a: '霜序' };
- // enum Status {
- // Todo = "未完成",
- // Complete = "完成"
- // }
- // enum Status {
- // "未完成",
- // "完成"
- // }
- const dom = 霜序;
- const dom1 = <Customer name="霜序" />;
复制代码 虽然有很多情况下会出现中文,在代码中存在的时候大部分是string或者模版字符串,在react中的时候一个是dom的子节点还是一种是props上的属性包含中文。- // const a = '霜序';
- {
- "type": "StringLiteral",
- "start": 10,
- "end": 14,
- "extra": {
- "rawValue": "霜序",
- "raw": "'霜序'"
- },
- "value": "霜序"
- }
复制代码 StringLiteral
对应的AST节点为StringLiteral,需要去遍历所有的StringLiteral节点,将当前的节点替换为我们需要的I18N.key这种节点。- // const b = `${finalRoles}(质量项目:${projects})`
- {
- "type": "TemplateLiteral",
- "start": 10,
- "end": 43,
- "expressions": [
- {
- "type": "Identifier",
- "start": 13,
- "end": 23,
- "name": "finalRoles"
- },
- {
- "type": "Identifier",
- "start": 32,
- "end": 40,
- "name": "projects"
- }
- ],
- "quasis": [
- {
- "type": "TemplateElement",
- "start": 11,
- "end": 11,
- "value": {
- "raw": "",
- "cooked": ""
- }
- },
- {
- "type": "TemplateElement",
- "start": 24,
- "end": 30,
- "value": {
- "raw": "(质量项目:",
- "cooked": "(质量项目:"
- }
- },
- {
- "type": "TemplateElement",
- "start": 41,
- "end": 42,
- "value": {
- "raw": ")",
- "cooked": ")"
- }
- }
- ]
- }
复制代码 TemplateLiteral
相对于字符串情况会复杂一些,TemplateLiteral中会出现变量的情况,能够看到在TemplateLiteral节点中存在expressions和quasis两个字段分别表示变量和字符串
其实可以发现对于字符串来说全部都在TemplateElement节点上,那么是否可以直接遍历所有的TemplateElement节点,和StringLiteral一样。
直接遍历TemplateElement的时候,处理之后的效果如下:- const b = `${finalRoles}(质量项目:${projects})`;
- const b = `${finalRoles}${I18N.K}${projects})`;
- // I18N.K = "(质量项目:"
复制代码 那么这种只提取中文不管变量的情况,会导致翻译不到的问题,上下文很缺失。
最后我们会处理成{val1}(质量项目:{val2})的情况,将对应val1和val2传入- I18N.get(I18N.K, {
- val1: finalRoles,
- val2: projects,
- });
复制代码 JSXText
对应的AST节点为JSXText,去遍历JSXElement节点,在遍历对应的children中的JSXText处理中文文本- {
- "type": "JSXElement",
- "start": 12,
- "end": 25,
- "children": [
- {
- "type": "JSXText",
- "start": 17,
- "end": 19,
- "extra": {
- "rawValue": "霜序",
- "raw": "霜序"
- },
- "value": "霜序"
- }
- ]
- }
复制代码 JSXAttribute
对应的AST节点为JSXAttribute,中文存在的节点还是StringLiteral,但是在处理的时候还是特殊处理JSXAttribute中的StringLiteral,因为对于这种JSX中的数据来说我们需要包裹{},不是直接做文本替换的- {
- "type": "JSXOpeningElement",
- "start": 13,
- "end": 35,
- "name": {
- "type": "JSXIdentifier",
- "start": 14,
- "end": 22,
- "name": "Customer"
- },
- "attributes": [
- {
- "type": "JSXAttribute",
- "start": 23,
- "end": 32,
- "name": {
- "type": "JSXIdentifier",
- "start": 23,
- "end": 27,
- "name": "name"
- },
- "value": {
- "type": "StringLiteral",
- "start": 28,
- "end": 32,
- "extra": {
- "rawValue": "霜序",
- "raw": ""霜序""
- },
- "value": "霜序"
- }
- }
- ],
- "selfClosing": true
- }
复制代码 使用 Babel 处理
使用 @babel/parser 将源代码转译为 AST
- const plugins: ParserOptions['plugins'] = ['decorators-legacy', 'typescript'];
- if (fileName.endsWith('text') || fileName.endsWith('text')) {
- plugins.push('text');
- }
- const ast = parse(sourceCode, {
- sourceType: 'module',
- plugins,
- });
复制代码 @babel/traverse 特殊处理上述的节点,转化 AST
- babelTraverse(ast, {
- StringLiteral(path) {
- const { node } = path;
- const { value } = node;
- if (
- !value.match(DOUBLE_BYTE_REGEX) ||
- (path.parentPath.node.type === 'CallExpression' &&
- path.parentPath.toString().includes('console'))
- ) {
- return;
- }
- path.replaceWithMultiple(template.ast(`I18N.${key}`));
- },
- TemplateLiteral(path) {
- const { node } = path;
- const { start, end } = node;
- if (!start || !end) return;
- let templateContent = sourceCode.slice(start + 1, end - 1);
- if (
- !templateContent.match(DOUBLE_BYTE_REGEX) ||
- (path.parentPath.node.type === 'CallExpression' &&
- path.parentPath.toString().includes('console')) ||
- path.parentPath.node.type === 'TaggedTemplateExpression'
- ) {
- return;
- }
- if (!node.expressions.length) {
- path.replaceWithMultiple(template.ast(`I18N.${key}`));
- path.skip();
- return;
- }
- const expressions = node.expressions.map((expression) => {
- const { start, end } = expression;
- if (!start || !end) return;
- return sourceCode.slice(start, end);
- });
- const kvPair = expressions.map((expression, index) => {
- templateContent = templateContent.replace(
- `\${${expression}}`,
- `{val${index + 1}}`,
- );
- return `val${index + 1}: ${expression}`;
- });
- path.replaceWithMultiple(
- template.ast(`I18N.get(I18N.${key},{${kvPair.join(',\n')}})`),
- );
- },
- JSXElement(path) {
- const children = path.node.children;
- const newChild = children.map((child) => {
- if (babelTypes.isJSXText(child)) {
- const { value } = child;
- if (value.match(DOUBLE_BYTE_REGEX)) {
- const newExpression = babelTypes.jsxExpressionContainer(
- babelTypes.identifier(`I18N.${key}`),
- );
- return newExpression;
- }
- }
- return child;
- });
- path.node.children = newChild;
- },
- JSXAttribute(path) {
- const { node } = path;
- if (
- babelTypes.isStringLiteral(node.value) &&
- node.value.value.match(DOUBLE_BYTE_REGEX)
- ) {
- const expression = babelTypes.jsxExpressionContainer(
- babelTypes.memberExpression(
- babelTypes.identifier('I18N'),
- babelTypes.identifier(`${key}`),
- ),
- );
- node.value = expression;
- }
- },
- });
复制代码 对于TemplateLiteral来说需要处理expression,通过截取的方式获取到对应的模版字符串 templateContent,如果不存在expressions,直接类似StringLiteral处理;存在expressions的情况下,遍历expressions通过${val(index)}替换掉templateContent中的expression,最后使用I18N.get的方式获取对应的值- const name = `${a}霜序`;
- // const name = I18N.get(I18N.test.A, { val1: a });
- const name1 = `${a ? '霜' : '序'}霜序`;
- // const name1 = I18N.get(I18N.test.B, { val1: a ? I18N.test.C : I18N.test.D });
复制代码 对于TemplateLiteral节点来说,如果是嵌套的情况,会出现问题。- const name1 = `${a ? `霜` : `序`}霜序`;
- // const name1 = I18N.get(I18N.test.B, { val1: a ? `霜` : `序` });
复制代码 <blockquote>
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |