JSF 2 中每个语言环境的不同 Facelet(用于模板)

2024-02-18

我在某个地方有一个模板<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 组件,以便填充表或对话框的示例看起来与视图本身中的相同。

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

我确实找到了这个相关问题 https://stackoverflow.com/questions/6497886/correct-way-to-do-internationalization-in-jsf-for-full-html-pages,但这似乎只有在我不想注入纯 HTML 内容时才有用。我想包含带有 JSF 内容的页面,以便它们实际上由 JSF servlet 处理和呈现。


以下是我对您的问题的解决方案。它体积庞大,但已经完成,内容丰富,而且据我所知,是完整的。有了它,您将能够根据当前语言,从带有语言后缀的视图系列中引入必要的视图。

我对你的设置的假设

  1. 您正在处理描述语言的语言环境,即位于Locale.ENGLISH format;
  2. 您选择的语言存储在会话范围的 bean 中;
  3. 您可以将国际化页面保留为以下格式:page.xhtml, page_en.xhtml, page_fr.xhtml, etc;
  4. 默认语言为英语;
  5. Your 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>

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

通过添加 _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/ prefix:

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

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JSF 2 中每个语言环境的不同 Facelet(用于模板) 的相关文章

随机推荐