在 Java 中最常见的实现国际化(i18n)的方式应该是使用 .properties 资源文件。本文将使用另一种方式实现 Java 代码的国际化与本地化(l10n)。
Gettext 是一个可用于多种语言的国际化工具。相比于传统的 key – value 资源文件方式,其不同点有:
使用 properties 的传统方式 | GNU gettext |
---|---|
key 是短文本 | key 是纯文本形式的未翻译内容 |
翻译文件通常命名为 resources_locale.properties , 文件内容只能包含 ASCII 文本,非 ASCII 文字会被用 Unicode 编码代替 |
翻译文件通常命名为resources.local.po ,文件内容可以使用任意编码可以包含任意字符 |
ResourceBundle.getString 会在找不到翻译内容时抛出异常 |
gettext 当找不到翻译内容时会返回 key. |
不支持单复数及上下文翻译 | 支持单复数和上下文相关的翻译 |
下面,将通过一个示例来演示如何在 Java 中使用 Gettext 实现国际化。
一般步骤:
- 相关工具下载。
- 引入依赖,编写工具类。
- 在需要翻译的地方调用相关翻译方法。
- 抽取待翻译内容。
- 翻译。
- 生成翻译后资源文件。
- 运行。
相关工具下载。
在 Gettext 主页上可以找到其源代码,*nix 通常自带了相关工具,不需另行下载。当然,也可以在主页上找到用于 Windows 的工具包:https://mlocati.github.io/articles/gettext-iconv-windows.html
我下载的是 64 位绿色版: gettext0.19.8.1-iconv1.14-static-64.zip 解压后建议添加其下 bin 目录到 Path 中,方便后续输入命令。
引入依赖,编写工具类。
首先随便新建一个工程,这里使用的是 Maven 空项目。添加 libintl
的依赖。
如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.youthlin.demo</groupId> <artifactId>I18N</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/org.gnu.gettext/libintl --> <dependency> <groupId>org.gnu.gettext</groupId> <artifactId>libintl</artifactId> <version>0.18.3</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <!-- 指定源码级别,防止每次编辑pom文件后IDEA都会认为源码级别为1.5 --> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> </plugins> </build> </project>
如果你不是使用 Maven, 那么可以搜索下载这个 jar 包。
编写翻译工具类:
package com.youthlin.demo.i18n.util; import gnu.gettext.GettextResource; import java.util.ResourceBundle; /** * Created by lin on 2016-09-20-020. * Translation * 抽象类可以保证不会生成该类的实例,因为我们只需使用静态方法,不需生成对象。 * 参考:http://www.gted.org/ */ public abstract class Translation { private static ResourceBundle catalog = ResourceBundle.getBundle("Messages"); public static String __(String msgid) { return GettextResource.gettext(catalog, msgid); } public static String _x(String msgid, String msgctxt) { return GettextResource.pgettext(catalog, msgctxt, msgid); } public static String _n(String msgid, String msgid_plural, long n) { return GettextResource.ngettext(catalog, msgid, msgid_plural, n); } public static String _nx(String msgid, String plural, long n, String context) { return GettextResource.npgettext(catalog, context, msgid, plural, n); } public static ResourceBundle getCatalog() { return catalog; } public static void setCatalog(ResourceBundle catalog) { Translation.catalog = catalog; } }
这里,我使用的是 WordPress 中的国际化函数名称,实际上 gettext 习惯上也是使用下划线作为其别名的,因为文字输出相对频繁,因此使用简短的名称。
__(msgid)
用于普通翻译。_x(msgid,context)
用于上下文翻译。如,post 一词,即可表示“文章”,也可表示“发表”,要区分则使用一个上下文字符串标识到底是哪一个意思。_n(msgid,msg_plural,n)
用于单复数翻译。当 n 为单数时返回 msgid 的翻译,当 n 为复数时,返回 msg_plural 的翻译。_nx(msgid,msg_plural,n,context)
就是上下文相关单复数翻译啦。
在需要翻译的地方调用相关翻译方法。
现在随便试一下输出:
package com.youthlin.demo.i18n; import com.youthlin.demo.i18n.util.Translation; import java.text.MessageFormat; import java.util.Locale; import java.util.ResourceBundle; import static com.youthlin.demo.i18n.util.Translation.__; import static com.youthlin.demo.i18n.util.Translation._n; import static com.youthlin.demo.i18n.util.Translation._x; import static com.youthlin.demo.i18n.util.Translation._nx; /** * Created by lin on 2016-09-20-020. * <p> * 主页:https://www.gnu.org/software/gettext/<br> * Windows工具包:https://mlocati.github.io/articles/gettext-iconv-windows.html<br> * 用法:https://www.gnu.org/software/gettext/manual/html_node/Program-Index.html<br> * Java示例:https://www.gnu.org/software/gettext/manual/html_node/Java.html#Java<br> * 一般步骤:<ol> * <li>编写程序,需要翻译的地方使用工具类的方法</li> * <li> * 抽取 .pot 翻译模板文件<pre>xgettext -k__ -k_x:2c,1 -k_n:1,2 -k_nx:4c,1,2 -o resources/lang/messages.pot java/com/youthlin/demo/i18n/Main.java --from-code UTF-8</pre> * xgettext 用法:<a href="https://www.gnu.org/software/gettext/manual/html_node/xgettext-Invocation.html#Language-specific-options">xgettext-Invocation.html#Language-specific-options</a> * </li> * <li>复制模板文件为 .po 文件进行翻译,记得修改 .po 中元数据<pre>"Content-Type: text/plain; charset=<strong>UTF-8</strong>\n"</pre></li> * <li> * 生成资源文件<pre>msgfmt --java2 -d resources -r Messages -l zh_CN resources/lang/messages_zh_CN.po</pre> * msgfmt 用法:<a href="https://www.gnu.org/software/gettext/manual/html_node/msgfmt-Invocation.html">msgfmt-Invocation.html</a> * </li> * </ol> */ public class Main { public static void main(String[] args) { print(); Translation.setCatalog(ResourceBundle.getBundle("Messages", Locale.ENGLISH)); print(); } private static void print() { System.out.println(__("Hello,World")); System.out.println(_x("post", "a post")); System.out.println(_x("post", "to post")); System.out.printf(_n("%d comment\n", "%d comments\n", 1), 1); System.out.printf(_n("%d comment\n", "%d comments\n", 2), 2); System.out.printf(_nx("%d comment\n", "%d comments\n", 1, "another context"), 1); System.out.printf(_nx("%d comment\n", "%d comments\n", 2, "another context"), 2); //System.out.println(MessageFormat.format(_n("one apple", "{0} apples", 2), 2)); //System.out.println(__("未翻译内容")); } }
抽取待翻译内容。
使用下载的工具提取翻译字符串。命令用法
xgettext [option] [inputfile]...
网上的示例都是只有最简单的提取普通翻译字符串的方法,没有写如何提取单复数翻译字符串。看了一圈文档终于找到了。使用 -kxxx即可,但具体 xxx 该怎么写呢?
文档中有示例:
For Java: GettextResource.gettext:2, GettextResource.ngettext:2,3, GettextResource.pgettext:2c,3, GettextResource.npgettext:2c,3,4, gettext, ngettext:1,2, pgettext:1c,2, npgettext:1c,2,3, getString.
因此,对应过来我们的工具类,完整的命令就是:(在src/main/目录下运行的示例)
xgettext -k__ -k_x:2c,1 -k_n:1,2 -k_nx:4c,1,2 -o resources/lang/messages.pot java/com/youthlin/demo/i18n/Main.java --from-code UTF-8
- -kxxx 用于指明抽取哪些函数同时指明第几个参数是什么含义。
- -o 用于指明输出文件
- 后面跟着要扫描的 java 源文件
- 最后使用 –from-code UTF-8 指明编码。
该命令将生成一个翻译模板文件(.pot),内容如下:
# 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: 2016-09-21 14:44+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <[email protected]>\n" "Language: \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" #: java/com/youthlin/demo/i18n/Main.java:42 msgid "Hello,World" msgstr "" #: java/com/youthlin/demo/i18n/Main.java:43 msgctxt "a post" msgid "post" msgstr "" #: java/com/youthlin/demo/i18n/Main.java:44 msgctxt "to post" msgid "post" msgstr "" #: java/com/youthlin/demo/i18n/Main.java:45 #: java/com/youthlin/demo/i18n/Main.java:46 msgid "%d comment\n" msgid_plural "%d comments\n" msgstr[0] "" msgstr[1] "" #: java/com/youthlin/demo/i18n/Main.java:47 #: java/com/youthlin/demo/i18n/Main.java:48 msgctxt "another context" msgid "%d comment\n" msgid_plural "%d comments\n" msgstr[0] "" msgstr[1] ""
翻译。
复制模板文件为待翻译文件: messages_zh_CN.po
修改需要的翻译的地方
# 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 , YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-09-21 11:36+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: YouthLin Chen \n" "Language-Team: LANGUAGE \n" "Language: zh_CN\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" #: java/com/youthlin/demo/i18n/Main.java:23 msgid "Hello,World" msgstr "你好,世界" #: java/com/youthlin/demo/i18n/Main.java:24 msgctxt "a post" msgid "post" msgstr "文章" #: java/com/youthlin/demo/i18n/Main.java:25 msgctxt "to post" msgid "post" msgstr "发表" #: java/com/youthlin/demo/i18n/Main.java:26 #: java/com/youthlin/demo/i18n/Main.java:27 msgid "%d comment\n" msgid_plural "%d comments\n" msgstr[0] "一则评论\n" msgstr[1] "%d 条评论\n" #: java/com/youthlin/demo/i18n/Main.java:28 #: java/com/youthlin/demo/i18n/Main.java:29 msgctxt "another context" msgid "%d comment\n" msgid_plural "%d comments\n" msgstr[0] "1条评论\n" msgstr[1] "%d 则评论\n"
特别注意需要修改第 17 行文件编码,否则下一步报错。
生成翻译后资源文件。
使用 msgfmt
命令。(在src/main/目录下运行的示例)
msgfmt --java2 -d resources -r Messages -l zh_CN resources/lang/messages_zh_CN.po
- –java2 表明生成 JDK1.2+ 的class 文件(内部使用的是 Object 数组,如果用 –java 则内部使用的是 Hashtable )。
- -d resources 保存在 resources 目录
- -r Messages 生成的类名的 BaseName.
- -l zh_CN 该翻译文件的语言编码
- 最后是翻译完的文件。
可能 gettext 使用的编译命令是 1.3 版本的, path 中 javac 是 1.8 的则会报警告。觉得烦的话,可以使用 –source 选项生成 .java 文件再自行编译。(不过这样直接编译还是报不安全,因为public java.util.Enumeration getKeys ()
方法没有用泛型)
运行。
运行效果:
修改合并。
当之后源文件有修改时,可以使用 msgmerge
合并更改,而不需删除原翻译。
比如上述 java 文件最后一行语句去掉注释,直接运行的话是没有翻译的,会显示原文,因此我们打算把新的待翻译内容也抽取出来。
仍用 xgettext
命令抽取,不过你可以保存为另一个 .pot 文件名,当然覆盖也是可以的。
然后合并到已翻译文件:
用法:
msgmerge [options] existfile.po newpot.pot
msgmerge -U resources/lang/messages_zh_CN.po resources/lang/messages_new.pot
- -U 表示更新已有翻译文件。
然后再进行翻译、生成的步骤即可。
参考连接:
- Gettext 主页
- Windows工具包
- gettext 在线文档
- Gettext Java示例
- Gted 示例
- WordPress 翻译中 __()、_e()、_x、_ex 和 _n 的用法及区别
- MessageFormat 用法
声明
- 本作品采用署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。除非特别注明, 霖博客文章均为原创。
- 转载请保留本文(《Java 使用 GNU 的 Gettext 工具实现国际化》)链接地址: https://youthlin.com/?p=1315
- 订阅本站:https://youthlin.com/feed/
“Java 使用 GNU 的 Gettext 工具实现国际化”上的3条回复
有maven插件吗?这个
备忘:PoEdit
复数形式—— nplurals=2; plural=n==1?0:1;
国庆节快乐!