Support for BCP 47 language tags

Issue: SPR-13032
This commit is contained in:
Juergen Hoeller 2015-12-21 18:40:26 +01:00
parent cad2ce0ac2
commit 0dd320f92e
3 changed files with 153 additions and 7 deletions

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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
public static final String DEFAULT_COOKIE_NAME = CookieLocaleResolver.class.getName() + ".LOCALE"; public static final String DEFAULT_COOKIE_NAME = CookieLocaleResolver.class.getName() + ".LOCALE";
private boolean languageTagCompliant = false;
private Locale defaultLocale; private Locale defaultLocale;
private TimeZone defaultTimeZone; private TimeZone defaultTimeZone;
@ -94,6 +96,30 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
setCookieName(DEFAULT_COOKIE_NAME); 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. * Set a fixed Locale that this resolver will return if no cookie found.
*/ */
@ -111,6 +137,7 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
/** /**
* Set a fixed TimeZone that this resolver will return if no cookie found. * Set a fixed TimeZone that this resolver will return if no cookie found.
* @since 4.0
*/ */
public void setDefaultTimeZone(TimeZone defaultTimeZone) { public void setDefaultTimeZone(TimeZone defaultTimeZone) {
this.defaultTimeZone = defaultTimeZone; this.defaultTimeZone = defaultTimeZone;
@ -119,6 +146,7 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
/** /**
* Return the fixed TimeZone that this resolver will return if no cookie found, * Return the fixed TimeZone that this resolver will return if no cookie found,
* if any. * if any.
* @since 4.0
*/ */
protected TimeZone getDefaultTimeZone() { protected TimeZone getDefaultTimeZone() {
return this.defaultTimeZone; return this.defaultTimeZone;
@ -161,7 +189,7 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
localePart = value.substring(0, spaceIndex); localePart = value.substring(0, spaceIndex);
timeZonePart = value.substring(spaceIndex + 1); timeZonePart = value.substring(spaceIndex + 1);
} }
locale = (!"-".equals(localePart) ? StringUtils.parseLocaleString(localePart) : null); locale = (!"-".equals(localePart) ? parseLocaleValue(localePart) : null);
if (timeZonePart != null) { if (timeZonePart != null) {
timeZone = StringUtils.parseTimeZoneString(timeZonePart); timeZone = StringUtils.parseTimeZoneString(timeZonePart);
} }
@ -191,7 +219,8 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
if (localeContext instanceof TimeZoneAwareLocaleContext) { if (localeContext instanceof TimeZoneAwareLocaleContext) {
timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone(); timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
} }
addCookie(response, (locale != null ? locale : "-") + (timeZone != null ? ' ' + timeZone.getID() : "")); addCookie(response,
(locale != null ? toLocaleValue(locale) : "-") + (timeZone != null ? ' ' + timeZone.getID() : ""));
} }
else { else {
removeCookie(response); removeCookie(response);
@ -203,6 +232,32 @@ public class CookieLocaleResolver extends CookieGenerator implements LocaleConte
} }
/**
* 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, * Determine the default locale for the given request,
* Called if no locale cookie has been found. * Called if no locale cookie has been found.

View File

@ -16,6 +16,7 @@
package org.springframework.web.servlet.i18n; package org.springframework.web.servlet.i18n;
import java.util.Locale;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -54,6 +55,8 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
private boolean ignoreInvalidLocale = false; private boolean ignoreInvalidLocale = false;
private boolean languageTagCompliant = false;
/** /**
* Set the name of the parameter that contains a locale specification * Set the name of the parameter that contains a locale specification
@ -104,6 +107,29 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
return this.ignoreInvalidLocale; 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 @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
@ -118,7 +144,7 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
"No LocaleResolver found: not in a DispatcherServlet request?"); "No LocaleResolver found: not in a DispatcherServlet request?");
} }
try { try {
localeResolver.setLocale(request, response, StringUtils.parseLocaleString(newLocale)); localeResolver.setLocale(request, response, parseLocaleValue(newLocale));
} }
catch (IllegalArgumentException ex) { catch (IllegalArgumentException ex) {
if (isIgnoreInvalidLocale()) { if (isIgnoreInvalidLocale()) {
@ -147,4 +173,17 @@ public class LocaleChangeInterceptor extends HandlerInterceptorAdapter {
return false; 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));
}
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -164,6 +164,58 @@ public class CookieLocaleResolverTests {
assertEquals(TimeZone.getTimeZone("GMT+1"), ((TimeZoneAwareLocaleContext) loc).getTimeZone()); 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 @Test
public void testCustomCookie() { public void testCustomCookie() {
MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletRequest request = new MockHttpServletRequest();