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

本文是用在window系统下面使用linux的国际化方式,来实现项目的国际化。
因为项目原本没有实现国际化,都是中文的硬编码,到后期的时候,才决定做国际化。
不想去抽取所有硬编码写到properties文件,所以只得考虑用gettext-commons工具来实现。
使用到的工具:
gettext - commons : [url]https://code.google.com/p/gettext-commons/[/url]
GetText for Windows:
资源:[url]http://gnuwin32.sourceforge.net/packages/gettext.htm[/url]
安装:[url]http://blog.longwin.com.tw/2010/08/windows-gettext-2010/[/url]

[b][color=red]附件包含所用到的jar[/color][/b]
扫描的时候,也可以扫描js等文件,但是要是以单引号标志的字符串,是得不到支持的。
js国际化可以考虑:
1.gettext-js [url]https://code.google.com/p/gettext-js/wiki/MainDocumentation[/url]
2.Javascript Gettext [url]http://jsgettext.berlios.de/[/url]


大致过程:
-------------------------------------------------------
GNU Gettext - commons 大致过程:
过程:
--->修改java的硬编码
--->msginit得到po文件
--->msgfmt命令得到class文件
--->反编译class
--->替换原有的java文件(所有多语言内容都会放在java的数组里面)
--->运行


gettext-commono整理

步骤:
[b]1.配置gettext-0.14.4-bin,记得dll文件存放到bin下面去,两个dll文件(libexpat.dll,libiconv2.dll)。[/b]
————————————————————————————————————————————————————

[b]2.配置gettext-commons[/b]
————————————————————————————————————————————————————
到[url]https://code.google.com/p/gettext-commons/downloads/list[/url]下载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下面

[b]3.修改源码:[/b]
————————————————————————————————————————————————————
先创建一个实例: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"));//返回默认
}



[b]4.在ant用gettext-extract命令生成po文件。build.xml文件[/b]
————————————————————————————————————————————————————
<?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] ""



[b]5.对po文件人工翻译。其实,因为源码本来都是硬编码,所以通常只需要直接把每一个msgid的东西,复制给msgstr。就可以了。[/b]
————————————————————————————————————————————————————
"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] "翻译:参数第二句翻译"



[b]6.生成class文件:[/b]
————————————————————————————————————————————————————
生成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>


[b]7.原来class的java文件内容[/b],要手动建立这个类,然后等待下面的步骤替换:这个在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;
}
}



[b]8.反编译得到的java去替换上面的java模板。[/b]
到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的一部分。


[b]9.替换java内容,Messages_zh_CN.class反编译得到的,去替换原来java的内容:[/b]
————————————————————————————————————————————————————
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;
}
}



[b]10.运行src/com/i18n/I18nTest.java类 查看国际化信息[/b]
————————————————————————————————————————————————————
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


这只是实现过程的记录,还没有完整整理,特别注意的地方是反编译后替换,这里最好写程序来实现编译后替换,就安全一点,不容易错误。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值