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}