In my app, user should be able to switch the locale (the language used to render text on pages). Tons of tutorials are using FacesContext.getCurrentInstance().getViewRoot().setLocale(). For example: http://www.mkyong.com/jsf2/jsf-2-internationalization-example/. But, that simply doesn't work in JSF 2.0 (it did work in 1.2). The language never switches. No errors or anything. The same code worked fine in JSF 1.2.
What is the correct and definitive approach? I have cobbled together a solution, but not sure if this is the correct one. This works fine. The language switches after user clicks on English or French. Here is code snippet to give you some idea.
@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
}
The 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>
Just to give you more info, here is the config file.
<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>
Once again, this works. But, I haven't changed the locale of JSF itself by calling any API in any way. This gives me somewhat of a creepy feeling. Is this the correct way to change user's locale?
OK, at the risk of answering my own question, I will like to summarize all the different approaches that I have found.
The basic approach is what I am already doing. That is, have a managed bean in session scope that returns the Locale of the user. This locale needs to be used in every XHTML using <f:view locale="...">
. I learned this technique from a post by BalusC, so thanks are due there.
Now, the concern is the use of the f:view element. This needs to be repeated in every page, a potential source of defect if omitted by mistake. I have found a couple of ways of solving this problem.
Approach #1: Create a Facelet template and add the f:view element there. Individual template user pages don't have to worry about adding this element.
Approach #2 uses a phase listener. @meriton has posted the solution here. Thank you for that.
Approach #3 uses a custom view handler that extends MultiViewHandler and returns user's locale from the calculateLocale() method. This is described in the book Beginning JSF 2 APIs and JBoss Seam By: Kent Ka Iok Tong. Here is a slightly altered example from the book:
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);
}
}
Then register it in faces config.
<application>
<view-handler>com.package.MyViewHandler</view-handler>
<!-- Other stuff ... -->
</application>
This is somewhat more elegant than the phase listener. Unfortunately, MultiViewHandler is an internal non-API class from the com.sun.faces.application.view package. That incurs some risk going forward.
With either approach #2 or #3, there is no need for the f:view element in the pages.
One can use custom view handler that extends javax.faces.application.ViewHandlerWrapper
and returns user's locale from the calculateLocale() method.
This is definitely better than extending MultiViewHandler
from the proprietary SUN package com.sun.faces.application.view
, no matter what is described in the book Beginning JSF 2 APIs mentioned in your suggestion. Apart from that, your original approach is absolutely OK:
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);
}
}
Then register it in faces config.
<application>
<view-handler>com.package.MyViewHandler</view-handler>
<!-- Other stuff ... -->
</application>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With