Implementing Internationalization (i18n)

1. Configuring Message Sources (MessageSource)

Example: Configure reloadable message source

@Bean
MessageSource messageSource() {
  ReloadableResourceBundleMessageSource ms = new ReloadableResourceBundleMessageSource();
  ms.setBasename("classpath:messages");
  ms.setDefaultEncoding("UTF-8");
  ms.setCacheSeconds(60);
  ms.setFallbackToSystemLocale(false);
  return ms;
}

2. Creating Message Properties Files

File Locale
messages.properties Default fallback
messages_en.properties English
messages_fr.properties French
messages_es_MX.properties Spanish (Mexico)

Example: Message bundle with plural form

greeting=Hello, {0}!
items.count={0,choice,0#no items|1#one item|1<{0,number,integer} items}

3. Creating Locale-Specific Files

Resolution Order Example for fr_CA
1 messages_fr_CA.properties
2 messages_fr.properties
3 messages.properties

4. Resolving Locale (LocaleResolver)

Resolver Source
AcceptHeaderLocaleResolver (default) Accept-Language header
SessionLocaleResolver HTTP session attribute
CookieLocaleResolver Cookie
FixedLocaleResolver Hard-coded

5. Using Accept-Language Header

Example: Locale from Accept-Language header

@Bean
LocaleResolver localeResolver() {
  AcceptHeaderLocaleResolver r = new AcceptHeaderLocaleResolver();
  r.setSupportedLocales(List.of(Locale.ENGLISH, Locale.FRENCH, Locale.forLanguageTag("es")));
  r.setDefaultLocale(Locale.ENGLISH);
  return r;
}

6. Using Session/Cookie Locale Resolver

@Bean
LocaleResolver localeResolver() {
  CookieLocaleResolver r = new CookieLocaleResolver();
  r.setCookieName("APP_LOCALE");
  r.setCookieMaxAge(Duration.ofDays(365));
  r.setDefaultLocale(Locale.ENGLISH);
  return r;
}

7. Accessing Messages in Controllers

Example: Resolve i18n message in controller

@RestController
public class GreetController {
  private final MessageSource ms;
  public GreetController(MessageSource ms) { this.ms = ms; }
  @GetMapping("/hello/{name}")
  public String hello(@PathVariable String name, Locale locale) {
    return ms.getMessage("greeting", new Object[]{name}, locale);
  }
}

8. Using Message Keys in Templates

Example: Use message key in Thymeleaf template

<p th:text="#{greeting('World')}">Hello</p>
<label th:text="#{form.email}">Email</label>

9. Implementing Locale Change Interceptor

Example: Change locale via query parameter

@Override
public void addInterceptors(InterceptorRegistry r) {
  LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
  lci.setParamName("lang"); // ?lang=fr
  r.addInterceptor(lci);
}

10. Formatting Dates and Numbers by Locale

API Use
NumberFormat.getInstance(locale) Numbers
NumberFormat.getCurrencyInstance(locale) Currency
DateTimeFormatter.ofLocalizedDate(...).withLocale(...) Dates
spring.mvc.format.date Global date pattern
spring.mvc.format.date-time Global datetime pattern

11. Implementing URL-Based Locale Resolution

Example: Resolve locale from URL path segment

public class PathLocaleResolver implements LocaleResolver {
  private final Pattern P = Pattern.compile("^/(\\w{2})/.*$");
  @Override public Locale resolveLocale(HttpServletRequest req) {
    Matcher m = P.matcher(req.getRequestURI());
    return m.matches() ? Locale.forLanguageTag(m.group(1)) : Locale.ENGLISH;
  }
  @Override public void setLocale(HttpServletRequest r, HttpServletResponse s, Locale l) {}
}