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…...
突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合
强化学习(Reinforcement Learning, RL)是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程,然后使用强化学习的Actor-Critic机制(中文译作“知行互动”机制),逐步迭代求解…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
智能AI电话机器人系统的识别能力现状与发展水平
一、引言 随着人工智能技术的飞速发展,AI电话机器人系统已经从简单的自动应答工具演变为具备复杂交互能力的智能助手。这类系统结合了语音识别、自然语言处理、情感计算和机器学习等多项前沿技术,在客户服务、营销推广、信息查询等领域发挥着越来越重要…...
给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...
在 Spring Boot 中使用 JSP
jsp? 好多年没用了。重新整一下 还费了点时间,记录一下。 项目结构: pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...
