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}