Android國際化資源文件自動化生成工具
一、工具起源
如果在做一個產品的過程當中,可能會涉及到多個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國際化參考資料:
https://blog.csdn.net/xyang81/article/details/8870819
最後更新:2017-04-03 18:51:58