001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.i18n; 018 019import java.util.ArrayList; 020import java.util.Enumeration; 021import java.util.List; 022import java.util.Locale; 023 024import javax.servlet.http.HttpServletRequest; 025import javax.servlet.http.HttpServletResponse; 026 027import org.springframework.lang.Nullable; 028import org.springframework.util.StringUtils; 029import org.springframework.web.servlet.LocaleResolver; 030 031/** 032 * {@link LocaleResolver} implementation that simply uses the primary locale 033 * specified in the "accept-language" header of the HTTP request (that is, 034 * the locale sent by the client browser, normally that of the client's OS). 035 * 036 * <p>Note: Does not support {@code setLocale}, since the accept header 037 * can only be changed through changing the client's locale settings. 038 * 039 * @author Juergen Hoeller 040 * @author Rossen Stoyanchev 041 * @since 27.02.2003 042 * @see javax.servlet.http.HttpServletRequest#getLocale() 043 */ 044public class AcceptHeaderLocaleResolver implements LocaleResolver { 045 046 private final List<Locale> supportedLocales = new ArrayList<>(4); 047 048 @Nullable 049 private Locale defaultLocale; 050 051 052 /** 053 * Configure supported locales to check against the requested locales 054 * determined via {@link HttpServletRequest#getLocales()}. If this is not 055 * configured then {@link HttpServletRequest#getLocale()} is used instead. 056 * @param locales the supported locales 057 * @since 4.3 058 */ 059 public void setSupportedLocales(List<Locale> locales) { 060 this.supportedLocales.clear(); 061 this.supportedLocales.addAll(locales); 062 } 063 064 /** 065 * Return the configured list of supported locales. 066 * @since 4.3 067 */ 068 public List<Locale> getSupportedLocales() { 069 return this.supportedLocales; 070 } 071 072 /** 073 * Configure a fixed default locale to fall back on if the request does not 074 * have an "Accept-Language" header. 075 * <p>By default this is not set in which case when there is "Accept-Language" 076 * header, the default locale for the server is used as defined in 077 * {@link HttpServletRequest#getLocale()}. 078 * @param defaultLocale the default locale to use 079 * @since 4.3 080 */ 081 public void setDefaultLocale(@Nullable Locale defaultLocale) { 082 this.defaultLocale = defaultLocale; 083 } 084 085 /** 086 * The configured default locale, if any. 087 * <p>This method may be overridden in subclasses. 088 * @since 4.3 089 */ 090 @Nullable 091 public Locale getDefaultLocale() { 092 return this.defaultLocale; 093 } 094 095 096 @Override 097 public Locale resolveLocale(HttpServletRequest request) { 098 Locale defaultLocale = getDefaultLocale(); 099 if (defaultLocale != null && request.getHeader("Accept-Language") == null) { 100 return defaultLocale; 101 } 102 Locale requestLocale = request.getLocale(); 103 List<Locale> supportedLocales = getSupportedLocales(); 104 if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) { 105 return requestLocale; 106 } 107 Locale supportedLocale = findSupportedLocale(request, supportedLocales); 108 if (supportedLocale != null) { 109 return supportedLocale; 110 } 111 return (defaultLocale != null ? defaultLocale : requestLocale); 112 } 113 114 @Nullable 115 private Locale findSupportedLocale(HttpServletRequest request, List<Locale> supportedLocales) { 116 Enumeration<Locale> requestLocales = request.getLocales(); 117 Locale languageMatch = null; 118 while (requestLocales.hasMoreElements()) { 119 Locale locale = requestLocales.nextElement(); 120 if (supportedLocales.contains(locale)) { 121 if (languageMatch == null || languageMatch.getLanguage().equals(locale.getLanguage())) { 122 // Full match: language + country, possibly narrowed from earlier language-only match 123 return locale; 124 } 125 } 126 else if (languageMatch == null) { 127 // Let's try to find a language-only match as a fallback 128 for (Locale candidate : supportedLocales) { 129 if (!StringUtils.hasLength(candidate.getCountry()) && 130 candidate.getLanguage().equals(locale.getLanguage())) { 131 languageMatch = candidate; 132 break; 133 } 134 } 135 } 136 } 137 return languageMatch; 138 } 139 140 @Override 141 public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) { 142 throw new UnsupportedOperationException( 143 "Cannot change HTTP accept header - use a different locale resolution strategy"); 144 } 145 146}