Enum translations in Spring MVC

I recently came across this requirement but it wasn’t immediately obvious how to implement it. I have an enum in my application which defines the locales supported. For instance you might setup enum values of EN, FR and ES. (if English, French and Spanish are the languages you have translation properties files for) I then wanted to display a dropdown on the user screen with the values of this enum as available languages to select.

First stab
Here is the enum in question and my first go at displaying it.

/**
 * Languages supported with translations for the UI. A user can view the application in one 
 * of these languages.
 * 
 * @author Ben Thurley
 */
public enum SupportedLanguage 
{
  EN, FR, ES;
}

I got this piece of JSP from an answer on Stack Overflow

<form:select path="supportedLanguage">
   <form:options/>
</form:select>

This worked ok except the select dropdown just displayed EN, FR and ES. I needed a way of translating these codes into text for the users current selected language. I did see some solutions suggesting doing this in the controller. You could read all values and create a custom map of options with the right piece of translated text. I didn’t like the idea of this though as it was adding translation logic to the controller when for every other element it’s taken care of by Spring. There had to be another way and as it turned out, there was.

Spring custom formatter
Spring MVC allows us to write our own custom formatters. I just needed one that would take my enum code and format it to the translated string equivalent for the users locale.

This is what I ended up with:

public class SupportedLanguageFormatter implements Formatter<SupportedLanguage>
{

  @Resource
  private MessageSource messageSource;
	
  @Override
  public String print(SupportedLanguage object, Locale locale) 
  {
    // Lookup message translation, no args, default of English
    return messageSource.getMessage("supported.language." + object.name().toLowerCase(), null, "English", locale);
  }

  @Override
  public SupportedLanguage parse(String text, Locale locale) throws ParseException 
  {
    return SupportedLanguage.parse(text);
  }
}

This formatter does a two way translation. The print method takes a SupportedLanguage and looks up the relevant translated String. This is easily located using the injected MessageSource. For the reverse operation the parse method takes in a code from the UI (such as EN or FR) and returns an instance of the SupportedLanguage enum.

I tweaked the enum a bit so the parsing was encapsulated within the SupportedLanguage enum. Here’s the final version.

public enum SupportedLanguage 
{
  EN, FR, ES;

  public static List<SupportedLanguage> list() 
  {
    return Arrays.asList(values());
  }
	
  public static SupportedLanguage parse(String value)
  {
    SupportedLanguage lang = null;
    	
    for(SupportedLanguage test : list())
    {
      if(test.name().equalsIgnoreCase(value))
      {
        lang = test;
    	break;
      }
    }
    	
    return lang;
  }  
}

We then register the new formatter with spring.

  <mvc:annotation-driven conversion-service="conversionService">
  </mvc:annotation-driven>

  <!-- Custom formatters -->
  <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="formatters">
      <set>
        <bean class= "org.languagetool.web.formatter.SupportedLanguageFormatter" />
      </set>
    </property>
  </bean>

Now the SupportedLanguage enum is ready to be used. I did this by registering a ModelAttribute in the controller.

@Controller
@RequestMapping(value="admin/users")
@SessionAttributes({"userForm", "authoritiesList", "supportedLanguagesList"})
public class UserController
{
  ...
  @ModelAttribute("supportedLanguagesList")
  public List<SupportedLanguage> getAllSupportedLanguages() 
  {
    return SupportedLanguage.list();
  }  
  ...
}

This can now be used in the JSP as a set of options, I also added in an empty option if no language is selected on a new user form.

<form:select path="supportedLanguage">
  <spring:message code="option.select" var="selectOptionText" />
  <form:option value="" label="${selectOptionText}" />
  <form:options items="${supportedLanguagesList}" />
</form:select>

That’s almost it, now you just have to remember to add the translations to the relevant properties files.

# Languages
supported.language.en=English
supported.language.es=Spanish
supported.language.fr=French