Add getAcceptLanguageAsLocales

The use of Locale.LanguageRange for the Accept-Language header makes
sense as it gives the most flexibility for a client to set a weighted
list and for a server to do filtering via Locale#filter.

This commit adds an additional convenience method that turns
the LangugeRange list to a list of Locale's also filtering out a
wildcard (i.e. "*"). A List<Locale> is the most basic way to access
prefered languages and needed when filtering is not required.

Issue: SPR-15024
This commit is contained in:
Rossen Stoyanchev 2016-12-16 16:15:37 -05:00
parent c85d768b3c
commit fa56361ad2
2 changed files with 46 additions and 15 deletions

View File

@ -443,13 +443,16 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* Set the acceptable language ranges,
* as specified by the {@literal Accept-Language} header.
* @see Locale.LanguageRange
* @since 5.0
*/
public void setAcceptLanguage(List<Locale.LanguageRange> languages) {
Assert.notNull(languages, "'languages' must not be null");
DecimalFormat df = new DecimalFormat("0.0", DECIMAL_FORMAT_SYMBOLS);
List<String> values = languages
.stream()
.map(r -> (r.getWeight() == Locale.LanguageRange.MAX_WEIGHT ? r.getRange() : r.getRange() + ";q=" + df.format(r.getWeight())))
DecimalFormat decimal = new DecimalFormat("0.0", DECIMAL_FORMAT_SYMBOLS);
List<String> values = languages.stream()
.map(range ->
range.getWeight() == Locale.LanguageRange.MAX_WEIGHT ?
range.getRange() :
range.getRange() + ";q=" + decimal.format(range.getWeight()))
.collect(Collectors.toList());
set(ACCEPT_LANGUAGE, toCommaDelimitedString(values));
}
@ -458,6 +461,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* Return the acceptable language ranges,
* as specified by the {@literal Accept-Language} header
* @see Locale.LanguageRange
* @since 5.0
*/
public List<Locale.LanguageRange> getAcceptLanguage() {
String value = getFirst(ACCEPT_LANGUAGE);
@ -467,6 +471,22 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
return Collections.emptyList();
}
/**
* A variant of {@link #getAcceptLanguage()} that converts each
* {@link java.util.Locale.LanguageRange} to a {@link Locale}.
* @since 5.0
*/
public List<Locale> getAcceptLanguageAsLocales() {
List<Locale.LanguageRange> ranges = getAcceptLanguage();
if (ranges.isEmpty()) {
return Collections.emptyList();
}
return ranges.stream()
.map(range -> Locale.forLanguageTag(range.getRange()))
.filter(locale -> StringUtils.hasText(locale.getDisplayName()))
.collect(Collectors.toList());
}
/**
* Set the (new) value of the {@code Access-Control-Allow-Credentials} response header.
*/
@ -767,6 +787,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* as specified by the {@literal Content-Language} header.
* <p>Use {@code set(CONTENT_LANGUAGE, ...)} if you need
* to set multiple content languages.</p>
* @since 5.0
*/
public void setContentLanguage(Locale locale) {
Assert.notNull(locale, "'locale' must not be null");
@ -779,6 +800,7 @@ public class HttpHeaders implements MultiValueMap<String, String>, Serializable
* <p>Returns {@code null} when the content language is unknown.
* <p>Use {@code getValuesAsList(CONTENT_LANGUAGE)} if you need
* to get multiple content languages.</p>
* @since 5.0
*/
public Locale getContentLanguage() {
return getValuesAsList(CONTENT_LANGUAGE)

View File

@ -424,30 +424,39 @@ public class HttpHeadersTests {
@Test
public void acceptLanguage() {
assertTrue(headers.getAcceptLanguage().isEmpty());
String headerValue = "fr-ch, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5";
String headerValue = "fr-ch, fr;q=0.9, en-*;q=0.8, de;q=0.7, *;q=0.5";
headers.setAcceptLanguage(Locale.LanguageRange.parse(headerValue));
assertEquals(headerValue, headers.getFirst(HttpHeaders.ACCEPT_LANGUAGE));
List<Locale.LanguageRange> languages = headers.getAcceptLanguage();
Locale.LanguageRange[] languageArray = new Locale.LanguageRange[]{
List<Locale.LanguageRange> expectedRanges = Arrays.asList(
new Locale.LanguageRange("fr-ch"),
new Locale.LanguageRange("fr", 0.9),
new Locale.LanguageRange("en", 0.8),
new Locale.LanguageRange("en-*", 0.8),
new Locale.LanguageRange("de", 0.7),
new Locale.LanguageRange("*", 0.5)
};
assertArrayEquals(languageArray, languages.toArray());
);
assertEquals(expectedRanges, headers.getAcceptLanguage());
List<Locale> expectedLocales = Arrays.asList(
Locale.forLanguageTag("fr-ch"),
Locale.forLanguageTag("fr"),
Locale.forLanguageTag("en"),
Locale.forLanguageTag("de")
);
assertEquals(expectedLocales, headers.getAcceptLanguageAsLocales());
}
@Test
public void contentLanguage() {
assertNull(headers.getContentLanguage());
headers.setContentLanguage(Locale.FRANCE);
assertEquals(Locale.FRANCE, headers.getContentLanguage());
assertEquals("fr-FR", headers.getFirst(HttpHeaders.CONTENT_LANGUAGE));
headers.clear();
headers.set(HttpHeaders.CONTENT_LANGUAGE, Locale.GERMAN.toLanguageTag() + ", " + Locale.CANADA);
assertEquals(Locale.GERMAN, headers.getContentLanguage());
}
@Test
public void contentLanguageSerialized() {
headers.set(HttpHeaders.CONTENT_LANGUAGE, "de, en_CA");
assertEquals("Expected one (first) locale", Locale.GERMAN, headers.getContentLanguage());
}
}