jjzjj

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

coder 2024-03-31 原文

我在某个地方有一个模板 <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 。

我对您的设置的假设

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

有关java - 每个语言环境的 JSF 2 中的不同 facelets(用于模板)的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  4. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  5. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  6. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  7. ruby-on-rails - Rails 3.2.1 中 ActionMailer 中的未定义方法 'default_content_type=' - 2

    我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer

  8. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  9. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  10. ruby-on-rails - form_for 中不在模型中的自定义字段 - 2

    我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢

随机推荐