11

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

语言(有 id)
能力(有 id)
能力名称(有能力参考、语言参考和字符串)

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

有没有人对这个问题有任何想法或设计模式?

4

2 回答 2

21

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

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

了解这些事实后,应该可以使用自定义ResourceBundleControl. 这是一个启动示例:

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}" />
于 2010-12-21T15:04:52.080 回答
0

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

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

就像是:

<fmt:message key="${competence}" />
于 2010-12-21T13:39:05.453 回答