如何在JSF 2.0中更改语言环境?

10
在我的应用程序中,用户应该能够切换区域设置(用于呈现页面文本的语言)。许多教程都使用FacesContext.getCurrentInstance().getViewRoot().setLocale()。例如:http://www.mkyong.com/jsf2/jsf-2-internationalization-example/。但是,在JSF 2.0中简单地使用这种方式无法实现(在1.2中可以)。语言永远不会切换。没有错误或任何提示。相同的代码在JSF 1.2中可以正常工作。
什么是正确和最终方案?我拼凑了一个解决方案,但不确定是否正确。这个方案运行良好。用户点击英语或法语后,语言会切换。以下是一些代码片段,以便让您了解一些想法。
@ManagedBean(name = "switcher")
@SessionScoped
public class LanguageSwitcher {
    Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
    public String switchLocale(String lang) {
        locale = new Locale(lang);

        return FacesContext.getCurrentInstance().getViewRoot().getViewId() +
            "?faces-redirect=true";
    }
    //getLocale() etc. omitted for brevity
}

XHTML的格式:

<f:view locale="#{switcher.locale}">
    <h:outputText value="#{msg.greeting}" />
    <h:commandLink value="English" action="#{switcher.switchLocale('en')}" />
    <h:commandLink value="French" action="#{switcher.switchLocale('fr')}" />
</f:view>

仅为了给您更多信息,这是配置文件。

<application>
    <locale-config>
        <supported-locale>en</supported-locale>
        <supported-locale>fr</supported-locale>
    </locale-config>
    <resource-bundle>
        <base-name>com.resources.Messages</base-name>
        <var>msg</var>
    </resource-bundle>
</application>

这次工作了。但是,我没有以任何方式调用任何API更改JSF本身的区域设置。这让我感到有点毛骨悚然。这是更改用户区域设置的正确方法吗?


如果您希望被理解,那么请更加具体地描述问题,而不是使用"无法工作"和"可以工作"这样的泛泛之言。请问您具体的测试用例是什么?"更改语言环境"到底是指什么?(更改哪个语言环境?) - meriton
3
请看BalusC(JSF专家)的回答:JSF区域设置是每个请求独立设置的,与会话无关 - Luiggi Mendoza
@Luiggi,这基本上就是我在我的解决方案中所做的。除了我不明白为什么他们都要调用FacesContext.getCurrentInstance().getViewRoot().setLocale(locale);,因为它似乎什么也没做。 - RajV
2
@RajV 在我看来,Bauke Scholtz(又名BalusC)在他的回答和提供的进一步解释链接(也是由他制作的)中都非常清晰。 - Luiggi Mendoza
谢谢你的帖子。在添加支持的语言环境后,我的问题得到了解决。 - makkasi
显示剩余2条评论
3个回答

9

好的,冒着自问自答的风险,我想总结一下我找到的所有不同方法。

基本方法就是我已经在做的。也就是说,在会话范围内有一个托管 bean 返回用户的区域设置。这个区域设置需要在每个使用 <f:view locale="..."> 的 XHTML 中使用。我从 BalusC 的一篇文章中学到了这个技巧,所以要感谢他。

现在,问题是 f:view 元素的使用。如果错误地省略了它,那么每个页面都需要重复添加,这可能导致缺陷。我发现了一些解决这个问题的方法。

方法 #1:创建一个 Facelet 模板并在其中添加 f:view 元素。单独的模板用户页面不必担心添加此元素。

方法 #2 使用一个阶段侦听器。@meriton 在这里发布了解决方案。谢谢你。

方法 #3 使用一个自定义视图处理器,它扩展了 MultiViewHandler 并从 calculateLocale() 方法返回用户的区域设置。这在《Beginning JSF 2 APIs and JBoss Seam By: Kent Ka Iok Tong》一书中有描述。这是一本略有改动的书中的示例:

public class MyViewHandler extends MultiViewHandler {
    public Locale calculateLocale(FacesContext context) {
        HttpSession session = (HttpSession) context.getExternalContext()
            .getSession(false);
        if (session != null) {
            //Return the locale saved by the managed bean earlier
            Locale locale = (Locale) session.getAttribute("locale");
            if (locale != null) {
                return locale;
            }
        }
       return super.calculateLocale(context);
    }
}

然后在faces config中注册它。
<application>
    <view-handler>com.package.MyViewHandler</view-handler>
<!-- Other stuff ... -->
</application>

这种方式比阶段监听器更加优雅。不幸的是,MultiViewHandler是来自com.sun.faces.application.view包的内部非API类。这将带来一些风险。

使用方法2或方法3,页面中无需添加f:view元素。


2
现在,问题是使用f:view元素。这需要在每个页面中重复,如果不小心省略,可能会导致缺陷的潜在来源。只需使用Facelets模板? - BalusC

2

可以使用自定义视图处理程序来扩展javax.faces.application.ViewHandlerWrapper,并从calculateLocale()方法返回用户的语言环境。

这绝对比扩展专有的SUN包com.sun.faces.application.view中的MultiViewHandler要好,无论您在建议中提到的书籍Beginning JSF 2 APIs中描述了什么。除此之外,您最初的方法是完全正确的:

public class MyViewHandler extends ViewHandlerWrapper {

  public Locale calculateLocale(FacesContext context) {
    HttpSession session = (HttpSession) context.getExternalContext()
        .getSession(false);
    if (session != null) {
      //Return the locale saved by the managed bean earlier
      Locale locale = (Locale) session.getAttribute("locale");
      if (locale != null) {
        return locale;
      }
    }
    return super.calculateLocale(context);
  }
}

然后在faces配置中注册它。
    <application>
      <view-handler>com.package.MyViewHandler</view-handler>
      <!-- Other stuff ... -->
    </application>

在这种情况下,您如何实现getWrapped()方法? - sola

1

我最近遇到了一个相关的问题。在我的情况下,JSF实现在导航到不同视图后忘记了UIViewRoot.setLocale()设置的视图语言环境。我认为这是JSF实现中的一个错误,但我没有时间确认。

我并不特别喜欢<f:view>的方法,因为该标签已被facelets淘汰 - 除了保留语言环境外,似乎没有其他作用。这让我对将其包含在Facelets模板中感到犹豫。因此,我编写了以下PhaseListener:

/**
 * PhaseListener that keeps the current view locale in the session while no request is being processed, to work around
 * bugs where JSF forgets the changed locale.
 */
public class SaveViewLocaleToSessionPhaseListener implements PhaseListener {

    private static final String key = "locale";

    @Override
    public PhaseId getPhaseId() {
        return PhaseId.ANY_PHASE;
    }

    @Override
    public void beforePhase(PhaseEvent event) {
        // do nothing
    }

    @Override
    public void afterPhase(PhaseEvent event) {
        PhaseId currentPhase = event.getPhaseId();
        if (currentPhase == PhaseId.RESTORE_VIEW) {
            viewRoot().setLocale((Locale) sessionMap().get(key));
        } else if (currentPhase == PhaseId.RENDER_RESPONSE) {
            sessionMap().put(key, viewRoot().getLocale());
        }
    }

    private  Map<String, Object> sessionMap() {
        return FacesContext.getCurrentInstance().getExternalContext().getSessionMap();
    }

    private UIViewRoot viewRoot() {
        return FacesContext.getCurrentInstance().getViewRoot();
    }
}

然而,我无法提供任何确凿的证据表明这比简单地使用<f:view>更好。

但是,我没有以任何方式更改JSF本身的语言环境。

当然你做了: <f:view>标签从值表达式中读取语言环境,并将其传递给UIViewRoot.setLocale()


我同意使用<f:view>有点奇怪。其他人使用了阶段监听器,这似乎对于框架应该有一个简单的解决方案来说有些过度。让我去在Safari上读几本书,看看他们建议什么。我会很快发布。 - RajV

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接