49

faces-config.xml:

<application>
    <locale-config>
        <default-locale>ru</default-locale>
        <supported-locale>ua</supported-locale>
    </locale-config>
</application> 

In a bean action method, I'm changing the locale in the current view as follows:

FacesContext.getCurrentInstance().getViewRoot().setLocale(new Locale("ua"));

The problem is that ua Locale is applied, but only per request/view and not for session. Another request/view within the same session resets the locale back to default ru value.

How can I apply the locale for session?

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
sergionni
  • 13,290
  • 42
  • 132
  • 189
  • I'm curious, how often is this technique of setting the language used? I have all my browsers in English, is this that reliable as an i18n tech? – treaz Apr 11 '13 at 15:43

5 Answers5

89

You need to store the selected locale in the session scope and set it in the viewroot in two places: once by UIViewRoot#setLocale() immediately after changing the locale (which changes the locale of the current viewroot and thus get reflected in the postback; this part is not necessary when you perform a redirect afterwards) and once in the locale attribute of the <f:view> (which sets/retains the locale in the subsequent requests/views).

Here's an example how such a LocaleBean should look like:

package com.example.faces;

import java.util.Locale;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import javax.faces.context.FacesContext;

@ManagedBean
@SessionScoped
public class LocaleBean {

    private Locale locale;

    @PostConstruct
    public void init() {
        locale = FacesContext.getCurrentInstance().getExternalContext().getRequestLocale();
    }

    public Locale getLocale() {
        return locale;
    }

    public String getLanguage() {
        return locale.getLanguage();
    }

    public void setLanguage(String language) {
        locale = new Locale(language);
        FacesContext.getCurrentInstance().getViewRoot().setLocale(locale);
    }

}

And here's an example of the view should look like:

<!DOCTYPE html>
<html lang="#{localeBean.language}"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:h="http://java.sun.com/jsf/html">
<f:view locale="#{localeBean.locale}">
    <h:head>
        <title>JSF/Facelets i18n example</title>
    </h:head>
    <h:body>
        <h:form>
            <h:selectOneMenu value="#{localeBean.language}" onchange="submit()">
                <f:selectItem itemValue="en" itemLabel="English" />
                <f:selectItem itemValue="nl" itemLabel="Nederlands" />
                <f:selectItem itemValue="es" itemLabel="Español" />
            </h:selectOneMenu>
        </h:form>
        <p><h:outputText value="#{text['some.text']}" /></p>
    </h:body>
</f:view>
</html>

Which assumes that #{text} is already configured in faces-config.xml as below:

<application>
    <resource-bundle>
        <base-name>com.example.i18n.text</base-name>
        <var>text</var>
    </resource-bundle>
</application>

Note that <html lang> is not required for functioning of JSF, but it's mandatory how search bots interpret your page. Otherwise it would possibly be marked as duplicate content which is bad for SEO.

See also:

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • shouldn't f:view go inside the body tag ? – Kemoda Aug 26 '13 at 06:16
  • @Kemoda: your choice. Under the covers, properties of `` go in `UIViewRoot` which is the root component. – BalusC Aug 26 '13 at 11:00
  • @BalusC, can you please fix code in your main article? People again and again 'stepping on a rake' especially inexperienced programmers. Thank you. – Anatoly May 21 '14 at 08:12
  • @BalusC One additional question. Is it mandatory to edit faces-config.xml this way http://pastebin.com/uEm9eYVg – Peter Penzov Oct 24 '15 at 15:40
  • Where is used actually the method getLanguage or what is the point of having it in the bean? – karlihnos Jun 29 '17 at 11:42
  • @karlihnos Read last paragraph of answer. – BalusC Jun 29 '17 at 11:45
  • @BalusC if you mean the paragraph with , then this is calling an action localeBean.language which is not present in the java class, shouldn't be localeBean.getLanguage? – karlihnos Jul 21 '17 at 13:09
  • @karlihnos Where exactly did you learn that it works like that? – BalusC Jul 21 '17 at 13:56
  • @BalusC sorry, I think I am missing something. localeBean.language looks for the property language and it is not present in the Bean, correct? My last comment is not accurate, to call a method should be bean.method(). https://stackoverflow.com/questions/6594920/calling-methods-from-jsf-page-doubts – karlihnos Jul 21 '17 at 14:10
  • @karlihnos The property is definitely present in bean. It's represented by `public String getLanguage() {}`. If you're having problems with getting your code to work, it's caused elsewhere and you'd better ask a new question. – BalusC Jul 21 '17 at 15:44
  • Question: are `` (on the XHTML) and `FacesContext.getCurrentInstance().getViewRoot().setLocale(locale);` (on Bean `setLanguage()` somewhat redundant? Do I *need* to set both for a working locale switching or would _either_ work? I've noticed in your book you did not use `setLocale()`, and it seems to work great. – MestreLion Jul 07 '20 at 13:21
  • 1
    @MestreLion: see 1st paragraph of the answer. It depends on whether you reuse or recreate the view. If you reuse the view, then you need to manually set it on the view. If you recreate the view, then it will automatically obtain it from the bean. – BalusC Jul 07 '20 at 13:24
4

I see that the problem is also with .properties file name. Java Locale us codes (lowercase) like: en_gb But automaticly created locale (by Netbeans) is lowercase_uppercase i.e.: messages_en_GB.properties Change name to: messages_en_gb.properties and it should work - if you tried everything

Sebastian
  • 41
  • 1
3

This component f:view is not there your JSF page it will not work and It will shows only default english language.Provide the localae value for this f:view component then it will work fine. I faced the same problem now its working fine.

3

One small remark to @BalusC great solution. If we have <f:viewAction> which executes some method in backing bean. Locale available from call to FacesContext.getCurrentInstance().getViewRoot().getLocale() inside that method would be locale that is set by user browser or default application locale, not that locale that is set on session bean by user selection(of course they can match if browser locale equals that locale that user selected).

I can stand corrected, because maybe I did something wrong when implementing solution provided by @BalusC.

EDIT. After playing with JSF lifecycle, this behavior with locale is not related to <f:viewAction>, because there is similar behavior also with @PostContruct. <f:view locale="#{localeBean.locale}"> in request(after user selected locale) is executed in render response phase. <f:viewAction> and @PostContruct methods are executed in invoke application phase. That is why logic that is executed in this method do not have access to user selected locale.

Solution that we using when we need correct locale is to inject(CDI) localeBean in other backing bean that contains <f:viewAction> and @PostContruct methods, and then set locale with UIViewRoot#setLocale() from localeBean in beginning of these methods.

Znas Me
  • 190
  • 1
  • 15
  • This is a comment and not an answer! Please gain enough reputation and comment on the answer. – Kukeltje Dec 24 '16 at 15:06
  • @Kukeltje probably you are right, but when and if I get enough reputation I will probably forget about this issue. From other side and yours experience with Jsf, you could verify is there any issue with `f:viewAction` that I mention here. – Znas Me Dec 25 '16 at 18:42
0

If you can use CDI and deltaspike (JSF module) in your environment, you could add the following to your LocaleBean to automatically reset the locale on the current view:

@javax.enterprise.context.SessionScoped
public class LocaleBean implements Serializable {

    ...

    public void resetLocale(@Observes @BeforePhase(JsfPhaseId.RENDER_RESPONSE) PhaseEvent event) {
        event.getFacesContext().getViewRoot().setLocale(this.locale);
    }
}
Xavier Dury
  • 1,530
  • 1
  • 16
  • 23