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