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 #[expect(clippy::new_ret_no_self)] // keeping constructors together
108 pub const fn new<'a>() -> LocaleFallbackerBorrowed<'a> {
109 // Safety: we're transmuting down from LocaleFallbackerBorrowed<'static> to LocaleFallbackerBorrowed<'a>
110 // ZeroMaps use associated types in a way that confuse the compiler which gives up and marks them
111 // as invariant. However, they are covariant, and in non-const code this covariance can be safely triggered
112 // using Yokeable::transform. In const code we must transmute. In the long run we should
113 // be able to `transform()` in const code, and also we will have hopefully improved map polymorphism (#3128)
114 unsafe { core::mem::transmute(LocaleFallbackerBorrowed::<'static>::new()) }
115 }
116
117 icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
118 functions: [
119 new: skip,
120 try_new_with_buffer_provider,
121 try_new_unstable,
122 Self
123 ]);
124
125 #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
126 pub fn try_new_unstable<P>(provider: &P) -> Result<Self, DataError>
127 where
128 P: DataProvider<LocaleLikelySubtagsLanguageV1> + DataProvider<LocaleParentsV1> + ?Sized,
129 {
130 let likely_subtags = provider.load(Default::default())?.payload;
131 let parents = provider.load(Default::default())?.payload;
132 Ok(LocaleFallbacker {
133 likely_subtags,
134 parents,
135 })
136 }
137
138 /// Creates a [`LocaleFallbacker`] without fallback data. Using this constructor may result in
139 /// surprising behavior, especially in multi-script languages.
140 pub fn new_without_data() -> Self {
141 LocaleFallbacker {
142 likely_subtags: DataPayload::from_owned(LikelySubtagsForLanguage {
143 language: Default::default(),
144 language_region: Default::default(),
145 language_script: Default::default(),
146 // Unused
147 und: (
148 Language::UNKNOWN,
149 crate::subtags::script!("Zzzz"),
150 crate::subtags::region!("ZZ"),
151 ),
152 }),
153 parents: DataPayload::from_owned(Default::default()),
154 }
155 }
156
157 /// Associates a configuration with this fallbacker.
158 #[inline]
159 pub fn for_config(&self, config: LocaleFallbackConfig) -> LocaleFallbackerWithConfig<'_> {
160 self.as_borrowed().for_config(config)
161 }
162
163 /// Creates a borrowed version of this fallbacker for performance.
164 pub fn as_borrowed(&self) -> LocaleFallbackerBorrowed<'_> {
165 LocaleFallbackerBorrowed {
166 likely_subtags: self.likely_subtags.get(),
167 parents: self.parents.get(),
168 }
169 }
170}
171
172impl<'a> LocaleFallbackerBorrowed<'a> {
173 /// Associates a configuration with this fallbacker.
174 #[inline]
175 pub const fn for_config(self, config: LocaleFallbackConfig) -> LocaleFallbackerWithConfig<'a> {
176 LocaleFallbackerWithConfig {
177 likely_subtags: self.likely_subtags,
178 parents: self.parents,
179 config,
180 }
181 }
182}
183
184impl LocaleFallbackerBorrowed<'static> {
185 /// Creates a [`LocaleFallbackerBorrowed`] with compiled fallback data (likely subtags and parent locales).
186 ///
187 /// ✨ *Enabled with the `compiled_data` Cargo feature.*
188 ///
189 /// [📚 Help choosing a constructor](icu_provider::constructors)
190 #[cfg(feature = "compiled_data")]
191 #[expect(clippy::new_without_default)]
192 pub const fn new() -> Self {
193 Self {
194 likely_subtags: crate::provider::Baked::SINGLETON_LOCALE_LIKELY_SUBTAGS_LANGUAGE_V1,
195 parents: crate::provider::Baked::SINGLETON_LOCALE_PARENTS_V1,
196 }
197 }
198
199 /// Cheaply converts a [`LocaleFallbackerBorrowed<'static>`] into a [`LocaleFallbacker`].
200 ///
201 /// Note: Due to branching and indirection, using [`LocaleFallbacker`] might inhibit some
202 /// compile-time optimizations that are possible with [`LocaleFallbackerBorrowed`].
203 pub const fn static_to_owned(self) -> LocaleFallbacker {
204 LocaleFallbacker {
205 likely_subtags: DataPayload::from_static_ref(self.likely_subtags),
206 parents: DataPayload::from_static_ref(self.parents),
207 }
208 }
209}
210
211impl<'a> LocaleFallbackerWithConfig<'a> {
212 /// Creates an iterator based on a [`DataLocale`].
213 ///
214 /// If you have a [`Locale`](icu_locale_core::Locale), call `.into()` to get a [`DataLocale`].
215 ///
216 /// When first initialized, the locale is normalized according to the fallback algorithm.
217 pub fn fallback_for(&self, mut locale: DataLocale) -> LocaleFallbackIterator<'a> {
218 let mut default_script = None;
219 self.normalize(&mut locale, &mut default_script);
220 let max_script = locale.script.or(default_script);
221 LocaleFallbackIterator {
222 current: locale,
223 inner: LocaleFallbackIteratorInner {
224 likely_subtags: self.likely_subtags,
225 parents: self.parents,
226 config: self.config,
227 backup_subdivision: None,
228 backup_variant: None,
229 backup_region: None,
230 max_script,
231 },
232 }
233 }
234}
235
236impl LocaleFallbackIterator<'_> {
237 /// Borrows the current [`DataLocale`] under fallback.
238 pub fn get(&self) -> &DataLocale {
239 &self.current
240 }
241
242 /// Takes the current [`DataLocale`] under fallback.
243 pub fn take(self) -> DataLocale {
244 self.current
245 }
246
247 /// Performs one step of the locale fallback algorithm.
248 ///
249 /// The fallback is completed once the inner [`DataLocale`] becomes [`DataLocale::default()`].
250 pub fn step(&mut self) -> &mut Self {
251 self.inner.step(&mut self.current);
252 self
253 }
254}