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

        在做 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.mzph.cn/diannao/68143.shtml

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

相关文章

最新最详细的配置Node.js环境教程

配置Node.js环境 一、前言 &#xff08;一&#xff09;为什么要配置Node.js&#xff1f;&#xff08;二&#xff09;NPM生态是什么&#xff08;三&#xff09;Node和NPM的区别 二、如何配置Node.js环境 第一步、安装环境第二步、安装步骤第三步、验证安装第四步、修改全局模块…

Greenplum临时表未清除导致库龄过高处理

1.问题 Greenplum集群segment后台日志报错 2.回收库龄 master上执行 vacuumdb -F -d cxy vacuumdb -F -d template1 vacuumdb -F -d rptdb 3.回收完成后检查 仍然发现segment还是有库龄报警警告信息发出 4.检查 4.1 在master上检查库年龄 SELECT datname, datfrozen…

CAPL自动化测试

CAPL自动化测试 目录 CAPL自动化测试1. 引言2. 测试用例设计与实现2.1 测试用例设计2.2 测试用例实现3. 测试报告生成与分析3.1 测试报告生成3.2 测试报告分析4. 自动化测试框架搭建4.1 自动化测试框架设计4.2 自动化测试框架实现5. 案例说明5.1 案例1:测试用例设计与实现5.2 …

【go语言】map 和 list

一、map map 是一种无序的键值对的集合。 无序 &#xff1a;map[key]键值对&#xff1a;key - value map 最重要的一点是通过 key 来快速检索数据&#xff0c;key 类似于索引&#xff0c;指向数据的值。map 是一种集合&#xff0c;所以我们可以像迭代数组和切片那样迭代他。…

Python自动化运维:一键掌控服务器的高效之道

《Python OpenCV从菜鸟到高手》带你进入图像处理与计算机视觉的大门! 解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 在互联网和云计算高速发展的今天,服务器数量的指数增长使得手动运维和管理变得异常繁琐。Python凭借其强大的可读性和丰富的生态系统,成为…

数据融合的经典模型:早期融合、中期融合与后期融合的对比

在多模态数据融合中&#xff0c;如何将不同模态&#xff08;如图像、文本、语音等&#xff09;的数据整合到一个统一的表示中&#xff0c;是至关重要的环节。不同的任务需求和数据特点决定了我们应该采用哪种融合策略&#xff0c;而早期融合、中期融合和后期融合是多模态数据处…

使用python调用JIRA6 进行OAuth1认证获取AccessToken

Jira配置应用程序链接 1) 创建应用程序链接 登录 JIRA 管理后台。转到 Administration > Applications > Application Links。在输入框中输入外部应用程序的 URL&#xff08;例如 GitLab 或自定义应用&#xff09;&#xff0c;然后点击 Create new link。 2) 配置 Con…

WPF基础 | WPF 基础概念全解析:布局、控件与事件

WPF基础 | WPF 基础概念全解析&#xff1a;布局、控件与事件 一、前言二、WPF 布局系统2.1 布局的重要性与基本原理2.2 常见布局面板2.3 布局的测量与排列过程 三、WPF 控件3.1 控件概述与分类3.2 常见控件的属性、方法与事件3.3 自定义控件 四、WPF 事件4.1 路由事件概述4.2 事…

2K高刷电竞显示器推荐

2K高刷电竞显示器推荐&#xff0c;各位喜欢打游戏&#xff0c;身为电竞迷的小伙伴&#xff0c;如果你想选一款2K高刷电竞显示器&#xff0c;那么下面的内容不容错过。 1.HKC G27H4Pro - 2K高刷电竞显示器推荐 外观 - HKC G27H4Pro 2K高刷电竞显示器 初见 HKC G27H4Pro&#x…

【29】Word:李楠-学术期刊❗

目录 题目​ NO1.2.3.4.5 NO6.7.8 NO9.10.11 NO12.13.14.15 NO16 题目 NO1.2.3.4.5 另存为手动/F12Fn光标来到开头位置处→插入→封面→选择花丝→根据样例图片&#xff0c;对应位置填入对应文字 (手动调整即可&#xff09;复制样式&#xff1a;开始→样式对话框→管理…

破解浏览器渲染“死锁”:CSS与JS如何影响页面加载速度?

破解浏览器渲染“死锁”&#xff1a;CSS与JS如何影响页面加载速度&#xff1f; 在这个快速发展的Web世界里&#xff0c;性能是开发者们永恒的追求。当你打开一个网页&#xff0c;可能会注意到一些页面加载特别慢&#xff0c;甚至产生短暂的“白屏”&#xff0c;你有没有想过&a…

1.CSS的三大特性

css有三个非常重要的三个特性&#xff1a;层叠性、继承性、优先级 1.1 层叠性 想通选择器给设置想听的样式&#xff0c;此时一个样式就会覆盖&#xff08;层叠&#xff09;另一个冲突的样式。层叠性主要是解决样式冲突的问题。 <!DOCTYPE html> <html lang"en&…

随机矩阵投影长度保持引理及其证明

原论文中的引理 2 \textbf{2} 2 1. \textbf{1. } 1. 引理 1 \textbf{1} 1(前提之一) 1.1. \textbf{1.1. } 1.1. 引理 1 \textbf{1} 1的内容 &#x1f449;前提&#xff1a; X ∼ N ( 0 , σ ) X\sim{}N(0,\sigma) X∼N(0,σ)即 f ( x ) 1 2 π σ e – x 2 2 σ 2 f(x)\text{}…

C语言-构造数据类型

1、构造数据类型 结构体、共用体、枚举。 2、结构体 1、结构体的定义 结构体是一个自定义的复合数据类型&#xff0c;它允许将不同类型的数据组合在一起。 struct 结构体名 {数据类型1 成员变量1;数据类型2 成员变量2;数据类型3 成员变量3;数据类型4 成员变量4; } 2、结构体变…

Effective C++ 规则48: 认识 Template 元编程

1、什么是模板元编程 模板元编程是指利用 C 的模板机制&#xff0c;在编译期执行某些逻辑运算或代码生成的技术。通过模板元编程&#xff0c;可以在编译阶段完成类型推导、条件分支、递归计算等任务。C 语言的模板功能不仅仅是为了解决类型参数化的问题&#xff0c;它还可以在…

CSS:跑马灯

<div class"swiper-container"><div class"swiper-wrapper"><!-- 第一组 --><div class"item" v-for"item in cardList" :key"first-item.id"><img :src"item.image" alt""…

99.16 金融难点通俗解释:营业总收入

目录 0. 承前1. 简述2. 比喻&#xff1a;小明家的小卖部2.1 第一步&#xff1a;了解小卖部的收入来源2.2 第二步&#xff1a;计算一天的收入2.3 第三步&#xff1a;理解营业总收入 3. 生活中的例子3.1 小卖部的一周营业3.2 不同季节的变化 4. 小朋友要注意4.1 营业总收入不等于…

MyBatis Plus 的 InnerInterceptor:更轻量级的 SQL 拦截器

在 Spring Boot 项目中使用 MyBatis Plus 时&#xff0c;你可能会遇到 InnerInterceptor 这个概念。 InnerInterceptor 是 MyBatis Plus 提供的一种轻量级 SQL 拦截器&#xff0c;它与传统的 MyBatis 拦截器&#xff08;Interceptor&#xff09;有所不同&#xff0c;具有更简单…

CLOUDFLARE代理请求重定向你太多次

现象 使用CLOUDFLARE代理前请求正常&#xff0c;使用CLOUDFLARE代理请求后出现 原因分析 以下是我的猜测&#xff0c;在默认情况下 CLOUDFLARE代理&#xff0c;可能是直接请求我们服务器的IP&#xff0c;比如&#xff1a;http://1.1.1.1 而不是通过域名的方式&#xff08;如…

大模型开发 | RAG在实际开发中可能遇到的坑

近年来&#xff0c;大语言模型 (LLM) 的飞速发展令人瞩目&#xff0c;它们在各个领域展现出强大的应用潜力。然而&#xff0c;LLM 也存在一些固有的局限性&#xff0c;例如知识更新滞后、信息编造 (幻觉) 等问题。为了克服这些挑战&#xff0c;检索增强生成 (Retrieval-Augment…