每个区域设置JSF 2中的不同facelets(用于模板)

时间:2013-02-27 12:34:26

标签: java jsf jsf-2 internationalization facelets

我有一个模板,其中有一个<ui:insert name="help_contents" />和一个定义<ui:define name="help_contents><!-- actual contents --></ui:define>的页面,其中定义中的内容应该是基于JSF的(不仅仅是普通的html / xhtml),由面部处理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来完成?也许为此编写一个自定义标签可能很有用,但我不确定这需要做多少工作。

我确实找到了this related question,但如果我不想注入纯HTML内容,这似乎才有用。我想包含带有JSF内容的页面,以便它们实际上由JSF servlet处理和呈现。

3 个答案:

答案 0 :(得分: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>

使用myResolver方法创建名为@ApplicationScoped的托管bean,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

根据您的需要配置此{{1}}的映射。

答案 1 :(得分:2)

以下是我的问题解决方案。它很笨重,但完成后,信息丰富,据我所知,完成。有了它,您将能够根据当前语言从一系列以语言为后缀的视图中包含必要的视图。

我对您的设置的假设

  1. 您正在处理描述语言的语言环境,即采用Locale.ENGLISH格式;
  2. 您选择的语言存储在会话范围的bean中;
  3. 您将国际化网页保持为以下格式:page.xhtmlpage_en.xhtmlpage_fr.xhtml等;
  4. 默认语言为英语;
  5. 您的FacesServlet已映射到*.xhtml
  6. 我的解决方案的标准设置

    会话范围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/,其中包含internatiaonalised includes:

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

    这样,解析器将根据当前区域设置选择正确的国际化包含视图。

答案 2 :(得分:1)

通过此link @ SO,您可以动态添加内容(查看已选中的答案)。在后备文件中,如果你有一个钩子,你可以适当地设置文件名,我认为这可以做到这一点。

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

希望这有帮助。

更新(回答评论):

是的。您可以查看链接JSF 2 fu, Part 2: Templating and composite components