1 | // This file is part of ICU4X. For terms of use, please see the file |
2 | // called LICENSE at the top level of the ICU4X source tree |
3 | // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). |
4 | |
5 | use crate::{provider::*, LocaleTransformError}; |
6 | |
7 | use core::mem; |
8 | use icu_locid::subtags::{Language, Region, Script}; |
9 | use icu_locid::LanguageIdentifier; |
10 | use icu_provider::prelude::*; |
11 | |
12 | use crate::TransformResult; |
13 | |
14 | /// Implements the *Add Likely Subtags* and *Remove Likely Subtags* |
15 | /// algorithms as defined in *[UTS #35: Likely Subtags]*. |
16 | /// |
17 | /// # Examples |
18 | /// |
19 | /// Add likely subtags: |
20 | /// |
21 | /// ``` |
22 | /// use icu_locid::locale; |
23 | /// use icu_locid_transform::{LocaleExpander, TransformResult}; |
24 | /// |
25 | /// let lc = LocaleExpander::new(); |
26 | /// |
27 | /// let mut locale = locale!("zh-CN" ); |
28 | /// assert_eq!(lc.maximize(&mut locale), TransformResult::Modified); |
29 | /// assert_eq!(locale, locale!("zh-Hans-CN" )); |
30 | /// |
31 | /// let mut locale = locale!("zh-Hant-TW" ); |
32 | /// assert_eq!(lc.maximize(&mut locale), TransformResult::Unmodified); |
33 | /// assert_eq!(locale, locale!("zh-Hant-TW" )); |
34 | /// ``` |
35 | /// |
36 | /// Remove likely subtags: |
37 | /// |
38 | /// ``` |
39 | /// use icu_locid::locale; |
40 | /// use icu_locid_transform::{LocaleExpander, TransformResult}; |
41 | /// |
42 | /// let lc = LocaleExpander::new(); |
43 | /// |
44 | /// let mut locale = locale!("zh-Hans-CN" ); |
45 | /// assert_eq!(lc.minimize(&mut locale), TransformResult::Modified); |
46 | /// assert_eq!(locale, locale!("zh" )); |
47 | /// |
48 | /// let mut locale = locale!("zh" ); |
49 | /// assert_eq!(lc.minimize(&mut locale), TransformResult::Unmodified); |
50 | /// assert_eq!(locale, locale!("zh" )); |
51 | /// ``` |
52 | /// |
53 | /// Normally, only CLDR locales with Basic or higher coverage are included. To include more |
54 | /// locales for maximization, use [`try_new_extended`](Self::try_new_extended_unstable): |
55 | /// |
56 | /// ``` |
57 | /// use icu_locid::locale; |
58 | /// use icu_locid_transform::{LocaleExpander, TransformResult}; |
59 | /// |
60 | /// let lc = LocaleExpander::new_extended(); |
61 | /// |
62 | /// let mut locale = locale!("atj" ); |
63 | /// assert_eq!(lc.maximize(&mut locale), TransformResult::Modified); |
64 | /// assert_eq!(locale, locale!("atj-Latn-CA" )); |
65 | /// ``` |
66 | /// |
67 | /// [UTS #35: Likely Subtags]: https://www.unicode.org/reports/tr35/#Likely_Subtags |
68 | #[derive (Debug, Clone)] |
69 | pub struct LocaleExpander { |
70 | likely_subtags_l: DataPayload<LikelySubtagsForLanguageV1Marker>, |
71 | likely_subtags_sr: DataPayload<LikelySubtagsForScriptRegionV1Marker>, |
72 | likely_subtags_ext: Option<DataPayload<LikelySubtagsExtendedV1Marker>>, |
73 | } |
74 | |
75 | struct LocaleExpanderBorrowed<'a> { |
76 | likely_subtags_l: &'a LikelySubtagsForLanguageV1<'a>, |
77 | likely_subtags_sr: &'a LikelySubtagsForScriptRegionV1<'a>, |
78 | likely_subtags_ext: Option<&'a LikelySubtagsExtendedV1<'a>>, |
79 | } |
80 | |
81 | impl LocaleExpanderBorrowed<'_> { |
82 | fn get_l(&self, l: Language) -> Option<(Script, Region)> { |
83 | let key = &l.into_tinystr().to_unvalidated(); |
84 | self.likely_subtags_l.language.get_copied(key).or_else(|| { |
85 | self.likely_subtags_ext |
86 | .and_then(|ext| ext.language.get_copied(key)) |
87 | }) |
88 | } |
89 | |
90 | fn get_ls(&self, l: Language, s: Script) -> Option<Region> { |
91 | let key = &( |
92 | l.into_tinystr().to_unvalidated(), |
93 | s.into_tinystr().to_unvalidated(), |
94 | ); |
95 | self.likely_subtags_l |
96 | .language_script |
97 | .get_copied(key) |
98 | .or_else(|| { |
99 | self.likely_subtags_ext |
100 | .and_then(|ext| ext.language_script.get_copied(key)) |
101 | }) |
102 | } |
103 | |
104 | fn get_lr(&self, l: Language, r: Region) -> Option<Script> { |
105 | let key = &( |
106 | l.into_tinystr().to_unvalidated(), |
107 | r.into_tinystr().to_unvalidated(), |
108 | ); |
109 | self.likely_subtags_l |
110 | .language_region |
111 | .get_copied(key) |
112 | .or_else(|| { |
113 | self.likely_subtags_ext |
114 | .and_then(|ext| ext.language_region.get_copied(key)) |
115 | }) |
116 | } |
117 | |
118 | fn get_s(&self, s: Script) -> Option<(Language, Region)> { |
119 | let key = &s.into_tinystr().to_unvalidated(); |
120 | self.likely_subtags_sr.script.get_copied(key).or_else(|| { |
121 | self.likely_subtags_ext |
122 | .and_then(|ext| ext.script.get_copied(key)) |
123 | }) |
124 | } |
125 | |
126 | fn get_sr(&self, s: Script, r: Region) -> Option<Language> { |
127 | let key = &( |
128 | s.into_tinystr().to_unvalidated(), |
129 | r.into_tinystr().to_unvalidated(), |
130 | ); |
131 | self.likely_subtags_sr |
132 | .script_region |
133 | .get_copied(key) |
134 | .or_else(|| { |
135 | self.likely_subtags_ext |
136 | .and_then(|ext| ext.script_region.get_copied(key)) |
137 | }) |
138 | } |
139 | |
140 | fn get_r(&self, r: Region) -> Option<(Language, Script)> { |
141 | let key = &r.into_tinystr().to_unvalidated(); |
142 | self.likely_subtags_sr.region.get_copied(key).or_else(|| { |
143 | self.likely_subtags_ext |
144 | .and_then(|ext| ext.region.get_copied(key)) |
145 | }) |
146 | } |
147 | |
148 | fn get_und(&self) -> (Language, Script, Region) { |
149 | self.likely_subtags_l.und |
150 | } |
151 | } |
152 | |
153 | #[inline ] |
154 | fn update_langid( |
155 | language: Language, |
156 | script: Option<Script>, |
157 | region: Option<Region>, |
158 | langid: &mut LanguageIdentifier, |
159 | ) -> TransformResult { |
160 | let mut modified: bool = false; |
161 | |
162 | if langid.language.is_empty() && !language.is_empty() { |
163 | langid.language = language; |
164 | modified = true; |
165 | } |
166 | |
167 | if langid.script.is_none() && script.is_some() { |
168 | langid.script = script; |
169 | modified = true; |
170 | } |
171 | |
172 | if langid.region.is_none() && region.is_some() { |
173 | langid.region = region; |
174 | modified = true; |
175 | } |
176 | |
177 | if modified { |
178 | TransformResult::Modified |
179 | } else { |
180 | TransformResult::Unmodified |
181 | } |
182 | } |
183 | |
184 | impl LocaleExpander { |
185 | /// Creates a [`LocaleExpander`] with compiled data for commonly-used locales |
186 | /// (locales with *Basic* or higher [CLDR coverage]). |
187 | /// |
188 | /// Use this constructor if you want limited likely subtags for data-oriented use cases. |
189 | /// |
190 | /// ✨ *Enabled with the `compiled_data` Cargo feature.* |
191 | /// |
192 | /// [📚 Help choosing a constructor](icu_provider::constructors) |
193 | /// |
194 | /// [CLDR coverage]: https://www.unicode.org/reports/tr35/tr35-info.html#Coverage_Levels |
195 | #[cfg (feature = "compiled_data" )] |
196 | pub const fn new() -> Self { |
197 | LocaleExpander { |
198 | likely_subtags_l: DataPayload::from_static_ref( |
199 | crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_L_V1, |
200 | ), |
201 | likely_subtags_sr: DataPayload::from_static_ref( |
202 | crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_SR_V1, |
203 | ), |
204 | likely_subtags_ext: None, |
205 | } |
206 | } |
207 | |
208 | /// Creates a [`LocaleExpander`] with compiled data for all locales. |
209 | /// |
210 | /// Use this constructor if you want to include data for all locales, including ones |
211 | /// that may not have data for other services (i.e. [CLDR coverage] below *Basic*). |
212 | /// |
213 | /// ✨ *Enabled with the `compiled_data` Cargo feature.* |
214 | /// |
215 | /// [📚 Help choosing a constructor](icu_provider::constructors) |
216 | /// |
217 | /// [CLDR coverage]: https://www.unicode.org/reports/tr35/tr35-info.html#Coverage_Levels |
218 | #[cfg (feature = "compiled_data" )] |
219 | pub const fn new_extended() -> Self { |
220 | LocaleExpander { |
221 | likely_subtags_l: DataPayload::from_static_ref( |
222 | crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_L_V1, |
223 | ), |
224 | likely_subtags_sr: DataPayload::from_static_ref( |
225 | crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_SR_V1, |
226 | ), |
227 | likely_subtags_ext: Some(DataPayload::from_static_ref( |
228 | crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_EXT_V1, |
229 | )), |
230 | } |
231 | } |
232 | |
233 | #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new_extended)] |
234 | pub fn try_new_extended_unstable<P>( |
235 | provider: &P, |
236 | ) -> Result<LocaleExpander, LocaleTransformError> |
237 | where |
238 | P: DataProvider<LikelySubtagsForLanguageV1Marker> |
239 | + DataProvider<LikelySubtagsForScriptRegionV1Marker> |
240 | + DataProvider<LikelySubtagsExtendedV1Marker> |
241 | + ?Sized, |
242 | { |
243 | let likely_subtags_l = provider.load(Default::default())?.take_payload()?; |
244 | let likely_subtags_sr = provider.load(Default::default())?.take_payload()?; |
245 | let likely_subtags_ext = Some(provider.load(Default::default())?.take_payload()?); |
246 | |
247 | Ok(LocaleExpander { |
248 | likely_subtags_l, |
249 | likely_subtags_sr, |
250 | likely_subtags_ext, |
251 | }) |
252 | } |
253 | |
254 | icu_provider::gen_any_buffer_data_constructors!(locale: skip, options: skip, error: LocaleTransformError, |
255 | #[cfg (skip)] |
256 | functions: [ |
257 | new_extended, |
258 | try_new_extended_with_any_provider, |
259 | try_new_extended_with_buffer_provider, |
260 | try_new_extended_unstable, |
261 | Self |
262 | ]); |
263 | |
264 | #[doc = icu_provider::gen_any_buffer_unstable_docs!(ANY, Self::new)] |
265 | pub fn try_new_with_any_provider( |
266 | provider: &(impl AnyProvider + ?Sized), |
267 | ) -> Result<LocaleExpander, LocaleTransformError> { |
268 | Self::try_new_compat(&provider.as_downcasting()) |
269 | } |
270 | |
271 | #[doc = icu_provider::gen_any_buffer_unstable_docs!(BUFFER, Self::new)] |
272 | #[cfg (feature = "serde" )] |
273 | pub fn try_new_with_buffer_provider( |
274 | provider: &(impl BufferProvider + ?Sized), |
275 | ) -> Result<LocaleExpander, LocaleTransformError> { |
276 | Self::try_new_compat(&provider.as_deserializing()) |
277 | } |
278 | |
279 | #[doc = icu_provider::gen_any_buffer_unstable_docs!(UNSTABLE, Self::new)] |
280 | pub fn try_new_unstable<P>(provider: &P) -> Result<LocaleExpander, LocaleTransformError> |
281 | where |
282 | P: DataProvider<LikelySubtagsForLanguageV1Marker> |
283 | + DataProvider<LikelySubtagsForScriptRegionV1Marker> |
284 | + ?Sized, |
285 | { |
286 | let likely_subtags_l = provider.load(Default::default())?.take_payload()?; |
287 | let likely_subtags_sr = provider.load(Default::default())?.take_payload()?; |
288 | |
289 | Ok(LocaleExpander { |
290 | likely_subtags_l, |
291 | likely_subtags_sr, |
292 | likely_subtags_ext: None, |
293 | }) |
294 | } |
295 | |
296 | fn try_new_compat<P>(provider: &P) -> Result<LocaleExpander, LocaleTransformError> |
297 | where |
298 | P: DataProvider<LikelySubtagsForLanguageV1Marker> |
299 | + DataProvider<LikelySubtagsForScriptRegionV1Marker> |
300 | + DataProvider<LikelySubtagsExtendedV1Marker> |
301 | + DataProvider<LikelySubtagsV1Marker> |
302 | + ?Sized, |
303 | { |
304 | let payload_l = provider |
305 | .load(Default::default()) |
306 | .and_then(DataResponse::take_payload); |
307 | let payload_sr = provider |
308 | .load(Default::default()) |
309 | .and_then(DataResponse::take_payload); |
310 | let payload_ext = provider |
311 | .load(Default::default()) |
312 | .and_then(DataResponse::take_payload); |
313 | |
314 | let (likely_subtags_l, likely_subtags_sr, likely_subtags_ext) = |
315 | match (payload_l, payload_sr, payload_ext) { |
316 | (Ok(l), Ok(sr), Err(_)) => (l, sr, None), |
317 | (Ok(l), Ok(sr), Ok(ext)) => (l, sr, Some(ext)), |
318 | _ => { |
319 | let result: DataPayload<LikelySubtagsV1Marker> = |
320 | provider.load(Default::default())?.take_payload()?; |
321 | ( |
322 | result.map_project_cloned(|st, _| { |
323 | LikelySubtagsForLanguageV1::clone_from_borrowed(st) |
324 | }), |
325 | result.map_project(|st, _| st.into()), |
326 | None, |
327 | ) |
328 | } |
329 | }; |
330 | |
331 | Ok(LocaleExpander { |
332 | likely_subtags_l, |
333 | likely_subtags_sr, |
334 | likely_subtags_ext, |
335 | }) |
336 | } |
337 | |
338 | fn as_borrowed(&self) -> LocaleExpanderBorrowed { |
339 | LocaleExpanderBorrowed { |
340 | likely_subtags_l: self.likely_subtags_l.get(), |
341 | likely_subtags_sr: self.likely_subtags_sr.get(), |
342 | likely_subtags_ext: self.likely_subtags_ext.as_ref().map(|p| p.get()), |
343 | } |
344 | } |
345 | |
346 | /// The maximize method potentially updates a passed in locale in place |
347 | /// depending up the results of running the 'Add Likely Subtags' algorithm |
348 | /// from <https://www.unicode.org/reports/tr35/#Likely_Subtags>. |
349 | /// |
350 | /// If the result of running the algorithm would result in a new locale, the |
351 | /// locale argument is updated in place to match the result, and the method |
352 | /// returns [`TransformResult::Modified`]. Otherwise, the method |
353 | /// returns [`TransformResult::Unmodified`] and the locale argument is |
354 | /// unchanged. |
355 | /// |
356 | /// # Examples |
357 | /// |
358 | /// ``` |
359 | /// use icu_locid::locale; |
360 | /// use icu_locid_transform::{LocaleExpander, TransformResult}; |
361 | /// |
362 | /// let lc = LocaleExpander::new(); |
363 | /// |
364 | /// let mut locale = locale!("zh-CN" ); |
365 | /// assert_eq!(lc.maximize(&mut locale), TransformResult::Modified); |
366 | /// assert_eq!(locale, locale!("zh-Hans-CN" )); |
367 | /// |
368 | /// let mut locale = locale!("zh-Hant-TW" ); |
369 | /// assert_eq!(lc.maximize(&mut locale), TransformResult::Unmodified); |
370 | /// assert_eq!(locale, locale!("zh-Hant-TW" )); |
371 | /// ``` |
372 | pub fn maximize<T: AsMut<LanguageIdentifier>>(&self, mut langid: T) -> TransformResult { |
373 | let langid = langid.as_mut(); |
374 | let data = self.as_borrowed(); |
375 | |
376 | if !langid.language.is_empty() && langid.script.is_some() && langid.region.is_some() { |
377 | return TransformResult::Unmodified; |
378 | } |
379 | |
380 | if !langid.language.is_empty() { |
381 | if let Some(region) = langid.region { |
382 | if let Some(script) = data.get_lr(langid.language, region) { |
383 | return update_langid(Language::UND, Some(script), None, langid); |
384 | } |
385 | } |
386 | if let Some(script) = langid.script { |
387 | if let Some(region) = data.get_ls(langid.language, script) { |
388 | return update_langid(Language::UND, None, Some(region), langid); |
389 | } |
390 | } |
391 | if let Some((script, region)) = data.get_l(langid.language) { |
392 | return update_langid(Language::UND, Some(script), Some(region), langid); |
393 | } |
394 | } |
395 | if let Some(script) = langid.script { |
396 | if let Some(region) = langid.region { |
397 | if let Some(language) = data.get_sr(script, region) { |
398 | return update_langid(language, None, None, langid); |
399 | } |
400 | } |
401 | if let Some((language, region)) = data.get_s(script) { |
402 | return update_langid(language, None, Some(region), langid); |
403 | } |
404 | } |
405 | if let Some(region) = langid.region { |
406 | if let Some((language, script)) = data.get_r(region) { |
407 | return update_langid(language, Some(script), None, langid); |
408 | } |
409 | } |
410 | |
411 | update_langid( |
412 | data.get_und().0, |
413 | Some(data.get_und().1), |
414 | Some(data.get_und().2), |
415 | langid, |
416 | ) |
417 | } |
418 | |
419 | /// This returns a new Locale that is the result of running the |
420 | /// 'Remove Likely Subtags' algorithm from |
421 | /// <https://www.unicode.org/reports/tr35/#Likely_Subtags>. |
422 | /// |
423 | /// If the result of running the algorithm would result in a new locale, the |
424 | /// locale argument is updated in place to match the result, and the method |
425 | /// returns [`TransformResult::Modified`]. Otherwise, the method |
426 | /// returns [`TransformResult::Unmodified`] and the locale argument is |
427 | /// unchanged. |
428 | /// |
429 | /// # Examples |
430 | /// |
431 | /// ``` |
432 | /// use icu_locid::locale; |
433 | /// use icu_locid_transform::{LocaleExpander, TransformResult}; |
434 | /// |
435 | /// let lc = LocaleExpander::new(); |
436 | /// |
437 | /// let mut locale = locale!("zh-Hans-CN" ); |
438 | /// assert_eq!(lc.minimize(&mut locale), TransformResult::Modified); |
439 | /// assert_eq!(locale, locale!("zh" )); |
440 | /// |
441 | /// let mut locale = locale!("zh" ); |
442 | /// assert_eq!(lc.minimize(&mut locale), TransformResult::Unmodified); |
443 | /// assert_eq!(locale, locale!("zh" )); |
444 | /// ``` |
445 | pub fn minimize<T: AsMut<LanguageIdentifier>>(&self, mut langid: T) -> TransformResult { |
446 | let langid = langid.as_mut(); |
447 | |
448 | let mut max = langid.clone(); |
449 | self.maximize(&mut max); |
450 | let variants = mem::take(&mut max.variants); |
451 | max.variants.clear(); |
452 | let mut trial = max.clone(); |
453 | |
454 | trial.script = None; |
455 | trial.region = None; |
456 | self.maximize(&mut trial); |
457 | if trial == max { |
458 | if langid.language != max.language || langid.script.is_some() || langid.region.is_some() |
459 | { |
460 | if langid.language != max.language { |
461 | langid.language = max.language |
462 | } |
463 | if langid.script.is_some() { |
464 | langid.script = None; |
465 | } |
466 | if langid.region.is_some() { |
467 | langid.region = None; |
468 | } |
469 | langid.variants = variants; |
470 | return TransformResult::Modified; |
471 | } else { |
472 | return TransformResult::Unmodified; |
473 | } |
474 | } |
475 | |
476 | trial.script = None; |
477 | trial.region = max.region; |
478 | self.maximize(&mut trial); |
479 | if trial == max { |
480 | if langid.language != max.language |
481 | || langid.script.is_some() |
482 | || langid.region != max.region |
483 | { |
484 | if langid.language != max.language { |
485 | langid.language = max.language |
486 | } |
487 | if langid.script.is_some() { |
488 | langid.script = None; |
489 | } |
490 | if langid.region != max.region { |
491 | langid.region = max.region; |
492 | } |
493 | langid.variants = variants; |
494 | return TransformResult::Modified; |
495 | } else { |
496 | return TransformResult::Unmodified; |
497 | } |
498 | } |
499 | |
500 | trial.script = max.script; |
501 | trial.region = None; |
502 | self.maximize(&mut trial); |
503 | if trial == max { |
504 | if langid.language != max.language |
505 | || langid.script != max.script |
506 | || langid.region.is_some() |
507 | { |
508 | if langid.language != max.language { |
509 | langid.language = max.language |
510 | } |
511 | if langid.script != max.script { |
512 | langid.script = max.script; |
513 | } |
514 | if langid.region.is_some() { |
515 | langid.region = None; |
516 | } |
517 | langid.variants = variants; |
518 | return TransformResult::Modified; |
519 | } else { |
520 | return TransformResult::Unmodified; |
521 | } |
522 | } |
523 | |
524 | if langid.language != max.language |
525 | || langid.script != max.script |
526 | || langid.region != max.region |
527 | { |
528 | if langid.language != max.language { |
529 | langid.language = max.language |
530 | } |
531 | if langid.script != max.script { |
532 | langid.script = max.script; |
533 | } |
534 | if langid.region != max.region { |
535 | langid.region = max.region; |
536 | } |
537 | TransformResult::Modified |
538 | } else { |
539 | TransformResult::Unmodified |
540 | } |
541 | } |
542 | |
543 | // TODO(3492): consider turning this and a future get_likely_region/get_likely_language public |
544 | #[inline ] |
545 | pub(crate) fn get_likely_script<T: AsRef<LanguageIdentifier>>( |
546 | &self, |
547 | langid: T, |
548 | ) -> Option<Script> { |
549 | let langid = langid.as_ref(); |
550 | langid |
551 | .script |
552 | .or_else(|| self.infer_likely_script(langid.language, langid.region)) |
553 | } |
554 | |
555 | fn infer_likely_script(&self, language: Language, region: Option<Region>) -> Option<Script> { |
556 | let data = self.as_borrowed(); |
557 | |
558 | // proceed through _all possible cases_ in order of specificity |
559 | // (borrowed from LocaleExpander::maximize): |
560 | // 1. language + region |
561 | // 2. language |
562 | // 3. region |
563 | // we need to check all cases, because e.g. for "en-US" the default script is associated |
564 | // with "en" but not "en-US" |
565 | if language != Language::UND { |
566 | if let Some(region) = region { |
567 | // 1. we know both language and region |
568 | if let Some(script) = data.get_lr(language, region) { |
569 | return Some(script); |
570 | } |
571 | } |
572 | // 2. we know language, but we either do not know region or knowing region did not help |
573 | if let Some((script, _)) = data.get_l(language) { |
574 | return Some(script); |
575 | } |
576 | } |
577 | if let Some(region) = region { |
578 | // 3. we know region, but we either do not know language or knowing language did not help |
579 | if let Some((_, script)) = data.get_r(region) { |
580 | return Some(script); |
581 | } |
582 | } |
583 | // we could not figure out the script from the given locale |
584 | None |
585 | } |
586 | } |
587 | |
588 | #[cfg (feature = "serde" )] |
589 | #[cfg (test)] |
590 | mod tests { |
591 | use super::*; |
592 | use icu_locid::locale; |
593 | |
594 | struct RejectByKeyProvider { |
595 | keys: Vec<DataKey>, |
596 | } |
597 | |
598 | impl AnyProvider for RejectByKeyProvider { |
599 | fn load_any(&self, key: DataKey, _: DataRequest) -> Result<AnyResponse, DataError> { |
600 | if self.keys.contains(&key) { |
601 | return Err(DataErrorKind::MissingDataKey.with_str_context("rejected" )); |
602 | } |
603 | |
604 | let l = crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_L_V1; |
605 | let ext = crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_EXT_V1; |
606 | let sr = crate::provider::Baked::SINGLETON_LOCID_TRANSFORM_LIKELYSUBTAGS_SR_V1; |
607 | |
608 | let payload = if key.hashed() == LikelySubtagsV1Marker::KEY.hashed() { |
609 | DataPayload::<LikelySubtagsV1Marker>::from_owned(LikelySubtagsV1 { |
610 | language_script: l |
611 | .language_script |
612 | .iter_copied() |
613 | .chain(ext.language_script.iter_copied()) |
614 | .collect(), |
615 | language_region: l |
616 | .language_region |
617 | .iter_copied() |
618 | .chain(ext.language_region.iter_copied()) |
619 | .collect(), |
620 | language: l |
621 | .language |
622 | .iter_copied() |
623 | .chain(ext.language.iter_copied()) |
624 | .collect(), |
625 | script_region: ext.script_region.clone(), |
626 | script: ext.script.clone(), |
627 | region: ext.region.clone(), |
628 | und: l.und, |
629 | }) |
630 | .wrap_into_any_payload() |
631 | } else if key.hashed() == LikelySubtagsForLanguageV1Marker::KEY.hashed() { |
632 | DataPayload::<LikelySubtagsForLanguageV1Marker>::from_static_ref(l) |
633 | .wrap_into_any_payload() |
634 | } else if key.hashed() == LikelySubtagsExtendedV1Marker::KEY.hashed() { |
635 | DataPayload::<LikelySubtagsExtendedV1Marker>::from_static_ref(ext) |
636 | .wrap_into_any_payload() |
637 | } else if key.hashed() == LikelySubtagsForScriptRegionV1Marker::KEY.hashed() { |
638 | DataPayload::<LikelySubtagsForScriptRegionV1Marker>::from_static_ref(sr) |
639 | .wrap_into_any_payload() |
640 | } else { |
641 | return Err(DataErrorKind::MissingDataKey.into_error()); |
642 | }; |
643 | |
644 | Ok(AnyResponse { |
645 | payload: Some(payload), |
646 | metadata: Default::default(), |
647 | }) |
648 | } |
649 | } |
650 | |
651 | #[test ] |
652 | fn test_old_keys() { |
653 | let provider = RejectByKeyProvider { |
654 | keys: vec![ |
655 | LikelySubtagsForLanguageV1Marker::KEY, |
656 | LikelySubtagsForScriptRegionV1Marker::KEY, |
657 | LikelySubtagsExtendedV1Marker::KEY, |
658 | ], |
659 | }; |
660 | let lc = LocaleExpander::try_new_with_any_provider(&provider) |
661 | .expect("should create with old keys" ); |
662 | let mut locale = locale!("zh-CN" ); |
663 | assert_eq!(lc.maximize(&mut locale), TransformResult::Modified); |
664 | assert_eq!(locale, locale!("zh-Hans-CN" )); |
665 | } |
666 | |
667 | #[test ] |
668 | fn test_new_keys() { |
669 | let provider = RejectByKeyProvider { |
670 | keys: vec![LikelySubtagsV1Marker::KEY], |
671 | }; |
672 | let lc = LocaleExpander::try_new_with_any_provider(&provider) |
673 | .expect("should create with new keys" ); |
674 | let mut locale = locale!("zh-CN" ); |
675 | assert_eq!(lc.maximize(&mut locale), TransformResult::Modified); |
676 | assert_eq!(locale, locale!("zh-Hans-CN" )); |
677 | } |
678 | |
679 | #[test ] |
680 | fn test_mixed_keys() { |
681 | // Include the old key and one of the new keys but not both new keys. |
682 | // Not sure if this is a useful test. |
683 | let provider = RejectByKeyProvider { |
684 | keys: vec![LikelySubtagsForScriptRegionV1Marker::KEY], |
685 | }; |
686 | let lc = LocaleExpander::try_new_with_any_provider(&provider) |
687 | .expect("should create with mixed keys" ); |
688 | let mut locale = locale!("zh-CN" ); |
689 | assert_eq!(lc.maximize(&mut locale), TransformResult::Modified); |
690 | assert_eq!(locale, locale!("zh-Hans-CN" )); |
691 | } |
692 | |
693 | #[test ] |
694 | fn test_no_keys() { |
695 | let provider = RejectByKeyProvider { |
696 | keys: vec![ |
697 | LikelySubtagsForLanguageV1Marker::KEY, |
698 | LikelySubtagsForScriptRegionV1Marker::KEY, |
699 | LikelySubtagsV1Marker::KEY, |
700 | ], |
701 | }; |
702 | if LocaleExpander::try_new_with_any_provider(&provider).is_ok() { |
703 | panic!("should not create: no data present" ) |
704 | }; |
705 | } |
706 | |
707 | #[test ] |
708 | fn test_new_small_keys() { |
709 | // Include the new small keys but not the extended key |
710 | let provider = RejectByKeyProvider { |
711 | keys: vec![ |
712 | LikelySubtagsExtendedV1Marker::KEY, |
713 | LikelySubtagsV1Marker::KEY, |
714 | ], |
715 | }; |
716 | let lc = LocaleExpander::try_new_with_any_provider(&provider) |
717 | .expect("should create with mixed keys" ); |
718 | let mut locale = locale!("zh-CN" ); |
719 | assert_eq!(lc.maximize(&mut locale), TransformResult::Modified); |
720 | assert_eq!(locale, locale!("zh-Hans-CN" )); |
721 | } |
722 | } |
723 | |