Download

Get Loom

Internationalization

Using messages

Loom is designed with i18n strongly in mind, which means that all messages must be stored in properties files.

// action classes can just invoke these methods
public Resolution save() {
  info("save.success");
  if (count == 0)
     warn("noEntitiesProcessed");
}

// service classes may invoke them using MessageUtils
public void foo(int param1) {
  MessageUtils.error("validationError").addArgument("param1", param1);
}

To display messages, use a l:messages tag:

<l:messages level="warn"/>
<l:messages level="error"/>

Messages will be serialized on a redirect, in which case they will be displayed on the next page request.

The messages files

Loom messages are stored in a MessagesRepository instance specific to each Locale and configured by MessagesRepositoryFactory. MessagesRepository is very similar to the ResourceBundle class included in the JDK.

If a ResourcesWatchdog class has been configured, any modification to these properties files will trigger a reload. Only changes to files that are not in the classpath will be detected, though.

By default MessagesRepository will use classpath:resources/loom-messages.properties (provided by loom.jar) and /WEB-INF/classes/resources/messages.properties. The latest is where the application is expected to store its own messages.

Note that if a specific Locale is not found the system will fallback to MessagesRepositoryFactory.defaultLocale (set by default to the OS locale).

Using parameters

Messages may include parameters:

loom.validation.dateMaxValueFailed=The value of ${propertyName} is bigger than the maximum allowed (${validator.maxValue.date})

When setting parameter values you must indicate if they should be translated or not:

Message message = new Message();
message.setMessageKey("loom.validation.dateMaxValueFailed");
message.addArg("validator", validator);
message.addTranslatedArg("propertyName", "customer.name");

MessagesRepository.guessString()

The guessString() method is invoked to translate messages: when asked for "customer.manager.name" it will first search the whole string, then "manager.name", then "name". If none is found it will return "[missing: customer.manager.name]" and mark it as not found to skip further searches.

Guess results are calculated just once to improve performance.

i18n and javascript

The browser will get a JSON copy of the messsages bundle for the current user locale. This copy will include:

  • Any key that starts with "loom."
  • Any key contained in MessagesRepositoryFactory.browserMessages.
  • Any key contained in the @BrowserMessages annotation, used in any Action class

Thus, these two examples are equivalent:

spring.xml:

<loom:config>
   <loom:messages browserMessages="payment.commit payment.rollback edit.approve"/>
</loom:config>

MyAction.java

@BrowserMessages({"payment.commit", "payment.rollback", "edit.approve"})
public class MyAction extends AbstractAction {
}

Both will generate a JSON object that can be used at the browser with:

<l:script action="Resources"/> 
<script> 
   alert(loom.messages['payment.commit']);
</script> 

Using i18n from your JSP files

There are several ways to print i18n contents inside your JSP pages:

<!-- Print foo -->
<l:out value="foo" /> 

<!-- Print foo -->
${messages['foo']}

<!-- Print foo if printFoo is true; else, print bar -->
<l:out value="foo" if="${printFoo}" else="bar"/> 
${messages[printFoo? 'foo' : 'bar']}