ResourceBundle 找到了中文包,为什么某个键仍返回英文
- 发布时间:2026-10-23 22:36:26
- 本文热度:浏览 2 赞 0 评论 0
- 文章标签: Java ResourceBundle 国际化
- 全文共1字,阅读约需1分钟
界面标题已经是中文,按钮文字却仍是英文。日志打印bundle的locale也是zh_CN,并不能证明按钮键出现在这份文件中。ResourceBundle查一个键时,可以继续沿父包链查找。
这种回退是国际化系统的功能:区域文件只覆盖与通用语言包不同的部分,通用包再继承根包。它也会让缺译暂时看起来“有值”,因此不能用containsKey或一次getString成功,证明目标语言翻译完整。
这里采用带 ResourceBundle.Control 的加载入口,运行范围是 classpath 上的未命名模块。示例的单文件运行方式符合这个条件;如果应用已经放进命名模块,这个重载会抛出 UnsupportedOperationException,需要按模块资源机制调整,不能直接照搬。
下面创建三份临时属性文件,使用独立类加载器,不改进程默认地区。资源加载策略关闭默认Locale的额外回退,只保留当前请求locale的候选与父链,便于看清发生了什么。
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Stream;
public class BundleParents {
public static void main(String[] args) throws Exception {
Path dir = Files.createTempDirectory("bundle-parent-");
Files.writeString(dir.resolve("Messages.properties"), "title=Root\nsave=Save\ncancel=Cancel\n", StandardCharsets.UTF_8);
Files.writeString(dir.resolve("Messages_zh.properties"), "save=保存\n", StandardCharsets.UTF_8);
Files.writeString(dir.resolve("Messages_zh_CN.properties"), "title=简体标题\n", StandardCharsets.UTF_8);
try (URLClassLoader loader = new URLClassLoader(new URL[]{dir.toUri().toURL()}, null)) {
ResourceBundle.Control policy = ResourceBundle.Control.getNoFallbackControl(ResourceBundle.Control.FORMAT_PROPERTIES);
ResourceBundle bundle = ResourceBundle.getBundle("Messages", Locale.SIMPLIFIED_CHINESE, loader, policy);
System.out.println("locale=" + bundle.getLocale());
System.out.println("title=" + bundle.getString("title"));
System.out.println("save=" + bundle.getString("save"));
System.out.println("cancel=" + bundle.getString("cancel"));
System.out.println("combined_has_cancel=" + bundle.containsKey("cancel"));
Properties local = new Properties();
try (Reader reader = Files.newBufferedReader(dir.resolve("Messages_zh_CN.properties"), StandardCharsets.UTF_8)) {
local.load(reader);
}
System.out.println("local_has_cancel=" + local.containsKey("cancel"));
ResourceBundle.clearCache(loader);
} finally {
try (Stream<Path> paths = Files.walk(dir)) {
for (Path path : paths.sorted(Comparator.reverseOrder()).toList()) Files.deleteIfExists(path);
}
}
}
}
JDK 17中保存为 BundleParents.java,执行 java BundleParents.java:
locale=zh_CN
title=简体标题
save=保存
cancel=Cancel
combined_has_cancel=true
local_has_cancel=false
命中的地区包,只是查找的起点
简体中文包提供title,中文通用包提供save,根包提供cancel。于是同一份bundle.getString可以从三层得到结果。getLocale反映返回的bundle对应地区,不为每个键标注实际来源。
keySet()和containsKey也会考虑父包内容,所以cancel存在不代表简体中文文件已经翻译它。代码额外直接读取目标属性文件,检查局部键集合,才能回答“这个文件本身有没有cancel”。这项检查属于翻译完整性,不应与运行时的可用回退混为一谈。
如果地区文件本来就只想覆盖少数差异,局部缺少键完全合理。质量工具应根据项目约定区分完整语言包与区域覆盖包,不能把所有继承项都报成错误。反过来,要求完整翻译的包也不该因为运行时能退回英文就永远跳过检查。
找不到包,与包里缺一个键并非同一路径
资源包候选会结合语言、国家等信息进行查找;默认策略还可能尝试进程默认Locale。目标locale根本没有匹配包时,行为与已经找到目标包但键缺失不同。样本关闭额外默认地区回退,是为了减少本机环境干扰,不是建议所有应用都改变默认策略。
如果整个父链都没有这个键,getString会失败,而不是自动把键名当值。应用可以设计缺译展示策略,例如显示稳定占位并记录告警,但不应把任意MissingResourceException都吞成空字符串。空按钮可能比清楚的占位更难定位,也可能破坏可访问名称。
同名class资源包与properties包并存时,还要核对加载优先级。编辑了属性文件却看不到变化,原因不一定只是缓存,也可能运行时选中了另一种形式。检查实际打包产物与返回bundle类型,比根据源码目录猜测更可靠。
编码与缓存别顺手混进回退结论
样本在Java 17上写入UTF-8属性资源。ResourceBundle属性包的加载规则与直接Properties.load(InputStream)不是同一个入口,不能拿另一方法的旧编码直觉推断这里怎样读取中文。固定Reader编码直接读取局部键,则是本例检查器自己的明确选择。
getBundle还会缓存资源。修改文件以后同一进程是否重载,是另一项生命周期问题;本例只创建一次资源并验证父链,不宣称文件变化能够自动刷新。动态外部配置若需要更新,应有独立重载与验证协议。
实际排查可以依次保存请求Locale、命中Locale、bundle类型、键名以及局部文件是否含键。记录时通常不需要输出整份翻译内容,也不应把带凭据的配置资源当国际化文本随意打印。先确认值来自哪一层,才能决定补翻译、修地区选择,还是调整资源加载方式。