JSF 2中每个地区都有不同的facelets(用于模板中)



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

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

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

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

然而,包含基于区域设置的组合内容似乎没有我想象的那么简单。我希望拥有带有区域后缀(如_en或_fr)的XHTML页面(facelets),并根据活动的区域设置选择正确的页面。如果不存在这样的页面,它应该默认为_en页面(或者不带仅包含英文内容的后缀的页面)。从facescontext获取区域设置字符串不是问题,但是检测页面是否存在似乎比较困难。在JSF中或通过EL是否有办法做到这一点,或者应该通过托管bean来做到这一点?也许写一个自定义标签是有用的,但我不确定这需要多少工作。

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

下面是我对你的问题的解决方案。这本书很笨重,但很完整,内容丰富,而且在我看来是完整的。有了它,您将能够根据当前语言,从一系列带有语言后缀的视图中包含必要的视图。

我对你的设置的假设

  1. 您正在处理描述语言的区域设置,即Locale.ENGLISH格式;
  2. 您选择的语言存储在会话作用域bean中;
  3. 您以以下格式保留国际化页面:page.xhtmlpage_en.xhtmlpage_fr.xhtml等;
  4. 默认为英文;
  5. 您的FacesServlet被映射到*.xhtml

my solution的标准设置

会话作用域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>

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

执行重定向的管理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" />

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

您可以定义复合组件,例如,它将只是标准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>

创建命名为myResolver的托管bean,它可以是@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的映射。

从这个链接@你可以动态地包含一个内容(检查选中的答案)。在备份文件中,如果你有一个钩子,你可以适当地设置文件名,我认为这可以做到。

不确定这个,你可以检查,如果你可以传递参数,即部分路径到EL中的方法,其余的可以在方法内部处理,如构造完整路径,附加当前区域设置和检查文件是否存在。

希望对你有帮助。

更新(回答评论):

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

相关内容

  • 没有找到相关文章

最新更新