Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting localized message from resourceBundle via annotations in Spring Framework

Is it possible to do this ? Currently it is done like this :

<bean id="resource" class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames">
        <list>
            <value>content.Language</value> 
        </list>
    </property>
</bean>

@Autowired
protected MessageSource resource;

protected String getMessage(String code, Object[] object, Locale locale) {
    return resource.getMessage(code, object, locale);
}

Is there a way for it to be like getting properties via @Value annotation ?

<util:properties id="generals" location="classpath:portlet.properties" />

    @Value("#{generals['supported.lang.codes']}")
    public String langCodes;

Because having to call the method is usually fine, but for instance when unit testing, this is pain in ... ... Well in some cases, webdriver's PageObject pattern where objects don't have no initialization, this would be really helpful

like image 276
lisak Avatar asked Jun 05 '11 22:06

lisak


2 Answers

I believe you mixed two concepts:

  • property files
  • message resource bundles

Property files contains properties (locale independent). In Spring they can be loaded for example via util:properties and can be used in @Value annotations.

But message resource bundles (which are bases on files that look like property files) are language dependend. In Spring you can load them via org.springframework.context.support.ResourceBundleMessageSource. But not inject in a String via @Value. You can not inject them because the @Value injection is done once per bean, the @Value will be evaluated once (most at start time), and the calculated value will be injected. But this is not what you normally need when you use Message Resource Bundles. Because then you need to evaluate the value every time the variable is used, depending on the language of an user.


But you can build it easely by your own!

The only things you need is this class:

import java.util.Locale;    
import javax.annotation.Resource;    
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;

@Configurable
public class MSG {

    private String key;

    @Resource(name = "messageSource")
    private MessageSource messageSource;

    public MSG(String key) {
        super();
        this.key = key;        
    }

    public String value() {
        Locale locale = LocaleContextHolder.getLocale();                        
        return messageSource.getMessage(key, new Object[0], locale);
    }

    @Override
    public String toString() {
        return value();
    }
}

Then you can use it in this way:

@Service
public class Demo {

    @Value("demo.output.hallo")
    private MSG hallo;

    @Value("demo.output.world")
    private MSG world;

    public void demo(){
        System.out.println("demo: " + hello + " " + world);
    }    
}

To get it running, you need to enable <context:spring-configured /> to turn on AspectJ @Configurable support, and (That is importent) you need to instanciate the Ressouce Bundle Message Source in the same application context (for example in web apps you put the ReloadableResourceBundleMessageSource definition in most cases in the web app context, but this does not work in this case, because the MSG object is in the "normal" application context.

like image 123
Ralph Avatar answered Oct 04 '22 18:10

Ralph


The point is that this is really useful only for Unit Testing. In real application, Locale is a runtime information that cannot be hardcoded in the annotation. Locale is decided based on Users locales in Runtime.

Btw you can easily implement this by yourself, something like :

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
public @interface Localize {

    String value();

}

And

public class CustomAnnotationBeanPostProcessor implements BeanPostProcessor {

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }

    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        Class clazz = bean.getClass();
        do {
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(Localize.class)) {
                    // get message from ResourceBundle and populate the field with it
                }
            }
            clazz = clazz.getSuperclass();
        } while (clazz != null);
        return bean;
    }
like image 37
lisak Avatar answered Oct 04 '22 20:10

lisak