@@ -11,8 +11,9 @@ use crate::{
1111 JSONEngineUrls , JSONEngineVariant , JSONSearchConfigurationRecords , RefinedSearchConfig ,
1212 SearchEngineDefinition , SearchEngineUrl , SearchEngineUrls , SearchUserEnvironment ,
1313} ;
14- use crate :: { sort_helpers, JSONEngineOrdersRecord } ;
14+ use crate :: { sort_helpers, JSONAvailableLocalesRecord , JSONEngineOrdersRecord } ;
1515use remote_settings:: RemoteSettingsRecord ;
16+ use std:: collections:: HashSet ;
1617
1718impl From < JSONEngineUrl > for SearchEngineUrl {
1819 fn from ( url : JSONEngineUrl ) -> Self {
@@ -143,7 +144,7 @@ pub(crate) struct FilterRecordsResult {
143144pub ( crate ) trait Filter {
144145 fn filter_records (
145146 & self ,
146- user_environment : & SearchUserEnvironment ,
147+ user_environment : & mut SearchUserEnvironment ,
147148 overrides : Option < Vec < JSONOverridesRecord > > ,
148149 ) -> Result < FilterRecordsResult , Error > ;
149150}
@@ -158,12 +159,48 @@ fn apply_overrides(engines: &mut [SearchEngineDefinition], overrides: &[JSONOver
158159 }
159160}
160161
162+ fn negotiate_languages ( user_environment : & mut SearchUserEnvironment , available_locales : & [ String ] ) {
163+ let user_locale = user_environment. locale . to_lowercase ( ) ;
164+
165+ let available_locales_set: HashSet < String > = available_locales
166+ . iter ( )
167+ . map ( |locale| locale. to_lowercase ( ) )
168+ . collect ( ) ;
169+
170+ if available_locales_set. contains ( & user_locale) {
171+ return ;
172+ }
173+ if user_locale. starts_with ( "en-" ) {
174+ user_environment. locale = "en-us" . to_string ( ) ;
175+ return ;
176+ }
177+ if let Some ( index) = user_locale. find ( '-' ) {
178+ let base_locale = & user_locale[ ..index] ;
179+ if available_locales_set. contains ( base_locale) {
180+ user_environment. locale = base_locale. to_string ( ) ;
181+ }
182+ }
183+ }
184+
161185impl Filter for Vec < RemoteSettingsRecord > {
162186 fn filter_records (
163187 & self ,
164- user_environment : & SearchUserEnvironment ,
188+ user_environment : & mut SearchUserEnvironment ,
165189 overrides : Option < Vec < JSONOverridesRecord > > ,
166190 ) -> Result < FilterRecordsResult , Error > {
191+ let mut available_locales = Vec :: new ( ) ;
192+ for record in self {
193+ if let Some ( val) = record. fields . get ( "recordType" ) {
194+ if * val == "availableLocales" {
195+ let stringified = serde_json:: to_string ( & record. fields ) ?;
196+ let locales_record: Option < JSONAvailableLocalesRecord > =
197+ serde_json:: from_str ( & stringified) ?;
198+ available_locales = locales_record. unwrap ( ) . locales ;
199+ }
200+ }
201+ }
202+ negotiate_languages ( user_environment, & available_locales) ;
203+
167204 let mut engines = Vec :: new ( ) ;
168205 let mut default_engines_record = None ;
169206 let mut engine_orders_record = None ;
@@ -188,6 +225,9 @@ impl Filter for Vec<RemoteSettingsRecord> {
188225 Some ( val) if * val == "engineOrders" => {
189226 engine_orders_record = serde_json:: from_str ( & stringified) ?;
190227 }
228+ Some ( val) if * val == "availableLocales" => {
229+ // Handled above
230+ }
191231 // These cases are acceptable - we expect the potential for new
192232 // record types/options so that we can be flexible.
193233 Some ( _val) => { }
@@ -210,9 +250,17 @@ impl Filter for Vec<RemoteSettingsRecord> {
210250impl Filter for Vec < JSONSearchConfigurationRecords > {
211251 fn filter_records (
212252 & self ,
213- user_environment : & SearchUserEnvironment ,
253+ user_environment : & mut SearchUserEnvironment ,
214254 overrides : Option < Vec < JSONOverridesRecord > > ,
215255 ) -> Result < FilterRecordsResult , Error > {
256+ let mut available_locales = Vec :: new ( ) ;
257+ for record in self {
258+ if let JSONSearchConfigurationRecords :: AvailableLocales ( locales_record) = record {
259+ available_locales = locales_record. locales . clone ( ) ;
260+ }
261+ }
262+ negotiate_languages ( user_environment, & available_locales) ;
263+
216264 let mut engines = Vec :: new ( ) ;
217265 let mut default_engines_record = None ;
218266 let mut engine_orders_record = None ;
@@ -229,6 +277,9 @@ impl Filter for Vec<JSONSearchConfigurationRecords> {
229277 JSONSearchConfigurationRecords :: EngineOrders ( engine_orders) => {
230278 engine_orders_record = Some ( engine_orders)
231279 }
280+ JSONSearchConfigurationRecords :: AvailableLocales ( _) => {
281+ // Handled above
282+ }
232283 JSONSearchConfigurationRecords :: Unknown => {
233284 // Prevents panics if a new record type is added in future.
234285 }
@@ -246,6 +297,7 @@ impl Filter for Vec<JSONSearchConfigurationRecords> {
246297 } )
247298 }
248299}
300+
249301pub ( crate ) fn filter_engine_configuration_impl (
250302 user_environment : SearchUserEnvironment ,
251303 configuration : & impl Filter ,
@@ -256,7 +308,7 @@ pub(crate) fn filter_engine_configuration_impl(
256308 user_environment. region = user_environment. region . to_lowercase ( ) ;
257309 user_environment. version = user_environment. version . to_lowercase ( ) ;
258310
259- let filtered_result = configuration. filter_records ( & user_environment, overrides) ;
311+ let filtered_result = configuration. filter_records ( & mut user_environment, overrides) ;
260312
261313 filtered_result. map ( |result| {
262314 let ( default_engine_id, default_private_engine_id) = determine_default_engines (
@@ -1327,4 +1379,61 @@ mod tests {
13271379 "Should have returned the specific default for private mode when using a wildcard match"
13281380 ) ;
13291381 }
1382+
1383+ #[ test]
1384+ fn test_locale_matched_exactly ( ) {
1385+ let mut user_env = SearchUserEnvironment {
1386+ locale : "en-CA" . into ( ) ,
1387+ ..Default :: default ( )
1388+ } ;
1389+ negotiate_languages ( & mut user_env, & [ "en-CA" . to_string ( ) , "fr" . to_string ( ) ] ) ;
1390+ assert_eq ! (
1391+ user_env. locale, "en-CA" ,
1392+ "Should return user locale unchanged if in available locales"
1393+ ) ;
1394+ }
1395+
1396+ #[ test]
1397+ fn test_locale_fallback_to_base_locale ( ) {
1398+ let mut user_env = SearchUserEnvironment {
1399+ locale : "de-AT" . into ( ) ,
1400+ ..Default :: default ( )
1401+ } ;
1402+ negotiate_languages ( & mut user_env, & [ "de" . to_string ( ) ] ) ;
1403+ assert_eq ! (
1404+ user_env. locale, "de" ,
1405+ "Should fallback to base locale if base is in available locales"
1406+ ) ;
1407+ }
1408+
1409+ static ENGLISH_LOCALES : & [ & str ] = & [ "en-AU" , "en-IE" , "en-RU" , "en-ZA" ] ;
1410+
1411+ #[ test]
1412+ fn test_english_locales_fallbacks_to_en_us ( ) {
1413+ for user_locale in ENGLISH_LOCALES {
1414+ let mut user_env = SearchUserEnvironment {
1415+ locale : user_locale. to_string ( ) ,
1416+ ..Default :: default ( )
1417+ } ;
1418+ negotiate_languages ( & mut user_env, & [ "en-US" . to_string ( ) ] ) ;
1419+ assert_eq ! (
1420+ user_env. locale, "en-us" ,
1421+ "Should remap {} to en-us when en-us is available" ,
1422+ user_locale
1423+ ) ;
1424+ }
1425+ }
1426+
1427+ #[ test]
1428+ fn test_locale_unmatched ( ) {
1429+ let mut user_env = SearchUserEnvironment {
1430+ locale : "fr-CA" . into ( ) ,
1431+ ..Default :: default ( )
1432+ } ;
1433+ negotiate_languages ( & mut user_env, & [ "de" . to_string ( ) , "en-US" . to_string ( ) ] ) ;
1434+ assert_eq ! (
1435+ user_env. locale, "fr-CA" ,
1436+ "Should leave locale unchanged if no match or english locale fallback is not found"
1437+ ) ;
1438+ }
13301439}
0 commit comments