• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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