閱讀352 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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模板文件和資源文件生成後的存放路徑


相關資源下載地址:

項目源代碼 語言文字Excel模板


android國際化參考資料:

https://blog.csdn.net/xyang81/article/details/8870819

最後更新:2017-04-03 18:51:58

  上一篇:go Google I/O大會:穀歌現在比蘋果酷了?
  下一篇:go Velocity簡單語法及VelocityHelper封裝