本文是用在window系统下面使用linux的国际化方式,来实现项目的国际化。
因为项目原本没有实现国际化,都是中文的硬编码,到后期的时候,才决定做国际化。
不想去抽取所有硬编码写到properties文件,所以只得考虑用gettext-commons工具来实现。
使用到的工具:
gettext - commons : https://code.google.com/p/gettext-commons/
GetText for Windows:
资源:http://gnuwin32.sourceforge.net/packages/gettext.htm
安装:http://blog.longwin.com.tw/2010/08/windows-gettext-2010/
附件包含所用到的jar
扫描的时候,也可以扫描js等文件,但是要是以单引号标志的字符串,是得不到支持的。
js国际化可以考虑:
1.gettext-js https://code.google.com/p/gettext-js/wiki/MainDocumentation
2.Javascript Gettext http://jsgettext.berlios.de/
大致过程:
-------------------------------------------------------
GNU Gettext - commons 大致过程:
过程:
--->修改java的硬编码
--->msginit得到po文件
--->msgfmt命令得到class文件
--->反编译class
--->替换原有的java文件(所有多语言内容都会放在java的数组里面)
--->运行
gettext-commono整理
步骤:
1.配置gettext-0.14.4-bin,记得dll文件存放到bin下面去,两个dll文件(libexpat.dll,libiconv2.dll)。
――――――――――――――――――――――――――――――――――――――――――――――――――――
2.配置gettext-commons
――――――――――――――――――――――――――――――――――――――――――――――――――――
到https://code.google.com/p/gettext-commons/downloads/list下载gettext-commons-0.9.6.jar,gettext-ant-tasks-0.9.7.jar
gettext-commons-0.9.6.jar:就是国际化的工具,这个放到项目的lib下面去。
gettext-ant-tasks-0.9.7.jar:提供给Ant的来运行msginit,msgfmt命令,这个随便放,但是build.xml的时候会制定它的路径。
src/i18n.properties
说明:在com.i18n包下面,存在Message_zh_CN.class的文件
basename=com.i18n.messages.Messages
#说明class文件要存放在com/i18n/messages下面
3.修改源码:
――――――――――――――――――――――――――――――――――――――――――――――――――――
先创建一个实例:I18n i18n = I18nFactory.getI18n(getClass());
然后用它的tr(string),tr(string,object[])方法来实现国际化。
只注意这里两个方法,其他的类似C语言里面的东西,代码只是测试,不用理会。
第一个方法是没有带参数的国际化方法,第二个是带参数的国际化方法。
public void test(){ I18n i18n = I18nFactory.getI18n(getClass()); I18n i18n = I18nFactory.getI18n(getClass()); System.out.println(i18n.tr("This is a test message1"));//返回翻译 String[] array = new String[]{"Pandy","Brian"}; System.out.println(i18n.tr("这句话是中文原文,第一个参数{0},第二个参数{1}",array));//返回翻译 System.out.println(i18n.trn("This is a test message2","This is a test message2-2",0)); System.out.println(i18n.trc("This is a test message3","this is a default message"));//返回默认 }
4.在ant用gettext-extract命令生成po文件。build.xml文件
――――――――――――――――――――――――――――――――――――――――――――――――――――
<?xml version="1.0"?> <project name="ApplicationDemos" default="generate-default-bundle" basedir="."> <!-- properies --> <property name="src.otherLibs" value="otherLibs" /> <property name="src.src" value="src" /> <property name="src.bin" value="bin" /> <property name="src.poFile" value="Message_zh_CN.po" /> <property name="src.poDirectory" value="src/com/i18n/messages" /> <property name="src.targetBundle" value="com.i18n.messages.Messages_zh_CN" /> <property name="src.poTmpDir" value="po" /> <!-- 定义classpath --> <path id="classpathGettext"> <fileset file="${src.otherLibs}/gettext-ant-tasks-0.9.7.jar" /> </path> <!-- <property name="gettexttasks.jar" value="${src.otherLibs}/gettext-ant-tasks-0.9.3.jar" /> --> <!-- 国际化抽取和实现的任务 --> <!-- 生成Po文件 --> <taskdef name="gettext-extract" classname="org.xnap.commons.ant.gettext.GettextExtractKeysTask" classpathref="classpathGettext" /> <!-- 合并Po文件 --> <taskdef name="gettext-merge" classname="org.xnap.commons.ant.gettext.GettextMergeKeysTask" classpathref="classpathGettext" /> <!-- 生成默认的class文件 --> <taskdef name="gettext-generate-default" classname="org.xnap.commons.ant.gettext.GenerateDefaultBundleTask" classpathref="classpathGettext"/> <!-- 为生成jar方式的国际化文件 --> <taskdef name="gettext-dist" classname="org.xnap.commons.ant.gettext.GettextDistTask" classpathref="classpathGettext"/> <!-- 初始化任务--> <target name="initI18N"> </target> <target name="init.gettext" description="Loads the Ant gettext tasks"> </target> <!-- 生成po文件 --> <target name="messages-extract" description="Extracts message keys from the source code" depends="init.gettext"> <gettext-extract keysFile="${src.poFile}" poDirectory="${src.poDirectory}" encoding="UTF-8"> <fileset dir="src" includes="**/*.java"/> </gettext-extract> </target> <!-- 合并po文件 --> <target name="messages-merge" description="Merges newly extracted messages into existing po files" depends="init.gettext"> <gettext-merge keysFile="${src.poFile}" poDirectory="${src.poDirectory}"/> </target> <!-- 生成默认的class文件 --> <target name="generate-default-bundle" description="Generates a default bundle" depends="init.gettext"> <gettext-generate-default targetBundle="${src.targetBundle}" outputDirectory="${src.poTmpDir}" potfile="${src.poDirectory}/${src.poFile}"/> </target> <!-- 生成默认的jar文件 --> <target name="generate-bundles-jar" description="Generates Java ResourceBundles and jars them up" depends="init.gettext"> <gettext-dist targetBundle="${src.targetBundle}" poDirectory="${src.poTmpDir}" outputDirectory="${src.poTmpDir}" percentage="65"/> <jar destfile="lib/messages.jar" basedir="${src.poTmpDir}" includes="org/**"/> </target> <!-- 编译--> <target name="compile" depends="initI18N" description="compile the source files"> <mkdir dir="${src.bin}" /> <javac srcdir="${src.src}" destdir="${src.bin}" target="6.0" includeantruntime="true"> <classpath refid="classpath" /> </javac> </target> </project>
上面是编译命令,使用messages-extract命令,得到下面的po文件,存放在src/com/i18n/messages/Message_zh_CN.po, 这个路径会在生成class文件的时候用到,
记得修改版本号,不修改也不影响?没试过。
# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2012-08-19 20:00+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: src\com\i18n\I18nTest.java:16 msgid "This is a test message1" msgstr "" #: src\com\i18n\I18nTest.java:18 #, java-format msgid "这句话是中文原文,第一个参数{0},第二个参数{1}" msgstr "" #. 返回翻译 #: src\com\i18n\I18nTest.java:19 msgid "This is a test message2" msgid_plural "This is a test message2-2" msgstr[0] "" msgstr[1] ""
5.对po文件人工翻译。其实,因为源码本来都是硬编码,所以通常只需要直接把每一个msgid的东西,复制给msgstr。就可以了。
――――――――――――――――――――――――――――――――――――――――――――――――――――
"Project-Id-Version: 0.1\n" #: src\com\i18n\I18nTest.java:16 msgid "This is a test message1" msgstr "翻译:第一句翻译" #: src\com\i18n\I18nTest.java:18 #, java-format msgid "这句话是中文原文,第一个参数{0},第二个参数{1}" msgstr "翻译:这句话是中文原文,第一个参数{0},第二个参数{1}" #. 返回翻译 #: src\com\i18n\I18nTest.java:19 msgid "This is a test message2" msgid_plural "This is a test message2-2" msgstr[0] "翻译:参数第一句翻译" msgstr[1] "翻译:参数第二句翻译"
6.生成class文件:
――――――――――――――――――――――――――――――――――――――――――――――――――――
生成class的第一种方式:
msgfmt --java2 -d D:\dev-workspace\workspace\ApplicationDemos\po -r com.i18n.Messages -l zh_CN D:\dev-workspace\workspace\ApplicationDemos\src\com\messages\messages.po
格式化po文件:格式化得到的class文件存放到po文件夹下面,按照com.i18n路径存放为Messages+zh_CN.class,po的存放位置:D:\dev-workspace\workspace\ApplicationDemos\src\com\messages\messages.po
po的路径在上面已经指定了。
生成class的第二种方式(推荐):
=====>可以在Ant下面用命令
<target name="generate-default-bundle" description="Generates a default bundle" depends="init.gettext"> <gettext-generate-default targetBundle="${src.targetBundle}" outputDirectory="${src.poTmpDir}" potfile="${src.poDirectory}/${src.poFile}"/> </target>
7.原来class的java文件内容,要手动建立这个类,然后等待下面的步骤替换:这个在cygwin里面编译看见的java. 应该是一个标准的class模板。当我们用GetText for Windows,然后用ant命令得到class文件的时候,默认会删除java文件,直接给出class文件。要是没有发现cygwin里面的java文件,那么可能要傻傻的去做反编译。它得到两个class文件,其中一个class是另一个class的匿名类。
――――――――――――――――――――――――――――――――――――――――――――――――――――
src/com/i18n/Messages_zh_CN.java
package com.i18n.messages; import java.util.Enumeration; import java.util.MissingResourceException; import java.util.ResourceBundle; public class Messages_zh_CN extends ResourceBundle { private static final String[] table;//要注意的地方:当没有参数的国际化的时候,这里是String[]类型,否则是Object[]类型。 public Object handleGetObject(String paramString) throws MissingResourceException { int i = paramString.hashCode() & 0x7FFFFFFF; int j = i % 1 << 1;//要注意的地方:中间的那个数字,要被替换的,因为它跟国际化信息的记录数有关 String str = table[j]; if ((str != null) && (paramString.equals(str))) return table[(j + 1)]; return null; } public Enumeration getKeys() { return new java.util.Enumeration() {//要注意的地方:这里就是生成的一个class匿名类的代码,只要注意,但不被替换。 private int idx = 0; { while (idx < 2 && table[idx] == null) idx += 2; } public boolean hasMoreElements() { return (idx < 2); } public java.lang.Object nextElement() { java.lang.Object key = table[idx]; do idx += 2; while (idx < 2 && table[idx] == null); return key; } }; } public ResourceBundle getParent() { return this.parent; } //要注意的地方:这里就是国际化的信息,会保存到一个数组,而不是properties,这里就是特别的地方 static { String[] arrayOfString = new String[2]; arrayOfString[0] = ""; arrayOfString[1] = "Project-Id-Version: 0.01\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-08-17 14:38+0800\nPO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\nLast-Translator: FULL NAME <EMAIL@ADDRESS>\nLanguage-Team: LANGUAGE <LL@li.org>\nLanguage: \nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\n"; table = arrayOfString; } }
8.反编译得到的java去替换上面的java模板。
到ApplicationDemos\po\com\i18n去看到Messages_zh_CN.class,Messages_zh_CN$1.class两个class文件,后面的那个class其实是前面class的一个内部类。
――――――――――――――――――――――――――――――――――――――――――――――――――――
反编译:Messages_zh_CN.class:
package com.i18n.messages; import java.util.Enumeration; import java.util.MissingResourceException; import java.util.ResourceBundle; public class Messages_zh_CN extends ResourceBundle { private static final Object[] table; //拿这里去java模板替换 public static final String[] get_msgid_plural_table() { return new String[] { "This is a test message2-2" }; } public Object lookup(String paramString) { int i = paramString.hashCode() & 0x7FFFFFFF; int j = i % 4 << 1; //拿这里去java模板替换 Object localObject = table[j]; if ((localObject != null) && (paramString.equals(localObject))) return table[(j + 1)]; return null; } public Object handleGetObject(String paramString) throws MissingResourceException { Object localObject = lookup(paramString); return (localObject instanceof String[]) ? ((String[])(String[])localObject)[0] : localObject; } public Enumeration getKeys() { return new Object() { private int idx; public boolean hasMoreElements() { return this.idx < 22; } public Object nextElement() { Object localObject = Messages_zh_CN.table[this.idx]; do this.idx += 2; while ((this.idx < 22) && (Messages_zh_CN.table[this.idx] == null)); return localObject; } } ; } public static long pluralEval(long paramLong) { return paramLong != 1L ? 1 : 0; } public ResourceBundle getParent() { return this.parent; } //拿这里去java模板替换 static { Object[] arrayOfObject = new Object[8]; arrayOfObject[0] = ""; arrayOfObject[1] = "Project-Id-Version: PACKAGE VERSION\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-08-19 20:00+0800\nPO-Revision-Date: 2012-08-19 20:00+0800\nLast-Translator: Automatically generated\nLanguage-Team: none\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"; arrayOfObject[2] = "This is a test message1"; arrayOfObject[3] = "翻译:第一句翻译"; arrayOfObject[4] = "This is a test message2"; arrayOfObject[5] = { "翻译:参数第一句翻译", "翻译:参数第二句翻译" };//这里是反编译得到的,要进行修改,否则出错。 arrayOfObject[6] = "这句话是中文原文,第一个参数{0},第二个参数{1}"; arrayOfObject[7] = "翻译:这句话是中文原文,第一个参数{0},第二个参数{1}"; table = arrayOfObject; } }
反编译:Messages_zh_CN$1.class:这个不需要什么特别注意的地方
package com.i18n.messages; import java.util.Enumeration; class Messages_zh_CN$1 implements Enumeration { private int idx; public boolean hasMoreElements() { return this.idx < 22; } public Object nextElement() { Object localObject = Messages_zh_CN.access$000()[this.idx]; do this.idx += 2; while ((this.idx < 22) && (Messages_zh_CN.access$000()[this.idx] == null)); return localObject; } }
可以看见Messages_zh_CN$1.class其实就是Messages_zh_CN.java的一部分。
9.替换java内容,Messages_zh_CN.class反编译得到的,去替换原来java的内容:
――――――――――――――――――――――――――――――――――――――――――――――――――――
package com.i18n.messages; import java.util.Enumeration; import java.util.MissingResourceException; import java.util.ResourceBundle; public class Messages_zh_CN extends ResourceBundle { //private static final String[] table; private static final Object[] table;//这里原来是String[],但是看Po文件,因为数组里面的元素,有存在一个数组的元素,所以这里变成Object[] public Object handleGetObject(String paramString) throws MissingResourceException { System.out.println("Messages_zh_CN_______________________1"); int i = paramString.hashCode() & 0x7FFFFFFF; int j = i % 11 << 1;//这里要注意:当翻译的数据变化的时候,i % 11 << 1 中间的11这个数组也会跟着变化,才能在数组里面找到真正的信息 Object localObject = table[j]; if ((localObject != null) && (paramString.equals(localObject))) return table[(j + 1)]; return null; } public Enumeration getKeys() { System.out.println("Messages_zh_CN_______________________2"); return new java.util.Enumeration() { private int idx = 0; { while (idx < 2 && table[idx] == null) idx += 2; } public boolean hasMoreElements() { return (idx < 2); } public java.lang.Object nextElement() { java.lang.Object key = table[idx]; do idx += 2; while (idx < 2 && table[idx] == null); return key; } }; } public ResourceBundle getParent() { System.out.println("Messages_zh_CN_______________________3"); return this.parent; } static { System.out.println("Messages_zh_CN_______________________0"); //下面其实就是po做msgfmt得到的信息,存放在一个数组里面,注意:第二个元素没有修改版本号,也没有设定编码类型,这个可能引起其他问题么?待解..... Object[] arrayOfObject = new Object[8]; arrayOfObject[0] = ""; arrayOfObject[1] = "Project-Id-Version: PACKAGE VERSION\nReport-Msgid-Bugs-To: \nPOT-Creation-Date: 2012-08-19 20:00+0800\nPO-Revision-Date: 2012-08-19 20:00+0800\nLast-Translator: Automatically generated\nLanguage-Team: none\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit\nPlural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"; arrayOfObject[2] = "This is a test message1"; arrayOfObject[3] = "翻译:第一句翻译"; arrayOfObject[4] = "This is a test message2"; //arrayOfObject[5] = { "翻译:参数第一句翻译", "翻译:参数第二句翻译" };//这里是反编译得到的,要进行修改,否则出错。 arrayOfObject[5] = new String[]{ "翻译:参数第一句翻译", "翻译:参数第二句翻译" };//修改,反编译的错误 arrayOfObject[6] = "这句话是中文原文,第一个参数{0},第二个参数{1}"; arrayOfObject[7] = "翻译:这句话是中文原文,第一个参数{0},第二个参数{1}"; table = arrayOfObject; } }
10.运行src/com/i18n/I18nTest.java类 查看国际化信息
――――――――――――――――――――――――――――――――――――――――――――――――――――
I18n i18n = I18nFactory.getI18n(getClass()); System.out.println(i18n.tr("This is a test message1"));//返回翻译 String[] array = new String[]{"Pandy","Brian"}; System.out.println(i18n.tr("这句话是中文原文,第一个参数{0},第二个参数{1}",array));//返回翻译 System.out.println(i18n.trn("This is a test message2","This is a test message2-2",0)); System.out.println(i18n.trc("This is a test message3","this is a default message"));//返回默认
最后输出:
=====================================>
翻译:第一句翻译
翻译:这句话是中文原文,第一个参数Pandy,第二个参数Brian
This is a test message2-2
this is a default message
这只是实现过程的记录,还没有完整整理,特别注意的地方是反编译后替换,这里最好写程序来实现编译后替换,就安全一点,不容易错误。