一、工具起源
如果在做一个产品的过程当中,可能会涉及到多个apk的开发,而且要求实现多国语言。而这些apk可能会由多人分工共同完成。但如果这样的话,每个人都需要整理各自apk所要显示的文字交给专人来翻译。专人负责收集和翻译文字,翻译完了之后再交给每个开发者。比如这个产品中的所有apk都需要支持10国语言,也就是说每个开发人员,要拿着翻译好的文字,在各自负责的项目中创建这10个语种的资源文件,并且将这10个语种的文字依次放入到不同语种目录下的资源文件中。而且当apk写完后,后续有修改,并在界面上添加了新的文字显示,又需要翻译,并要修改各国语言的资源文件。可想而知这是一件多么烦锁的事情。。。(负责翻译的人要收集所有文字,整理并翻译,开发人拿到翻译后的文字之后,要修改各个语言下的资源文件)!所以为了管理方便,由一个人负责收集所有apk中需要翻译的所有文字,并统一交给负责翻译的人。最后拿到翻译后的文字,用一个工具来统一管理并生成所有apk所需要的各国语言的资源文件,这样是不是很爽呢??那是肯定的。不但能减少团队成员之间的工作量,还能提高工作效率。下面将介绍一下这个工具的使用,希望日后你也遇到类似的项目,对你有所有帮助。
二、国际化实现方式
实现Android国际化,分为两步:
1、在工程的res目录下创建不同国家和语种的资源目录(values或drawable),系统会根据设备当前的语言环境自动选择相应的资源文件。
2、翻译各国语言的文字,放入不同国家和语句的资源目录,即strings.xml或arrays.xml。
三、工具实现原理介绍
1、准备一个存放各个apk各国语言文字的excel模板文件。模板数据格式说明:
1> 每个sheet代表一个apk
2> sheet中的第一列存放strings.xml或arrays.xml文件中的id
3> 第二列存放默认文字(当设备找不到当前语言的文字时,使用默认的)
4> 第三列存放各个国家的语言缩写(列名使用语言缩写,比如中文:cn)
5> sheet命名注意:
a、生成strings.xml文件:直接用模块名即可,如:MusicPlayer
b、生成arrays.xml文件:用模块名+_arrays,如:MusicPlayer_arrays
2、使用Apache的开源框架POI解析excel读取各个apk的语言文字,并通过dom4j生成strings.xml和arrays.xml
四、工具类源代码
1、生成资源文件工具类
package com.i18n.i18nbuilder; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.util.CellRangeAddress; import org.dom4j.Document; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.dom4j.io.OutputFormat; import org.dom4j.io.XMLWriter; import com.mltsagem.i18nbuilder.model.ArrayEntity; import com.mltsagem.i18nbuilder.model.StringEntity; /** * 读取EXCEL语言模板文件,生成各个国家语言的资源文件 * @author yangxin */ public class ResourcesBuilder { public static final String DEFAULT_LANGUAGE_FLAG = "values"; /** * 各个国家的语言 */ public static final String[] LANGUAGE = { DEFAULT_LANGUAGE_FLAG, "en-rGB","de-rDE","fr-rFR","es-rES","it-rIT","pt-rPT","nl-rNL","sv-rSE","no-rNO", "fi-rFI","da-rDK","hu-rHU","pl-rPL","cs-rCZ","tr-rTR","ru-rRU","el-rGR","ro-rRO" }; // 读取需要生成strings.xml的sheet public static final String[] STRINGS_SHEETS = { "MusicPlayer", "VideoPlayer" }; // 读取需要生成arrays.xml的sheet public static final String[] ARRAYS_SHEETS = { "MusicPlayer", // 读取MusicPlayer_arrays sheet中的数据 //"VideoPlayer" }; /** * 资源文件生成的临时目录 */ public static final String I18N_TEMP_DIR = "/tmp/i18n/"; /** * 语言文件夹前缀 */ public static final String RESOURCES_DIR_PREFIX = "values-"; /** * 资源文件名 */ public static final String STRING_RESOURCES_FILE_NAME = "strings.xml"; public static final String ARRAY_RESOURCES_FILE_NAME = "arrays.xml"; public static void main(String[] args) { try { String file = "/Users/yangxin/Desktop/language.xls"; // 清除以前生成的文件和目录 clearDir(new File(I18N_TEMP_DIR)); // 创建语言文件夹 createI18nDir(); // 生成各个模块中各个国家的strings.xml语言资源文件 builderStringResources(new FileInputStream(file)); // 生成各个模块中各个国家的arrays.xml语言资源文件 builderArrayResources(new FileInputStream(file)); System.out.println("全部生成成功:" + I18N_TEMP_DIR); } catch (Exception e) { e.printStackTrace(); } } /** * 创建语言文件夹 */ public static void createI18nDir() { for (int i = 0; i < STRINGS_SHEETS.length; i++) { // 创建模块所对应的目录 File parent = new File(I18N_TEMP_DIR,STRINGS_SHEETS[i]); parent.mkdirs(); // 创建各个国家语言的资源目录 for (int j = 0; j < LANGUAGE.length; j++) { String language = null; if (j == 0) { language = LANGUAGE[j]; } else { language = RESOURCES_DIR_PREFIX + LANGUAGE[j]; } File file = new File(parent,language); if (!file.exists()) { file.mkdirs(); } } } } /** * 生成strings.xml资源文件 */ public static void builderStringResources(InputStream is) throws Exception { HSSFWorkbook book = new HSSFWorkbook(is); for (int i = 0; i < STRINGS_SHEETS.length; i++) { HSSFSheet sheet = book.getSheetAt(book.getSheetIndex(STRINGS_SHEETS[i])); System.out.println("build strings for " + sheet.getSheetName()); int rowNum = sheet.getLastRowNum(); for (int j = 0; j < LANGUAGE.length; j++) { String language = LANGUAGE[j]; ArrayList<StringEntity> stringEntitys = new ArrayList<StringEntity>(); File dir = null; if (DEFAULT_LANGUAGE_FLAG.equals(language)) { // 创建默认语言 dir = new File(I18N_TEMP_DIR + STRINGS_SHEETS[i] + File.separator + language); } else { dir = new File(I18N_TEMP_DIR + STRINGS_SHEETS[i] + File.separator + RESOURCES_DIR_PREFIX + language); } File file = new File(dir,STRING_RESOURCES_FILE_NAME); for (int k = 1; k <= rowNum; k++) { HSSFRow row = sheet.getRow(k); if (row.getLastCellNum() < 1) continue; String resId = row.getCell(0).getStringCellValue().trim(); // resId HSSFCell cell = row.getCell(j+1); String value = null; if (cell != null) { value = cell.getStringCellValue(); // 某一个国家的语言 if (value == null || "".equals(value.trim())) { continue; } StringEntity entity = new StringEntity(resId, value.trim()); stringEntitys.add(entity); } } // 创建资源文件 builderStringResources(stringEntitys,file); } } is.close(); System.out.println("------------------strings.xml资源文件生成成功!------------------"); } private static void builderStringResources(List<StringEntity> stringEntitys,File file) throws Exception { OutputFormat format = OutputFormat.createPrettyPrint(); format.setEncoding("utf-8"); XMLWriter writer = new XMLWriter(new FileOutputStream(file),format); Document document = DocumentHelper.createDocument(); Element root = document.addElement("resources"); for (StringEntity stringEntity : stringEntitys) { Element stringElement = root.addElement("string"); stringElement.addAttribute("name", stringEntity.getResId()); stringElement.setText(stringEntity.getValue()); } writer.write(document); writer.close(); } /** * 生成arrays.xml资源文件 */ public static void builderArrayResources(InputStream is) throws Exception { HSSFWorkbook book = new HSSFWorkbook(is); for (int i = 0; i < ARRAYS_SHEETS.length; i++) { // 功能模块 HSSFSheet sheet = book.getSheetAt(book.getSheetIndex(ARRAYS_SHEETS[i]+"_arrays")); System.out.println("build arrays for " + sheet.getSheetName()); int rowNum = sheet.getNumMergedRegions(); // sheet.getLastRowNum(); for (int j = 0; j < LANGUAGE.length; j++) { // 语言 String language = LANGUAGE[j]; ArrayList<ArrayEntity> arrayEntities = new ArrayList<ArrayEntity>(); File dir = null; if (DEFAULT_LANGUAGE_FLAG.equals(language)) { // 创建默认语言 dir = new File(I18N_TEMP_DIR + ARRAYS_SHEETS[i] + File.separator + language); } else { dir = new File(I18N_TEMP_DIR + ARRAYS_SHEETS[i] + File.separator + RESOURCES_DIR_PREFIX + language); } File file = new File(dir,ARRAY_RESOURCES_FILE_NAME); for (int k = 1; k <= rowNum; k++) { CellRangeAddress range = sheet.getMergedRegion(k-1); int mergedRows = range.getNumberOfCells(); int lastRow = range.getLastRow(); int rowIndex = (lastRow - mergedRows) + 1; String resId = sheet.getRow(rowIndex).getCell(0).getStringCellValue().trim(); // resId ArrayEntity entity = new ArrayEntity(resId); ArrayList<String> items = new ArrayList<String>(); for (int z = rowIndex; z <= lastRow; z++) { HSSFCell cell = sheet.getRow(z).getCell(j+1); String value = getValue(cell); if (value == null || "".equals(value.trim())) { // 如果该语言没有对应的翻译,默认使用英语 HSSFCell defaultCell = sheet.getRow(z).getCell(1); value = getValue(defaultCell); } if ("temp".equalsIgnoreCase(value.trim())) { continue; } items.add(value); } entity.setItems(items); arrayEntities.add(entity); } // 创建资源文件 builderArrayResources(arrayEntities,file); } } System.out.println("------------------arrays.xml资源文件生成成功!------------------"); } /** * 获取单元格的值 * @param cell 单元格 * @return 单元格对应的值 */ private static String getValue(HSSFCell cell) { String value = ""; if (cell != null) { switch (cell.getCellType()) { case Cell.CELL_TYPE_NUMERIC: value = String.valueOf((int)cell.getNumericCellValue()).trim(); break; case Cell.CELL_TYPE_STRING: value = cell.getStringCellValue().trim(); break; case Cell.CELL_TYPE_BOOLEAN: value = String.valueOf(cell.getBooleanCellValue()).trim(); break; default: value = cell.getStringCellValue().trim(); break; } } return value; } private static void builderArrayResources(ArrayList<ArrayEntity> arrayEntities, File file) throws Exception { OutputFormat format = OutputFormat.createPrettyPrint(); format.setEncoding("utf-8"); XMLWriter writer = new XMLWriter(new FileOutputStream(file),format); Document document = DocumentHelper.createDocument(); Element root = document.addElement("resources"); for (ArrayEntity arrayEntity : arrayEntities) { Element arrayElement = root.addElement("string-array"); arrayElement.addAttribute("name", arrayEntity.getName()); List<String> items = arrayEntity.getItems(); for (String item : items) { Element itemElement = arrayElement.addElement("item"); itemElement.setText(item); } } writer.write(document); writer.close(); } /** * 清除以前生成的文件和目录 */ public static void clearDir(File dir) { if (!dir.exists()) return; File[] files = dir.listFiles(); for (File file : files) { if (file.isDirectory()) { clearDir(file); } else { file.delete(); } } dir.delete(); } }
package com.i18n.i18nbuilder.model; import java.util.List; public class ArrayEntity { private String name; private List<String> items; public ArrayEntity() { super(); } public ArrayEntity(String name) { super(); this.name = name; } public ArrayEntity(String name, List<String> items) { super(); this.name = name; this.items = items; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<String> getItems() { return items; } public void setItems(List<String> items) { this.items = items; } }
package com.i18n.i18nbuilder.model; public class StringEntity { private String resId; private String value; public StringEntity() { super(); } public StringEntity(String resId, String value) { super(); this.resId = resId; this.value = value; } public String getResId() { return resId; } public void setResId(String resId) { this.resId = resId; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } }
2、解析资源文件(strings.xml/arrays.xml)中ID和value的工具类(解决手工复制id和value到excel模板中的问题)
package com.i18n.i18nbuilder; import java.io.FileInputStream; import java.io.InputStream; import java.util.List; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; /** * 解析strings.xml和array.xml字符串资源文件,获取资源文件中的键和值 * @author yangxin */ public class ParseStringResources { public static void main(String[] args) { try { getStringsIds(); getArraysIds(); } catch (Exception e) { e.printStackTrace(); } } public static void getStringsIds() throws Exception { System.out.println("-------------------MusicPlayer----------------------------"); ParseStringResources.parseStringsResourcesKey(new FileInputStream("musicplayer_strings.xml")); // 解析id ParseStringResources.parseStringsResourcesValue(new FileInputStream("musicplayer_strings.xml")); // 解析value System.out.println("-------------------VideoPlayer----------------------------"); ParseStringResources.parseStringsResourcesKey(new FileInputStream("video_strings.xml")); ParseStringResources.parseStringsResourcesValue(new FileInputStream("video_strings.xml")); } public static void getArraysIds() throws Exception { InputStream is = null; is = new FileInputStream("musicplayer_arrays.xml"); ParseStringResources.parseArraysResources(is); } /** * 解析strings.xml中的key */ public static void parseStringsResourcesKey(InputStream is) throws Exception { SAXReader saxReader = new SAXReader(); Document document = saxReader.read(is); Element rootElement = document.getRootElement(); List<Element> elements = rootElement.elements(); for (Element element : elements) { String resid = element.attribute("name").getValue(); System.out.println(resid); } System.out.println("-----------------------------------key解析完成---------------------------------"); } /** * 解析strings.xml中的value */ public static void parseStringsResourcesValue(InputStream is) throws Exception { SAXReader saxReader = new SAXReader(); Document document = saxReader.read(is); Element rootElement = document.getRootElement(); List<Element> elements = rootElement.elements(); for (Element element : elements) { String text = element.getTextTrim(); System.out.println(text); } System.out.println("-----------------------------------value解析完成---------------------------------"); } /** * 解析arrays.xml文件 */ public static void parseArraysResources(InputStream is) throws Exception { SAXReader saxReader = new SAXReader(); Document document = saxReader.read(is); Element rootElement = document.getRootElement(); List<Element> elements = rootElement.elements(); for (Element element : elements) { Attribute attribute = element.attribute("name"); if (attribute == null) continue; String resid = attribute.getValue(); System.out.println(resid); List<Element> items = element.elements(); for (Element item : items) { String text = item.getTextTrim(); System.out.println(" " + text); } } } }3、各国语言缩写映射关系
<?xml version="1.0" encoding="UTF-8"?> <date_formats> <date_format> <language>cs</language> <format>dd.M.yyyy</format> <english>Czech</english> <chinese>捷克文</chinese> </date_format> <date_format> <language>da</language> <format>dd-MM-yyyy</format> <english>Deutsch</english> <chinese>丹麦文</chinese> </date_format> <date_format> <language>de</language> <format>dd.MM.yyyy</format> <english>German</english> <chinese>德文</chinese> </date_format> <date_format> <language>en</language> <format>dd/MM/yyyy</format> <english>English</english> <chinese>英文</chinese> </date_format> <date_format> <language>es</language> <format>dd/MM/yyyy</format> <english>Spanish</english> <chinese>西班牙文</chinese> </date_format> <date_format> <language>fr</language> <format>dd/MM/yyyy</format> <english>French</english> <chinese>法文</chinese> </date_format> <date_format> <language>it</language> <format>dd/MM/yyyy</format> <english>Italian</english> <chinese>意大利文</chinese> </date_format> <!--没有--> <date_format> <language>hu</language> <format>yyyy.MM.dd</format> <english>Hungarian</english> <chinese>匈牙利文</chinese> </date_format> <date_format> <language>nl</language> <format>dd-M-yyyy</format> <english>Dutch</english> <chinese>荷兰文</chinese> </date_format> <!-- 没有 android.mk也没有--> <date_format> <language>no</language> <format>dd.MM.yyyy</format> <english>Norwegian</english> <chinese>挪威文</chinese> </date_format> <date_format> <language>pl</language> <format>yyyy-MM-dd</format> <english>Polish</english> <chinese>波兰文</chinese> </date_format> <date_format> <language>pt</language> <format>dd-MM-yyyy</format> <english>Portuguese</english> <chinese>葡萄牙文</chinese> </date_format> <!--没有--> <date_format> <language>ro</language> <format>dd.MM.yyyy</format> <english>Romanian</english> <chinese>罗马尼亚文</chinese> </date_format> <!--没有--> <date_format> <language>fi</language> <format>dd.M.yyyy</format> <english>Finnish</english> <chinese>芬兰文</chinese> </date_format> <date_format> <language>sv</language> <format>yyyy-MM-dd</format> <english>Swedish</english> <chinese>瑞典文</chinese> </date_format> <date_format> <language>tr</language> <format>dd.MM.yyyy</format> <english>Turkish</english> <chinese>土耳其文</chinese> </date_format> <date_format> <language>el</language> <format>dd/M/yyyy</format> <english>Greece</english> <chinese>希腊文</chinese> </date_format> <date_format> <language>ru</language> <format>dd.MM.yyyy</format> <english>Russian</english> <chinese>俄文</chinese> </date_format> <date_format> <language>zh</language> <format>yyyy-M-dd</format> <english>Chinese</english> <chinese>中文</chinese> </date_format> </date_formats>
五、资源文件生成效果
1、控制台打印信息
2、资源文件存放目录及strings.xml内容
工具使用注意事项:
1>、sheet的命名必须和工具类中”STRINGS_SHEETS“和”ARRAYS_SHEETS“数组存储的元素名称保持一致(区分大小写)
2>、删除sheet中的空白单元格
3>、修改languages.xls模板文件和资源文件生成后的存放路径
相关资源下载地址:
android国际化参考资料: