使用从数据库加载的 ResourceBundle 条目在 JSF 中进行国际化 [英] internationalization in JSF with ResourceBundle entries which are loaded from database

查看:25
本文介绍了使用从数据库加载的 ResourceBundle 条目在 JSF 中进行国际化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 JPA/EJB/JSF 处理 Java EE6 项目,但在为实体设计多语言支持时遇到了一些问题.存在三个相关实体:

I'm working on a Java EE6 project using JPA/EJB/JSF and I'm having some trouble designing multiple language support for entities. There are three relevant entities:

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

Language (has id)
Competence (has id)
CompetenceName (has Competence reference, Language reference and a String)

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

Competence has a one-to-many reference to CompetenceName implemented with a Map, containing one object for every Language that there exists a name for a Competence. Note that competences are created dynamically and their names can thus not exist in a resource bundle.

在网页上列出能力时,我希望它们以当前登录用户的语言显示,这存储在会话范围的托管 Bean 中.

When listing the Competences on a web page, I want them to show with the language of the currently logged in user, this is stored in a Session Scoped Managed Bean.

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

Is there any good way to accomplish this without breaking good MVC design? My first idea was to get the session scoped bean directly from a "getName" method in the Competence entity via FacesContext, and look in the map of CompetenceNames for it as following:

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 中获取的语言,这也不是一个好的解决方案.

This solution feels extremly crude since the entity relies on the Controller layer, and have to fetch a session controller every time I want its name. A more MVC compliant solution would be to take a Language parameter, but this means that every single call from JSF will have to include the language fetched from the session scoped managed bean which does not feel like a good solution either.

有人对这个问题有什么想法或设计模式吗?

Does anyone have any thoughts or design patterns for this issue?

推荐答案

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

Internationalization/localization should preferably be entirely done in the view side. The model shouldn't be aware of this.

在JSF中,faces-config.xml中的条目和XHTML 也可以指向一个完整的ResourceBundle 类,而不是.properties 文件的基名.在 Java SE 6 中有一个新的 ResourceBundle.Control API 可用,允许完全控制加载和填充包.

In JSF, the <resource-bundle> entry in faces-config.xml and the <f:loadBundle> in XHTML can also point to a fullworthy ResourceBundle class instead of basename of .properties files. In Java SE 6 there's a new ResourceBundle.Control API available which allows full control over loading and filling the bundle.

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

Knowing those facts, it should be possible to load the bundle messages from the DB with a custom ResourceBundle and Control. Here's a kickoff example:

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中声明如下:

This way you can declare it as follows in faces-config.xml:

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

或者在Facelet中如下:

Or as follows in the Facelet:

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

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

Either way, you can use it the usual way:

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

这篇关于使用从数据库加载的 ResourceBundle 条目在 JSF 中进行国际化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆