• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 //! Tools for locale fallback, enabling arbitrary input locales to be mapped into the nearest
6 //! locale with data.
7 
8 use crate::provider::*;
9 use icu_locale_core::subtags::*;
10 use icu_provider::prelude::*;
11 
12 #[doc(inline)]
13 pub use icu_provider::fallback::{LocaleFallbackConfig, LocaleFallbackPriority};
14 
15 mod algorithms;
16 
17 /// Implements the algorithm defined in *[UTS #35: Locale Inheritance and Matching]*.
18 ///
19 /// Note that this implementation performs some additional steps compared to the *UTS #35*
20 /// algorithm. See *[the design doc]* for a detailed description and [#2243](
21 /// https://github.com/unicode-org/icu4x/issues/2243) to track alignment with *UTS #35*.
22 ///
23 /// If running fallback in a loop, use [`DataLocale::is_default()`] to break from the loop.
24 ///
25 /// # Examples
26 ///
27 /// ```
28 /// use icu::locale::fallback::LocaleFallbacker;
29 /// use icu::locale::locale;
30 ///
31 /// // Set up a LocaleFallbacker with data.
32 /// let fallbacker = LocaleFallbacker::new();
33 ///
34 /// // Create a LocaleFallbackerIterator with a default configuration.
35 /// // By default, uses language priority.
36 /// let mut fallback_iterator = fallbacker
37 ///     .for_config(Default::default())
38 ///     .fallback_for(locale!("hi-Latn-IN").into());
39 ///
40 /// // Run the algorithm and check the results.
41 /// assert_eq!(fallback_iterator.get(), &locale!("hi-Latn-IN").into());
42 /// fallback_iterator.step();
43 /// assert_eq!(fallback_iterator.get(), &locale!("hi-Latn").into());
44 /// fallback_iterator.step();
45 /// assert_eq!(fallback_iterator.get(), &locale!("en-IN").into());
46 /// fallback_iterator.step();
47 /// assert_eq!(fallback_iterator.get(), &locale!("en-001").into());
48 /// fallback_iterator.step();
49 /// assert_eq!(fallback_iterator.get(), &locale!("en").into());
50 /// fallback_iterator.step();
51 /// assert_eq!(fallback_iterator.get(), &locale!("und").into());
52 /// ```
53 ///
54 /// [UTS #35: Locale Inheritance and Matching]: https://www.unicode.org/reports/tr35/#Locale_Inheritance
55 /// [the design doc]: https://docs.google.com/document/d/1Mp7EUyl-sFh_HZYgyeVwj88vJGpCBIWxzlCwGgLCDwM/edit
56 #[doc(hidden)] // canonical location in super
57 #[derive(Debug, Clone, PartialEq)]
58 pub struct LocaleFallbacker {
59     likely_subtags: DataPayload<LocaleLikelySubtagsLanguageV1>,
60     parents: DataPayload<LocaleParentsV1>,
61 }
62 
63 /// Borrowed version of [`LocaleFallbacker`].
64 #[derive(Debug, Clone, Copy, PartialEq)]
65 pub struct LocaleFallbackerBorrowed<'a> {
66     likely_subtags: &'a LikelySubtagsForLanguage<'a>,
67     parents: &'a Parents<'a>,
68 }
69 
70 /// A [`LocaleFallbackerBorrowed`] with an associated [`LocaleFallbackConfig`].
71 #[derive(Debug, Clone, Copy, PartialEq)]
72 pub struct LocaleFallbackerWithConfig<'a> {
73     likely_subtags: &'a LikelySubtagsForLanguage<'a>,
74     parents: &'a Parents<'a>,
75     config: LocaleFallbackConfig,
76 }
77 
78 /// Inner iteration type. Does not own the item under fallback.
79 #[derive(Debug)]
80 struct LocaleFallbackIteratorInner<'a> {
81     likely_subtags: &'a LikelySubtagsForLanguage<'a>,
82     parents: &'a Parents<'a>,
83     config: LocaleFallbackConfig,
84     backup_subdivision: Option<Subtag>,
85     backup_variant: Option<Variant>,
86     backup_region: Option<Region>,
87     max_script: Option<Script>,
88 }
89 
90 /// Iteration type for locale fallback operations.
91 ///
92 /// Because the `Iterator` trait does not allow items to borrow from the iterator, this class does
93 /// not implement that trait. Instead, use `.step()` and `.get()`.
94 #[derive(Debug)]
95 pub struct LocaleFallbackIterator<'a> {
96     current: DataLocale,
97     inner: LocaleFallbackIteratorInner<'a>,
98 }
99 
100 impl LocaleFallbacker {
101     /// Creates a [`LocaleFallbacker`] with compiled fallback data (likely subtags and parent locales).
102     ///
103     /// ✨ *Enabled with the `compiled_data` Cargo feature.*
104     ///
105     /// [�� Help choosing a constructor](icu_provider::constructors)
106     #[cfg(feature = "compiled_data")]
107     #[allow(clippy::new_ret_no_self)] // keeping constructors together
108     #[allow(clippy::new_without_default)] // Deliberate choice, see #5554
new<'a>() -> LocaleFallbackerBorrowed<'a>109     pub const fn new<'a>() -> LocaleFallbackerBorrowed<'a> {
110         // Safety: we're transmuting down from LocaleFallbackerBorrowed<'static> to LocaleFallbackerBorrowed<'a>
111         // ZeroMaps use associated types in a way that confuse the compiler which gives up and marks them
112         // as invariant. However, they are covariant, and in non-const code this covariance can be safely triggered
113         // using Yokeable::transform. In const code we must transmute. In the long run we should
114         // be able to `transform()` in const code, and also we will have hopefully improved map polymorphism (#3128)
115         unsafe { core::mem::transmute(LocaleFallbackerBorrowed::<'static>::new()) }
116     }
117 
118     icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
119         functions: [
120             new: skip,
121                         try_new_with_buffer_provider,
122             try_new_unstable,
123             Self
124     ]);
125 
126     #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
try_new_unstable<P>(provider: &P) -> Result<Self, DataError> where P: DataProvider<LocaleLikelySubtagsLanguageV1> + DataProvider<LocaleParentsV1> + ?Sized,127     pub fn try_new_unstable<P>(provider: &P) -> Result<Self, DataError>
128     where
129         P: DataProvider<LocaleLikelySubtagsLanguageV1> + DataProvider<LocaleParentsV1> + ?Sized,
130     {
131         let likely_subtags = provider.load(Default::default())?.payload;
132         let parents = provider.load(Default::default())?.payload;
133         Ok(LocaleFallbacker {
134             likely_subtags,
135             parents,
136         })
137     }
138 
139     /// Creates a [`LocaleFallbacker`] without fallback data. Using this constructor may result in
140     /// surprising behavior, especially in multi-script languages.
new_without_data() -> Self141     pub fn new_without_data() -> Self {
142         LocaleFallbacker {
143             likely_subtags: DataPayload::from_owned(LikelySubtagsForLanguage {
144                 language: Default::default(),
145                 language_region: Default::default(),
146                 language_script: Default::default(),
147                 // Unused
148                 und: (
149                     Default::default(),
150                     crate::subtags::script!("Zzzz"),
151                     crate::subtags::region!("ZZ"),
152                 ),
153             }),
154             parents: DataPayload::from_owned(Default::default()),
155         }
156     }
157 
158     /// Associates a configuration with this fallbacker.
159     #[inline]
for_config(&self, config: LocaleFallbackConfig) -> LocaleFallbackerWithConfig160     pub fn for_config(&self, config: LocaleFallbackConfig) -> LocaleFallbackerWithConfig {
161         self.as_borrowed().for_config(config)
162     }
163 
164     /// Creates a borrowed version of this fallbacker for performance.
as_borrowed(&self) -> LocaleFallbackerBorrowed165     pub fn as_borrowed(&self) -> LocaleFallbackerBorrowed {
166         LocaleFallbackerBorrowed {
167             likely_subtags: self.likely_subtags.get(),
168             parents: self.parents.get(),
169         }
170     }
171 }
172 
173 impl<'a> LocaleFallbackerBorrowed<'a> {
174     /// Associates a configuration with this fallbacker.
175     #[inline]
for_config(self, config: LocaleFallbackConfig) -> LocaleFallbackerWithConfig<'a>176     pub const fn for_config(self, config: LocaleFallbackConfig) -> LocaleFallbackerWithConfig<'a> {
177         LocaleFallbackerWithConfig {
178             likely_subtags: self.likely_subtags,
179             parents: self.parents,
180             config,
181         }
182     }
183 }
184 
185 impl LocaleFallbackerBorrowed<'static> {
186     /// Creates a [`LocaleFallbackerBorrowed`] with compiled fallback data (likely subtags and parent locales).
187     ///
188     /// ✨ *Enabled with the `compiled_data` Cargo feature.*
189     ///
190     /// [�� Help choosing a constructor](icu_provider::constructors)
191     #[cfg(feature = "compiled_data")]
192     #[allow(clippy::new_without_default)]
new() -> Self193     pub const fn new() -> Self {
194         Self {
195             likely_subtags: crate::provider::Baked::SINGLETON_LOCALE_LIKELY_SUBTAGS_LANGUAGE_V1,
196             parents: crate::provider::Baked::SINGLETON_LOCALE_PARENTS_V1,
197         }
198     }
199 
200     /// Cheaply converts a [`LocaleFallbackerBorrowed<'static>`] into a [`LocaleFallbacker`].
201     ///
202     /// Note: Due to branching and indirection, using [`LocaleFallbacker`] might inhibit some
203     /// compile-time optimizations that are possible with [`LocaleFallbackerBorrowed`].
static_to_owned(self) -> LocaleFallbacker204     pub const fn static_to_owned(self) -> LocaleFallbacker {
205         LocaleFallbacker {
206             likely_subtags: DataPayload::from_static_ref(self.likely_subtags),
207             parents: DataPayload::from_static_ref(self.parents),
208         }
209     }
210 }
211 
212 impl<'a> LocaleFallbackerWithConfig<'a> {
213     /// Creates an iterator based on a [`DataLocale`].
214     ///
215     /// If you have a [`Locale`](icu_locale_core::Locale), call `.into()` to get a [`DataLocale`].
216     ///
217     /// When first initialized, the locale is normalized according to the fallback algorithm.
fallback_for(&self, mut locale: DataLocale) -> LocaleFallbackIterator<'a>218     pub fn fallback_for(&self, mut locale: DataLocale) -> LocaleFallbackIterator<'a> {
219         let mut default_script = None;
220         self.normalize(&mut locale, &mut default_script);
221         let max_script = locale.script.or(default_script);
222         LocaleFallbackIterator {
223             current: locale,
224             inner: LocaleFallbackIteratorInner {
225                 likely_subtags: self.likely_subtags,
226                 parents: self.parents,
227                 config: self.config,
228                 backup_subdivision: None,
229                 backup_variant: None,
230                 backup_region: None,
231                 max_script,
232             },
233         }
234     }
235 }
236 
237 impl LocaleFallbackIterator<'_> {
238     /// Borrows the current [`DataLocale`] under fallback.
get(&self) -> &DataLocale239     pub fn get(&self) -> &DataLocale {
240         &self.current
241     }
242 
243     /// Takes the current [`DataLocale`] under fallback.
take(self) -> DataLocale244     pub fn take(self) -> DataLocale {
245         self.current
246     }
247 
248     /// Performs one step of the locale fallback algorithm.
249     ///
250     /// The fallback is completed once the inner [`DataLocale`] becomes [`DataLocale::default()`].
step(&mut self) -> &mut Self251     pub fn step(&mut self) -> &mut Self {
252         self.inner.step(&mut self.current);
253         self
254     }
255 }
256