当前位置: 代码迷 >> Web前端 >> Java Web Application 另类的国际化形式gettext - commons for Java
  详细解决方案

Java Web Application 另类的国际化形式gettext - commons for Java

热度:698   发布时间:2012-10-25 10:58:57.0
Java Web Application 另类的国际化方式gettext - commons for Java
本文是用在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


这只是实现过程的记录,还没有完整整理,特别注意的地方是反编译后替换,这里最好写程序来实现编译后替换,就安全一点,不容易错误。
  相关解决方案