Skip to main content

Changing Locale Dynamically

8 replies [Last post]
martyhall
Offline
Joined: 2009-08-19

I am confused about how you are supposed to go about changing the Locale. I am doing this:

<f:view locale="#{person.locale}">
<f:loadBundle basename="messages" var="msgs"/>

<h:form>
<h:commandButton value="#{msgs.switchLanguage}"
actionListener="#{person.swapLocale}"
immediate="true"/>
</h:form>

</f:view>

The swapLocale method swaps the Locale to/from English and Spanish, and my Person bean is session-scoped. The problem is that when I press the button, I don't get the new Locale until I manually reload the page. I put a trace in the swapLocale and getLocale methods, and I find that when I press the button, the getLocale method is called, then the swapLocale method is called, and the getLocale method is not called again after the call to swapLocale. I get the same behavior with action instead of actionListener, and also if I remove immediate="true".

Clearly, I am misunderstanding the lifecycle here. Why isn't getLocale being called after the actionListener method [swapLocale]? What is the proper way to let the user set the Locale?

Thanks!

- Marty
-----
JSF 2.0 Training Course: http://courses.coreservlets.com/public-courses/jsf2/

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
nash_era
Offline
Joined: 2010-05-10

hi,
here is another retake on the whole issue. Since locale change is a cross cutting concern, its better that locale is set for pages without the page doing anything special for it. Plus the "do both" approach is bit bothersome. If you add a page in future,where you forget to add f:view, then again you will have locale problem for that page. Here is a clean approach to change locale,I did the following:

1)remove all f:view tags from pages(also remove f:loadBundle from pages).

2)change the swapLocale() method to:

public void swapLocale(ActionEvent event) {
System.out.println("[swapLocale] Setting isEnglish to " + !isEnglish);
isEnglish = !isEnglish;

//FacesContext.getCurrentInstance().getViewRoot().setLocale(isEnglish?ENGLISH:SPANISH);
}

notice that i commented out the last statement in the method. So now, its no longer the responsibility of a page to set locale.

3) getLocale() method as before, no changes in it:
public Locale getLocale() {

if (isEnglish) {

return (ENGLISH); // ENGLISH is Locale.ENGLISH
} else {

return (SPANISH);// SPANISH is Locale("es")
}
}

4)Here's the interesting part:
register a listener:

coreservlets.LifeCycleListener

the listener code is:
import java.util.Locale;
import javax.el.ELContext;
import javax.el.ValueExpression;
import javax.faces.application.Application;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

public class LifeCycleListener implements PhaseListener {

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

public void beforePhase(PhaseEvent event) {
System.out.println("START PHASE " + event.getPhaseId());
FacesContext context;

if (event.getPhaseId() == PhaseId.RENDER_RESPONSE) {
context = event.getFacesContext();
Application application = context.getApplication();
ValueExpression ve = application.getExpressionFactory().
createValueExpression(context.getELContext(),
"#{person.locale}", Locale.class);
try {
System.out.println("setting locale in phase listener");
Locale localeToSet = (Locale) ve.getValue(context.getELContext());
context.getViewRoot().setLocale(localeToSet);

} catch (Exception e) {
System.out.println("error in getting locale");
}
}

}

public void afterPhase(PhaseEvent event) {
System.out.println("END PHASE " + event.getPhaseId());
}
}

In the above code, the if block is run just before any page is rendered. Here we, take the #{person.locale} value and set it as UIViewRoot locale.

So now, no code in any page to change locale, no worry about forgetting the f:view, plus no dual setting.

Dont know, if you have read this http://balusc.blogspot.com/2006/09/debug-jsf-lifecycle.html . Its quite a informative article.

ps:am from india

scorpioy
Offline
Joined: 2009-01-16

In the last part of your post, you said '... Here we, take the #{person.locale} value and set it as UIViewRoot locale.' what exactly is this UIViewRoot locale should I use to replace "#{person.locale}"?

I use another way to retrieve the current locale setting, which is stored in session. But now the problem is I cannot get the correct English/French link to be displayed without refreshing the page first. It looks like it alternately creates the correct and wrong links, the wrong link means the method bundled with actionListener in the command link is not called.

Please help, thanks.

Message was edited by: scorpioy

nash_era
Offline
Joined: 2010-05-10

hi, actually i was replying to the example given by Mr. Marty Hall. So it may have seemed incomplete to you.

Anyways, the user presses a whose actionListener is #{person.swapLocale}. Now the swapLocale methods only duty is to change locale field in person bean like this:

@ManagedBean
@SessionScoped
public class Person implements Serializable {

private String firstName, lastName, emailAddress;
private boolean isEnglish = true;
private final Locale ENGLISH = Locale.ENGLISH;
private Locale SPANISH = new Locale("es");

//getters and setters for firstName, lastName, emailAddress

public void swapLocale(ActionEvent event) {
System.out.println("[swapLocale] Setting isEnglish to " + !isEnglish);
isEnglish = !isEnglish; //swap boolean field isEnglish

}
}

notice the above method just sets the boolean field to true or false.

Now there is another method in person bean:

@ManagedBean
@SessionScoped
public class Person implements Serializable {
.
.
.

public Locale getLocale() {

if (isEnglish) {

return (ENGLISH);
} else {

return (SPANISH);
}
}

}

so basically #{person.locale} which runs the getLocale() method returns the currently selected locale.

So finally in the phaselistener beforePhase method, the #{person.locale} is read, and set as the current locale.

Now for any page in the application, whether the page swaps locale or not, the phaselistener beforePhase will check the #{person.locale} value and set it as current locale. This was done as the original post had problem with locale when a page was refreshed/reloaded in the browser.

martyhall
Offline
Joined: 2009-08-19

Well, thanks to some advice from folks here (thanks nash_era!), here is what I finally did:

Problem:
- Calling setLocale on the view
*** Triggered when you reload the page
*** [b]Not [/b]triggered when you redisplay the page after running action listener (or submitting form)
- Setting the Locale of the UIViewRoot
*** Triggered when you redisplay the page after running action listener (or submitting form)
*** [b]Not [/b]triggered when you reload the page (Because the Locale is reset to the default)

Solution
- Do both!

So, in the ActionListener I did this:
public void swapLocale1(ActionEvent event) {
switchLocale();
}

private void switchLocale() {
isEnglish = !isEnglish;
Locale newLocale;
if (isEnglish) {
newLocale = ENGLISH;
} else {
newLocale = SPANISH;
}
FacesContext.getCurrentInstance().getViewRoot().setLocale(newLocale);
}

Then, in the facelets page, I did this:

I also added this to the FormSettings class:
public Locale getLocale() {
if (isEnglish) {
return(ENGLISH);
} else {
return(SPANISH);
}
}

As I said before, doing either one (setting the UIViewRoot or using f:view with the locale attribute) was not enough. The above works, but it feels like a kludgy hack to me. I strongly suspect that I am misunderstanding the JSF lifecycle, and if I understood it properly, I would have a simpler approach.

Any suggestions? BTW, I put a ZIP of the above project in http://coreservlets.com/temp/ in case anyone wants to try it out or look at my code.

Thanks!

- Marty
------
JSF 2.0 Training: http://courses.coreservlets.com/public-courses/jsf2/

fatabangi007
Offline
Joined: 2010-06-04

Changing the locale in the Browser perfectly changes the locale used but only changing it dynamically seems not to work at all. It looks as if the locale given from the Browser has more priority and overwrites always the configured locale in the session.I am using CRS (Crystal Reports Server) 2008 v1 and we are viewing reports through a JSP application deployed on the built-in Tomcat server that comes with CRS. I would like to know how to dynamically change the locale of reports through this application (i.e. through Java). The below code seems to work fine for CRS XI but it is not working in CRS 2008 v1. The locale seems to get stuck to Swedish regional settings in my case!

[url=http://www.carpartswarehouse.com/carmodels/CP18/Mercedes_Benz/300D.html]Mercedes Benz 300D Parts[/url]

Message was edited by: fatabangi007

martyhall
Offline
Joined: 2009-08-19

PS Just in case anyone wants to try out my actual code, I exported my Eclipse project as a ZIP file, and you can grab it from http://www.coreservlets.com/temp/. I included the JAR files in WEB-INF/lib, so you can deploy on any servlet 2.5 container. Deploy it and run http://localhost/event-handling/.

The key behavior to observe is that when you press the button, the getLocale method (called because of the attribute on f:view) is called before the swapLocale method (the button's actionListener).

Cheers-

- Marty

nash_era
Offline
Joined: 2010-05-10

Hi,
If we use swapLocale() like this, it works

public void swapLocale(ActionEvent e) {

if (locale == Locale.ENGLISH) {
locale = new Locale("es");
FacesContext.getCurrentInstance().getViewRoot().setLocale(locale);
} else {
locale = Locale.ENGLISH;
FacesContext.getCurrentInstance().getViewRoot().setLocale(locale);
}
}

(Now, we don't even need the locale attribute on f:view)

even i am curious about the lifecycle issue in your code. Hopefully somebody will put some light on it.
=====
Plus one more thing, jsf 2: complete reference says f:loadBundle is not recommended, use

com.all.Messages
msgs

reason given is( from the complete reference book): Previous versions of JSF recommended using the f:loadBundle tag. Since JSF 1.2, this is no longer recommended because it fails in the case of Ajax and partial page updates because the f:loadBundle tag may not always be executed in those cases.

martyhall
Offline
Joined: 2009-08-19

> If we use swapLocale() like this, it works
>
> public void swapLocale(ActionEvent e) {
> if (locale == Locale.ENGLISH) {
> locale = new Locale("es");
> FacesContext.getCurrentInstance().getViewRoot().setLocale(locale);
> } else {
> locale = Locale.ENGLISH;
> FacesContext.getCurrentInstance().getViewRoot().setLocale(locale);
> }
> }
> (Now, we don't even need the locale attribute on
> f:view)

Actually, I should have mentioned in my original post that I had already tried this. I just went back and tried it again to be sure.

If I do the above but STILL set the attribute on f:view, I have the exact same behavior as before: when I press the button it calls getLocale BEFORE calling swapLocale, and I don't see the right properties until I manually reload the page.

If I do the above but WITHOUT setting the attribute on f:view, the properties never change at all.

> even i am curious about the lifecycle issue in your
> code. Hopefully somebody will put some light on it.

I also went back and carefully tested my original version as posted above, restarting the server and closing all browsers to be sure no session data was cached. Same behavior: pressing the button results in calling getLocale [b]first [/b]and swapLocale [b]second[/b].

I had assumed that I was just totally misunderstanding the lifecycle. I suppose it is also possible that there is a Mojarra bug. Just in case, I am running Mojarra 2.0.2 (FCS b10) on JDK 1.6.0.15 on Windows.

> Plus one more thing, jsf 2: complete reference says
> f:loadBundle is not recommended, use
>
> com.all.Messages
> msgs
>

>
> reason given is( from the complete reference book):
> Previous versions of JSF recommended using the
> f:loadBundle tag. Since JSF 1.2, this is no longer
> recommended because it fails in the case of Ajax and
> partial page updates because the f:loadBundle tag may
> not always be executed in those cases.

Hmm. Who am I to argue with Burns and Schalk? I have that book sitting right in front of me but hadn't read that part yet. This advice is news to me, so I will take it into account in the future. However, I am a little confused by the book example, since the advice you quote is at the top of page 286, yet the diagram at the bottom of page 287 still uses f:loadBundle. I am guessing the 287 diagram is just a leftover from the previous edition that they forgot to update.

Thanks for the tip!