Android多语言开发自动化生成工具
在做 Android 开发的过程中,经常会遇到多语言开发的场景,尤其在车载项目中,多语言开发更为常见。对应多语言开发,通常都是在中文版本的基础上开发其他国家语言,这里我们会拿到中-外语言对照表,这里的工作难度其实并不高,但是工作量却是非常的大,而且都是复制/粘贴的无聊操作,如何能快速的完成这种简单重复的操作呢?这里我们就来简单实现一下。
一、准备工作
1、多语言需求
假如我们需要中文、英文和俄文三种语言的开发,同时我们拿到了多语言的翻译表格:
| 模块 | name | zh | en | ru |
|---|---|---|---|---|
| SystemUI | cancel | 取消 | Cancel | отмен |
| save | 保存 | Save | сохран | |
| file_name | 文件名称 | File Name | Имя файла | |
| Launcher | save_path | 保存路径 | Saved To | Путь сохранения. |
| text_error_tip | 字数超过限制 | The number of words exceeds the limit. | Число слов превышает предел |
可以看到,对于车载开发来说,多语言开发肯定是所以应用都需要修改的,这里以 SystemUI 和 Launcher 为例。其中 name 表示在 strings.xml 中的 name 字段,zh、en 和 ru 分别表示中文、英文和俄文的简写。
2、制作xls表格
这里我们用的是 xls(暂时只支持 xls 格式的表格解析)的表格来罗列国际化的语言字段,形如下表这样的 translation.xls。

这里使用两个 Sheet 分别存在 SystemUI 和 Launcher 多语言数据,多模块继续增加 Sheet 即可(这里的 Sheet 其实就是 string.mxl 的数量)。再看一下 Launcher 的表格数据:

表格转化
在实际的操作中,我们创建的表格都是 xlsx 格式的表格文件,直接转换一下即可,操作如下:
1)点击表格左上角的文件。

2)这里选择另存为或导出都可以。

3)另存为在保存文件是将格式修改为 Excel 97-2003 工作簿即可。

同样选择导出时也是同样的选择:

二、功能实现
这里我们选择使用 Android 项目来实现多语言 strings.xml 的自动化生成工作,所以先创建一个 Android 项目,然后按照下面的步骤一步一步实现即可。
1、依赖引用
我们是基于 jxl 进行,所以还需要依赖一个 jxl。
dependencies{implementation 'net.sourceforge.jexcelapi:jxl:2.6.12'
}
2、解析工具类
实现 xls 文件解析及生成 strings.xml 的工具类。
import org.jxls.reader.XLSReader;
import jxl.Workbook;
import jxl.Sheet;
import jxl.Cell;
import java.io.File;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Map;public class TranslationHandler {private static final HashMap<Integer, String> codeMap = new HashMap<>();private static final HashMap<String, ArrayList<TranslationBean>> keyAndValueMap = new HashMap<>();private static final String DIR = "/storage/emulated/0/Download/";private static final String XLS_PATH = DIR + "translation.xls";private static final File xlsFile = new File(XLS_PATH);private static final WorkbookSettings workbookSettings = new WorkbookSettings();static {if (!codeMap.isEmpty()) codeMap.clear();if (!keyAndValueMap.isEmpty()) keyAndValueMap.clear();// 设置编码防止其他国字乱码workbookSettings.setEncoding("ISO-8859-1");}// 开始入口函数public static void startAnalyze(){int sheetNums = 0;try{sheetNums = Workbook.getWorkbook(xlsFile, worlkbookSettings).getNumberOfSheets();for (int sheetNum = 0; sheetNum < sheetNums; sheetNUm++)// 第2列(column=B)开始国际化,一共有3列是需要国网示化handleXlsExcel(sheetNum, startColumn: 1, 3);}} catch (Exception e){e.printStackTrace();}/*** @param sheetNum: 表示sheet页数量(0表示第1张sheet)* @param startColumn: 从0开始,第几列开始是国际化* @param columnCount: 一共有多少列是国际化*/private static void handleXlsExcel(int sheetNum, int startColumn, int columnCount) throws Exception {// workbook 与 sheet 是一对一Workbook workbook = Workbook.getWorkbook(xlsFile, workbookSettings);Sheet sheet = workbook.getSheet(sheetNum);System.out.println("sheet0 = " + sheet.getName());// 表示从第1行开始读取for (int row = 0; row < sheet.getRows(); row++) {if (row == 0) {for (int column = 0; column < columnCount; column++) {int columnIndex = startColumn + column;// B1, C1, D1单元格的内容表示国家代码String code = sheet.getCell(columnIndex, row).getContents();codeMap.put(columnIndex, code);keyAndValueMap.put(code, new ArrayList<>());}} else {// A1 ~ A[num] 单元格String key = sheet.getCell(0, row).getContents();if (key == null || "".equals(key)) break;for (int column = 0; column < columnCount; column++) {int columnIndex = startColumn + column;String code = codeMap.getOrDefault(columnIndex, "null");TranslationBean bean = new TranslationBean(key,sheet.getCell(columnIndex, row).getContents());ArrayList<TranslationBean> translationList = keyAndValueMap.get(code);if (translationList != null) {translationList.add(bean);}}}}workbook.close();File dir = new File(DIR, sheet.getName());if (!dir.exists()) dir.mkdir();// 使用 try-with-resources 确保文件流正确关闭for (String code : keyAndValueMap.keySet()) {File defaultDir = new File(dir, "values-" + code);if (!defaultDir.exists() && !defaultDir.mkdirs()) {System.err.println("Failed to create directory: " + defaultDir.getAbsolutePath());continue;}File file = new File(defaultDir, "strings.xml");try {if (!file.exists() && !file.createNewFile()) {System.err.println("Failed to create file: " + file.getAbsolutePath());continue;}System.out.println("Creating or updating file at: " + file.getAbsolutePath());try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {bw.write("<resources>");bw.newLine();for (TranslationBean value : keyAndValueMap.get(code)) {bw.write("\t<string name=\"" + escapeXml(value.getKey()) + "\">" + escapeXml(value.getValue()) + "</string>");bw.newLine();}bw.write("</resources>");}System.out.println("成功生成文件: " + file.getAbsolutePath());} catch (Exception e) {e.printStackTrace();System.err.println("Error writing to file: " + file.getAbsolutePath());}}}private static String escapeXml(String input) {return input.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """).replace("'", "'");}static class TranslationBean {private final String key;private final String value;public TranslationBean(String key, String value) {this.key = key;this.value = value;}public String getKey() {return key;}public String getValue() {return value;}}
}
这里只需要调用 startAnalyze() 方法就可以开始自动化生成多语言 strings.xml 文件,但是需要有一个前提,那就是在对应目录中放置上面的 .xls 文件,这里我们放置在 /storage/emulated/0/Download/ 下,文件名为 translation.xls。
2、开始接口调用
如果是调用开始接口是很简单的,在我们的 Activity 中直接调用或者增加一个按钮再点击事件中调用 TranslationTools.startAnalyze() 方法即可。但是在 Android 文件读写是需要相关权限的,这里我就直接上代码了,对于代码的理解部分可以参考《Android 开发中的权限申请》。
添加静态权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"tools:ignore="ScopedStorage" />
增加动态权限
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M&& context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {//请求权限((Activity)context).requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}
外部权限设置
public static boolean checkStorageManagerPermission(Context context) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);context.startActivity(intent);return false;}return true;
}
在拿到所有权限后就可以调用上面的 startAnalyze() 开始接口了。
三、文件生成/导出
1、文件生成
在执行上面的 startAnalyze() 方法后,会在同目录 /storage/emulated/0/Download/ 下生成如下结构的文件:

这里 translation.xls 是我们最开始保存的 .xls 文件,而 SystemUI 和 Launcher 文件夹及下面的文件则是我们执行完代码自动生成的。下面我们简单看一下其中的文件。



可以看到这里的 SystemUI 对应的多语言 strings.xml 文件都是与上面的 .xls 表格对应的。
2、文件导出
1)在 Studio 中选择 View > Tool Windows > Device File Explorer,就会显示虚拟机的存储信息。

2)在虚拟机的存储设备中,找到 mnt > sdcard > Download,就会看到上面生成文件列表。
3)选择对应文件保存到制定路径即可。

相关文章:
Android多语言开发自动化生成工具
在做 Android 开发的过程中,经常会遇到多语言开发的场景,尤其在车载项目中,多语言开发更为常见。对应多语言开发,通常都是在中文版本的基础上开发其他国家语言,这里我们会拿到中-外语言对照表,这里的工作难…...
回首2024,展望2025
2024年,是个充满挑战与惊喜的年份。在这366个日夜里,我站在编程与博客的交汇点,穿越了无数的风景与挑战,也迎来了自我成长的丰收时刻。作为开发者的第十年,我依然步伐坚定,心中始终带着对知识的渴望与对自我…...
Android SystemUI——快捷面板的显示(十五)
上一篇文章我们分析了 QSTileHost 初始化以及快捷设置面板的创建流程,这里我们继续来看一下快捷设置面板显示流程。 一、QS显示 对于界面的显示,我们同样从 Fragment 的 onViewCreated() 方法开始分析。 1、QSFragment 源码位置:/frameworks/base/packages/SystemUI/src/…...
放弃使用Dockerfiles 平替 docker init
您是那种觉得编写 Dockerfile 和 docker-compose.yml 文件很痛苦的人之一吗? 我承认,我就是其中之一。 我总是想知道我是否遵循了 Dockerfile、 docker-compose 文件的最佳编写实践,我害怕在不知不觉中引入了安全漏洞。 但是现在,…...
前端jquery 实现文本框输入出现自动补全提示功能
git仓库:web_study/some-demos/inputAutoFit at main Cong0925/web_study (github.com) 压缩包:已绑定到指定资源 示例图: 实现说明: 1.首先,html部分设置好相关的定位标签如图: 2.主要函数 3.默认数据...
vulfocus/fastjson-cnvd_2017_02833复现
漏洞概述 Fastjson 是阿里巴巴开发的一个高性能的 Java 库,用于将 Java 对象转换成 JSON 格式(序列化),以及将 JSON 字符串转换回 Java 对象(反序列化)。 fastjson在解析json的过程中,支持使用type字段来指…...
华为支付接入规范
为了确保用户获得良好的支付体验,Payment Kit制定了相关接入设计规范,请开发者遵照执行,具体要求(非强制性)如下: 一、支付方式呈现 涉及支付公司名称,请统一使用:花瓣支付ÿ…...
MySQL训练营-慢查询诊断问题
慢查询相关参数和建议配置 slow_query_log long_query_time 日志开关,是否记慢查询日志以及超过多长时间判定为慢查询。 查看参数设置: SHOW VARIABLES LIKE ‘slow_query_log’;SHOW VARIABLES LIKE ‘long_query_time’; 实践建议: …...
如何给自己的域名配置免费的HTTPS How to configure free HTTPS for your domain name
今天有小伙伴给我发私信,你的 https 到期啦 并且随手丢给我一个截图。 还真到期了。 javapub.net.cn 这个网站作为一个用爱发电的编程学习网站,用来存编程知识和面试题等,平时我都用业余时间来维护,并且还自费买了服务器和阿里云…...
.Net Core微服务入门全纪录(六)——EventBus-事件总线
系列文章目录 1、.Net Core微服务入门系列(一)——项目搭建 2、.Net Core微服务入门全纪录(二)——Consul-服务注册与发现(上) 3、.Net Core微服务入门全纪录(三)——Consul-服务注…...
1/20赛后总结
1/20赛后总结 T1『讨论区管理员』的旅行 - BBC编程训练营 算法:IDA* 分数:0 damn it! Ac_code走丢了~~(主要是没有写出来)~~ T2华强买瓜 - BBC编程训练营 算法:双向DFS或者DFS剪枝 分数:0 Ac_code…...
PVE 虚拟机安装 Debian 无图形化界面服务器
Debian 安装 Debian 镜像下载 找一个Debian镜像服务器,根据需要的版本和自己硬件选择。 iso-cd/:较小,仅包含安装所需的基础组件,可能需要网络访问来完成安装。有镜像 debian-12.9.0-amd64-netinst.isoiso-dvd/:较…...
第17篇:python进阶:详解数据分析与处理
第17篇:数据分析与处理 内容简介 本篇文章将深入探讨数据分析与处理在Python中的应用。您将学习如何使用pandas库进行数据清洗与分析,掌握matplotlib和seaborn库进行数据可视化,以及处理大型数据集的技巧。通过丰富的代码示例和实战案例&am…...
三天急速通关Java基础知识:Day1 基本语法
三天急速通关JAVA基础知识:Day1 基本语法 0 文章说明1 关键字 Keywords2 注释 Comments2.1 单行注释2.2 多行注释2.3 文档注释 3 数据类型 Data Types3.1 基本数据类型3.2 引用数据类型 4 变量与常量 Variables and Constant5 运算符 Operators6 字符串 String7 输入…...
Python的进程和线程
ref 接受几个设定: 进程是一家almost密不透风的公司,缅甸KK园区 线程里面工作的…人 进程**[园区]**内公共资源对于进程来说,可以共享. 别的园区[进程],一般不能和自己的园区共享人员资源,除非… 好的,现在再接受设定: 单个CPU在任一时刻只能执行单个线程,只有…...
【Mysql】记录锁、间隙锁和临键锁的区别
InnoDB通过MVCCNext-Key Locks,解决了可重复读的事务隔离级别出现的幻读问题。 记录锁 记录锁就是为某行数据进行加锁,它封锁该行的索引记录 SELECT * FROM table WHERE id 1 FOR UPDATE id为1的记录行会被锁住。需要注意的的:id列必须为…...
神经网络|(二)sigmoid神经元函数
【1】引言 在前序学习进程中,我们已经了解了基本的二元分类器和神经元的构成,文章学习链接为: 神经网络|(一)加权平均法,感知机和神经元-CSDN博客 在此基础上,我们认识到神经元本身在做二元分类,是一种非…...
w-form-select.vue(自定义下拉框组件)(与后端字段直接相关性)
文章目录 1、w-form-select.vue 组件中每个属性的含义2、实例3、源代码 1、w-form-select.vue 组件中每个属性的含义 好的,我们来详细解释 w-form-select.vue 组件中每个属性的含义,并用表格列出它们是否与后端字段直接相关: 属性解释表格&…...
【JVM】垃圾收集器详解
你将学到 1. Serial 收集器 2. ParNew 收集器 3. Parallel Scavenge 收集器 4. Serial Old 收集器 5. Parallel Old 收集器 6. CMS 收集器 7. G1 收集器 在 Java 中,垃圾回收(GC)是自动管理内存的一个重要机制。HotSpot JVM 提供了多种…...
python创建一个httpServer网页上传文件到httpServer
一、代码 1.server.py import os from http.server import SimpleHTTPRequestHandler, HTTPServer import cgi # 自定义请求处理类 class MyRequestHandler(SimpleHTTPRequestHandler):# 处理GET请求def do_GET(self):if self.path /:# 响应200状态码self.send_response(2…...
质量体系的重要
质量体系是为确保产品、服务或过程质量满足规定要求,由相互关联的要素构成的有机整体。其核心内容可归纳为以下五个方面: 🏛️ 一、组织架构与职责 质量体系明确组织内各部门、岗位的职责与权限,形成层级清晰的管理网络…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】,分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...
莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
GraphQL 实战篇:Apollo Client 配置与缓存
GraphQL 实战篇:Apollo Client 配置与缓存 上一篇:GraphQL 入门篇:基础查询语法 依旧和上一篇的笔记一样,主实操,没啥过多的细节讲解,代码具体在: https://github.com/GoldenaArcher/graphql…...
AD学习(3)
1 PCB封装元素组成及简单的PCB封装创建 封装的组成部分: (1)PCB焊盘:表层的铜 ,top层的铜 (2)管脚序号:用来关联原理图中的管脚的序号,原理图的序号需要和PCB封装一一…...
算法—栈系列
一:删除字符串中的所有相邻重复项 class Solution { public:string removeDuplicates(string s) {stack<char> st;for(int i 0; i < s.size(); i){char target s[i];if(!st.empty() && target st.top())st.pop();elsest.push(s[i]);}string ret…...
