Spring - get all resolvable message keys

Viewed 22950

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?

5 Answers

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.

Related