icu_locale/fallback/mod.rs
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
8use crate::provider::*;
9use icu_locale_core::subtags::*;
10use icu_provider::prelude::*;
11
12#[doc(inline)]
13pub use icu_provider::fallback::{LocaleFallbackConfig, LocaleFallbackPriority};
14
15mod 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_unknown()`] 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)]
58pub struct LocaleFallbacker {
59 likely_subtags: DataPayload<LocaleLikelySubtagsLanguageV1>,
60 parents: DataPayload<LocaleParentsV1>,
61}
62
63/// Borrowed version of [`LocaleFallbacker`].
64#[derive(Debug, Clone, Copy, PartialEq)]
65pub 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)]
72pub 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)]
80struct 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)]
95pub struct LocaleFallbackIterator<'a> {
96 current: DataLocale,
97 inner: LocaleFallbackIteratorInner<'a>,
98}
99
100impl 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
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)]
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.
141 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 Language::UNKNOWN,
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]
160 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.
165 pub fn as_borrowed(&self) -> LocaleFallbackerBorrowed {
166 LocaleFallbackerBorrowed {
167 likely_subtags: self.likely_subtags.get(),
168 parents: self.parents.get(),
169 }
170 }
171}
172
173impl<'a> LocaleFallbackerBorrowed<'a> {
174 /// Associates a configuration with this fallbacker.
175 #[inline]
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
185impl 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)]
193 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`].
204 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
212impl<'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.
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
237impl LocaleFallbackIterator<'_> {
238 /// Borrows the current [`DataLocale`] under fallback.
239 pub fn get(&self) -> &DataLocale {
240 &self.current
241 }
242
243 /// Takes the current [`DataLocale`] under fallback.
244 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()`].
251 pub fn step(&mut self) -> &mut Self {
252 self.inner.step(&mut self.current);
253 self
254 }
255}