4

How can I access messages bundle from java code to get message according to current locale?

I tried using @ManagedProperty like below:

@Named 
@SessionScoped 
public class UserBean implements Serializable {

    @ManagedProperty("#{msg}")
    private ResourceBundle bundle;
    
    // ...

    public void setBundle(ResourceBundle bundle) {
        this.bundle = bundle;
    }

}

However, it remains null. It seems that it doesn't work inside a @Named.

This is how I registered the resource bundle in faces-context.xml:

<application>
    
    <message-bundle>validator.messages</message-bundle>
    
    <locale-config>
        <supported-locale>en_US</supported-locale>
        <supported-locale>ua_UA</supported-locale>
    </locale-config>
    
    <resource-bundle>
        <base-name>lang.messages</base-name>
        <var>msg</var>
    </resource-bundle>
    
</application>

updated by author:

I get error

16:29:10,968 ERROR [org.apache.catalina.core.ContainerBase.[jboss.web].[default-host].[/WEBSearchPrime_JB_lang].[Faces Servlet]] (http-localhost-127.0.0.1-8080-1) Servlet.service() for servlet Faces Servlet threw exception: org.jboss.weld.exceptions.IllegalProductException: WELD-000054 Producers cannot produce non-serializable instances for injection into non-transient fields of passivating beans\\n\\nProducer\: Producer Method [PropertyResourceBundle] with qualifiers [@Any @Default] declared as [[method] @Produces public util.BundleProducer.getBundle()]\\nInjection Point\: [field] @Inject private model.UserBean.bundle

note, that I also put Serializable interface

Vasil Lukach
  • 3,658
  • 3
  • 31
  • 40
  • @Jens I tried solution provided by your link but ManagedProperty does not work inside Named bean ... I get error where bundle.getString("_login_error") –  Jan 20 '15 at 13:35
  • NullPointerException –  Jan 20 '15 at 13:53
  • @Jens `context.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, bundle.getString("_login_error"), null));` –  Jan 20 '15 at 14:13

3 Answers3

10

You can't use @javax.faces.bean.ManagedProperty in a CDI managed bean as annotated with @Named. You can only use it in a JSF managed bean as annotated with @ManagedBean.

You need use @javax.faces.annotation.ManagedProperty instead, along with an @Inject. This was introduced in JSF 2.3.

@Inject @javax.faces.annotation.ManagedProperty("#{msg}")
private ResourceBundle bundle;

Noted should be that this gets injected as a @Dependent. So be aware that when you inject this into a @SessionScoped bean, then it would basically become @SessionScoped too and thus stick to the originally injected value forever. So any potential locale changes later on in the session won't be reflected there. If this is a blocker, then you should really inject it into a @RequestScoped or @ViewScoped only, or make use of a @Producer as shown below.

CDI doesn't have native annotations to inject the evaluation result of an EL expression. The CDI approach is using a "CDI producer" with @Produces wherein you return the concrete type, which is PropertyResourceBundle in case of .properties file based resource bundles.

So, if you cannot upgrade to JSF 2.3, then just drop this class somewhere in your WAR:

@RequestScoped
public class BundleProducer {

    @Produces
    public PropertyResourceBundle getBundle() {
        FacesContext context = FacesContext.getCurrentInstance();
        return context.getApplication().evaluateExpressionGet(context, "#{msg}", PropertyResourceBundle.class);
    }

}

With this, can inject it as below:

@Inject
private PropertyResourceBundle bundle;
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • 1
    Make the bean request or application scoped. Resource bundles shouldn't be stored in session. Otherwise you'd need to manually EL-evaluate it or create a wrapper around it. – BalusC Jan 20 '15 at 14:37
  • I can't make it ApplicationScoped because it is 'UserBean' so it must be unique and at the same time something wrong with request scoped relating to other logic... So what do you mean by `manually EL-evaluate it`? Sample or reference? –  Jan 20 '15 at 14:51
  • Just inject the request/application scoped one in turn in the session scoped one. As to "manually EL-evaluate it", the code is just outright in your front in `getBundle()` method. – BalusC Jan 20 '15 at 14:52
  • yea, that's a solution ) –  Jan 20 '15 at 14:59
  • @developer10: that can happen when `@Produces PropertyResourceBundle` is missing or broken in runtime classpath. – BalusC Oct 05 '16 at 11:07
  • I have my `properties` files here (path to the default lang/file): `mavenproject1-web\src\main\resources\com\example\i18n\ButtonLabels.properties` And the path when I browse the `target` folder of my project: `mavenproject1-web\target\classes\com\example\i18n\ButtonLabels.properties` Is this what you referred to as possibly being absent from the classpath? Also note that the very same `.properties` files are being called elsewhere in the app. – developer10 Oct 05 '16 at 11:36
  • @developer10: I was referring to the producer method. It's in first place never invoked, right? So whatever it does with properties files is not relevant to the problem. – BalusC Oct 05 '16 at 11:38
  • Just FYI, I got rid of this error by changing "annotated" to "all" in my beans.xml. Actually, now I'm at the beginning - my resouce-bundle value still doesn't respect newly selected languages/locales, I just moved calling the bundle to a producer class. If you're willing to take a look, here's my original question: http://stackoverflow.com/questions/39870696/after-turning-my-jsf-bean-managedbean-into-a-cdi-bean-named-resource-bund – developer10 Oct 05 '16 at 13:59
2

In addition to BalusC's answer:

Since JSF 2.3 it is also possible to inject a resource bundle defined in faces-config.xml without the use of a producer method. There is a new annotation javax.faces.annotation.ManagedProperty (note it is in the ...annotation package, not the ...bean package) that works with @Inject:

// ...
import javax.faces.annotation.ManagedProperty;
// ...

@Named 
@SessionScoped 
public class UserBean implements Serializable {

  // ...

  @Inject
  @ManagedProperty("#{msg}")
  private ResourceBundle bundle;

  // ...

}
hjoeren
  • 549
  • 1
  • 5
  • 12
  • 1
    If there is an already (historically) good answer, it is (unfortunately) sometimes better to improve the existing answer instead of creating a new one that is more appropriate for newer versions. Stackoverflow users that search for answers (so not users like you that provide answers) are often lazy and just read the most upvoted one and not the most appropriate one. But I upvoted yours since it is for modern versions the better answer! Thanks! – Kukeltje May 19 '19 at 18:08
  • 1
    That's a nice solution, but it doesn't work if the user can change the locale, as the EL expression is only evaluated when the bean is created. – areus Apr 21 '20 at 11:02
0

Perhaps I'm getting something wrong, but actually neither of the solutions provided worked for my use case. So I'm providing another answer that worked for me.

With the answer provided by BalusC I encountered the following problems:

  • As I'm using ajaxbased validation, my beans are @ViewScoped, and must be serializable. As neither ResourceBundle nor PropertyResourceBundle are serializable they can't be injected with @Dependent scope.

  • If I try to change the producer to use @RequestScoped it also fails because ResourceBundle is not proxyable as it defines final methods.

As I'm using JSF 2.3 (JBoss 7.2EAP) I went with the solution provided by Jören Haag and initially it seemed to work. Like the original question, I also have multiple supported locales, and the user can change between locales (in my case ca and es)

The problems I faced with Jören answer are

  • The ResourceBundle returned is evaluated when the bean is created. In his example he is using a @SessionScoped bean. The ResourceBundle will be resolved with the current locale when the session is created. If the user later changes the locale, the ResourceBundle will not get updated. This also happens with @ViewScoped beans if the user can change the language without changing the view.

  • I also encountered another issue with beans that need to preload data with a <f:viewAction>. In this case, the bean is instantiated earlier, so the ResourceBundle gets injected before the user locale is set with <f:view locale="#{sessionBean.locale}">. So If the user browser is using es, but the user changed the locale to ca, the bundle will be loaded with es instead, because the view locale is not set to ca with the sessionBean.locale until the render phase.

To overcome this issues that's the solution that worked for me for the use case of the question using injection would be:

@SessionScoped
public class UserBean implements Serializable {

    @Inject
    @ManagedProperty("#{msg}")
    private Instance<ResourceBundle> bundle;

    // ....

    public void someAction() {
        String message = bundle.get().getString("someLabel");
        // ...
    }

}

As the original question doesn't require injection, only asks how to access a resource bundle with the current locale, a solution without injection, and without the overhead of evaluating EL expression #{msg} every time bundle.get() is called would be:

@SessionScoped
public class UserBean implements Serializable {

    @Inject
    private FacesContext context;

    // ...

    public void someAction() {
        ResourceBundle bundle = context.getApplication().getResourceBundle(context, "msg");
        String message = bundle.getString("someLabel");
        // ...
    }

}
areus
  • 2,880
  • 2
  • 7
  • 17