From d54474b81cd7e1ae08d1e7200da725df2fd23e13 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 16 Jun 2016 14:48:55 +0200 Subject: [PATCH] Allow locale to be overridden by "Accept-Language" Previously, when `spring.mvc.locale` was specified, that locale was used regardless of the client's preferences. This commit adds an extra `spring.mvc.locale-resolver` property that can control how the locale is resolved. The default is `ACCEPT_HEADER` but can be set to `FIXED` to restore the previous behaviour. Closes gh-6083 --- .../web/WebMvcAutoConfiguration.java | 11 +++++- .../autoconfigure/web/WebMvcProperties.java | 30 +++++++++++++++- .../web/WebMvcAutoConfigurationTests.java | 34 +++++++++++++++++-- .../appendix-application-properties.adoc | 3 +- 4 files changed, 73 insertions(+), 5 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java index 661e0ea344..65f4d5bba6 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java @@ -82,6 +82,7 @@ import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import org.springframework.web.servlet.i18n.FixedLocaleResolver; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; @@ -103,6 +104,7 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver; * @author Andy Wilkinson * @author Sébastien Deleuze * @author Eddú Meléndez + * @author Stephane Nicoll */ @Configuration @ConditionalOnWebApplication @@ -224,7 +226,14 @@ public class WebMvcAutoConfiguration { @ConditionalOnMissingBean @ConditionalOnProperty(prefix = "spring.mvc", name = "locale") public LocaleResolver localeResolver() { - return new FixedLocaleResolver(this.mvcProperties.getLocale()); + if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { + return new FixedLocaleResolver(this.mvcProperties.getLocale()); + } + else { + AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); + localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); + return localeResolver; + } } @Bean diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcProperties.java index 85c8b78fca..690f7d98a5 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcProperties.java @@ -42,10 +42,15 @@ public class WebMvcProperties { private DefaultMessageCodesResolver.Format messageCodesResolverFormat; /** - * Locale to use. + * Locale to use. By default, this locale is overridden by the "Accept-Language" header. */ private Locale locale; + /** + * Define how the locale should be resolved. + */ + private LocaleResolver localeResolver = LocaleResolver.ACCEPT_HEADER; + /** * Date format to use (e.g. dd/MM/yyyy). */ @@ -105,6 +110,14 @@ public class WebMvcProperties { this.locale = locale; } + public LocaleResolver getLocaleResolver() { + return this.localeResolver; + } + + public void setLocaleResolver(LocaleResolver localeResolver) { + this.localeResolver = localeResolver; + } + public String getDateFormat() { return this.dateFormat; } @@ -240,4 +253,19 @@ public class WebMvcProperties { } + public enum LocaleResolver { + + /** + * Always use the configured locale. + */ + FIXED, + + /** + * Use the "Accept-Language" header or the configured locale if the header + * is not set. + */ + ACCEPT_HEADER + + } + } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java index b78c6a1265..d3e791f8b2 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java @@ -49,6 +49,7 @@ import org.springframework.context.annotation.Import; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.format.support.FormattingConversionService; +import org.springframework.http.HttpHeaders; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.util.ReflectionUtils; @@ -64,6 +65,7 @@ import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import org.springframework.web.servlet.i18n.FixedLocaleResolver; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; @@ -258,14 +260,42 @@ public class WebMvcAutoConfigurationTests { @Test public void overrideLocale() throws Exception { + load(AllResources.class, "spring.mvc.locale:en_UK", + "spring.mvc.locale-resolver=fixed"); + // mock request and set user preferred locale + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addPreferredLocale(StringUtils.parseLocaleString("nl_NL")); + request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "nl_NL"); + LocaleResolver localeResolver = this.context.getBean(LocaleResolver.class); + assertThat(localeResolver).isInstanceOf(FixedLocaleResolver.class); + Locale locale = localeResolver.resolveLocale(request); + // test locale resolver uses fixed locale and not user preferred locale + assertThat(locale.toString()).isEqualTo("en_UK"); + } + + @Test + public void useAcceptHeaderLocale() { load(AllResources.class, "spring.mvc.locale:en_UK"); // mock request and set user preferred locale MockHttpServletRequest request = new MockHttpServletRequest(); request.addPreferredLocale(StringUtils.parseLocaleString("nl_NL")); + request.addHeader(HttpHeaders.ACCEPT_LANGUAGE, "nl_NL"); LocaleResolver localeResolver = this.context.getBean(LocaleResolver.class); + assertThat(localeResolver).isInstanceOf(AcceptHeaderLocaleResolver.class); Locale locale = localeResolver.resolveLocale(request); - assertThat(localeResolver).isInstanceOf(FixedLocaleResolver.class); - // test locale resolver uses fixed locale and not user preferred locale + // test locale resolver uses user preferred locale + assertThat(locale.toString()).isEqualTo("nl_NL"); + } + + @Test + public void useDefaultLocaleIfAcceptHeaderNoSet() { + load(AllResources.class, "spring.mvc.locale:en_UK"); + // mock request and set user preferred locale + MockHttpServletRequest request = new MockHttpServletRequest(); + LocaleResolver localeResolver = this.context.getBean(LocaleResolver.class); + assertThat(localeResolver).isInstanceOf(AcceptHeaderLocaleResolver.class); + Locale locale = localeResolver.resolveLocale(request); + // test locale resolver uses default locale if no header is set assertThat(locale.toString()).isEqualTo("en_UK"); } diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 9a51d5b26a..0f27696bef 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -336,7 +336,8 @@ content into your application; rather pick only the properties that you need. spring.mvc.dispatch-options-request=false # Dispatch OPTIONS requests to the FrameworkServlet doService method. spring.mvc.favicon.enabled=true # Enable resolution of favicon.ico. spring.mvc.ignore-default-model-on-redirect=true # If the content of the "default" model should be ignored during redirect scenarios. - spring.mvc.locale= # Locale to use. + spring.mvc.locale= # Locale to use. By default, this locale is overridden by the "Accept-Language" header. + spring.mvc.locale-resolver=accept-header # Define how the locale should be resolved. spring.mvc.media-types.*= # Maps file extensions to media types for content negotiation. spring.mvc.message-codes-resolver-format= # Formatting strategy for message codes. For instance `PREFIX_ERROR_CODE`. spring.mvc.servlet.load-on-startup=-1 # Load on startup priority of the Spring Web Services servlet.