Can not get locale use LocaleContextHolder.getLocale() in Spring Boot - java

I use Spring boot in my source, and set Locale like bellow:
#Controller
#RequestMapping(value = "/language")
public class LocaleController {
#Bean
public LocaleResolver localeResolver(){
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setDefaultLocale(new Locale("en", "US"));
return localeResolver;
}
#RequestMapping(value = "/{lang}", method = RequestMethod.GET)
public String changeSessionLanguage(HttpServletRequest request, HttpServletResponse response,
#PathVariable("lang") String lang) {
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
Locale locale = LocaleContextHolder.getLocale();
if ("zh".equals(lang)) {
localeResolver.setLocale(request, response, new Locale("zh", "CN"));
} else if ("jp".equals(lang)) {
localeResolver.setLocale(request, response, new Locale("ja", "JP"));
} else {
localeResolver.setLocale(request, response, new Locale("en", "US"));
}
return "redirect:/";
}
}
And it works good.
But in Spring security UserDetailsService source, I can't get locale as this:
Locale locale = LocaleContextHolder.getLocale();
It always show my browser language no matter I had changed the locale to what.
How can I get the locale correctly?
Thanks!
UPDATE
I changed code, move LocaleResolver to Configuration class and add RequestContextListener like bellow:
#Configuration
#WebListener
public class Config extends RequestContextListener{
#Bean
public RequestContextListener requestContextListener(){
return new RequestContextListener();
}
#Bean
public LocaleResolver localeResolver(){
CookieLocaleResolver localeResolver = new CookieLocaleResolver();
localeResolver.setDefaultLocale(new Locale("en", "US"));
return localeResolver;
}
}
But it also can't work.

Related

Sending emails in different languages using Thymeleaf

I am sending emails using Thymeleaf. I've set up the templates that I need and it works perfectly. I'm now trying to translate this email depending on the receiver's language. I know the language I need to use through a previously recovered variable.
I'm using properties files to set up the variables that will be translated.
So far, I have three languages: French, English, and Chinese. So I have three properties files :
mail.messages_fr_FR
mail.messages_en_US
mail.messages_zh_CN
I have the following methods in my email configuration class :
#Bean
public ReloadableResourceBundleMessageSource emailMessageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("mailMessages");
return messageSource;
}
#Bean
public LocaleResolver localeResolver() {
SessionLocaleResolver slr = new SessionLocaleResolver();
slr.setDefaultLocale(Locale.US);
return slr;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
registry.addInterceptor(localeChangeInterceptor);
}
I have enabled the Mvc :
#SpringBootApplication
#EnableWebMvc
public class AccessRequestEventHookApplication extends SpringBootServletInitializer implements WebMvcConfigurer {
I have a method in my email configuration class which is the following :
public void sendMessageUsingThymeleafTemplate(String to, String subject, Map<String, Object> templateModel, String appName)
throws MessagingException {
Context thymeleafContext = new Context();
thymeleafContext.setVariables(templateModel);
String htmlBody;
if (appName == "Test1"){
htmlBody = thymeleafTemplateEngine.process("Test1-template", thymeleafContext);
} else {
htmlBody = thymeleafTemplateEngine.process("Test2-template", thymeleafContext);
}
sendHtmlMessage(to, subject, htmlBody);
}
This function is then called in my controller.
I also have my template resolver and engine as follow :
#Bean
#Primary
public ITemplateResolver thymeleafTemplateResolver() {
ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
templateResolver.setPrefix("mail-templates/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("HTML");
templateResolver.setCharacterEncoding("UTF-8");
return templateResolver;
}
#Bean
public SpringTemplateEngine thymeleafTemplateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
templateEngine.setTemplateEngineMessageSource(emailMessageSource());
return templateEngine;
}
I tried changing the locale resolver type with cookie, acceptLanguage, it still does not work.
If I change the language on my computer (from French to English for example), it works, the English translation is used. But I can't figure out how to change the locale variable directly in the code.
As it is sent to a user, I can not base myself on their locale.
If I was not clear, please let me know and I will provide any info needed.
Thanks !
You need to set the locale on the Context object you create for Thymeleaf:
Context thymeleafContext = new Context();
thymeleafContext.setLocale(locale);
If you are calling your sendMessageUsingThymeleafTemplate method from a web controller, you can do this as well:
#Controller
public class MyController {
private final ServletContext servletContext;
public MyController(ServletContext servletContext) {
this.servletContext = servletContext;
}
#GetMapping
public void myControllerMethod(HttpServletRequest request,
HttpServletResponse response,
Locale locale) {
WebContext context = new WebContext(request, response, servletContext);
context.setLocale(locale);
service.sendMessageUSingThymeleafTemplate(context, ...);
}
}

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.
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;
}
}
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.
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;
}
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) {
}
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.
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;
}
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");
}
}
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;
}
}

Spring default locale not working as I expected for CookieLocaleResolver

I have two files for the text I want to translate:
messages_es_ES.properties
messages_en_US.properties
I'm using a CookieLocaleResolver:
#Bean
public LocaleResolver localeResolver() {
CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
cookieLocaleResolver.setCookieName("language");
cookieLocaleResolver.setCookieMaxAge(3600);
cookieLocaleResolver.setDefaultLocale(new Locale("es_ES"));
return cookieLocaleResolver;
}
And this LocaleChangeInterceptor:
#Bean
public LocaleChangeInterceptor localeChangeInterceptor() {
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("language");
return localeChangeInterceptor;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
In my jsp, I use this links:
EN
ES
Everything works, and the language is correctly changed when I click on one link, but everything is shown in English (in the system locale, to be more specific) when I start the application, even if I set "es_ES" as the default locale.
Solved, I changed the property setFallbackToSystemLocale from the ResourceBundleMessageSource to false, as mentioned by #noobandy here.
Now everything is working as expected.
You can set default locale as follow:
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource=new ResourceBundleMessageSource();
messageSource.setBasename("messages");
// SET DEFAULT LOCALE AS WELL
Locale.setDefault(Locale.US);
return messageSource;
}
When locale is null, AbstractMessageSource calls Locale.getDefault()

Java, Spring internationalization: how to use values from .properties in simple String?

I use the next code
#Value("${app.user.root}")
private String userRoot;
to get constant value from my application.properties file.
In my GetMapping method I need to redirect to the error page and to pass a String as parameter.
#GetMapping("/user/activate")
public String activate(String activation) {
Users u = usersService.activate(activation);
if (u != null) {
usersService.autoLogin(u);
return "redirect:/";
}
return "redirect:/error?message=Could not activate with this activation code, please contact support";
But I need to have different String values with different languages. So, I am using Spring i18n, but how can I get the value I need at runtime? I need something like this:
return "redirect:/error?message=${errorMessage}";
Thank you, hope you will help me.
First you have to create multiple properties file for multiple languages
messages_en.properties
messages_fr.properties
The configuration of i18n should be following
#Configuration
public class LanguageConfig extends WebMvcConfigurerAdapter {
#Bean
public ReloadableResourceBundleMessageSource messageSource(){
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:messages");
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
#Bean
public LocaleResolver localeResolver() {
SmartLocaleResolver slr = new SmartLocaleResolver();
Locale locale = new Locale("en", "us");
slr.setDefaultLocale(locale); // Set default Locale as en_cos
return slr;
}
#Bean
public LocaleChangeInterceptor localeInterceptor() {
LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
interceptor.setParamName("lang");
return interceptor;
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeInterceptor());
}
class SmartLocaleResolver extends CookieLocaleResolver {
#Override
public Locale resolveLocale(HttpServletRequest request) {
String acceptLanguage = request.getHeader("Accept-Language");
if (acceptLanguage == null || acceptLanguage.trim().isEmpty()) {
return super.determineDefaultLocale(request);
}
return request.getLocale();
}
}
}
Now update your controller code and autowire org.springframework.context.MessageSource and then use it to get localized message.
#Autowired
private MessageSource messageSource;
Then you can get the localized message using following code.
String errorMessage = messageSource.getMessage("project.errorMessage", new Object[]{"John Doe"}, LocaleContextHolder.getLocale());
You can also use Locale object from controller method parameter instead of LocaleContextHolder.getLocale(), but it works just fine.

How do I prevent Spring 4.0 MVC #ModelAttribute variables from appearing in URL?

I have a method that let you comment about customer and after adding a comment redirect you again to site with comments.
#RequestMapping(value="customers/details/{id}", method = RequestMethod.GET)
public String showCustomerComments(#ModelAttribute("commentContent") String commentContent, #PathVariable int id, Model model){
model.addAttribute("comment",commentRepository.getAllComments(id));
return "details";
}
#RequestMapping(value ="customers/details/{id}", method = RequestMethod.POST)
public String processAddCustomerComment(#ModelAttribute("commentContent") String commentContent, #PathVariable int id){
commentRepository.
addComment(commentContent, localDate.now().toString(), id);
return "redirect:/customers/details/{id}";
}
}
everything works fine but in the url appears model veriable:
http://localhost:8080/customers/details/62?commentContent=some_text
I already know the solution but I don't know how to implement it. The solution is to set ignoreDefaultModelOnRedirect true on ignoreDefaultModelOnRedirect. In this topic enter link description here they say to just put sth like <mvc:annotation-driven ignoreDefaultModelOnRedirect="true" /> to our xml file. But How to do this in Java Based configuration?
I have a such class:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.packt.webstore")
public class WebConfiguration extends WebMvcConfigurerAdapter {
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
#Bean
public CommonsMultipartResolver multipartResolver(){
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(10240000);
return multipartResolver;
}
#Bean
public LocaleResolver localeResolver(){
SessionLocaleResolver sessionLocaleResolver = new SessionLocaleResolver();
sessionLocaleResolver.setDefaultLocale(new Locale("en"));
return sessionLocaleResolver;
}
#Bean
public LocaleChangeInterceptor localeChangeInterceptor(){
LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
localeChangeInterceptor.setParamName("lang");
return localeChangeInterceptor;
}
#Bean
public MessageSource messageSource(){
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
return messageSource;
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("resources/");
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(localeChangeInterceptor());
}
}
and I thought to add to it:
#Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(){
RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
requestMappingHandlerAdapter.ignoreDefaultModelOnRedirect(true);
return requestMappingHandlerAdapter;
}
but it dosen't work.
I could add to processAddCustomerComment mehtod:
model.asMap().clear();
return "redirect:" + news.getUrl();
but I am not happy with this solution. Because suppose that we have 20 methods like mine and I don't want to put those two lines of code to every of those 20 methods.
How do I solve the problem?
It is possible that your implementation is not working because a RequestMappingHandlerAdapter is already on the context. Adding another one doesn't change the one used de facto. This article suggests that you autowire the existing RequestMappingHandlerAdapter and set the property instead.
#EnableWebMvc
#Configuration
public class MyWebConfig {
#Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
#PostConstruct
public void init() {
requestMappingHandlerAdapter.setIgnoreDefaultModelOnRedirect(true);
}
......
}
All credits go to http://www.logicbig.com/ for the quoted code.
That being said, unless you have a specific reason for using #ModelAttribute, you should perhaps switch to #RequestParam, which is simpler, less strings attached. Here's this topic discussed at length.

Categories

Resources