7

我在某个地方有一个模板<ui:insert name="help_contents" />和一个定义 的页面,<ui:define name="help_contents><!-- actual contents --></ui:define>其中定义中的内容应该是基于 JSF 的(不仅仅是普通的 html/xhtml),由 faces servlet 处理并根据语言环境而有所不同。但我不想对资源包执行此操作,因为这将需要每个属性的大量文本,并且必须为散布在文本中的每个组件分解它。换句话说,我想要每个区域设置一个 facelet,然后根据活动区域设置包含正确的 facelet。

基本上就是这个问题。下面的上下文是为了其他正在搜索的人,如果您已经理解我的意思,请跳过。

在很大程度上,JSF 2 中的国际化非常容易。您制作一个或多个资源包,在 faces-config.xml 中声明它们,然后就可以使用这些属性了。但我觉得这样的属性文件只适用于短标签文本、列标题、可能带有几个参数的小消息……当涉及到大部分文本时,它们看起来很笨拙。特别是如果文本应该散布有 XHTML 标记或 JSF 组件,在这种情况下,您需要将其分解得太多。

目前我正在开发一些使用 JSF 2 的 Web 应用程序,PrimeFaces 作为组件包,它在常规意义上使用 i18n 的资源包。但是各种视图需要一个帮助页面。我也想在这些帮助页面中使用 JSF/PrimeFaces 组件,以便填充表或对话框的示例看起来与视图本身相同。

但是,包含基于语言环境的合成内容似乎没有我想象的那么简单。我希望 XHTML 页面(facelets)带有像 _en 或 _fr 这样的语言环境后缀,并根据活动的语言环境选择正确的页面。如果不存在这样的页面,它应该默认为 _en 页面(或没有后缀的仅包含英文内容的页面)。从 facescontext 获取语言环境字符串不是问题,但检测页面是否存在似乎更难。有什么方法可以在 JSF 中或通过 EL 执行此操作,还是应该通过托管 bean 完成?也许为此编写一个自定义标签会很有用,但我不确定这需要做多少工作。

我确实找到了这个相关的问题,但这似乎只有在我不想注入纯 HTML 内容时才有用。我想包含带有 JSF 内容的页面,以便它们实际上由 JSF servlet 处理和呈现。

4

3 回答 3

3

以下是我对您的问题的解决方案。它体积庞大,但已完成,内容丰富,据我所知,完整。有了它,您将能够根据当前语言从一组以语言为后缀的视图中包含必要的视图。

我对您的设置的假设

  1. 您正在处理描述语言的语言环境,即Locale.ENGLISH格式;
  2. 您选择的语言存储在会话范围的 bean 中;
  3. 您将国际化页面保存为以下格式:page.xhtml、、、page_en.xhtmlpage_fr.xhtml
  4. 默认语言为英语;
  5. FacesServlet的映射到*.xhtml.

我的解决方案的标准设置

会话范围的 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>

基于多个页面的解决方案 - 每种语言一个

page-multiple.xhtml通过添加 _lang 后缀 ( )国际化的基页

<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>

德语(无视图,模拟丢失文件)。

执行重定向的托管 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请求视图时,它都会重定向到以语言为后缀的视图,或者如果找不到目标语言的视图,则重定向到英语视图。当前语言取自会话范围的 bean,所有视图必须位于服务器上的同一文件夹中。当然,这可以重做,而是基于视图参数中定义的语言。目标页面可以使用合成。默认数据可以在非后缀视图中提供,preRenderView侦听器不执行重定向。

作为备注,我的(三个)视图存储在international/网页文件夹中。

基于所有语言的单一页面的解决方案

尽管您的问题应该由以前的设置解决,但我想到了另一个想法,我将在下面描述。

有时,不创建与支持的语言一样多的视图(+1 用于重定向)可能更容易,而是创建一个单一的视图,根据当前选择的语言有条件地呈现其输出。

视图(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>

使用此视图,您可以指定内部的所有数据,有条件地仅呈现所需语言所需的数据或默认数据。

提供自定义资源解析器

资源解析器将根据视图的当前语言环境包含所需的文件。

资源解析器:

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>

使用此设置,可以呈现以下视图:

查看哪些使用<ui:include>,其中国际化包含将使用创建的//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,但会有每种语言的视图,例如:

page-include_en.xhtml

<h:outputText value="Welcome" />

page-include_fr.xhtml

<h:outputText value="Bienvenue" />

这样,解析器将根据当前语言环境选择正确的国际化包含视图。

于 2013-02-27T19:54:00.300 回答
2

例如,您可以定义复合组件,它将只是标准的外观ui:include

资源/myComponents/localeInclude.xhtml:

<cc:interface>
  <cc:attribute name="src" required="true" type="java.lang.String"/>
</cc:interface>

<cc:implementation>
  <ui:include src="#{myResolver.resolve(cc.attrs.src)}">
    <cc:insertChildren/>
  </ui:inclue>
</cc:implementation>

创建托管bean,命名myResolver它可以@ApplicationScoped是完全无状态的resolve()方法:

public String resolve(String src) {
  String srcWithoutExt = src.replace(".xhtml", "");
  FacesContext facesContext = FacesContext.getCurrentInstance();
  ServletContext servletContext = (ServletContext) facesContext.getExternalContext().getContext();
  Locale locale = facesContext.getViewRoot().getLocale();
  String localizedSrc = srcWithoutExt + "_" + locale.getLanguage();
  URL url = null;
  if (src.startsWith("/")) {
    url = facesContext.getExternalContext().getResource(localizedSrc + ".xhtml");
  } else {
    try {
      url = new URL((HttpServletRequest) request).getRequestURL(), localizedSrc + ".xhtml");
    } catch (Exception e) { /* Doesn't exist */ }
  }
  if (url != null) {
    return localizedSrc + ".xhtml";
  } else {
    return src;
  }
}

在这种情况下,只需放入src没有区域设置扩展的页面并让方法解决此问题:

<my:localeInclude src="myPage.xhtml/>

因为我包括儿童,你可以传递ui:param给你包括喜欢的原创。

此外,对于那些不习惯根据语言环境(不仅仅是部分)解析整个页面的人来说,使用Filter. 在doFilter()方法中,您可以检查该资源是否存在,如果不将请求转发到另一个页面:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws java.io.IOException, ServletException {

  if (request.getServletContext().getResource(request.getRequestURI()) == null) {
    // Here your page doesn't exist so forward user somewhere else...
  }
}

Filter根据您的需要为此配置映射。

于 2013-02-27T12:52:11.737 回答
1

从此链接@ SO,您可以动态包含内容(检查选中的答案)。在支持文件中,如果您有一个可以适当设置文件名的钩子,我认为这可以解决问题。

不确定这一点,您可以检查,如果您可以将参数(即部分路径)传递给 EL 中的方法,其余的可以在方法内部处理,如构造完整路径、附加当前语言环境和检查文件是否存在。

希望这可以帮助。

更新(回答评论):

是的。您可以查看链接JSF 2 fu,第 2 部分:模板和复合组件

于 2013-02-27T12:58:21.447 回答