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
5use crate::{provider::*, LocaleTransformError};
6
7use core::mem;
8use icu_locid::subtags::{Language, Region, Script};
9use icu_locid::LanguageIdentifier;
10use icu_provider::prelude::*;
11
12use 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)]
69pub struct LocaleExpander {
70 likely_subtags_l: DataPayload<LikelySubtagsForLanguageV1Marker>,
71 likely_subtags_sr: DataPayload<LikelySubtagsForScriptRegionV1Marker>,
72 likely_subtags_ext: Option<DataPayload<LikelySubtagsExtendedV1Marker>>,
73}
74
75struct 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
81impl 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]
154fn 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
184impl 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)]
590mod 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