Browse Source

Support for BCP 47 language tags

Issue: SPR-13032
pull/925/merge
Juergen Hoeller 9 years ago
parent
commit
0dd320f92e
  1. 65
      spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java
  2. 41
      spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java
  3. 54
      spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java

65
spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/CookieLocaleResolver.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -81,6 +81,8 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte @@ -81,6 +81,8 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
public static final String DEFAULT_COOKIE_NAME = CookieLocaleResolver.class.getName() + ".LOCALE";
private boolean languageTagCompliant = false;
private Locale defaultLocale;
private TimeZone defaultTimeZone;
@ -94,6 +96,30 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte @@ -94,6 +96,30 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
setCookieName(DEFAULT_COOKIE_NAME);
}
/**
* Specify whether this resolver's cookies should be compliant with BCP 47
* language tags instead of Java's legacy locale specification format.
* The default is {@code false}.
* <p>Note: This mode requires JDK 7 or higher. Set this flag to {@code true}
* for BCP 47 compliance on JDK 7+ only.
* @since 4.3
* @see Locale#forLanguageTag(String)
* @see Locale#toLanguageTag()
*/
public void setLanguageTagCompliant(boolean languageTagCompliant) {
this.languageTagCompliant = languageTagCompliant;
}
/**
* Return whether this resolver's cookies should be compliant with BCP 47
* language tags instead of Java's legacy locale specification format.
* @since 4.3
*/
public boolean isLanguageTagCompliant() {
return this.languageTagCompliant;
}
/**
* Set a fixed Locale that this resolver will return if no cookie found.
*/
@ -111,6 +137,7 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte @@ -111,6 +137,7 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
/**
* Set a fixed TimeZone that this resolver will return if no cookie found.
* @since 4.0
*/
public void setDefaultTimeZone(TimeZone defaultTimeZone) {
this.defaultTimeZone = defaultTimeZone;
@ -119,6 +146,7 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte @@ -119,6 +146,7 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
/**
* Return the fixed TimeZone that this resolver will return if no cookie found,
* if any.
* @since 4.0
*/
protected TimeZone getDefaultTimeZone() {
return this.defaultTimeZone;
@ -161,7 +189,7 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte @@ -161,7 +189,7 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
localePart = value.substring(0, spaceIndex);
timeZonePart = value.substring(spaceIndex + 1);
}
locale = (!"-".equals(localePart) ? StringUtils.parseLocaleString(localePart) : null);
locale = (!"-".equals(localePart) ? parseLocaleValue(localePart) : null);
if (timeZonePart != null) {
timeZone = StringUtils.parseTimeZoneString(timeZonePart);
}
@ -171,7 +199,7 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte @@ -171,7 +199,7 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
}
}
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
(locale != null ? locale: determineDefaultLocale(request)));
(locale != null ? locale : determineDefaultLocale(request)));
request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,
(timeZone != null ? timeZone : determineDefaultTimeZone(request)));
}
@ -191,18 +219,45 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte @@ -191,18 +219,45 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
if (localeContext instanceof TimeZoneAwareLocaleContext) {
timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
}
addCookie(response, (locale != null ? locale : "-") + (timeZone != null ? ' ' + timeZone.getID() : ""));
addCookie(response,
(locale != null ? toLocaleValue(locale) : "-") + (timeZone != null ? ' ' + timeZone.getID() : ""));
}
else {
removeCookie(response);
}
request.setAttribute(LOCALE_REQUEST_ATTRIBUTE_NAME,
(locale != null ? locale: determineDefaultLocale(request)));
(locale != null ? locale : determineDefaultLocale(request)));
request.setAttribute(TIME_ZONE_REQUEST_ATTRIBUTE_NAME,
(timeZone != null ? timeZone : determineDefaultTimeZone(request)));
}
/**
* Parse the given locale value coming from an incoming cookie.
* <p>The default implementation calls {@link StringUtils#parseLocaleString(String)}
* or JDK 7's {@link Locale#forLanguageTag(String)}, depending on the
* {@link #setLanguageTagCompliant "languageTagCompliant"} configuration property.
* @param locale the locale value to parse
* @return the corresponding {@code Locale} instance
* @since 4.3
*/
protected Locale parseLocaleValue(String locale) {
return (isLanguageTagCompliant() ? Locale.forLanguageTag(locale) : StringUtils.parseLocaleString(locale));
}
/**
* Render the given locale as a text value for inclusion in a cookie.
* <p>The default implementation calls {@link Locale#toString()}
* or JDK 7's {@link Locale#toLanguageTag()}, depending on the
* {@link #setLanguageTagCompliant "languageTagCompliant"} configuration property.
* @param locale the locale to stringify
* @return a String representation for the given locale
* @since 4.3
*/
protected String toLocaleValue(Locale locale) {
return (isLanguageTagCompliant() ? locale.toLanguageTag() : locale.toString());
}
/**
* Determine the default locale for the given request,
* Called if no locale cookie has been found.

41
spring-webmvc/src/main/java/org/springframework/web/servlet/i18n/LocaleChangeInterceptor.java

@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
package org.springframework.web.servlet.i18n;
import java.util.Locale;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -54,6 +55,8 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter { @@ -54,6 +55,8 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
private boolean ignoreInvalidLocale = false;
private boolean languageTagCompliant = false;
/**
* Set the name of the parameter that contains a locale specification
@ -104,6 +107,29 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter { @@ -104,6 +107,29 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
return this.ignoreInvalidLocale;
}
/**
* Specify whether to parse request parameter values as BCP 47 language tags
* instead of Java's legacy locale specification format.
* The default is {@code false}.
* <p>Note: This mode requires JDK 7 or higher. Set this flag to {@code true}
* for BCP 47 compliance on JDK 7+ only.
* @since 4.3
* @see Locale#forLanguageTag(String)
* @see Locale#toLanguageTag()
*/
public void setLanguageTagCompliant(boolean languageTagCompliant) {
this.languageTagCompliant = languageTagCompliant;
}
/**
* Return whether to use BCP 47 language tags instead of Java's legacy
* locale specification format.
* @since 4.3
*/
public boolean isLanguageTagCompliant() {
return this.languageTagCompliant;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
@ -118,7 +144,7 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter { @@ -118,7 +144,7 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
"No LocaleResolver found: not in a DispatcherServlet request?");
}
try {
localeResolver.setLocale(request, response, StringUtils.parseLocaleString(newLocale));
localeResolver.setLocale(request, response, parseLocaleValue(newLocale));
}
catch (IllegalArgumentException ex) {
if (isIgnoreInvalidLocale()) {
@ -147,4 +173,17 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter { @@ -147,4 +173,17 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
return false;
}
/**
* Parse the given locale value as coming from a request parameter.
* <p>The default implementation calls {@link StringUtils#parseLocaleString(String)}
* or JDK 7's {@link Locale#forLanguageTag(String)}, depending on the
* {@link #setLanguageTagCompliant "languageTagCompliant"} configuration property.
* @param locale the locale value to parse
* @return the corresponding {@code Locale} instance
* @since 4.3
*/
protected Locale parseLocaleValue(String locale) {
return (isLanguageTagCompliant() ? Locale.forLanguageTag(locale) : StringUtils.parseLocaleString(locale));
}
}

54
spring-webmvc/src/test/java/org/springframework/web/servlet/i18n/CookieLocaleResolverTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -164,6 +164,58 @@ public class CookieLocaleResolverTests { @@ -164,6 +164,58 @@ public class CookieLocaleResolverTests {
assertEquals(TimeZone.getTimeZone("GMT+1"), ((TimeZoneAwareLocaleContext) loc).getTimeZone());
}
@Test
public void testSetAndResolveLocaleWithCountry() {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
CookieLocaleResolver resolver = new CookieLocaleResolver();
resolver.setLocale(request, response, new Locale("de", "AT"));
Cookie cookie = response.getCookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME);
assertNotNull(cookie);
assertEquals(CookieLocaleResolver.DEFAULT_COOKIE_NAME, cookie.getName());
assertEquals(null, cookie.getDomain());
assertEquals(CookieLocaleResolver.DEFAULT_COOKIE_PATH, cookie.getPath());
assertFalse(cookie.getSecure());
assertEquals("de_AT", cookie.getValue());
request = new MockHttpServletRequest();
request.setCookies(cookie);
resolver = new CookieLocaleResolver();
Locale loc = resolver.resolveLocale(request);
assertEquals("de", loc.getLanguage());
assertEquals("AT", loc.getCountry());
}
@Test
public void testSetAndResolveLocaleWithCountryAsLanguageTag() {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
CookieLocaleResolver resolver = new CookieLocaleResolver();
resolver.setLanguageTagCompliant(true);
resolver.setLocale(request, response, new Locale("de", "AT"));
Cookie cookie = response.getCookie(CookieLocaleResolver.DEFAULT_COOKIE_NAME);
assertNotNull(cookie);
assertEquals(CookieLocaleResolver.DEFAULT_COOKIE_NAME, cookie.getName());
assertEquals(null, cookie.getDomain());
assertEquals(CookieLocaleResolver.DEFAULT_COOKIE_PATH, cookie.getPath());
assertFalse(cookie.getSecure());
assertEquals("de-AT", cookie.getValue());
request = new MockHttpServletRequest();
request.setCookies(cookie);
resolver = new CookieLocaleResolver();
resolver.setLanguageTagCompliant(true);
Locale loc = resolver.resolveLocale(request);
assertEquals("de", loc.getLanguage());
assertEquals("AT", loc.getCountry());
}
@Test
public void testCustomCookie() {
MockHttpServletRequest request = new MockHttpServletRequest();

Loading…
Cancel
Save