资源包技巧和最佳实践

今天是资源捆绑日。 通常,这是Java中最著名的国际化机制(i18n)。 使用它应该很容易。 但是,在弄脏手时会出现许多小问题。 如果您有相同的想法,则此文章适合您。

基本

java.util.ResourceBundle定义了用于访问Java中翻译的标准化方法。 它们包含特定于语言环境的资源。 资源束属于其成员具有相同基本名称的族,但是其名称还具有标识
他们的语言环境。 族中的每个资源束都包含相同的项目,但是这些项目已针对该资源束所代表的语言环境进行了翻译。 这些是键/值对。 这些键唯一地标识捆绑软件中特定于语言环境的对象。

最基本的示例使用以下知识:
Messages.properties
Messages_de.properties Messages_en.properties

如果您需要在应用程序中查询包,则只需调用

ResourceBundle bundle = ResourceBundle.getBundle("Messages");

方法并查询返回的包:

bundle.getString("welcome.message");

如果您想在此处使用哪种语言环境,那是对的。 String构造函数隐式使用Locale.getDefault()解析语言。 那可能不是您想要的。 所以你应该ResourceBundle bundle =

ResourceBundle.getBundle("Messages", locale);

检索捆绑软件后,您将无法设置语言环境。 每个ResourceBundle都有一个定义的语言环境。

命名的东西
 
关于命名的一些想法。 用其内容命名捆绑属性。 您可以通过简单地将它们命名为“消息”和“错误”等来采用更通用的方式。但是,每个子系统或组件也可以具有捆绑软件。 无论您需要什么。 要维护内容,要输入大量条目并不容易。 因此,任何类型的上下文拆分都会使开发人员感到高兴。 捆绑软件属性文件等效于类。 相应地命名。 进一步,您应该找到一个用于命名密钥的通用系统。 根据为属性文件选择的拆分,还可能在密钥中引入某种子系统或组件名称空间。 页面前缀也是可能的。 明智地考虑一下,并加以解决。 您的目标是尽量减少密钥重复。

封装
 
如您所见,您经常使用包的字符串表示形式。 这些实际上是文件名(或更好的类名),您可以通过一个简单的枚举来更好地封装所有内容:

public enum ResourceBundles {
    MESSAGES("Messages"),
    ERRORS("Errors");
    private String bundleName;  

    ResourceBundles(String bundleName) {
        this.bundleName = bundleName;
    }

    public String getBundleName() {
        return bundleName;
    }

    @Override
    public String toString() {
        return bundleName;
    }
}

有了这个你就可以写

ResourceBundle bundle = ResourceBundle.getBundle(MESSAGES.getBundleName());

Java Server Faces和ResourceBundle
 
要在基于jsf的应用程序中使用资源包,您只需在faces-config.xml中定义它们,并使用xhtml文件中的快捷方式。

<resource-bundle>
<base-name>Messages</base-name>
<var>msgs</var>
<h:outputLabel value="#{msgs['welcome.general']}" />

JSF负责其余的工作。 那么参数替换呢? 考虑如下的键值对:

welcome.name=Hi {0}! How are you?

您可以通过f:param标签传递参数:

<h:outputFormat value="#{msgs['welcome.name']}">
         <f:param value="Markus" />
 </h:outputFormat>

要更改语言,您必须为当前的FacesContext实例设置特定的语言环境。 最好通过值更改侦听器执行此操作:

public void countryLocaleCodeChanged(ValueChangeEvent e) {
        String newLocaleValue = e.getNewValue().toString();
        //loop country map to compare the locale code
        for (Map.Entry<String, Object> entry : countries.entrySet()) {
            if (entry.getValue().toString().equals(newLocaleValue)) {
                FacesContext.getCurrentInstance()
                        .getViewRoot().setLocale((Locale) entry.getValue());
            }
        }
    }

EJB中的资源包
 
JSF显然很容易集成。 在EJB中使用这些捆绑包怎么办? 基本上是一样的。 您可以使用相同的机制来使用和使用捆绑包。 您应该记住一件事。 您可能不想始终使用默认语言环境。 因此,您必须找到一种从UI向下传递语言环境的方法。 如果您想通过@Produces批注@Injecting MessageBundle,则必须考虑多次。 尤其是在使用@Stateless EJB时。 这些实例将合并,您必须将语言环境传递给需要了解当前语言环境的任何业务方法。 通常,您可以使用参数对象或某种用户会话配置文件来执行此操作。 不要将语言环境全部添加为方法签名。

来自数据库的资源包
 
在大多数情况下,我看到您需要从数据库中提取密钥。 鉴于ResourceBundle的内部工作原理(每个语言环境一个“类”),您最终不得不在自己的ResourceBundle实现中实现逻辑。 您在网络上找到的大多数示例都是通过重写handleGetObject(String key)方法来实现的。 我不喜欢这种方法,尤其是因为我们有一个更好的方法来使用ResourceBundle.Control机制。 现在,您可以覆盖newBundle()方法并返回自己的ResourceBundle实现。 您所要做的就是将自己的Control设置为DatabaseResourceBundle的父级:

public DatabaseResourceBundle() {
        setParent(ResourceBundle.getBundle(BUNDLE_NAME,
        FacesContext.getCurrentInstance().getViewRoot().getLocale(), new DBControl()));
    }

DBControl返回MyResourceBundle,它是一个ListResourceBundle:

protected class DBControl extends Control {

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

        /**
         * A simple ListResourceBundle
         */
        protected class MyResources extends ListResourceBundle {

            private Locale locale;

            /**
             * ResourceBundle constructor with locale
             *
             * @param locale
             */
            public MyResources(Locale locale) {
                this.locale = locale;
            }

            @Override
            protected Object[][] getContents() {
                TypedQuery<ResourceEntity> query = _entityManager.createNamedQuery("ResourceEntity.findForLocale", ResourceEntity.class);
                query.setParameter("locale", locale);

                List<ResourceEntity> resources = query.getResultList();
                Object[][] all = new Object[resources.size()][2];
                int i = 0;
  for (Iterator<ResourceEntity> it = resources.iterator(); it.hasNext();) {
  ResourceEntity resource = it.next();
  all[i] = new Object[]{resource.getKey(), resource.getValue()};
  values.put(resource.getKey(), resource.getValue());
   i++;
  }
                return all;
            }
        }
    }

如您所见,这由一个entitymanager和一个简单的ResourceEntity作为后盾,该ResourceEntity具有构建不同捆绑软件所需的所有字段和NamedQueries。

@Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Column(name = "i18n_key")
    private String key;
    @Column(name = "i18n_value")
    private String value;
    @Column(name = "i18n_locale")
    private Locale locale;

通过将捆绑包放入私有Map <String,String>值= new HashMap <String,String>(); 在首次构建捆绑包之后,您还可以使用一种很好的方法来缓存结果。

这仍然不是最好的解决方案,因为ResourceBundles具有缓存的方式。 但我稍后可能会更详细地探讨这一点。 到现在为止,此捆绑包将被永久缓存(或至少直到下一次重新部署为止)。

改写为语言切换
 
最后要提到的是,您还可以在此处添加一些精美的插件。 如果您已经有了JSF语言切换魔术,则可以轻松地将ocpsoft的重写添加到您的应用程序中。 这是一种将网址中的语言编码的简单方法,例如http://yourhost.com/Bundle-Provider-Tricks/en/index.html 您要做的就是通过添加两个简单的依赖关系来向游戏添加重写:

<dependency>
            <groupId>org.ocpsoft.rewrite</groupId>
            <artifactId>rewrite-servlet</artifactId>
            <version>1.1.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.ocpsoft.rewrite</groupId>
            <artifactId>rewrite-integration-faces</artifactId>
            <version>1.1.0.Final</version>
        </dependency>

重写需要您添加自己的ConfigurationProvider,这是保存重写规则的中心位置。 执行以下操作:

public class BundleTricksProvider extends HttpConfigurationProvider {

    @Override
    public Configuration getConfiguration(ServletContext context) {
        return ConfigurationBuilder.begin()
                // Locale Switch
                .addRule(Join.path("/{locale}/{page}.html").to("/{page}.xhtml")
                .where("page").matches(".*")
                .where("locale").bindsTo(PhaseBinding.to(El.property("#{languageSwitch.localeCode}")).after(PhaseId.RESTORE_VIEW)));
    }

    @Override
    public int priority() {
        return 10;
    }
}

接下来是将一个名为“ org.ocpsoft.rewrite.config.ConfigurationProvider”的文件添加到您的META-INF / services文件夹,并在其中放置您的ConfigurationProvider实现的标准名称。 最后要调整的是LanguageSwitch bean中的逻辑。 重写不能触发ValueChangeEvent(据我所知:)),因此您必须在调用setter时添加一些魔术来更改Locale。 就是这样..非常简单!

参考:来自JCG合作伙伴 Markus Eisele的Resource Bundle技巧和最佳实践 ,位于Enterprise Software Development with Java博客上。


翻译自: https://www.javacodegeeks.com/2012/09/resource-bundle-tricks-and-best.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值