我在某个地方有一个模板 <ui:insert name="help_contents" />
和一个定义 <ui:define name="help_contents><!-- actual contents --></ui:define>
的页面,其中定义中的内容应该是基于 JSF 的(而不仅仅是普通的 html/xhtml),由 faces servlet 处理并根据语言环境而有所不同。但我不想对资源包执行此操作,因为这将需要每个属性的大量文本,并且必须将其分解为散布在文本中的每个组件。换句话说,我想要每个区域设置一个 facelet,然后根据 Activity 区域设置包含正确的一个。
基本上就是这个问题。以下上下文是为了其他正在搜索的人,如果您已经理解我的意思,请跳过。
在大多数情况下,JSF 2 中的国际化非常容易。您创建一个或多个资源包,在 faces-config.xml 中声明它们,然后您就可以使用这些属性了。但在我看来,此类属性文件仅适用于短标签文本、列标题、可能包含几个参数的小消息……当涉及到大部分文本时,它们似乎很笨拙。尤其是如果文本应该穿插 XHTML 标记或 JSF 组件,在这种情况下,您需要将其分解得太多。
目前我正在开发一些使用 JSF 2 的 Web 应用程序,将 PrimeFaces 作为组件包,它在常规意义上使用 i18n 的资源包。但是各种 View 需要一个帮助页面。我也想在这些帮助页面中使用 JSF/PrimeFaces 组件,以便填充表格或对话框的示例看起来与 View 本身相同。
但是,包括基于语言环境的合成内容似乎没有我想象的那么简单。我希望 XHTML 页面(facelets)带有 _en 或 _fr 等语言环境后缀,并根据 Activity 语言环境选择正确的页面。如果不存在这样的页面,它应该默认为 _en 页面(或没有后缀的只包含英文内容的页面)。从 facescontext 获取语言环境字符串不是问题,但检测页面是否存在似乎更难。在 JSF 中或通过 EL 有什么方法可以做到这一点,还是应该通过托管 bean 来完成?也许为此编写一个自定义标记可能会有用,但我不确定这需要多少工作。
我确实找到了 this related question ,但这似乎只有在我不想注入(inject)纯 HTML 内容时才有用。我想包含带有 JSF 内容的页面,以便它们实际上由 JSF servlet 处理和呈现。
最佳答案
下面是我对你的问题的解决方案。它体积庞大,但已完成,内容丰富,而且据我所知,完整。有了它,您将能够根据当前语言,从一系列带有语言后缀的 View 中包含必要的 View 。
我对您的设置的假设
- 您正在处理描述语言的语言环境,即在
Locale.ENGLISH
中格式; - 您选择的语言存储在 session 范围的 bean 中;
- 您将国际化页面保留为以下格式:
page.xhtml
,page_en.xhtml
,page_fr.xhtml
等; - 默认语言为英语;
- 你的
FacesServlet
映射到*.xhtml
.
我的解决方案的标准设置
session 范围的 bean,包含可用的语言和用户选择:
@ManagedBean
@SessionScoped
public class LanguageBean implements Serializable {
private List<Locale> languages;//getter
private Locale selectedLanguage;//getter + setter
public LanguageBean() {
languages = new ArrayList<Locale>();
languages.add(Locale.ENGLISH);
languages.add(Locale.FRENCH);
languages.add(Locale.GERMAN);
selectedLanguage = Locale.ENGLISH;
}
public Locale findLocale(String value) {
for(Locale locale : languages) {
if(locale.getLanguage().equals(new Locale(value).getLanguage())) {
return locale;
}
}
return null;
}
public void languageChanged(ValueChangeEvent e){
FacesContext.getCurrentInstance().getViewRoot().setLocale(selectedLanguage);
}
}
语言环境转换器:
@ManagedBean
@RequestScoped
public class LocaleConverter implements Converter {
@ManagedProperty("#{languageBean}")
private LanguageBean languageBean;//setter
public LocaleConverter() { }
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if(value == null || value.equals("")) {
return null;
}
Locale locale = languageBean.findLocale(value);
if(locale == null) {
throw new ConverterException(new FacesMessage("Locale not supported: " + value));
}
return locale;
}
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (!(value instanceof Locale) || (value == null)) {
return null;
}
return ((Locale)value).getLanguage();
}
}
主视图 ( main.xhtml
) 带有指向国际化页面的链接并能够通过下拉框更改当前语言:
<f:view locale="#{languageBean.selectedLanguage}">
<h:head>
<title>Links to internationalized pages</title>
</h:head>
<h:body>
<h:form>
<h:selectOneMenu converter="#{localeConverter}" value="#{languageBean.selectedLanguage}" valueChangeListener="#{languageBean.languageChanged}" onchange="submit()">
<f:selectItems value="#{languageBean.languages}"/>
</h:selectOneMenu>
</h:form>
<br/>
<h:link value="Show me internationalized page (single)" outcome="/international/page-single"/>
<br/>
<h:link value="Show me internationalized page (multiple)" outcome="/international/page-multiple"/>
</h:body>
</f:view>
基于多个页面的解决方案 - 每种语言一个
通过添加_lang 后缀(page-multiple.xhtml
)实现国际化的基础页面
<f:metadata>
<f:event type="preRenderView" listener="#{pageLoader.loadPage}"/>
</f:metadata>
国际化页面:
对于英语 ( page-multiple_en.xhtml
):
<h:head>
<title>Hello - English</title>
</h:head>
<h:body>
Internationalized page - English
</h:body>
对于法语(page-multiple_fr.xhtml
):
<h:head>
<title>Hello - Français</title>
</h:head>
<h:body>
Page internationalisé - Français
</h:body>
对于德语(没有 View ,模拟丢失的文件)。
执行重定向的托管 bean:
@ManagedBean
@RequestScoped
public class PageLoader {
@ManagedProperty("#{languageBean}")
private LanguageBean languageBean;//setter
public PageLoader() { }
public void loadPage() throws IOException {
Locale locale = languageBean.getSelectedLanguage();
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext external = context.getExternalContext();
String currentPath = context.getViewRoot().getViewId();
String resource = currentPath.replace(".xhtml", "_" + locale.toString() + ".xhtml");
if(external.getResource(resource) == null) {
resource = currentPath.replace(".xhtml", "_en.xhtml");
}
String redirectedResource = external.getRequestContextPath() + resource.replace(".xhtml", ".jsf");
external.redirect(redirectedResource);
}
}
每次查看page-multiple.xhtml
被请求它被重定向到语言后缀的 View ,或者如果没有找到目标语言的 View ,则重定向到英语 View 。当前语言取自 session 范围的 bean,所有 View 必须位于服务器上的同一文件夹中。当然,这可以重做,而是基于 View 参数中定义的语言。目标页面可以使用组合。可以使用 preRenderView
在无后缀 View 中提供默认数据监听器不执行重定向。
请注意,我的(三个) View 存储在 international/
中网页文件夹。
基于所有语言的单一页面的解决方案
虽然您的问题应该由以前的设置解决,但我想到了另一个想法,我将在下面描述。
有时不创建与支持的语言一样多的 View (+1 用于重定向)可能更容易,而是创建一个 View ,该 View 将根据当前选择的语言有条件地呈现其输出。
View (page-single.xhtml
,也位于服务器上的同一文件夹中)可能如下所示:
<ui:param name="lang" value="#{languageBean.selectedLanguage}"/>
<ui:fragment rendered="#{lang == 'en'}">
<h:head>
<title>Hello - English</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF8" />
</h:head>
<h:body>
Internationalized page - English
</h:body>
</ui:fragment>
<ui:fragment rendered="#{lang == 'fr'}">
<h:head>
<title>Hello - Français</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF8" />
</h:head>
<h:body>
Page internationalisé - Français
</h:body>
</ui:fragment>
<ui:fragment rendered="#{(lang ne 'en') and (lang ne 'fr')}">
<h:head>
<title>Hello - Default</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF8" />
</h:head>
<h:body>
Internationalized page - Default
</h:body>
</ui:fragment>
使用此 View ,您可以在其中指定所有数据,有条件地仅呈现所需语言所需的数据或默认数据。
提供自定义资源解析器
资源解析器将根据 View 的当前语言环境包含所需的文件。
资源解析器:
public class InternalizationResourceResolver extends ResourceResolver {
private String baseLanguage;
private String delimiter;
private ResourceResolver parent;
public InternalizationResourceResolver(ResourceResolver parent) {
this.parent = parent;
this.baseLanguage = "en";
this.delimiter = "_";
}
@Override
public URL resolveUrl(String path) {
URL url = parent.resolveUrl(path);
if(url == null) {
if(path.startsWith("//ml")) {
path = path.substring(4);
Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
URL urlInt = parent.resolveUrl(path.replace(".xhtml", delimiter + locale.toString() + ".xhtml"));
if(urlInt == null) {
URL urlBaseInt = parent.resolveUrl(path.replace(".xhtml", delimiter + baseLanguage + ".xhtml"));
if(urlBaseInt != null) {
url = urlBaseInt;
}
} else {
url = urlInt;
}
}
}
return url;
}
}
在 web.xml
中启用解析器:
<context-param>
<param-name>javax.faces.FACELETS_RESOURCE_RESOLVER</param-name>
<param-value>i18n.InternalizationResourceResolver</param-value>
</context-param>
使用此设置可以呈现以下 View :
使用 <ui:include>
的 View , 其中国际化包含将使用创建的 //ml/
定义前缀:
<f:view locale="#{languageBean.selectedLanguage}">
<h:head>
</h:head>
<h:body>
<ui:include src="//ml/international/page-include.xhtml" />
</h:body>
</f:view>
不会有page-include.xhtml
,但会有每种语言的 View ,例如:
page-include_en.xhtml
:
<h:outputText value="Welcome" />
page-include_fr.xhtml
:
<h:outputText value="Bienvenue" />
这样,解析器将根据当前语言环境选择正确的国际化包含 View 。
关于java - 每个语言环境的 JSF 2 中的不同 facelets(用于模板),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15112259/