不想再当人肉翻译机!我为 Android Studio 撸了个全自动国际化插件:SmartI18n 设计与原理解析
作者说 :
最近接到一个紧急的客户需求:公司 App 需要紧急适配英文版本 ,并且必须在短时间内完成全量国际化改造。
当我拉下分支,看着项目里成千上万行散落在
.kt、.java和layout.xml里的硬编码中文时,整个人是崩溃的。如果纯靠手工:复制中文 ➔ 查漏补缺 ➔ 百度翻译 ➔ 粘贴进
strings.xml➔ 源码改成R.string.xxx......估算了一下,几千个词条起码要耗费我三四天的时间,还极容易漏改、改错。"优秀程序员的准则:绝不容忍重复的机械劳动。" 于是,我决定自己写一个 Android Studio 插件------
SmartI18n。今天,我把这款插件的使用效果 以及背后的核心设计原理分享出来,教你如何优雅地实现"在文件夹上右键点一下,全工程多语言秒级自动搞定"!
一、我设计的 SmartI18n 究竟能干什么?
在动手写代码前,我给这个插件定下了三个核心目标:零手工介入、绝对安全不破坏语法、支持一键撤回。
整个自动化流水线设计如下:
它带来的实际改变:
- 过去:改一个页面,反复横跳 5 个文件,消耗 20 分钟;
- 现在:在目标文件夹上点右键 ➔ 点击【SmartI18n 提取】 ➔ 泡杯咖啡,30 秒后全模块多语言改造完毕。
二、保姆级体验指南:如何安装与使用?
为了让组内同事和社区朋友用得顺手,我把插件的交互设计得极度简单。
1. 安装方式
- 本地安装 :将我编译好的
SmartI18n.zip下载到本地。打开 Android Studio,进入Settings -> Plugins,点击右上角齿轮选择 Install Plugin from Disk...,选中 zip 包重启 AS 即可完成安装。
2. 配置翻译秘钥
插件需要连接翻译平台。我底层首选接入了百度翻译开放平台(个人认证后每月有免费翻译额度,薅羊毛足够用了)。
- 登录百度翻译开放平台,申请"通用翻译 API",拿到你的 APP ID 和 密钥。
- 打开 Android Studio 设置:
Settings -> Tools -> SmartI18n。 - 填入你的秘钥,勾选希望生成的目标语言(如英语
en、繁体中文zh-TW、日语ja)。 - 设置 Key 的生成前缀(比如
app_),点击保存。
3. 一键使用流程
- 在左侧 Project 目录树中,选中任何你想国际化的业务文件夹 (可以是一个功能模块,甚至是整个
src/main)。 - 鼠标右键 ,点击菜单里的 【SmartI18n - 提取国际化资源】。
- 界面会弹出一个清晰的预览确认弹窗 :
- 左列:扫描出来的所有硬编码中文;
- 中列:自动生成的 Key(如
app_btn_login); - 右列:即将写入外语文件的翻译结果。
- 点击 OK ,一瞬间,代码里的中文全自动变成了
R.string.xxx,多语言资源文件也全部齐齐整整地归档完毕!
三、深度揭秘:我是如何设计这个插件底层的?
很多朋友体验完后都问我:"你这效果看起来很神奇,到底是怎么做到的?能分享下底层原理吗?"
其实整个插件的研发,核心攻克了以下两个关键技术壁垒:
难点 1:为什么选中文件夹点右键就能触发?
很多刚接触 IDE 插件开发的人,不知道如何让功能"刚好出现在合适的位置"。
我利用了 IntelliJ 底层的 Action 动作路由机制:
1. 注册菜单插槽(plugin.xml)
在插件的元配置文件中,我把动作挂载到了系统的核心插槽上:
xml
<actions>
<action id="com.smart.i18n.ScanAction"
class="com.smart.i18n.ScanAction"
text="SmartI18n - 提取国际化资源"
description="自动提取中文并生成多语言">
<!-- 关键:挂载到项目目录树的右键上下文弹窗 -->
<add-to-group group-id="ProjectViewPopupMenu" anchor="last"/>
</action>
</actions>
ProjectViewPopupMenu:IntelliJ 系统规定,凡是挂在它下面的 Action,只要用户在左侧项目工程树里按下鼠标右键,就会被渲染出来。
2. 毫秒级动态过滤(update 鉴权)
如果用户点中了一张图片或者 .gradle 文件,弹出这个菜单就显得很多余。
因此在 ScanAction 类中,我重写了 update() 方法做毫秒级拦截:
kotlin
class ScanAction : AnAction() {
override fun update(e: AnActionEvent) {
// 从事件上下文中拿到鼠标当前选中的虚拟文件 (VirtualFile)
val file = e.getData(CommonDataKeys.VIRTUAL_FILE)
// 仅当选中的是"文件夹",或者是 Java/Kotlin/XML 源码时,菜单才显示
val isMatch = file != null && (file.isDirectory ||
file.extension in listOf("kt", "java", "xml"))
// 动态设置可见性:不符合条件直接隐身
e.presentation.isEnabledAndVisible = isMatch
}
}
3. 递归穿透遍历文件(actionPerformed)
当用户点击菜单后,系统会把被选中的文件夹传给 actionPerformed()。我调用平台提供的 VfsUtilCore 工具进行深度遍历,挖出它子目录下的所有代码文件:
kotlin
override fun actionPerformed(e: AnActionEvent) {
val selectedDir = e.getData(CommonDataKeys.VIRTUAL_FILE) ?: return
// 递归掘地三尺,找到目标文件送入处理队列
VfsUtilCore.visitChildrenRecursively(selectedDir, object : VirtualFileVisitor<Unit>() {
override fun visitFile(file: VirtualFile): Boolean {
if (file.extension in listOf("kt", "java", "xml")) {
parseFileContent(file) // 启动语法解析
}
return true
}
})
}
难点 2:为什么不用正则表达式,而是用 PSI 语法树?
这是我做这个插件时思考最多的问题。
最笨的办法是写个中文字符正则去全文匹配替换,但我坚决否定了这个方案。因为纯正则替换具有致命缺陷:
- 它会把代码里的
// 待办:修复登录问题(注释) 误当成字符串提出来; - 它会把
Log.d("TAG", "请求失败")也改掉,导致生产排查日志混乱; - 括号匹配错误还会导致整个类编译报红。
我的解决方案:基于 IntelliJ PSI(程序结构接口)
IntelliJ 平台在内存里维护了代码的抽象语法树(AST),这套机制被称为 PSI (Program Structure Interface)。
在解析 Kotlin/Java 代码时,我让插件像编译器一样去阅读代码:
- 智能过滤注释 :遇到
PsiComment节点,直接 pass。 - 白名单校验 :如果方法调用者是
android.util.Log,直接忽略。 - 精准命中字面量 :只有当节点类型是字面量字符串(Java 的
PsiLiteralExpression或 Kotlin 的KtStringTemplateExpression),且包含中文字符[\u4e00-\u9fa5]时,才判定为提取目标。
事务保护:赋予用户一键"后悔药"
直接修改用户的工程代码,风险极高。为了给用户足够的安全感,我将所有的 AST 替换与文件写入操作,全部包进了平台的**写事务(WriteCommandAction)**中:
kotlin
WriteCommandAction.runWriteCommandAction(project) {
// 所有的源码替换、XML回写都在这里执行
}
这么做的好处是:它完美集成了 Android Studio 的撤销栈!
如果用户批量替换后觉得不妥,只需轻按键盘的 Ctrl + Z(Mac 下 Cmd + Z) ,刚才修改的所有文件、追加的 strings.xml 都会在 1 秒内无损回滚!
四、写在最后:关于自动化的思考
| 维度 | 人工硬啃国际化 | 使用 SmartI18n 插件 |
|---|---|---|
| 效率 | 耗时几天,枯燥痛苦 | 选中目录右键,几十秒全自动完成 |
| 安全性 | 容易漏提、手抖改坏代码 | 基于 AST 语法树安全替换,支持 Ctrl+Z 撤销 |
| 复用度 | 依赖肉眼记忆,同名 Key 反复定义 | 自动校验现有 strings.xml,自动去重复用 |
在开发 SmartI18n 的过程中,我最深的感悟就是:
很多日常看起来无比头疼、繁重的"搬砖"脏活累活,只要你愿意往底层工具链走深一步,往往都能用程序的方式被降维打击。
🎁 源码与安装包获取
无论你是:
- 想直接使用:手头正好有项目要做多语言、出海适配,想直接安装提效;
- 想学习进阶:正在研究 Android Studio / IntelliJ 插件开发,想看真实的 PSI 语法树解析源码;
👉 欢迎在评论区回复【SmartI18n】或【求源码】 ,我看到后会第一时间私信发送完整的 GitHub 源码仓库地址与安装包网盘链接给你!
(如果觉得这套设计思路对你有启发,顺手点个赞 👍 和收藏 ⭐️,感谢大家的支持与鼓励!)