Spring - get all resolvable message keys
Solution 1
Well, I "solved" my problem by extending ResourceBundleMessageSource.
package org.springframework.context.support;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Set;
public class ExposedResourceBundleMessageSource extends
ResourceBundleMessageSource {
public Set<String> getKeys(String basename, Locale locale) {
ResourceBundle bundle = getResourceBundle(basename, locale);
return bundle.keySet();
}
}
Now I have access to the keys, but I have to do an ugly cast in my controller, in addition to having to specify the message source basename. Ugh, that's a lot of coupling.
Set<String> keys =
((ExposedResourceBundleMessageSource)
messageSource).getKeys("messages", locale);
Solution 2
I have solve this issue.
public class ExposedResourceBundleMessageSource extends
ResourceBundleMessageSource {
public static final String WHOLE = "whole";
private Set baseNames;
private Map> cachedData = new HashMap>();
public Set getKeys(String baseName, Locale locale) {
ResourceBundle bundle = getResourceBundle(baseName, locale);
return bundle.keySet();
}
public Map getKeyValues(String basename, Locale locale) {
String cacheKey = basename + locale.getCountry();
if (cachedData.containsKey(cacheKey)) {
return cachedData.get(cacheKey);
}
ResourceBundle bundle = getResourceBundle(basename, locale);
TreeMap treeMap = new TreeMap();
for (String key : bundle.keySet()) {
treeMap.put(key, getMessage(key, null, locale));
}
cachedData.put(cacheKey, treeMap);
return treeMap;
}
public Map getKeyValues(Locale locale) {
String cacheKey = WHOLE + locale.getCountry();
if (cachedData.containsKey(cacheKey)) {
return cachedData.get(cacheKey);
}
TreeMap treeMap = new TreeMap();
for (String baseName : baseNames) {
treeMap.putAll(getKeyValues(baseName, locale));
}
cachedData.put(cacheKey, treeMap);
return treeMap;
}
public void setBasenames(String[] basenames) {
baseNames = CollectionUtils.arrayAsSet(basenames);
super.setBasenames(basenames);
}
}
Solution 3
My solution:
use spring
<util:properties />
<util:properties id="message" location="classpath:messages.properties" />
Add an interceptor, with 'message' set to request attributes
request.setAttribute("msgKeys", RequestContextUtils.getWebApplicationContext(request).getBean("message"));
In JSP, use the
msgKeys
to retrieve message through<fmt:message />
tagvar msg = {<c:forEach items="${msgKeys}" var="m" varStatus="s"> <c:set var="key" value="${fn:substringBefore(m,'=')}"/> "${fn:substringAfter(key)}":"<fmt:message key="${key}"/>", </c:forEach>};
(Of course you need to further escape the output to match js string.)
So, for properties
app.name=Application Name
app.title=Some title
would output
var msg = { "name":"Application Name", "title":"Some title" };
The good thing about that is that you can do some more logic in c:forEach
loop.
Solution 4
If you do not want to extend the ResourceBundleMessageSource
class, you can do it like this:
@Component
class FrontendMessageLoader {
import scala.collection.JavaConverters._
@Resource(name = "messageSource")
var properties: ResourceBundleMessageSource = _
@PostConstruct def init(): Unit = {
val locale = new Locale("en", "US")
val baseNames: mutable.Set[String] = properties.getBasenameSet.asScala
val messageMap: Map[String, String] =
baseNames.flatMap { baseName =>
readMessages(baseName, locale)
}.toMap
}
}
private def readMessages(baseName: String, locale: Locale) = {
val bundle = ResourceBundle.getBundle(baseName, locale)
bundle.getKeys.asScala.map(key => key -> bundle.getString(key))
}
}
This is Scala but you get the idea and you can easily convert it to Java if needed. The point is that you get the base-names (which are the property files) and then load those files and then you can get the keys.
I am passing also locale, but there are multiple overloaded versions for ResourceBundle.getBundle
and you can choose the one that matches your needs.
Eero Heikkinen
Updated on May 25, 2020Comments
-
Eero Heikkinen almost 4 years
I have a web application which uses Spring MVC. I would like to have my interface consist of just a single page which retrieves all data dynamically as JSON via AJAX. My problem is with internationalization. When I render content in jsps, I can use JSTL tags to resolve my keys (super-easy with Spring MVC):
<fmt:message key="name"/>: ${name} <fmt:message key="title"/>: ${title} <fmt:message key="group"/>: ${group}
When properly configured, it renders in finnish locale as
Nimi: yay Otsikko: hoopla Ryhmä: doo
Now, when I use json, I have only this coming in from the server:
{ name: "yay", title: "hoopla", group: "doo" }
There's no keynames! But I have to provide them somehow. I considered changing the keynames to their localized forms or adding the localized keynames to json output (eg. name_localized="Nimi") but both of these options feel like bad practice. I'm using jackson json to automatically parse my domain objects into json and I like the encapsulation it provides.
The only feasible solution I came up with is this: dynamically create a javascript file with the localized keynames as variables.
<script type="text/javascript"> var name="Nimi"; var title="Otsikko"; var group="Ryhmä"; </script>
Once I have this loaded, I now have all the information in my javascript to handle the json! But there's a gotcha: my list of field names is dynamic. So actually static rendering in jsp would look like this:
<c:forEach var="field" values="${fields}"> <fmt:message key="${field.key}"/>: ${field.value} </c:forEach>
I need to find all of the messages specified in my messages.properties. Spring MessageSource interface only supports retrieving messages by key. How can I get a list of keynames in my JSP which renders the localized javascript variables?
-
Patrick over 11 yearsWell, you can clear up a little bit of the ugliness by making a custom message source be the project's default. Hard to fit it all in just a comment, but
<bean id="messageSource" class="com.yourproj.support.ExposedResourceBundleMessageSource">
in your config and then@Autowired private ExposedResourceBundleMessageSourcemessageSource;
in your class. -
JBCP over 11 yearsThis is a good solution because of the addition of caching and capturing the basenames automatically.
-
JBCP over 11 yearsHowever, using locale.getCountry() might be a problem if you support multiple languages within the same country. Just using locale itself is probably safer.
-
Adam Gent about 6 yearsFor those looking for how you should get the
Locale
you probably want to useLocaleContextHolder.getLocale()
. This will get the Locale bound to the thread which is typically what you want in a servlet/spring mvc environment.