在JSF中的国际化与从数据库加载的ResourceBundle条目 [英] internationalization in JSF with ResourceBundle entries which are loaded from database
问题描述
我正在使用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的一对多引用,每个语言包含一个对象,能力。注意,能力是动态创建的,因此他们的名称不能存在于资源包中。
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";
}
这个解决方案非常原始,因为实体依赖于Controller层,每次我想要它的名称时提取一个会话控制器。更多的符合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中,< resource-bundle>
faces-config.xml
和XHTML中的< f:loadBundle>
也可以指向一个完整的 ResourceBundle
类,而不是 .properties
文件的basename。在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.
知道这些事实,可以使用自定义 ResourceBundle
和控制
从数据库加载包消息。这是一个启动示例:
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}" />
这篇关于在JSF中的国际化与从数据库加载的ResourceBundle条目的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!