6

I am using ReloadableResourceBundleMessageSource to store value list of my system. So i can use the i18N functionality

i am using multiple resources in the basenames of ReloadableResourceBundleMessageSource. I want to pass all the localized labels of the web UI to the client (front-end) in order to be cached locally at the client. Is there a way to load the entire keys of my resource bundles?

here is my ReloadableResourceBundleMessageSource bean definition:

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <property name="basenames">
        <list>
            <value>classpath:resource1</value>
            <value>classpath:resource2</value>
        </list>
    </property>
    <property name="cacheSeconds" value="60"/>
    <property name="defaultEncoding" value="UTF-8"/>
    <property name="useCodeAsDefaultMessage" value="true"/>
</bean>

and this is my controller that pass all the keys:

@Component
@RequestMapping("/bundle")
public class ResourceBundleController {

    @Autowired
    private MessageSource messageSource;

    @RequestMapping(value = "/locales.js")
    public ModelAndView strings(HttpServletRequest request) {

        // Call the string.jsp view
        return new ModelAndView("/WEB-INF/includes/locales.jsp", "keys", ??? );// HERE IS MY PROBLEM. HOW SHOULD I GET ALL THE KEYS FROM MESSAGESOURCE
    }
}

and here is my the resource bundle keys for the client:

<%@page contentType="text/javascript" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="spring" uri="http://www.springframework.org/tags"%>

var messages = {};

<c:forEach var="key" items="${keys}">
    messages["<spring:message text='${key}' javaScriptEscape='true'/>"] = "<spring:message code='${key}' javaScriptEscape='true' />";
</c:forEach>

Any help will be appreciated.

Emad Bayat
  • 177
  • 1
  • 2
  • 12

1 Answers1

8

Updated information about spring configuration regards cache of resource bundle

You could make something like this:

Your own implementation of ReloadableResourceBundleMessageSource:

public class ExposedResourceMessageBundleSource extends ReloadableResourceBundleMessageSource {

    private static final Logger LOGGER = Logger.getLogger(ExposedResourceMessageBundleSource.class);

    @Override
    protected Properties loadProperties(Resource resource, String fileName) throws IOException {

        LOGGER.info("Load " + fileName);
        return super.loadProperties(resource, fileName);
    }

    /**
     * Gets all messages for presented Locale.
     * @param locale user request's locale
     * @return all messages
     */
    public Properties getMessages(Locale locale){
        return getMergedProperties(locale).getProperties();
    }
}

Service definition to handle reasource operations:

public interface MessageResolveService extends MessageSourceAware{
    String getMessage(String key, Object[] argumentsForKey, Locale locale);
    Map<String,String> getMessages(Locale locale);
}

And implementation:

    @Service
    public class MessageResolverServiceImpl implements MessageResolveService{
        private static final Logger LOGGER = Logger.getLogger(MessageResolverServiceImpl.class);

        private MessageSource messageSource;

        @Override
        public void setMessageSource(MessageSource messageSource) {
            LOGGER.info("Messages i18n injected");
            this.messageSource = messageSource;
        }

        public String getMessage(String key, Object[] arguments, Locale locale){
            String message = "";
            try{
                message = messageSource.getMessage(key,arguments,locale);
            } catch(NoSuchMessageException e){
                message = key;
                LOGGER.warn("No message found: "+key);
            }
            return message;
        }

        public Map<String,String> getMessages(Locale locale){
            Properties properties =  ((XmlWebApplicationContext)messageSource).getBean("messageSource",
                    ExposedResourceMessageBundleSource.class).getMessages(locale);
            Map<String,String> messagesMap = new HashMap<String,String>();
            for(Map.Entry<Object,Object> entry: properties.entrySet()){
                if(entry.getKey() != null && entry.getValue() != null) {
                    messagesMap.put(entry.getKey().toString(), entry.getValue().toString());
                }
            }
            return messagesMap;
        }
    }

Spring configuration:

<bean id="messageSource" class="your.package.ExposedResourceMessageBundleSource">
          <property name="basenames">
           <list>
                <value>classpath:yourFileName</value>
                <value>classpath:yourNextFileName</value>
            </list>
          </property>
          <property name="defaultEncoding" value="UTF-8"/>
          <property name="cacheSeconds" value="10"/> //If you want reload message every couple seconds. In this case every 10 seconds.
</bean>

And your @Controller(similar to this):

@Component
@RequestMapping("/bundle")
public class ResourceBundleController {

    @Autowired
    private MessageResolveService messageResolveService;


    @RequestMapping(value = "/locales.js")
    public ModelAndView strings(HttpServletRequest request) {

        // Call the string.jsp view
        return new ModelAndView("/WEB-INF/includes/locales.jsp", "keys", messageResolverService.getMessages(LocaleContextHolder.getLocale()));
}
Paweł Głowacz
  • 2,926
  • 3
  • 18
  • 25
  • Thanks Pawel for the reply:) you are awesome. but i have a problem with reloading the resources. if i change a value from my properties file, the value does not update in the map! also i set the cacheSeconds to 15. regards. – Emad Bayat Aug 02 '15 at 05:48
  • the problem is getMergedProperties(locale).getProperties() does not reload the result. but if i use getMessage(...) it return the correct value. – Emad Bayat Aug 02 '15 at 06:00
  • 1
    i also found a solution in the following link. http://forum.spring.io/forum/spring-projects/container/42714-get-all-content-from-resourcebundlemessagesource but i don't know how to initialize load method of CustomPropertiesPersistor through spring. (in fact i can initialize it when i call a fake getMessage('"",null, Locale.US) but it is not the right way) can you please have a look. warm regards – Emad Bayat Aug 02 '15 at 07:05
  • This is actually very simple. Default `ReloadableResourceBundleMessageSource` has property `cacheSeconds` sets to -1. This mean that it wont reload because it is cached forever. You need to set property `` to reload properties every 10 seconds in your bean in spring configuration. I have just updated an answer. Just tested this and it is working. Remember one thing you need to refresh page to reload messages. – Paweł Głowacz Aug 02 '15 at 10:42
  • 1
    For all who are not using XML Configuration for your beans, but java-configuration, the messageSource is a AnnotationConfigServletWebServerApplicationContext instead of XmlWebApplicationContext – Maarkoize Jun 11 '19 at 09:54