Android多语言开发自动化生成工具

news2025/1/27 8:55:38

        在做 Android 开发的过程中,经常会遇到多语言开发的场景,尤其在车载项目中,多语言开发更为常见。对应多语言开发,通常都是在中文版本的基础上开发其他国家语言,这里我们会拿到中-外语言对照表,这里的工作难度其实并不高,但是工作量却是非常的大,而且都是复制/粘贴的无聊操作,如何能快速的完成这种简单重复的操作呢?这里我们就来简单实现一下。

一、准备工作

1、多语言需求

        假如我们需要中文、英文和俄文三种语言的开发,同时我们拿到了多语言的翻译表格:

模块namezhenru
SystemUIcancel取消Cancelотмен
save保存Saveсохран
file_name文件名称File NameИмя файла
Launchersave_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("&", "&amp;")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace("\"", "&quot;")
                .replace("'", "&apos;");
    }

    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)选择对应文件保存到制定路径即可。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java数据结构 (链表反转(LinkedList----Leetcode206))

1. 链表的当前结构 每个方框代表一个节点&#xff0c;每个节点包含两个部分&#xff1a; 左侧的数字&#xff1a;节点存储的值&#xff0c;例如 45、34 等。右侧的地址&#xff08;如 0x90&#xff09;&#xff1a;表示该节点 next 指针指向的下一个节点的内存地址。 例子中&a…

LabVIEW 太阳能光伏发电系统智能监控

本文介绍了基于 LabVIEW 的太阳能光伏发电监控系统的设计与实现&#xff0c;着重探讨了其硬件配置、软件架构以及系统的实现方法。该系统能够有效提高太阳能光伏发电的监控效率和精确性&#xff0c;实现了远程监控和数据管理的智能化。 ​ 项目背景 在当前能源紧张与环境污染…

记录让cursor帮我给ruoyi-vue后台管理项目整合mybatis-plus

自己整合过程中会出现 work.web.exception.GlobalExceptionHandler :100 | 请求地址/admin/device/install/detail/1,发生未知异常. org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.fire.mapper.DeviceInstallMapper.selectById at o…

Prometheus+grafana实践:Doris数据库的监控

文章来源&#xff1a;乐维社区 Doris数据库背景 Doris&#xff08;Apache Doris&#xff09;是一个现代化的MPP&#xff08;Massive Parallel Processing&#xff0c;大规模并行处理&#xff09;数据库&#xff0c;主要用于在线分析处理&#xff08;OLAP&#xff09;场景。 D…

CYT3BB_4BB:Clock system

CYT3BB/4BB的时钟系统包括8-MHz IMO、2个ILO、4个看门狗计时器、4个PLL、一个FLL、5个时钟监控器(CSV)、一个8-33.34MHzECO和一个32.768-kHz WCO。   该时钟系统支持三个主时钟域: CLK_HF、CLK_SLOW和CLK_LF。 - CLK_HFx: CLK_HFx是活动模式的时钟。每个人都可以使用任…

神经网络|(四)概率论基础知识-古典概型

【1】引言 前序学习了线性回归的基础知识&#xff0c;了解到最小二乘法可以做线性回归分析&#xff0c;但为何最小二乘法如此准确&#xff0c;这需要从概率论的角度给出依据。 因此从本文起&#xff0c;需要花一段时间来回顾概率论的基础知识。 【2】古典概型 古典概型是我…

OpenFGA

1.什么是OpenFGA Fine-Grained Authorization 细粒度关系型授权 2.什么是细粒度授权 细粒度授权 (FGA) 意味着能够授予特定用户在特定资源中执行特定操作的权限。 精心设计的 FGA 系统允许您管理数百万个对象和用户的权限。随着系统不断添加对象并更新用户的访问权限&#…

C语言程序设计:算法程序的灵魂

文章目录 C语言程序设计&#xff1a;算法程序的灵魂算法数据结构程序数据结构算法数值运算算法非数值运算算法 简单的算法举例【例2.1】求12345【例2.2】有50个学生&#xff0c;要求输出成绩在80分以上的学生的学号和成绩 简单的算法举例【例2.3】判定2000—2500年中的每一年是…

React和Vue有什么区别,如何选择?

React和Vue有什么区别&#xff0c;如何选择&#xff1f; React 和 Vue 是当前最受欢迎的前端框架之一&#xff0c;两者在开发者中都有极高的声誉。它们都旨在帮助开发人员构建用户界面&#xff0c;但在实现方式和适用场景上有所不同。如果你正考虑在项目中选择 React 或 Vue&a…

寒假1.23

题解 web&#xff1a;[极客大挑战 2019]Secret File&#xff08;文件包含漏洞&#xff09; 打开链接是一个普通的文字界面 查看一下源代码 发现一个链接&#xff0c;点进去看看 再点一次看看&#xff0c;没什么用 仔细看&#xff0c;有一个问题&#xff0c;当点击./action.ph…

从spec到iso的koji使用

了解一下Linux发行版流程&#xff1a;:从spec到iso的koji使用 for Fedora 41。 Fedora 41有24235个包&#xff0c;我们选择 minimal 的几十个源码包&#xff0c;百多个rpm包构建。 配3台服务器 40C64G 48C64G 80C128G&#xff0c;有点大材小用&#xff0c;一台就够了 &#xf…

【游戏设计原理】81 - 功能可见性暗示

一、什么是功能可见性&#xff1f; 功能可见性&#xff08;Affordance&#xff09;是一个设计心理学的概念&#xff0c;指的是物体或界面元素通过其外观或形态向用户传递的功能暗示。换句话说&#xff0c;功能可见性是指一个物体本身所具备的特性&#xff0c;使人能直接感知到…

mathematical-expression 实现 数学表达式解析 Java 篇(最新版本)

mathematical-expression &#xff08;MAE&#xff09; 切换至 中文文档 Community QQ group 访问链接进行交流信息的获取&#xff1a;https://diskmirror.lingyuzhao.top/DiskMirrorBackEnd/FsCrud/downLoad/18/Binary?fileNameArticle/Image/-56202138/1734319937274.jpg…

MVCC底层原理实现

MVCC的实现原理 了解实现原理之前&#xff0c;先理解下面几个组件的内容 1、 当前读和快照读 先普及一下什么是当前读和快照读。 当前读&#xff1a;读取数据的最新版本&#xff0c;并对数据进行加锁。 例如&#xff1a;insert、update、delete、select for update、 sele…

WPF实战案例 | C# WPF实现计算器源码

WPF实战案例 | C# WPF实现计算器源码 一、设计来源计算器应用程序讲解1.1 主界面1.2 计算界面 二、效果和源码2.1 界面设计&#xff08;XAML&#xff09;2.2 代码逻辑&#xff08;C#&#xff09;2.3 实现步骤总结 源码下载更多优质源码分享 作者&#xff1a;xcLeigh 文章地址&a…

vulnhub靶场【kioptrix-3】靶机

前言 靶机&#xff1a;kioptrix-3&#xff0c;IP地址为192.168.1.74 攻击&#xff1a;kali&#xff0c;IP地址为192.168.1.16 都采用虚拟机&#xff0c;网卡为桥接模式 文章中涉及的靶机&#xff0c;来源于vulnhub官网&#xff0c;想要下载&#xff0c;可自行访问官网下载&a…

无人机 PX4 飞控 | PX4源码添加自定义参数方法并用QGC显示与调整

无人机 PX4 飞控 | PX4源码添加自定义参数方法并用QGC显示与调整 0 前言 之前文章添加了一个自定义的模块&#xff0c;本篇文章在之前的自定义模块中&#xff0c;添加两个自定义参数 使用QGC显示出来&#xff0c;并通过QGC调整参数值&#xff0c;代码实现参数更新 新增的参…

【Linux】华为服务器使用U盘安装统信操作系统

目录 一、准备工作 1.1 下载UOS官方系统 &#xff11;.&#xff12;制作启动U盘 1.3 服务器智能管理系统iBMC 二、iBMC设置U盘启动 一、准备工作 1.1 下载UOS官方系统 服务器CPU的架构是x86-64还是aarch64&#xff09;,地址&#xff1a;统信UOS生态社区 - 打造操作系统创…

npm常见报错整理

npm install时报UNMET PEER DEPENDENCY 现象 npm install时报UNMET PEER DEPENDENCY,且执行npm install好几遍仍报这个。 原因 不是真的缺少某个包,而是安装的依赖版本不对,警告你应该安装某一个版本。 真的缺少某个包。 解决 看了下package.json文件,我的react是有的…

在宝塔安装部署mindoc

MinDoc简介 MinDoc 是一款针对IT团队开发的简单好用的文档管理系统。 MinDoc 的前身是 SmartWiki 文档系统。SmartWiki 是基于 PHP 框架 laravel 开发的一款文档管理系统。因 PHP 的部署对普通用户来说太复杂&#xff0c;所以改用 Golang 开发。可以方便用户部署和实用。 开…