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 use crate::provider::*; 6 use crate::LocaleExpander; 7 use icu_locale_core::subtags::Script; 8 use icu_locale_core::LanguageIdentifier; 9 use icu_provider::prelude::*; 10 11 /// Represents the direction of a script. 12 /// 13 /// [`LocaleDirectionality`] can be used to get this information. 14 #[derive(Debug, PartialEq, Eq, Clone, Copy)] 15 #[non_exhaustive] 16 pub enum Direction { 17 /// The script is left-to-right. 18 LeftToRight, 19 /// The script is right-to-left. 20 RightToLeft, 21 } 22 23 /// Provides methods to determine the direction of a locale. 24 /// 25 /// The `Expander` generic parameter wraps a [`LocaleExpander`]. 26 /// 27 /// # Examples 28 /// 29 /// ``` 30 /// use icu::locale::{langid, Direction, LocaleDirectionality}; 31 /// 32 /// let ld = LocaleDirectionality::new_common(); 33 /// 34 /// assert_eq!(ld.get(&langid!("en")), Some(Direction::LeftToRight)); 35 /// ``` 36 #[derive(Debug)] 37 pub struct LocaleDirectionality<Expander = LocaleExpander> { 38 script_direction: DataPayload<LocaleScriptDirectionV1>, 39 expander: Expander, 40 } 41 42 impl LocaleDirectionality<LocaleExpander> { 43 /// Creates a [`LocaleDirectionality`] from compiled data, using [`LocaleExpander`] 44 /// data for common locales. 45 /// 46 /// This includes limited likely subtags data, see [`LocaleExpander::new_common()`]. 47 #[cfg(feature = "compiled_data")] new_common() -> Self48 pub const fn new_common() -> Self { 49 Self::new_with_expander(LocaleExpander::new_common()) 50 } 51 52 // Note: This is a custom impl because the bounds on `try_new_unstable` don't suffice 53 #[doc = icu_provider::gen_buffer_unstable_docs!(BUFFER, Self::new_common)] 54 #[cfg(feature = "serde")] try_new_common_with_buffer_provider( provider: &(impl BufferProvider + ?Sized), ) -> Result<Self, DataError>55 pub fn try_new_common_with_buffer_provider( 56 provider: &(impl BufferProvider + ?Sized), 57 ) -> Result<Self, DataError> { 58 let expander = LocaleExpander::try_new_common_with_buffer_provider(provider)?; 59 Self::try_new_with_expander_unstable(&provider.as_deserializing(), expander) 60 } 61 #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_common)] try_new_common_unstable<P>(provider: &P) -> Result<LocaleDirectionality, DataError> where P: DataProvider<LocaleScriptDirectionV1> + DataProvider<LocaleLikelySubtagsLanguageV1> + DataProvider<LocaleLikelySubtagsScriptRegionV1> + ?Sized,62 pub fn try_new_common_unstable<P>(provider: &P) -> Result<LocaleDirectionality, DataError> 63 where 64 P: DataProvider<LocaleScriptDirectionV1> 65 + DataProvider<LocaleLikelySubtagsLanguageV1> 66 + DataProvider<LocaleLikelySubtagsScriptRegionV1> 67 + ?Sized, 68 { 69 let expander = LocaleExpander::try_new_common_unstable(provider)?; 70 Self::try_new_with_expander_unstable(provider, expander) 71 } 72 73 /// Creates a [`LocaleDirectionality`] from compiled data, using [`LocaleExpander`] 74 /// data for all locales. 75 /// 76 /// This includes all likely subtags data, see [`LocaleExpander::new_extended()`]. 77 #[cfg(feature = "compiled_data")] new_extended() -> Self78 pub const fn new_extended() -> Self { 79 Self::new_with_expander(LocaleExpander::new_extended()) 80 } 81 82 // Note: This is a custom impl because the bounds on `try_new_unstable` don't suffice 83 #[doc = icu_provider::gen_buffer_unstable_docs!(BUFFER, Self::new_extended)] 84 #[cfg(feature = "serde")] try_new_extended_with_buffer_provider( provider: &(impl BufferProvider + ?Sized), ) -> Result<Self, DataError>85 pub fn try_new_extended_with_buffer_provider( 86 provider: &(impl BufferProvider + ?Sized), 87 ) -> Result<Self, DataError> { 88 let expander = LocaleExpander::try_new_extended_with_buffer_provider(provider)?; 89 Self::try_new_with_expander_unstable(&provider.as_deserializing(), expander) 90 } 91 #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_extended)] try_new_extended_unstable<P>(provider: &P) -> Result<LocaleDirectionality, DataError> where P: DataProvider<LocaleScriptDirectionV1> + DataProvider<LocaleLikelySubtagsLanguageV1> + DataProvider<LocaleLikelySubtagsScriptRegionV1> + DataProvider<LocaleLikelySubtagsExtendedV1> + ?Sized,92 pub fn try_new_extended_unstable<P>(provider: &P) -> Result<LocaleDirectionality, DataError> 93 where 94 P: DataProvider<LocaleScriptDirectionV1> 95 + DataProvider<LocaleLikelySubtagsLanguageV1> 96 + DataProvider<LocaleLikelySubtagsScriptRegionV1> 97 + DataProvider<LocaleLikelySubtagsExtendedV1> 98 + ?Sized, 99 { 100 let expander = LocaleExpander::try_new_extended_unstable(provider)?; 101 Self::try_new_with_expander_unstable(provider, expander) 102 } 103 } 104 105 impl<Expander: AsRef<LocaleExpander>> LocaleDirectionality<Expander> { 106 /// Creates a [`LocaleDirectionality`] with a custom [`LocaleExpander`] and compiled data. 107 /// 108 /// This allows using [`LocaleExpander::new_extended()`] with data for all locales. 109 /// 110 /// # Examples 111 /// 112 /// ``` 113 /// use icu::locale::{ 114 /// langid, Direction, LocaleDirectionality, LocaleExpander, 115 /// }; 116 /// 117 /// let ld_default = LocaleDirectionality::new_common(); 118 /// 119 /// assert_eq!(ld_default.get(&langid!("jbn")), None); 120 /// 121 /// let expander = LocaleExpander::new_extended(); 122 /// let ld_extended = LocaleDirectionality::new_with_expander(expander); 123 /// 124 /// assert_eq!( 125 /// ld_extended.get(&langid!("jbn")), 126 /// Some(Direction::RightToLeft) 127 /// ); 128 /// ``` 129 #[cfg(feature = "compiled_data")] new_with_expander(expander: Expander) -> Self130 pub const fn new_with_expander(expander: Expander) -> Self { 131 LocaleDirectionality { 132 script_direction: DataPayload::from_static_ref( 133 crate::provider::Baked::SINGLETON_LOCALE_SCRIPT_DIRECTION_V1, 134 ), 135 expander, 136 } 137 } 138 139 #[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new_with_expander)] try_new_with_expander_unstable<P>( provider: &P, expander: Expander, ) -> Result<Self, DataError> where P: DataProvider<LocaleScriptDirectionV1> + ?Sized,140 pub fn try_new_with_expander_unstable<P>( 141 provider: &P, 142 expander: Expander, 143 ) -> Result<Self, DataError> 144 where 145 P: DataProvider<LocaleScriptDirectionV1> + ?Sized, 146 { 147 let script_direction = provider.load(Default::default())?.payload; 148 149 Ok(LocaleDirectionality { 150 script_direction, 151 expander, 152 }) 153 } 154 155 /// Returns the script direction of the given locale. 156 /// 157 /// Note that the direction is a property of the script of a locale, not of the language. As such, 158 /// when given a locale without an associated script tag (i.e., `locale!("en")` vs. `locale!("en-Latn")`), 159 /// this method first tries to infer the script using the language and region before returning its direction. 160 /// 161 /// If you already have a script struct and want to get its direction, you should use 162 /// `Locale::from(Some(my_script))` and call this method. 163 /// 164 /// This method will return `None` if either a locale's script cannot be determined, or there is no information 165 /// for the script. 166 /// 167 /// # Examples 168 /// 169 /// Using an existing locale: 170 /// 171 /// ``` 172 /// use icu::locale::{langid, Direction, LocaleDirectionality}; 173 /// 174 /// let ld = LocaleDirectionality::new_common(); 175 /// 176 /// assert_eq!(ld.get(&langid!("en-US")), Some(Direction::LeftToRight)); 177 /// 178 /// assert_eq!(ld.get(&langid!("ar")), Some(Direction::RightToLeft)); 179 /// 180 /// assert_eq!(ld.get(&langid!("en-Arab")), Some(Direction::RightToLeft)); 181 /// 182 /// assert_eq!(ld.get(&langid!("foo")), None); 183 /// ``` 184 /// 185 /// Using a script directly: 186 /// 187 /// ``` 188 /// use icu::locale::subtags::script; 189 /// use icu::locale::{Direction, LanguageIdentifier, LocaleDirectionality}; 190 /// 191 /// let ld = LocaleDirectionality::new_common(); 192 /// 193 /// assert_eq!( 194 /// ld.get(&LanguageIdentifier::from(Some(script!("Latn")))), 195 /// Some(Direction::LeftToRight) 196 /// ); 197 /// ``` get(&self, langid: &LanguageIdentifier) -> Option<Direction>198 pub fn get(&self, langid: &LanguageIdentifier) -> Option<Direction> { 199 let script = self.expander.as_ref().get_likely_script(langid)?; 200 201 if self.script_in_ltr(script) { 202 Some(Direction::LeftToRight) 203 } else if self.script_in_rtl(script) { 204 Some(Direction::RightToLeft) 205 } else { 206 None 207 } 208 } 209 210 /// Returns whether the given locale is right-to-left. 211 /// 212 /// Note that if this method returns `false`, the locale is either left-to-right or 213 /// the [`LocaleDirectionality`] does not include data for the locale. 214 /// You should use [`LocaleDirectionality::get`] if you need to differentiate between these cases. 215 /// 216 /// See [`LocaleDirectionality::get`] for more information. is_right_to_left(&self, langid: &LanguageIdentifier) -> bool217 pub fn is_right_to_left(&self, langid: &LanguageIdentifier) -> bool { 218 self.expander 219 .as_ref() 220 .get_likely_script(langid) 221 .map(|s| self.script_in_rtl(s)) 222 .unwrap_or(false) 223 } 224 225 /// Returns whether the given locale is left-to-right. 226 /// 227 /// Note that if this method returns `false`, the locale is either right-to-left or 228 /// the [`LocaleDirectionality`] does not include data for the locale. 229 /// You should use [`LocaleDirectionality::get`] if you need to differentiate between these cases. 230 /// 231 /// See [`LocaleDirectionality::get`] for more information. is_left_to_right(&self, langid: &LanguageIdentifier) -> bool232 pub fn is_left_to_right(&self, langid: &LanguageIdentifier) -> bool { 233 self.expander 234 .as_ref() 235 .get_likely_script(langid) 236 .map(|s| self.script_in_ltr(s)) 237 .unwrap_or(false) 238 } 239 script_in_rtl(&self, script: Script) -> bool240 fn script_in_rtl(&self, script: Script) -> bool { 241 self.script_direction 242 .get() 243 .rtl 244 .binary_search(&script.to_tinystr().to_unvalidated()) 245 .is_ok() 246 } 247 script_in_ltr(&self, script: Script) -> bool248 fn script_in_ltr(&self, script: Script) -> bool { 249 self.script_direction 250 .get() 251 .ltr 252 .binary_search(&script.to_tinystr().to_unvalidated()) 253 .is_ok() 254 } 255 } 256