user4695271
user4695271

Reputation:

Retrieve locale based on the Accept-Language in Spring Boot

I have a Spring Boot (2.1.3.RELEASE) application that uses Jersey to define the (RESTful) endpoints. I'm trying to read and propagate some messages based on the locale being sent by the user-agents.

I've configured these beans:

@Bean
public LocaleResolver localeResolver() {
  final AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver();
  resolver.setSupportedLocales(Arrays.asList(Locale.GERMANY, Locale.US));
  resolver.setDefaultLocale(Locale.ENGLISH);
  return resolver;
}

@Bean
public MessageSource messageSource() { // Not sure if this is needed
  final ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
  messageSource.setDefaultEncoding(StandardCharsets.UTF_8.name());
  messageSource.setBasenames("classpath:/messages");
  messageSource.setUseCodeAsDefaultMessage(true);
  messageSource.setCacheSeconds(5);
  return messageSource;
}

...and also the bundles (inside ../src/main/resources/) like: messages.properties (fallback), messages_en_US.properties, messages_de_DE.properties, etc.

Now, the challenge is that I'm not sure how to "read" the locale sent by the user-agents in order to read the messages from the bundles appropriately. I'm injecting a MessageSource ms, and programmatically reading messages like:

final Locale locale = ???
ms.getMessage("message.duplicate-token", null, locale);

Any clues?

I've tried LocaleContextHolder.getLocale() but it's always en_US. If I hardcode the corresponding locale for the getMessage call, I'm able to retrieve the correct message(s). So I know the setup/configuration works for the most part.


Clients are sending the locale using the Accept-Language header — and values like: de-DE, en-US, etc.

Upvotes: 11

Views: 40867

Answers (8)

Dinushika Rathnayake
Dinushika Rathnayake

Reputation: 419

I also implemented same scenario and it's works for me. For this, need to override the resolveLocale method in AcceptHeaderLocaleResolver.

Create component LanguageResolver for the custom implementation. Use Locale.forLanguageTag(language) to create locale from accept-header value. This will create a local with language and country code.

@Component
    public class LanguageResolver extends AcceptHeaderLocaleResolver {
        @Override
        public Locale resolveLocale(HttpServletRequest request) {
            String language = request.getHeader("Accept-Language");
            List<Locale> supportedLocales = getSupportedLocales();
            Locale defaultLocale = getDefaultLocale();
            Locale requestLocale = Locale.forLanguageTag(language);
    
            if (StringUtils.isEmpty(language)) {
                return defaultLocale;
            } else if (supportedLocales.contains(requestLocale)) {
                return requestLocale;
            } else {
                return defaultLocale;
            }
        }
    }  

In the configuration class create bean using custom LanguageResolver class.

@Configuration
    public class Internationalization extends WebMvcConfigurerAdapter {
        @Bean
        public AcceptHeaderLocaleResolver localeResolver() {
            final LanguageResolver resolver = new LanguageResolver();
            resolver.setSupportedLocales(Arrays.asList(Locale.GERMANY, Locale.US,Locale.UK));
            resolver.setDefaultLocale(Locale.US);
            return resolver;
        }
    
        @Bean
        public ResourceBundleMessageSource messageSource() {
            final ResourceBundleMessageSource source = new ResourceBundleMessageSource();
            source.setBasename("language/messages");
            source.setDefaultEncoding("UTF-8");
            return source;
        }
    }  

Here LocaleContextHolder.getLocale() will invoke the override method in LanguageResolver class.

@Service
public class LocaleService {
    @Autowired
    ResourceBundleMessageSource messageSource;

    public String getMessage(String code) {
        return messageSource.getMessage(code, null, LocaleContextHolder.getLocale());
    }
}

And property files are in path of resources -> language

messages_en_US.properties
messages_en_GB.properties
messages_de_DE.properties

Content of the file in the format of below

test.hello=Hello GERMANY

Tested method using below.

@RestController
    public class TestController {
        private LocaleService localeService;
    
        @Autowired
        public TestController(LocaleService localeService) {
            this.localeService = localeService;
        }
    
        @GetMapping("/local")
        public String getMessageForLocal() {
            return localeService.getMessage("test.hello");
        }
    }

enter image description here

Upvotes: 1

ajesh
ajesh

Reputation: 406

You have to add Accept-Language header in your api endpoint to get desired locale output. Then you have to add following configuration to parse and set the Accept-Language header value from incoming request.

@Configuration
public class I18NConfiguration {

    @Value("${i18n.locale.default:en-US}")
    private String defaultLocale;

    @Value("#{'${i18n.locale.supported: }'.split(',\\s*')}")
    private List<String> supportedLocales;

    @Bean
    public LocaleResolver localeResolver() {
        AcceptHeaderLocaleResolver acceptHeaderLocaleResolver = new AcceptHeaderLocaleResolver();
        acceptHeaderLocaleResolver.setDefaultLocale(Locale.forLanguageTag(defaultLocale));

        if (supportedLocales != null && !supportedLocales.isEmpty()) {
            List<Locale> localeList = supportedLocales.stream().map(Locale::forLanguageTag).collect(Collectors.toUnmodifiableList());
            acceptHeaderLocaleResolver.setSupportedLocales(localeList);
        }
        return acceptHeaderLocaleResolver;
    }
}

Upvotes: 0

jfk
jfk

Reputation: 5297

If you want to get this on a REST controller level, you can directly get the locale instance in the REST methods. Spring does this magic

@GetMapping("/status")
public ResponseEntitiy<String> getStatus(final Locale locale) {

}

Upvotes: 4

user2786531
user2786531

Reputation: 83

There is no need to extend AcceptHeaderLocaleResolver. Create a bean definition such as:

@Bean
public LocalResolver localeResolver() {
    AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
    localeResolver.setSupportedLocales(Arrays.asList(new Locale("fa"), new Locale("en")));
    localeResolver.setDefaultLocale(new Locale("fa"));
    return localeResolver;
}

Upvotes: 1

Lucas300
Lucas300

Reputation: 99

The LocaleResolver bean that you create only gets used in Spring MVC and not in the Jersey container. It is the Spring's DispatcherServlet that uses the LocaleResolver.

So LocaleContextHolder.getLocale(); will return different local depending on if call in a Jersey controller or in a Spring MVC controller.

Upvotes: 0

cardinal
cardinal

Reputation: 71

I m using a bean

  @Bean
  public LocaleResolver localeResolver() {
    AcceptHeaderLocaleResolver slr = new AcceptHeaderLocaleResolver();
    slr.setDefaultLocale(Locale.UK);
    return slr;
  }

then another one

  @Bean
  public LanguageUtil languageUtil() {
    return new LanguageUtil();
  }

with

  private Locale getLocale() {
    return LocaleContextHolder.getLocale();
  }

  public String getLocalizedMessage(String messageKey) {
    return messageSource.getMessage(messageKey, null, getLocale());
  }

The header is saved into the LocaleContextHolder, and you can use it when you need it.

Upvotes: 6

Mohhamed Nabil
Mohhamed Nabil

Reputation: 5502

Create a custom AcceptHeaderLocaleResolver

public class AcceptHeaderResolver extends AcceptHeaderLocaleResolver {

    List<Locale> LOCALES = Arrays.asList(new Locale("en"), new Locale("ar"));

    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String headerLang = request.getHeader("Accept-Language");
        return headerLang == null || headerLang.isEmpty()
                ? Locale.getDefault()
                : Locale.lookup(Locale.LanguageRange.parse(headerLang), LOCALES);
    }


}

And Don't forgot to use it in @Configuration file

@Bean
public LocaleResolver sessionLocaleResolver() {
    AcceptHeaderResolver localeResolver = new AcceptHeaderResolver();
    return localeResolver;
}

Upvotes: 4

DEBENDRA DHINDA
DEBENDRA DHINDA

Reputation: 1193

You need add an LocaleChangeInterceptor and configure the bean as follow: Refer Spring Boot internationalization for more

@Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
    LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
    lci.setParamName("lang");
    return lci;
}

If you want to use "Accept-Language" header only, then you can extend AcceptHeaderLocaleResolver and can customize:

package com.deb.demo.config;

import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import javax.servlet.http.HttpServletRequest;

import org.springframework.util.StringUtils;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;

public class CustomLocaleResolver extends AcceptHeaderLocaleResolver {


    List<Locale> LOCALES = Arrays.asList(new Locale("en"),new Locale("es"),new Locale("fr"));


  @Override
  public Locale resolveLocale(HttpServletRequest request) {
     if (StringUtils.isEmpty(request.getHeader("Accept-Language"))) {
         return Locale.getDefault();
       }
     List<Locale.LanguageRange> list = Locale.LanguageRange.parse(request.getHeader("Accept-Language"));
     Locale locale = Locale.lookup(list,LOCALES);
      return locale;
     }
}

Upvotes: 7

Related Questions