【问题标题】:internationalization in JSF with ResourceBundle entries which are loaded from database使用从数据库加载的 ResourceBundle 条目在 JSF 中进行国际化
【发布时间】:2011-05-28 20:04:01
【问题描述】:

我正在使用 JPA/EJB/JSF 开发一个 Java EE6 项目,但在为实体设计多语言支持时遇到了一些麻烦。共有三个相关实体:

语言(有 id)
能力(有 id)
CompetenceName(具有能力参考、语言参考和字符串)

Competence 具有对使用 Map 实现的 CompetenceName 的一对多引用,其中包含一个对象,对应于存在 Competence 名称的每种语言。请注意,权限是动态创建的,因此它们的名称不能存在于资源包中。

在网页上列出能力时,我希望它们以当前登录用户的语言显示,这存储在 Session Scoped Managed Bean 中。

有没有什么好的方法可以在不破坏良好的 MVC 设计的情况下实现这一点?我的第一个想法是通过 FacesContext 直接从 Competence 实体中的“getName”方法获取会话范围 bean,并在 CompetenceNames 的映射中查找它,如下所示:

public class Competence
{
...
@MapKey(name="language")
@OneToMany(mappedBy="competence", cascade=CascadeType.ALL, orphanRemoval=true)
private Map<Language, CompetenceName> competenceNames;

public String getName(String controller){
    FacesContext context = FacesContext.getCurrentInstance();
    ELResolver resolver = context.getApplication().getELResolver();
    SessionController sc = (SessionController)resolver.getValue(context.getELContext(), null, "sessionController");
    Language language = sc.getLoggedInUser().getLanguage();
    if(competenceNames.get(language) != null)
        return competenceNames.get(language).getName();
    else
        return "resource missing";
}

这个解决方案感觉非常粗糙,因为实体依赖于控制器层,并且每次我想要它的名称时都必须获取会话控制器。更符合 MVC 的解决方案是采用 Language 参数,但这意味着来自 JSF 的每个调用都必须包含从会话范围的托管 bean 获取的语言,这也不是一个好的解决方案。

有人对此问题有任何想法或设计模式吗?

【问题讨论】:

    标签: database jsf jpa internationalization resourcebundle


    【解决方案1】:

    国际化/本地化最好完全在视图侧完成。模型不应该意识到这一点。

    在 JSF 中,faces-config.xml 中的 &lt;resource-bundle&gt; 条目和 XHTML 中的 &lt;f:loadBundle&gt; 也可以指向一个完整的 ResourceBundle 类,而不是 .properties 文件的基本名称。在 Java SE 6 中,有一个新的 ResourceBundle.Control API 可用,它允许完全控制加载和填充包。

    了解这些事实后,应该可以使用自定义 ResourceBundleControl 从 DB 加载捆绑消息。这是一个启动示例:

    public class CompetenceBundle extends ResourceBundle {
    
        protected static final String BASE_NAME = "Competence.messages"; // Can be name of @NamedQuery
        protected static final Control DB_CONTROL = new DBControl();
    
        private Map<String, String> messages;
    
        public CompetenceBundle() {
            setParent(ResourceBundle.getBundle(BASE_NAME, 
                FacesContext.getCurrentInstance().getViewRoot().getLocale(), DB_CONTROL));
        }
    
        protected CompetenceBundle(Map<String, String> messages) {
            this.messages = messages;
        }
    
        @Override
        protected Object handleGetObject(String key) {
            return messages != null ? messages.get(key) : parent.getObject(key);
        }
    
        @Override
        public Enumeration<String> getKeys() {
            return messages != null ? Collections.enumeration(messages.keySet()) : parent.getKeys();
        }
    
        protected static class DBControl extends Control {
    
            @Override
            public ResourceBundle newBundle
                (String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
                    throws IllegalAccessException, InstantiationException, IOException
            {
                String language = locale.getLanguage();
                Map<String, String> messages = getItSomehow(baseName, language); // Do your JPA thing. The baseName can be used as @NamedQuery name.
                return new CompetenceBundle(messages);
            }
    
        }
    
    }
    

    这样就可以在faces-config.xml中声明如下:

    <resource-bundle>
        <base-name>com.example.i18n.CompetenceBundle</base-name>
        <var>competenceBundle</var>
    </resource-bundle>
    

    或者在 Facelet 中如下:

    <f:loadBundle basename="com.example.i18n.CompetenceBundle" var="competenceBundle" />
    

    无论哪种方式,您都可以按照通常的方式使用它:

    <h:outputText value="#{competenceBundle.name}" />
    

    【讨论】:

    • 感谢您的回答,这看起来很有趣。但是,关于捆绑包的一个问题是:它们有什么范围?假设一个用户更改了权限的名称并调用 newBundle 来刷新他的更改。这会反映所有用户的捆绑包,还是只反映他的捆绑包?
    • 捆绑包基本上由基本名称和语言环境缓存在应用程序范围内。因此,任何更改都会反映给所有用户。
    • 我现在已经实现了这个,感觉比让模型向控制器询问语言细节要好得多。我在重命名权限时遇到了一些麻烦,调用 CompetenceBundle.clearCache();当我从 java 读取包时清除缓存,但网站仍然显示旧值。应用服务器是否有自己的缓存?
    • 我猜这是在JPA级别处理的。
    • @BalusC 对于这项工作,我还需要创建几个带有语言和国家后缀的子类,例如 _pt_BR。否则不会设置自定义 DBControl。谢谢。
    【解决方案2】:

    我会将语言特定部分从您的模型移到资源包中。只需对能力、语言和用户进行建模。如果用户请求页面,您将显示他的能力并从资源包中查找特定于语言的能力 (CompetenceName)。

    我搜索了示例代码以帮助您入门并找到 this,请参见清单 19.16 customerDetails.jsp。

    类似:

    <fmt:message key="${competence}" />
    

    【讨论】:

    • 问题是能力不是静态的,管理员可以随时为系统添加新的能力或现有能力的能力名称。据我所知,无法以编程方式写入包。
    • jdk 6 允许重新加载资源包:stackoverflow.com/questions/156586/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-29
    • 1970-01-01
    • 2011-11-14
    • 2012-05-13
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    相关资源
    最近更新 更多