Skip to content
Advertisement

Spring MessageSource fallback to explicit locale

<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);
        }
    }
}
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement