There is a JSF application using Primefaces . The application must support a language change at any time. To do this, a combo box with a list of languages ​​in which the user selects the desired language. When selecting, the handler is called, changing the locale with the following code:

FacesContext.getCurrentInstance().getViewRoot().setLocale(locale); 

Everything works fine except for one detail. There is a menu that is not created in XHTML files, but dynamically, programmatically. In simplified form, it looks like this:

 @ManagedBean @SessionScoped public class MenuView implements Serializable { private MenuModel model; public MenuModel getModel() { return model; } @PostConstruct public void init() { model = new DefaultMenuModel(); ResourceBundle messages = ResourceBundle.getBundle("i18n.messages", (Locale) getSession().getAttribute("locale")); DefaultSubMenu submenu = new DefaultSubMenu(messages.getString("Menu 1")); DefaultMenuItem item = new DefaultMenuItem(messages.getString("Item 1")); submenu.addElement(item); model.addElement(submenu); } } 

XHTML contains the following string:

 <p:menu model="#{menuView.model}"> 

So, this menu ignores the language change and remains in the language that was at the time of its creation, which, of course, the user does not like.

How to intercept the locale change event and make the menu appear in the correct language? You can even recreate it, if necessary.


Now done as follows. When changing the language in the combo-box, a handler is called, which, in addition to changing the locale, also calls the static method

 MenuView.localeChanged(); 

The MenuView class acquires additional fields and methods:

 private MenuView instance; @PostConstruct public void init() { // ... instance = this; } public static void localeChanged() { if (instance != null) instance.init(); } 

But I don’t like this approach, because the class managing the locale acquires completely unnecessary knowledge about menu classes, i.e. there is an extra dependence of classes.

Is there any way to do without this dependence?

  • There was a similar problem: I decided that when the language was changed I forced the user to log in again to the system in order to reset the session and re-create the sessionscoped bins. Or, you can change SessionScoped to ViewScoped for a menu bean. But also not the best solutions. - Pavel Parshin
  • @PavelParshin Re-login - not an option, is clearly contrary to the TK. A language change should be done without re-login. And @ViewScoped tried - not recreated. - user194374
  • @PavelParshin Hmm ... I'm a bit of a fool. Not the import set. Took javax.faces.bean.ViewScoped , but it was necessary javax.faces.view.ViewScoped . It has become re-created. But now it is being recreated with every sneeze, which is also not good. - user194374
  • That's why I wrote that it was not the best solution. Alternatively, search for all SessionScoped bins and recreate them when the language is SessionScoped . Because, perhaps, not only the menu will be cached. - Pavel Parshin

2 answers 2

If the mountain does not go to Mohammed, then Mohammed goes to the mountain. In other words, if the framework does not generate a locale change event, then we will generate it ourselves. It turned out the following.

Event class change locale:

 public class LocaleChanged { } 

The class that handles the locale change event:

 import javax.enterprise.event.Event; @javax.inject.Named @javax.enterprise.context.SessionScoped public class LocaleBean implements Serializable { @javax.inject.Inject private Event<LocaleChanged> localeChangedEvent; public void localeChanged(final ValueChangeEvent event) { FacesContext.getCurrentInstance().getViewRoot().setLocale(event.getNewValue().toString())); localeChangedEvent.fire(new LocaleChanged()); } } 

The MenuView class that handles a locale change event:

 import javax.annotation.PostConstruct; import javax.enterprise.event.Observes; @javax.inject.Named @javax.faces.bean.ManagedBean @javax.enterprise.context.SessionScoped @javax.faces.bean.SessionScoped public class MenuView implements Serializable { private MenuModel model; public MenuModel getModel() { return model; } @PostConstruct public void init() { model = new DefaultMenuModel(); ResourceBundle messages = ResourceBundle.getBundle("i18n.messages", (Locale) getSession().getAttribute("locale")); DefaultSubMenu submenu = new DefaultSubMenu(messages.getString("Menu 1")); DefaultMenuItem item = new DefaultMenuItem(messages.getString("Item 1")); submenu.addElement(item); model.addElement(submenu); } private void onLocaleChanged(@Observes final LocaleChanged localeChanged) { init(); } } 

When changing the language, the menu is simply re-created.

Note the list of annotations of the MenuView class. Since the “mixed” technology ( JSF + CDI ) is used, it is necessary to mark the bean as session by two annotations - @javax.enterprise.context.SessionScoped for CDI and @javax.faces.bean.SessionScoped for JSF . Otherwise, the bin will be re-created.

The advantage of this approach is that a bin that changes a locale loses “knowledge” about the objects that need to change when the locale changes. The object simply "subscribes" to this event. This achieves weak connectivity.

    The premature optimization of many has already failed. You cache the state of the menu (by creating it once in the entire session) even before you have achieved its 100% functionality, and this is one of the main problems of premature optimization. Change the controller's visibility level to @RequestScoped , remove the init method along with the @PostConstruct annotation and transfer its contents to getModel() . Let it be formed "from scratch" at each call, but using the current locale.

    PS Then, when your application “bends” from the load (if it survives to such popularity) and you still do load testing and analysis of “narrow” places, then it becomes clear that caching this part is not the highest priority task.

    • Count for the sake of interest how many times getModel is getModel when generating one page. - user194374
    • It doesn’t matter how many times, until the performance and cost of resources are measured. Add code that counts the time of all method calls, and compare this time with the time the page was opened. If it turns out that the method "eats" more than one tenth of a percent - then I'm wrong - bobzer