<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> </parent>
In some spring boot application, i18n is used. Here is the config:
@Bean public MessageSource messageSource() { ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource(); messageSource.setBasename(translationsPath); messageSource.setDefaultEncoding("UTF-8"); messageSource.setFallbackToSystemLocale(false); messageSource.setCacheSeconds(5); return messageSource; } @Bean public LocaleResolver localeResolver() { AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(Locale.ENGLISH); return localeResolver; }
There is a requirement to obtain a locale from header Accept-language
, falling back to “en” if:
- the header is missing,
- the header has wrong value,
- the config is missing for the locale.
Two first points are resolved by provided LocaleResolver
(see config). However, I don’t know how to implement point 3: the reloadable list of translation configs is encapsulated in ReloadableResourceBundleMessageSource
, and I see no way how to make it fall back to “en” locale on some absent config.
There is setFallbackToSystemLocale
option, but system locale != “en” locale. Moreover, MessageSource
uses system locale only to resolve the token from translation config, not for localizing the message itself, which causes discrepancies with localizing the numbers.
I might try to limit the list of supported locales by using setSupportedLocales
on LocaleResolver
, but it is not suitable solution in my case, since the list of translation configs is supposed to change in runtime.
Is there a way to make ReloadableResourceBundleMessageSource
fallback to explicit locale?
Advertisement
Answer
Solved that in a bit dirty way by writing a wrapper around MessageSource
. It is not exactly what was requested: the fallback locale will be used if the token for requested one is absent (which is close but not the same as “config was absent”). Also, it swallows exceptions and uses them for flow control, which is not good.
final class FallbackMessageSource implements MessageSource { private final MessageSource delegate; private final Locale fallbackLocale; public FallbackMessageSource(MessageSource delegate, Locale fallbackLocale) { this.delegate = delegate; this.fallbackLocale = fallbackLocale; } @Override public String getMessage(String code, Object[] args, String defaultMessage, Locale locale) { try { return delegate.getMessage(code, args, defaultMessage, locale); } catch(NoSuchMessageException ex) { return delegate.getMessage(code, args, defaultMessage, fallbackLocale); } } @Override public String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException { try { return delegate.getMessage(code, args, locale); } catch(NoSuchMessageException ex) { return delegate.getMessage(code, args, fallbackLocale); } } @Override public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException { try { return delegate.getMessage(resolvable, locale); } catch(NoSuchMessageException ex) { return delegate.getMessage(resolvable, fallbackLocale); } } }