Android Studio 自动国际化插件:SmartI18n

不想再当人肉翻译机!我为 Android Studio 撸了个全自动国际化插件:SmartI18n 设计与原理解析

作者说

最近接到一个紧急的客户需求:公司 App 需要紧急适配英文版本 ,并且必须在短时间内完成全量国际化改造。

当我拉下分支,看着项目里成千上万行散落在 .kt.javalayout.xml 里的硬编码中文时,整个人是崩溃的。

如果纯靠手工:复制中文 ➔ 查漏补缺 ➔ 百度翻译 ➔ 粘贴进 strings.xml ➔ 源码改成 R.string.xxx......估算了一下,几千个词条起码要耗费我三四天的时间,还极容易漏改、改错。

"优秀程序员的准则:绝不容忍重复的机械劳动。" 于是,我决定自己写一个 Android Studio 插件------SmartI18n

今天,我把这款插件的使用效果 以及背后的核心设计原理分享出来,教你如何优雅地实现"在文件夹上右键点一下,全工程多语言秒级自动搞定"!


一、我设计的 SmartI18n 究竟能干什么?

在动手写代码前,我给这个插件定下了三个核心目标:零手工介入、绝对安全不破坏语法、支持一键撤回。

整个自动化流水线设计如下:

graph LR A[右键点击业务文件夹] --> B[AST 语法树自动提取中文] B --> C[智能去重并生成标准 Key] C --> D[并发调用翻译 API] D --> E[自动写入各语言 strings.xml] E --> F[无损原地替换源码为 R.string]

它带来的实际改变:

  • 过去:改一个页面,反复横跳 5 个文件,消耗 20 分钟;
  • 现在:在目标文件夹上点右键 ➔ 点击【SmartI18n 提取】 ➔ 泡杯咖啡,30 秒后全模块多语言改造完毕。

二、保姆级体验指南:如何安装与使用?

为了让组内同事和社区朋友用得顺手,我把插件的交互设计得极度简单。

1. 安装方式

  • 本地安装 :将我编译好的 SmartI18n.zip 下载到本地。打开 Android Studio,进入 Settings -> Plugins,点击右上角齿轮选择 Install Plugin from Disk...,选中 zip 包重启 AS 即可完成安装。

2. 配置翻译秘钥

插件需要连接翻译平台。我底层首选接入了百度翻译开放平台(个人认证后每月有免费翻译额度,薅羊毛足够用了)。

  1. 登录百度翻译开放平台,申请"通用翻译 API",拿到你的 APP ID密钥
  2. 打开 Android Studio 设置:Settings -> Tools -> SmartI18n
  3. 填入你的秘钥,勾选希望生成的目标语言(如英语 en、繁体中文 zh-TW、日语 ja)。
  4. 设置 Key 的生成前缀(比如 app_),点击保存。

3. 一键使用流程

  1. 在左侧 Project 目录树中,选中任何你想国际化的业务文件夹 (可以是一个功能模块,甚至是整个 src/main)。
  2. 鼠标右键 ,点击菜单里的 【SmartI18n - 提取国际化资源】
  3. 界面会弹出一个清晰的预览确认弹窗
    • 左列:扫描出来的所有硬编码中文;
    • 中列:自动生成的 Key(如 app_btn_login);
    • 右列:即将写入外语文件的翻译结果。
  4. 点击 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 代码时,我让插件像编译器一样去阅读代码:

  1. 智能过滤注释 :遇到 PsiComment 节点,直接 pass。
  2. 白名单校验 :如果方法调用者是 android.util.Log,直接忽略。
  3. 精准命中字面量 :只有当节点类型是字面量字符串(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 的过程中,我最深的感悟就是:

很多日常看起来无比头疼、繁重的"搬砖"脏活累活,只要你愿意往底层工具链走深一步,往往都能用程序的方式被降维打击。


🎁 源码与安装包获取

无论你是:

  1. 想直接使用:手头正好有项目要做多语言、出海适配,想直接安装提效;
  2. 想学习进阶:正在研究 Android Studio / IntelliJ 插件开发,想看真实的 PSI 语法树解析源码;

👉 欢迎在评论区回复【SmartI18n】或【求源码】 ,我看到后会第一时间私信发送完整的 GitHub 源码仓库地址与安装包网盘链接给你!

(如果觉得这套设计思路对你有启发,顺手点个赞 👍 和收藏 ⭐️,感谢大家的支持与鼓励!)

相关推荐
码农coding4 小时前
android12 状态栏图标的加载流程
android
hai_android5 小时前
Kotlin 协程作用域源码剖析
android·kotlin
Yeyu5 小时前
Android 画中画(PiP)小窗播放:怎么做、以及它到底是怎么工作的
android
千里马学框架5 小时前
安卓系统性能优化高级实战开发专题--开机,app冷启动优化
android·智能手机·性能优化·framework·性能·开机优化·app冷启动优化
律宏阔6 小时前
Android 手机通过 adblib 使用 ADB Wi-Fi 控制 Android 9 开发板
android
律宏阔6 小时前
Android App 里实现开机动画替换
android
消失的旧时光-19436 小时前
Android 系统层扫盲 05:Android 开机后发生了什么?从 Bootloader 到 Launcher
android·zygote·fork·aosp·cow
hunterandroid7 小时前
Android 线上卡顿治理:从布局层级到主线程负载的全链路排查
android
恋猫de小郭8 小时前
Dart Skills CLI 1.0 :AI 时代的 Dart 交付支持
android·前端·flutter