基于AST实现一键自动提取替换国际化文案

背景:在调研 @formatjs/cli 使用(使用 @formatjs/cli 进行国际化文案自动提取 )过程中,发现有以下需求@formatjs/cli 无法满足:

  1. id 需要一定的语义化;

  2. defaultMessage和Id不能直接hash转换;

  3. 需要直接从中文转换为formatMessage

  4. 需要显式注入ID(个人觉得编译时注入还是反直觉了一点);

另外也是希望借助这个机会好好学一下AST相关知识,所以决定自己写一个AST转换工具。

*注意:工具无法满足脱离中文文案和文件名的语义化ID需求。

实现效果

如何使用

https://www.npmjs.com/package/core-i18n-cli?activeTab=readme

安装

npm i -g core-i18n-cli

CLI 参数

corei18n -i, --init

初始化项目,生成配置文件 corei18n.config.json,方便根据你的项目需求进行配置。

默认配置包括以下参数:

export type ProjectConfig = {
  /** corei18n文件根目录,用于放置提取的langs文件 */
  corei18nDir: string;
  /** 导出的新增文案目录 */
  tempLangFile: string;
  /** 需要做国际化的文件目录 */
  path: string;
  /** 已有文案入口,用于过滤已经存在id的文案,支持js、ts、json */
  localLangFile?: string;
  /** 忽略的文件 string | string[],参考GlobOptions.ignore */
  ignoreFile?: GlobOptions["ignore"];

  /** 生成id的方式,默认为translate,需要提供baiduApiKey */
  idType: "translate" | "hash";
  /** 百度翻译开放平台配置,参考 https://fanyi-api.baidu.com/product/113 */
  baiduApiKey?: {
    appId: string;
    appKey: string;
  };
  /** 生成id前缀,会以.拼接在id前面 */
  idSuffix?: string;
  /** 替换后是否保留DefaultMessage,默认为false */
  keepDefaultMessage?: boolean;
  /** 格式化代码的选项,参考prettier.options */
  prettierOptions?: Options;
};

例子:

{
  "corei18nDir": "./.corei18n",
  "tempLangFile": "./.corei18n/tempLang.json",
  "path": "src/pages/**/*.{ts,js,jsx,tsx}",
  "localLangFile": "src/locales/zh-CN.ts",
  "ignoreFile": "src/pages/**/*.d.ts",
  "baiduApiKey": {
    "appId": "",
    "appKey": ""
  },
  "keepDefaultMessage": false,
  "idType": "hash",
  "idSuffix": "tools",
  "prettierOptions": {
    "parser": "typescript",
    "printWidth": 80,
    "singleQuote": true,
    "trailingComma": "all",
    "proseWrap": "never"
  }
}

corei18n -s, --scan

一键扫描指定文件夹下的所有中文文案,新增文案会存放至tempLangFile

corei18n -r, --replace

一键替换指定文件夹下的所有中文文案


实现过程

关于AST

AST explorer:https://astexplorer.net/

AST(抽象语法树)是源代码的抽象表示形式,它捕捉了代码的结构,而不关心具体的字符格式。AST是在编译器设计和解析源代码时常见的一种数据结构。

在编程语言的编译过程中,源代码首先被解析器解析成一种称为AST的中间表示。AST反映了代码的语法结构,每个节点代表代码中的一个结构元素,如表达式、语句、函数、变量等。这种树状结构使得程序的结构和语法可以被更容易地分析和处理。

操作流程

暂时无法在飞书文档外展示此内容

scan 阶段

  1. 根据pathignoreFile得到所有目标文件

  2. 对于每个文件,读取文件内容,将代码转换为AST

  3. 遍历AST节点,若是StringLiteral或者JSXText,判断是否符合要求(包含中文且不属于default Message),如果是则记录下来

  4. 过滤得到所有新增文案并生成id

  5. 将新增文案导出到目标文件

replace 阶段

  1. 根据pathignoreFile得到所有目标文件

  2. 获取所有文案对;

  3. 对于每个文件,读取文件内容,将代码转换为AST

  4. 遍历AST节点,若是StringLiteral或者JSXText,判断是否符合要求(包含中文且不属于default Message),如果是则替换当前AST节点;

  5. 使用prettier进行格式化;

  6. 根据AST生成代码写入文件路径;

依赖的npm包

babel

  1. @babel/core:负责整个编译过程的调度和控制;

  2. @babel/parser:用于将 JavaScript 源代码解析成抽象语法树(AST);

  3. @babel/traverse:用于遍历和修改 AST 的工具;

  4. @babel/types:用于创建、检查和修改 AST 节点

cli相关

  1. commander:解析命令行参数和生成帮助信息;

  2. inquirer:交互式命令行工具,用于收集用户输入;

  3. glob:匹配文件路径

  4. lodash:工具库

  5. prettier:代码格式化

遇到的问题

解决babel/generater生成中文等特殊字符被转义为Unicode编码

const newCode = generator.default( ast, { retainLines: true, jsescOption: { minimal: true } }, // add this code ).code;

Error [ERR_REQUIRE_ESM]: require() of ES Module

// tsconfig { "compilerOptions": { "module": "esnext", "target": "esnext", "moduleResolution": "node", } }

// package.json { "type": "module" }

Error [ERR_MODULE_NOT_FOUND]: Cannot find module

https://github.com/microsoft/TypeScript/issues/16577

https://stackoverflow.com/questions/62619058/appending-js-extension-on-relative-import-statements-during-typescript-compilat

原因:tsc输出时不会添加文件拓展名,nodejs运行时不会自动匹配文件拓展名(居然是个久远的未解决的问题==)

尝试在文件首行添加 --experimental-specifier-resolution=node 无效

使用tsc-alias为导出文件添加js后缀后解决:

npm install --save-dev tsc-alias

// tsconfig.json { "compilerOptions": { ... }, "tsc-alias": { "resolveFullPaths": true, "verbose": false } }

"scripts": { "compile": "tsc && tsc-alias" }

参考

  • 小玩具:利用AST实现代码文案的自动翻译与替换 - 掘金

  • https://github.com/alibaba/kiwi/tree/master/kiwi-cli

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/378849.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL篇----第七篇

系列文章目录 文章目录 系列文章目录前言一、水平分区二、分库分表之后,id 主键如何处理三、存储过程(特定功能的 SQL 语句集)前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你…

仰暮计划|“舅舅的大女儿失踪了,当时找遍了整个村庄,也报了警”

我的舅舅是1961年出生在一个偏僻的小山沟里,我只在很小的时候跟着我的妈妈回去过,我对于那里的印象很模糊,只有半镶在土窑里的小平房,门前的一条栽满樱桃树的很深的土沟,通往门前的陡峭的小路和露天的院子里那一颗茂盛…

LabVIEW动平衡测试与振动分析系统

LabVIEW动平衡测试与振动分析系统 介绍了利用LabVIEW软件和虚拟仪器技术开发一个动平衡测试与振动分析系统。该系统旨在提高旋转机械设备的测试精度和可靠性,通过精确测量和分析设备的振动数据,以识别和校正不平衡问题,从而保证机械设备的高…

图数据库 之 Neo4j - 环境搭建(2)

运行环境: centos7 Docker version 18.09.6 下载镜像 docker search neo4j docker pull neo4j 创建 neo4j 用户 # 创建 neo4j 用户 # -M 不创建用户的主目录 sudo useradd -M neo4j # usermod 用于修改用户属性命令 # -L 锁定用户,用户无法登录系统 user…

深入Pandas:精通文本数据处理的20+技巧与应用实例【第68篇—python:文本数据处理】

文章目录 Pandas文本数据处理方法详解1. str/object类型转换2. 大小写转换3. 文本对齐4. 获取长度5. 出现次数6. 编码方向7. 字符串切片8. 字符串替换9. 字符串拆分10. 字符串连接11. 字符串匹配12. 去除空格13. 多条件过滤14. 字符串排序15. 字符串格式化16. 多列文本操作17. …

Android Studio安装过程遇到SDK无法安装问题解决

首次打开studio遇到该类问题,需要下载SDK文件,后又发现SDK由于是Google源,无法进行正常安装,故转而进行SDK的镜像安装。 一、下载SDK Tools 地址:AndroidDevTools - Android开发工具 Android SDK下载 Android Studio…

c语言动态数组的实现

动态数组是在程序运行时动态分配内存空间的数组,可以根据需要随时改变大小。在C语言中,动态数组通常通过指针和malloc函数来实现。 使用malloc函数动态分配内存空间: int *arr; int size 10; arr (int*)malloc(size * sizeof(int));使用r…

【Java八股面试系列】并发编程-进程与线程

目录 进程 线程 线程和进程的区别 Java线程和操作系统的线程的区别 请简要描述一下进程和线程在Java中的关系,区别及优缺点?​编辑​编辑​编辑 并发和并行的区别 为什么要使用多线程? 线程的生命周期 什么是线程上下文切换? sleep() 方法和…

Java Stram 流对于返回对象的处理 (结束流)

Java Stram 流对于返回对象的处理 (结束流) package com.zhong.streamdemo.showdownstreamdemo;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import java.util.*; import java.util.stream.Collectors; im…

Springboot 整合 Elasticsearch(三):使用RestHighLevelClient操作ES ①

📁 前情提要: Springboot 整合 Elasticsearch(一):Linux下安装 Elasticsearch 8.x Springboot 整合 Elasticsearch(二):使用HTTP请求来操作ES 目录 一、Springboot 整合 Elasticsea…

【FPGA】快速学习路径

FPGA学习教程、功利式学习路径、以找工作为目的,早日入门FPGA_哔哩哔哩_bilibili

Redis篇之集群

一、主从复制 1.实现主从作用 单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。主节点用来写的操作,从节点用来读操作,并且主节点发生写操作后,会把数据同…

倒计时61天

M-智乃的36倍数(normal version)_2024牛客寒假算法基础集训营3 (nowcoder.com) //非ac代码,超时了,54.17/100#include<bits/stdc.h> using namespace std; const int N1e55; const int inf0x3f3f3f3f; #define int long long int n; string s1[N]; void solve() {cin>…

第五篇【传奇开心果系列】vant开发移动应用示例:深度解读高度可定制

传奇开心果博文系列 系列博文目录Vant 开发移动应用示例系列 博文目录前言一、Vant高度可定制的重要作用二、样式定制介绍和示例代码三、组件定制介绍和示例代码四、组件库定制介绍和示例代码五、主题定制介绍和示例代码六、语言环境定制介绍和示例代码七、资源加载定制介绍和示…

[当人工智能遇上安全] 11.威胁情报实体识别 (2)基于BiGRU-CRF的中文实体识别万字详解

您或许知道&#xff0c;作者后续分享网络安全的文章会越来越少。但如果您想学习人工智能和安全结合的应用&#xff0c;您就有福利了&#xff0c;作者将重新打造一个《当人工智能遇上安全》系列博客&#xff0c;详细介绍人工智能与安全相关的论文、实践&#xff0c;并分享各种案…

DMA直接内存访问,STM32实现高速数据传输使用配置

1、DMA运用场景 随着智能化、信息化的不断推进&#xff0c;嵌入式设备的数据处理量也呈现指数级增加&#xff0c;因此对于巨大的数据量处理的情况时&#xff0c;必须采取其它的方式去替CPU减负&#xff0c;以保证嵌入式设备性能。例如SD卡存储器和音视频、网络高速通信等其它情…

探讨CSDN等级制度:博客等级、原力等级、创作者等级

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…

数据结构:双向链表

文章目录 1. 双向带头循环链表的结构2. 相关操作2.1 创建节点2.2 尾插2.3 头插2.4 打印2.5 尾删2.6 头删2.7 查找2.8 指定位置前/后插入2.9 删除指定位置的节点2.10 删除指定位置后的节点2.11 销毁链表 3.顺序表与链表区别 1. 双向带头循环链表的结构 与单链表不同的是&#xf…

C#,奥西里斯数(Osiris Number)的算法与源代码

1 奥西里斯数(Osiris Number) 奥西里斯数(Osiris Number)是一个数字&#xff0c; 其值等于通过将其自身数字的所有排列相加而形成的所有数字的值之和。 计算结果&#xff1a; 2 源程序 using System; namespace Legalsoft.Truffer.Algorithm { /// <summary> /…

Django学习记录02

1.请求与响应 1.1get与post的区别 get 一般是从url输入地址&#xff0c;会调用get请求 post 一般是内部数据传输# get请求 def something(request):# req是一个对象&#xff0c;封装了用户发送过来的所有请求相关数据# 1.获取请求方式 http://localhost:8000/something# pri…