Java 本身已经可以很好地国际化(I18N),SUN 网站上也有关于国际化的[教程]和[文档],其中也包含了如何进行本地化(L10N)的过程。这里所谈的本地化指的是显示出来的字符串的本地化,不涉及数字、货币、日期时间等。Java 本身是如何进行本地化的呢?看一个简单的例子:
-
I18NExample.java:
-
-
package org.supermmx.example.i18n;
-
-
import java.util.Locale;
-
import java.util.ResourceBundle;
-
import java.awt.BorderLayout;
-
import javax.swing.JFrame;
-
import javax.swing.JButton;
-
-
public class I18NExample extends JFrame {
-
-
public I18NExample(String[] args) {
-
-
String language;
-
String country;
-
-
if (args.length != 2) {
-
language = new String("en");
-
country = new String("US");
-
} else {
-
language = new String(args[0]);
-
country = new String(args[1]);
-
}
-
-
Locale currentLocale = new Locale(language, country);
-
-
ResourceBundle bundle = ResourceBundle.getBundle("i18n",
-
currentLocale);
-
-
setDefaultCloseOperation(EXIT_ON_CLOSE);
-
-
setTitle(bundle.getString("title"));
-
JButton helloButton = new JButton(bundle.getString("greetings"));
-
JButton byeButton = new JButton(bundle.getString("farewell"));
-
-
getContentPane().add(helloButton, BorderLayout.CENTER);
-
getContentPane().add(byeButton, BorderLayout.SOUTH);
-
-
setSize(200, 150);
-
setVisible(true);
-
}
-
-
public static void main(String[] args) {
-
new I18NExample(args);
-
}
-
}
-
native2ascii.exe -encoding UTF-8 i18n_zh_CN.properties.orig > i18n_zh_CN.properties
这里用 UTF-8 编码,你可以选择其他编码,比如 GB2312 或者 GBK。
然后编译运行:
-
javac -d . I18NExample.java
-
java -classpath . org.supermmx.example.i18n.I18NExample zh CN
结果如图所示:
我能看到的唯一好处就是不用修改代码就可以修改显示出来的信息,但是在软件开发过程中,只修改显示信息而再次发布的次数会有多少呢?但带来的问题可能更多:
- 开发者不能直接在代码中看到应该出现的字符串是什么样,不管是英文的还是其他语言的,得去英文的 properties 文件去看看才知道。
- 开发者需要手动作很多事情,每一个字符串都需要定义一个键(Key),这样在程序中需要引用的键也就很多,很容易拼写错误、不容易改名等等不方便的地方。
- 翻译者没有很好的工具跟踪信息修改,比如两次发布之间有哪些信息增加、改动或者删除了,只能依靠英文 properties 文件的 diff 来做,很难保持同步。似乎没有看见这样的工具。
- properties 文件必须使用 ISO-8859-1 编码,所以对于大多数语言来说,必须要
native2ascii 将翻译语言本身的编码转换成 Unicode 转义代码才能以 ISO-8859-1 编码存储。这对翻译者要求有些高。
对于第 4 条来说,问题不大,只要维护一份源编码的,在编译项目的过程中加一步进行自动进行转换,再把转换出来的资源文件打包。翻译者只需要提交原始的翻译即可。
Linux 上基本上都是使用 [GNU gettext] 来进行本地化。它的基本思想就是在源代码中用某种方式标记出要本地化的字符串,当然一般情况下都是英文的字符串,然后根据这些标记把字符串提取出来,以某种方式(.POT)存下来,然后本地化人员可以对照这些字符串直接进行翻译(.PO),再转换二进制形式(.MO)。运行时根据运行的区域(locale)信息读取合适语言的相关字符串然后进行处理或者显示出来。这一套完整的流程是针对 C 程序的,这里不做具体解释。它也可以支持其他很多种语言,也包括 Java,但并不完整,需要再额外作点工作,和语言本身的国际化支持要结合起来使用。下面我们把上面的例子改写一下:
-
GettextI18NExample:
-
-
package org.supermmx.example.i18n;
-
-
import static org.supermmx.example.i18n.GettextI18NUtils._;
-
-
import java.util.Locale;
-
import java.util.ResourceBundle;
-
import java.awt.BorderLayout;
-
import javax.swing.JFrame;
-
import javax.swing.JButton;
-
-
/**
-
* Gettext I18N Example
-
*
-
* @author SuperMMX
-
*/
-
public class GettextI18NExample extends JFrame {
-
-
public GettextI18NExample(String[] args) {
-
-
String language;
-
String country;
-
-
if (args.length != 2) {
-
language = new String("en");
-
country = new String("US");
-
} else {
-
language = new String(args[0]);
-
country = new String(args[1]);
-
}
-
-
Locale currentLocale = new Locale(language, country);
-
-
Locale.setDefault(currentLocale);
-
-
setDefaultCloseOperation(EXIT_ON_CLOSE);
-
-
setTitle(_("I18N Example"));
-
JButton helloButton = new JButton(_("Hello"));
-
JButton byeButton = new JButton(_("Goodbye"));
-
-
getContentPane().add(helloButton, BorderLayout.CENTER);
-
getContentPane().add(byeButton, BorderLayout.SOUTH);
-
-
setSize(200, 150);
-
setVisible(true);
-
}
-
-
public static void main(String[] args) {
-
new GettextI18NExample(args);
-
}
-
}
-
-
GettextI18NUtils.java:
-
-
package org.supermmx.example.i18n;
-
-
import java.util.Locale;
-
import java.util.ResourceBundle;
-
-
/**
-
* Gettext I18N Example
-
*
-
* @author SuperMMX
-
*/
-
class GettextI18NUtils {
-
private static ResourceBundle bundle =
-
ResourceBundle.getBundle("gettext_i18n");
-
-
public static String _(String s) {
-
return bundle.containsKey(s) ? bundle.getString(s) : s;
-
}
-
}
-
# 提取需要翻译的字符串
-
xgettext -k_ -o gettext_i18n.pot -L java GettextI18NExample.java
-
-
# 为某种语言产生翻译文件
-
# 第一次使用 msginit
-
msginit -i gettext_i18n.pot -l zh_CN -o gettext_i18n_zh_CN.po
-
# 以后使用 msgmerge
-
msgmerge -U gettext_i18n_zh_CN.po gettext_i18n.pot
-
-
# 产生 Java 资源文件
-
msgcat -p -o gettext_i18n_zh_CN.properties gettext_i18n_zh_CN.po
所产生的 .pot 和 .po 文件的具体格式可以查看 GNU Gettext 的手册。最后生成的 .properties 资源文件以原本的字符串作为键,值是 \uXXXX 形式的语言内容。
-
javac -d . GettextI18NExample.java
-
java -classpath . org.supermmx.example.i18n.GettextI18NExample zh CN
结果如图所示:
那使用 GNU gettext 有什么好处呢?
- 开发者可以在源码中直接写出需要的字符串,不需要在 properties 文件中写一次,再用一个 KEY 去引用。
- 开发者很容易修改字符串,也不会因为 KEY 拼写错误而在运行时找不到资源,避免很多不必要的错误。
- 每次发布之前,开发者或者翻译者自己可以运行
xgettext 和 msgmerge 将新的字符串合并到各语言的 PO 文件中,翻译者就可以直接对原有的翻译进行更新。
- 已经有很多
PO 的编辑工具或者在线翻译工具,大大减轻了翻译者的工作,增强社区翻译的能力。
上面使用的都是 properties 文件作为资源,GNU gettext 也可以支持扩展 java.util.ResourceBundle 的类资源,如下所示:
-
msgfmt --java2 -r gettext_i18n -d . -l zh_CN gettext_i18n_zh_CN.po
这样它会生成 gettext_i18n_zh_CN.class,具有同样的效果。
[Gettext Commons] 跟上面用到的方法类似,但提供了更好的[复数]支持,不过这就必须使用 msgfmt 生成类形式的 ResourceBundle。我们把程序再改写一下:
-
package org.supermmx.example.i18n;
-
-
import org.xnap.commons.i18n.I18nFactory;
-
import org.xnap.commons.i18n.I18n;
-
-
import java.util.Locale;
-
import java.util.ResourceBundle;
-
import java.awt.BorderLayout;
-
import javax.swing.JFrame;
-
import javax.swing.JButton;
-
-
/**
-
* Gettext I18N Example
-
*
-
* @author SuperMMX
-
*/
-
public class GettextCommonsI18NExample extends JFrame {
-
-
private I18n i18n;
-
-
public GettextCommonsI18NExample(String[] args) {
-
-
String language;
-
String country;
-
-
if (args.length != 2) {
-
language = new String("en");
-
country = new String("US");
-
} else {
-
language = new String(args[0]);
-
country = new String(args[1]);
-
}
-
-
Locale currentLocale = new Locale(language, country);
-
-
i18n = I18nFactory.getI18n(this.getClass(), "gettext_commons_i18n", currentLocale);
-
-
setDefaultCloseOperation(EXIT_ON_CLOSE);
-
-
setTitle(i18n.tr("I18N Example - Gettext Commons"));
-
-
JButton helloButton = new JButton(i18n.tr("Hello"));
-
JButton byeButton = new JButton(i18n.tr("Goodbye"));
-
-
getContentPane().add(helloButton, BorderLayout.CENTER);
-
getContentPane().add(byeButton, BorderLayout.SOUTH);
-
-
setSize(300, 150);
-
setVisible(true);
-
}
-
-
public static void main(String[] args) {
-
new GettextCommonsI18NExample(args);
-
}
-
}
-
# 提取
-
xgettext -ktrc -ktr -kmarktr -ktrn:1,2 -L java -o gettext_commons_i18n.pot GettextCommonsI18NExample.java
-
-
# 翻译文件
-
msginit -i gettext_commons_i18n.pot -l zh_CN -o gettext_commons_i18n_zh_CN.po
-
msgmerge -U gettext_commons_i18n_zh_CN.po gettext_commons_i18n.pot
-
-
# 产生 .properties
-
msgcat -p -o gettext_commons_i18n_zh_CN.properties gettext_commons_i18n_zh_CN.po
-
javac -classpath .:gettext-commons-0.9.1.jar -d . GettextCommonsI18NExample.java
-
java -classpath .:gettext-commons-0.9.1.jar org.supermmx.example.i18n.GettextCommonsI18NExample zh CN
结果如图所示:
Gettext Commons 还带了 [Maven] plugin 和 [Ant] task,可以在 Maven 和 Ant 中直接使用,而不用象上面一样用命令行。
本文所附带的例子经整理以后,可以直接下载:[i18n.tar.gz]
Post new comment