• 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国际化参考资料:

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

  • 相关阅读:
    数据表管理admin
    HDU 5057
    HDU 5056
    HDU 6035(树形dp)
    CodeForces 586D
    Codeforces 940D
    CodeForces 820C
    TOJ4114(活用树状数组)
    2017CCPC中南地区赛 H题(最长路)
    CodeForces 544C (Writing Code)(dp,完全背包)
  • 原文地址:https://www.cnblogs.com/xyang0917/p/4172513.html
Copyright © 2020-2023  润新知