动态文本的国际化

动态文本的国际化

一般在Java项目中,文本的国际化可以通过property文件来实现。对于静态文本来说(比如按钮名称、表格列名等),property文件非常合适;但对于动态文本来说(比如业务表的状态字段),property文件的形式就有些局限了。

这里简单解释下动态文本和静态文本的差异。

动态文本是在运行期间,根据客户的实际情况,可能会有变化的内容。比如现在有一个业务字段,叫“差异原因”,现在有2种原因。随着业务的开展,将来可能有3,4,5种甚至更多。也就是说是会动态增长的。

而静态文本是不会变的。比如某个按钮,业务再怎么变,这个按钮还是在那里,除非对按钮有可见的限制,但那已经不是文本国际化的问题,而是可见权限的管理问题。

之前我一直有种误解,就是认为动态文本还分为两种,区分的标准就是,程序中是否会使用其code值做业务判断。比如业务状态字段,程序中可能会根据对状态值的判断,走不同的流程分支。因此,程序中会使用它,应该由程序员来维护和管理。另一种就是程序根本不关心该字段的具体内容,字段内容只是用作简单的显示使用。

但是后来我发现,这种区分标准的界限很模糊。你很难确定说,这个字段的值将来程序是否会使用。因为分类不同,其实现方式就不同。如果从一种分类变为另一种分类,就需要大量的修改代码。

现在我认为,其本质都是一种都是动态文本。程序员关心的始终都是动态文本的key,所有key对应的value,都应该交由java国际化框架来解决。

其实property文件也只是Java国际化文本的一种存储介质而已,你完全可以采用数据库、xml、Java类等等其他的持久化方案。或者,你完全可以自己写一套,就像以前很多老系统中,存在的字典表这种简化的国际化方案(基本只需支持中文,并且会有很严重的性能问题,因为你需要频繁的将字典表与业务表进行关联)。

property文件方案对于解决动态文本国际化的局限性主要有:

  1. 需要访问生产服务器的文件系统并定位具体文件(如果你的property文件分散,就更难维护了);
  2. 修改后需要重启服务器;
  3. 如果你像我一样是非英文用户,那每次编辑完文件后,你都要用native2ascii工具转换一下property文件(或者你使用一个支持这种编码转换的editor),因为property文件默认都是iso-8859-1编码(具体可参见wikipedia的相应段落http://en.wikipedia.org/wiki/.properties);

上面的第一点和第三点,就完全否决了由非java程序员进行维护的的可能。

因此,我们需要修改方案,使运维人员可以方便的维护动态文本。property数据库持久方案就可以解决以上问题。

关于采用数据库实现java国际化,网上也有很多现成的例子,我主要参考的是这篇文章:http://java.dzone.com/articles/resource-bundle-tricks-and

以下是我对ResourceBundle的Control的实现

public class ResourceBundleDBControl extends Control {
@Override
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
        throws IllegalAccessException, InstantiationException, IOException {
    return new MyResources(baseName,locale);
}


protected class MyResources extends ListResourceBundle {

    @SuppressWarnings("unused")
    private Locale locale;
    private String baseName;

    public MyResources(String baseName,Locale locale) {
        this.baseName = baseName;
        this.locale = locale;
    }

    @Override
    protected Object[][] getContents() {
        DetachedCriteria dc = DetachedCriteria.forClass(Code.class);
        dc.createAlias("codeType", "type");

        dc.setProjection(Projections.projectionList()
                .add(Projections.property("key"))
                .add(Projections.property("value")));
        dc.add(Restrictions.eq("type.code", baseName));

        ApplicationContext ctx = ApplicationContextProvider.getContext();
        IQueryService queryService = (IQueryService) ctx.getBean("queryService");

        @SuppressWarnings("unchecked")
        Listresult = (List) queryService.query(dc); Object [][] r = new Object[result.size()][2]; for(int i = 0;i < result.size(); i++){ r[i][0] = result.get(i)[0]; r[i][1] = result.get(i)[1]; } return r; } }} 

ResourceBundle的control机制可以参考其API文档。默认ResourceBundle可以从class和property文件加载内容。通过客户化control的实现,我们可以从数据库加载ResourceBundle。

对于新的ResourceBundle的使用可以参考以下代码(即将customerized control作为参数传入即可):

public String getTextFromDB(String bundleName,String key){
    ResourceBundle b = ResourceBundle.getBundle(bundleName, new ResourceBundleDBControl());
    return b.getString(key);
}

注意:ResourceBundle本身有缓存机制。比如ListResourceBundle本身有一个map作为内部的缓存。因此,修改动态文本后,需要重启应用服务器。或者修改ListResourceBundle的实现,不应用缓存。

下图中的lookup即为内部缓存: 

你可能感兴趣的:(java,J2EE,i18n)