icu_locale/directionality.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
5use crate::provider::*;
6use crate::LocaleExpander;
7use icu_locale_core::subtags::Script;
8use icu_locale_core::LanguageIdentifier;
9use 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]
16pub 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)]
37pub struct LocaleDirectionality<Expander = LocaleExpander> {
38 script_direction: DataPayload<LocaleScriptDirectionV1>,
39 expander: Expander,
40}
41
42impl 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")]
48 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")]
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)]
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")]
78 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")]
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)]
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
105impl<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")]
130 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)]
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 /// ```
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.
217 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.
232 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
240 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
248 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}