ResourceBundle 中文乱码问题解决

问题

国际化 i18n 中文的配置文件 i18n_zh_CN.properties 内容如下:

1
2
3
4
5
6
7
8
9
10
username=用户名
password=密码
sex=性别
age=年龄
register=注册
boy=
girl=
email=邮箱
reset=重置
submit=提交

下面 bundle.getString 会输出乱码:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void testI18n() {
Locale locale = Locale.CHINA;

ResourceBundle bundle = ResourceBundle.getBundle("i18n", locale);

// 输出中文会乱码
System.out.println("username: " + bundle.getString("username"));
System.out.println("password: " + bundle.getString("password"));
System.out.println("sex: " + bundle.getString("sex"));
System.out.println("age: " + bundle.getString("age"));

}

原因

Java 读取 .properties 文件时默认使用的编码是 ISO-8859-1ISO-8859-1 是单字节编码,自身不能显示中文,因此导致了乱码现象。

解决方案

方案一

bundle.getString 获取到的内容按 ISO-8859-1 单字节编码形式进行转换,得到对应的 byte[] 字节数组,然后使用 new String(byte[] bytes, String charsetName) 构造器再将 byte[] 字节数组转换为 utf-8 格式的字符串。

1
new String(bundle.getString("username").getBytes("ISO-8859-1"), "utf-8");

这种方法的缺点在于,每次 bundle.getString 都需要手工进行转换,如果需要进行国际化的文本信息很多,这种方法需要大量的手工转换,同时也造成代码冗余且可读性很差。

另外,在 jsp 页面中使用表达式脚本,尚能使用该方案:

1
<%=new String(bundle.getString("username").getBytes("ISO-8859-1"), "utf-8")%>

但如果希望使用 jstl 标签库中的 <fmt:message> 标签来进行国际化信息输出,显然该方案就无法使用了。

方案二

使用 Java SDK 提供的转码 native2ascii.exe 工具(在 bin 目录下可以找到)

首先将原文件 i18n_zh_CN.properties 重命名为 i18n_zh.properties

然后进入 i18n_zh.properties 所在的文件目录,使用如下命令进行转码(指定转换为 UTF-8 编码)

1
native2ascii -encoding utf-8 i18n_zh.properties i18n_zh_CN.properties

命令执行完后,会生成 i18n_zh_CN.properties 文件,该文件已经经过转码处理,原文件的中文在该文件中已经转码为 Unicode,如下:

1
2
3
4
5
6
7
8
9
10
username=\u7528\u6237\u540d
password=\u5bc6\u7801
sex=\u6027\u522b
age=\u5e74\u9f84
register=\u6ce8\u518c
boy=\u7537
girl=\u5973
email=\u90ae\u7bb1
reset=\u91cd\u7f6e
submit=\u63d0\u4ea4

补充说明:native2ascii 命令的 -encoding 参数表示转换为指定编码。Web 项目中一般统一使用 UTF-8 编码,因此我们这里指定转换为 UTF-8 编码。