我想向您保证,如果您需要对本地化文本进行不断更改,例如它们往往因部署而异,那么数据库是您的最佳选择。好吧,不仅仅是数据库,你需要以某种方式缓存你的字符串。当然,我想你不会想要完全重新构建你的资源访问层。
为此,我可以建议扩展ResourceBundle
类以自动从数据库加载字符串并将其存储在WeakHashMap
. 我之所以选择WeakHashMap
它是因为它的特性——当不再需要它时,它会从映射中删除一个键,从而减少内存占用。无论如何,您需要创建一个访问器类。既然您提到了 J2EE,这是一种非常古老的技术,我将给您提供 Java SE 1.4 兼容示例(它可以很容易地为较新的 Java 重新设计,只需在需要时添加 @Override 并在 Enumeration 中添加一些字符串泛化):
public class WeakResourceBundle extends ResourceBundle {
private Map cache = new WeakHashMap();
protected Locale locale = Locale.US; // default fall-back locale
// required - Base is abstract
// @Override
protected Object handleGetObject(String key) {
if (cache.containsKey(key))
return cache.get(key);
String value = loadFromDatabase(key, locale);
cache.put(key, value);
return value;
}
// required - Base is abstract
// @Override
public Enumeration getKeys() {
return loadKeysFromDatabase();
}
// optional but I believe needed
// @Override
public Locale getLocale() {
return locale;
}
// dummy testing method, you need to provide your own
// should throw MissingResourceException if key does not exist
private String loadFromDatabase(String key, Locale aLocale) {
System.out.println("Loading key: " + key
+ " from database for locale:"
+ aLocale );
return "dummy_" + aLocale.getDisplayLanguage(aLocale);
}
// dummy testing method, you need to provide your own
private Enumeration loadKeysFromDatabase() {
return Collections.enumeration(new ArrayList());
}
}
由于一些奇怪的 ResourceBundle 的加载规则,您实际上需要扩展WeakResourceBundle
类来为支持的语言创建一个类:
// Empty Base class for Invariant Language (usually English-US) resources
// Do not need to modify anything here since I already set fall-back language
package com.example.i18n;
public class MyBundle extends WeakResourceBundle {
}
每种支持一种语言(我知道这很糟糕):
// Example class for Polish ResourceBundles
package com.example.i18n;
import java.util.Locale;
public class MyBundle_pl extends WeakResourceBundle {
public MyBundle_pl() {
super();
locale = new Locale("pl");
}
}
现在,如果您需要实例化您的ResourceBundle
,您只需调用:
// You probably need to get Locale from web browser
Locale polishLocale = new Locale("pl", "PL");
ResourceBundle myBundle = ResourceBundle.getBundle(
"com.example.i18n.MyBundle", polishLocale);
并访问密钥:
String someValue = myBundle.getString("some.key");
可能的陷阱:
ResourceBundle
需要完全限定的类名(因此是包名)。
- 如果省略参数,将使用
Locale
默认值(表示服务器) 。Locale
一定要Locale
在实例化时总是通过ResourceBundle
。
myBundle.getString()
MissingResourceException
如果您遵循我的建议,可以抛出。您需要使用 try-catch 块来避免问题。相反,您可以决定在缺少键(如return "!" + key + "!"
)的情况下从数据库访问层返回一些虚拟字符串,但无论哪种方式,它都应该被记录为错误。
- 您应该始终尝试创建同时传递语言和国家代码的 Locale 对象。那是因为,像简体中文 (zh_CN) 和繁体中文 (zh_TW) 这样的语言是完全不同的语言(至少在写作方面),您需要支持它们的两种风格。对于其他国家/地区,ResourceBundle 实际上会自动加载正确的语言资源(请注意,我创建了
MyBundle_pl.java
,但MyBundle_pl_PL.java
它仍然有效。此外,MyBundle.java
如果给定语言没有资源类(即这就是为什么我使用如此奇怪的类层次结构)。
编辑
关于如何使它更糟糕的一些随机想法。
静态工厂(避免直接使用 ResourceBundle)
您可以添加静态工厂方法,而不是直接使用 ResourceBundle 实例化捆绑包:
public static ResourceBundle getInstance(Locale aLocale) {
return ResourceBundle.getBundle("com.example.i18n.MyBundle", aLocale);
}
如果您决定将WeakResourceBundle
类的名称更改为更合适的名称(我决定使用LocalizationProvider
),您现在可以轻松地从使用代码中实例化您的包:
ResourceBundle myBundle = LocalizationProvider.getInstance(polishLocale);
自动生成的资源类
本地化MyBundle
的类可以通过构建脚本轻松生成。该脚本可以是配置文件或数据库驱动的——它需要知道系统中正在使用哪个区域设置。无论哪种方式,这些类共享非常相似的代码,因此自动生成它们真的很有意义。
自动检测语言环境
由于您是实现该类的人,因此您可以完全控制其行为。因此(了解您的应用程序架构)您可以在此处包含区域设置检测并进行修改getInstance()
以实际自动加载适当的语言资源。
实施其他与本地化相关的方法
在本地化应用程序中需要完成一些常见任务 - 格式化和解析日期、数字、货币等是常见的示例。有了最终用户的区域设置,您可以简单地将此类方法包装在 LocalizationProvider 中。
哎呀,我真的很喜欢我的工作:)