• 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 //! This API provides necessary functionality for building user preferences structs.
6 //!
7 //! It includes the ability to merge information between the struct and a [`Locale`],
8 //! facilitating the resolution of attributes against default values.
9 //!
10 //! Preferences struct serve as a composable argument to `ICU4X` constructors, allowing
11 //! for ergonomic merging between information encoded in multiple sets of user inputs:
12 //! Locale, application preferences and operating system preferences.
13 //!
14 //! The crate is intended primarily to be used by components constructors to normalize the format
15 //! of ingesting preferences across all of `ICU4X`.
16 //!
17 //! # Preferences vs Options
18 //!
19 //! ICU4X introduces a separation between two classes of parameters that are used
20 //! to adjust the behavior of a component.
21 //!
22 //! `Preferences` represent the user-driven preferences on how the given user wants the internationalization
23 //! to behave. Those are items like language, script, calendar and numbering systems etc.
24 //!
25 //! `Options` represent the developer-driven adjustments that affect how given information is presented
26 //! based on the requirements of the application like available space or intended tone.
27 //!
28 //! # Options Division
29 //!
30 //! The `Options` themselves are also divided into options that are affecting data slicing, and ones that don't.
31 //! This is necessary to allow for DCE and FFI to produce minimal outputs avoiding loading unnecessary data that
32 //! is never to be used by a given component.
33 //! The result is that some option keys affect specialized constructors such as `try_new_short`, `try_new_long`, which
34 //! result in data provider loading only data necessary to format short or long values respectively.
35 //! For options that are not affecting data slicing, an `Options` struct is provided that the developer
36 //! can fill with selected key values, or use the defaults.
37 //!
38 //! # Preferences Merging
39 //!
40 //! In traditional internatonalization APIs, the argument passed to constructors is a locale.
41 //! ICU4X changes this paradigm by accepting a `Preferences`, which can be extracted from a [`Locale`] and combined with
42 //! other `Preferences`s provided by the environment.
43 //!
44 //! This approach makes it easy for developers to write code that takes just a locale, as in other systems,
45 //! as well as handle more sophisticated cases where the application may receive, for example, a locale,
46 //! a set of internationalization preferences specified within the application,
47 //! and a third set extracted from the operating system's preferences.
48 //!
49 //! # ECMA-402 vs ICU4X
50 //!
51 //! The result of the two paradigm shifts presented above is that the way constructors work is different.
52 //!
53 //! ## ECMA-402
54 //! ```ignore
55 //! let locale = new Locale("en-US-u-hc-h12");
56 //! let options = {
57 //!   hourCycle: "h24", // user preference
58 //!   timeStyle: "long", // developer option
59 //! };
60 //!
61 //! let dtf = new DateTimeFormat(locale, options);
62 //! ```
63 //!
64 //! ## ICU4X
65 //! ```ignore
66 //! let loc = locale!("en-US-u-hc-h12");
67 //! let prefs = DateTimeFormatterPreferences {
68 //!     hour_cycle: HourCycle::H24,
69 //! };
70 //! let options = DateTimeFormatterOptions {
71 //!     time_style: TimeStyle::Long,
72 //! };
73 //!
74 //! let mut combined_prefs = DateTimeFormatterPreferences::from(loc);
75 //! combined_prefs.extend(prefs);
76 //!
77 //! let dtf = DateTimeFormatter::try_new(combined_prefs, options);
78 //! ```
79 //!
80 //! This architecture allows for flexible composition of user and developer settings
81 //! sourced from different locations in custom ways based on the needs of each deployment.
82 //!
83 //! Below are some examples of how the `Preferences` model can be used in different setups.
84 //!
85 //! # Examples
86 //!
87 //! ```
88 //! use icu::locale::preferences::{
89 //!   define_preferences,
90 //!   extensions::unicode::keywords::HourCycle,
91 //! };
92 //! use icu::locale::locale;
93 //!
94 //! # fn get_data_locale_from_prefs(input: ExampleComponentPreferences) -> () { () }
95 //! # fn load_data(locale: ()) -> MyData { MyData {} }
96 //! # struct MyData {}
97 //! define_preferences!(
98 //!     /// Name of the preferences struct
99 //!     [Copy]
100 //!     ExampleComponentPreferences,
101 //!     {
102 //!         /// A preference relevant to the component
103 //!         hour_cycle: HourCycle
104 //!     }
105 //! );
106 //!
107 //! pub struct ExampleComponent {
108 //!     data: MyData,
109 //! }
110 //!
111 //! impl ExampleComponent {
112 //!     pub fn new(prefs: ExampleComponentPreferences) -> Self {
113 //!         let locale = get_data_locale_from_prefs(prefs);
114 //!         let data = load_data(locale);
115 //!
116 //!         Self { data }
117 //!     }
118 //! }
119 //! ```
120 //!
121 //! Now we can use that component in multiple different ways,
122 //!
123 //! ## Scenario 1: Use Locale as the only input
124 //! ```
125 //! # use icu::locale::preferences::{
126 //! #   define_preferences,
127 //! #   extensions::unicode::keywords::HourCycle,
128 //! # };
129 //! # use icu::locale::locale;
130 //! # fn get_data_locale_from_prefs(input: ExampleComponentPreferences) -> () { () }
131 //! # fn load_data(locale: ()) -> MyData { MyData {} }
132 //! # struct MyData {}
133 //! # define_preferences!(
134 //! #     /// Name of the preferences struct
135 //! #     [Copy]
136 //! #     ExampleComponentPreferences,
137 //! #     {
138 //! #         /// A preference relevant to the component
139 //! #         hour_cycle: HourCycle
140 //! #     }
141 //! # );
142 //! #
143 //! # pub struct ExampleComponent {
144 //! #     data: MyData,
145 //! # }
146 //! # impl ExampleComponent {
147 //! #     pub fn new(prefs: ExampleComponentPreferences) -> Self {
148 //! #         let locale = get_data_locale_from_prefs(prefs);
149 //! #         let data = load_data(locale);
150 //! #
151 //! #         Self { data }
152 //! #     }
153 //! # }
154 //! let loc = locale!("en-US-u-hc-h23");
155 //! let tf = ExampleComponent::new(loc.into());
156 //! ```
157 //!
158 //! ## Scenario 2: Compose Preferences and Locale
159 //! ```
160 //! # use icu::locale::preferences::{
161 //! #   define_preferences,
162 //! #   extensions::unicode::keywords::HourCycle,
163 //! # };
164 //! # use icu::locale::locale;
165 //! # fn get_data_locale_from_prefs(input: ExampleComponentPreferences) -> () { () }
166 //! # fn load_data(locale: ()) -> MyData { MyData {} }
167 //! # struct MyData {}
168 //! # define_preferences!(
169 //! #     /// Name of the preferences struct
170 //! #     [Copy]
171 //! #     ExampleComponentPreferences,
172 //! #     {
173 //! #         /// A preference relevant to the component
174 //! #         hour_cycle: HourCycle
175 //! #     }
176 //! # );
177 //! #
178 //! # pub struct ExampleComponent {
179 //! #     data: MyData,
180 //! # }
181 //! # impl ExampleComponent {
182 //! #     pub fn new(prefs: ExampleComponentPreferences) -> Self {
183 //! #         let locale = get_data_locale_from_prefs(prefs);
184 //! #         let data = load_data(locale);
185 //! #
186 //! #         Self { data }
187 //! #     }
188 //! # }
189 //! let loc = locale!("en-US-u-hc-h23");
190 //! let app_prefs = ExampleComponentPreferences {
191 //!     hour_cycle: Some(HourCycle::H12),
192 //!     ..Default::default()
193 //! };
194 //!
195 //! let mut combined_prefs = ExampleComponentPreferences::from(loc);
196 //! combined_prefs.extend(app_prefs);
197 //!
198 //! // HourCycle is set from the prefs bag and override the value from the locale
199 //! assert_eq!(combined_prefs.hour_cycle, Some(HourCycle::H12));
200 //!
201 //! let tf = ExampleComponent::new(combined_prefs);
202 //! ```
203 //!
204 //! ## Scenario 3: Merge Preferences from Locale, OS, and Application
205 //! ```
206 //! # use icu::locale::preferences::{
207 //! #   define_preferences,
208 //! #   extensions::unicode::keywords::HourCycle,
209 //! # };
210 //! # use icu::locale::locale;
211 //! # fn get_data_locale_from_prefs(input: ExampleComponentPreferences) -> () { () }
212 //! # fn load_data(locale: ()) -> MyData { MyData {} }
213 //! # struct MyData {}
214 //! # define_preferences!(
215 //! #     /// Name of the preferences struct
216 //! #     [Copy]
217 //! #     ExampleComponentPreferences,
218 //! #     {
219 //! #         /// A preference relevant to the component
220 //! #         hour_cycle: HourCycle
221 //! #     }
222 //! # );
223 //! #
224 //! # pub struct ExampleComponent {
225 //! #     data: MyData,
226 //! # }
227 //! # impl ExampleComponent {
228 //! #     pub fn new(prefs: ExampleComponentPreferences) -> Self {
229 //! #         let locale = get_data_locale_from_prefs(prefs);
230 //! #         let data = load_data(locale);
231 //! #
232 //! #         Self { data }
233 //! #     }
234 //! # }
235 //! let loc = locale!("en-US-u-hc-h24");
236 //!
237 //! // Simulate OS preferences
238 //! let os_prefs = ExampleComponentPreferences {
239 //!     hour_cycle: Some(HourCycle::H23),
240 //!     ..Default::default()
241 //! };
242 //!
243 //! // Application does not specify hour_cycle
244 //! let app_prefs = ExampleComponentPreferences {
245 //!     hour_cycle: Some(HourCycle::H12),
246 //!     ..Default::default()
247 //! };
248 //!
249 //! let mut combined_prefs = ExampleComponentPreferences::from(loc);
250 //! combined_prefs.extend(os_prefs);
251 //! combined_prefs.extend(app_prefs);
252 //!
253 //! // HourCycle is set from the OS preferences since the application didn't specify it
254 //! assert_eq!(combined_prefs.hour_cycle, Some(HourCycle::H12));
255 //!
256 //! let tf = ExampleComponent::new(combined_prefs);
257 //! ```
258 //!
259 //! ## Scenario 4: Neither Application nor OS specify the preference
260 //! ```
261 //! # use icu::locale::preferences::{
262 //! #   define_preferences,
263 //! #   extensions::unicode::keywords::HourCycle,
264 //! # };
265 //! # use icu::locale::locale;
266 //! # fn get_data_locale_from_prefs(input: ExampleComponentPreferences) -> () { () }
267 //! # fn load_data(locale: ()) -> MyData { MyData {} }
268 //! # struct MyData {}
269 //! # define_preferences!(
270 //! #     /// Name of the preferences struct
271 //! #     [Copy]
272 //! #     ExampleComponentPreferences,
273 //! #     {
274 //! #         /// A preference relevant to the component
275 //! #         hour_cycle: HourCycle
276 //! #     }
277 //! # );
278 //! #
279 //! # pub struct ExampleComponent {
280 //! #     data: MyData,
281 //! # }
282 //! # impl ExampleComponent {
283 //! #     pub fn new(prefs: ExampleComponentPreferences) -> Self {
284 //! #         let locale = get_data_locale_from_prefs(prefs);
285 //! #         let data = load_data(locale);
286 //! #
287 //! #         Self { data }
288 //! #     }
289 //! # }
290 //! let loc = locale!("en-US-u-hc-h24");
291 //!
292 //! // Simulate OS preferences
293 //! let os_prefs = ExampleComponentPreferences::default(); // OS does not specify hour_cycle
294 //! let app_prefs = ExampleComponentPreferences::default(); // Application does not specify hour_cycle
295 //!
296 //! let mut combined_prefs = ExampleComponentPreferences::from(loc);
297 //! combined_prefs.extend(os_prefs);
298 //! combined_prefs.extend(app_prefs);
299 //!
300 //! // HourCycle is taken from the locale
301 //! assert_eq!(combined_prefs.hour_cycle, Some(HourCycle::H24));
302 //!
303 //! let tf = ExampleComponent::new(combined_prefs);
304 //! ```
305 //!
306 //! [`ICU4X`]: ../icu/index.html
307 //! [`Locale`]: crate::Locale
308 
309 pub mod extensions;
310 mod locale;
311 pub use locale::*;
312 
313 /// A low-level trait implemented on each preference exposed in component preferences.
314 ///
315 /// [`PreferenceKey`] has to be implemented on
316 /// preferences that are to be included in Formatter preferences.
317 /// The trait may be implemented to indicate that the given preference has
318 /// a unicode key corresponding to it or be a custom one.
319 ///
320 /// `ICU4X` provides an implementation of [`PreferenceKey`] for all
321 /// Unicode Extension Keys. The only external use of this trait is to implement
322 /// it on custom preferences that are to be included in a component preferences bag.
323 ///
324 /// The below example show cases a manual generation of an `em` (emoji) unicode extension key
325 /// and a custom struct to showcase the difference in their behavior. For all use purposes,
326 /// the [`EmojiPresentationStyle`](crate::preferences::extensions::unicode::keywords::EmojiPresentationStyle) preference exposed by this crate should be used.
327 ///
328 /// # Examples
329 /// ```
330 /// use icu::locale::{
331 ///   extensions::unicode::{key, Key, value, Value},
332 ///   preferences::{
333 ///     define_preferences, PreferenceKey,
334 ///     extensions::unicode::errors::PreferencesParseError,
335 ///   },
336 /// };
337 ///
338 /// #[non_exhaustive]
339 /// #[derive(Debug, Clone, Eq, PartialEq, Copy, Hash, Default)]
340 /// pub enum EmojiPresentationStyle {
341 ///     Emoji,
342 ///     Text,
343 ///     #[default]
344 ///     Default,
345 /// }
346 ///
347 /// impl PreferenceKey for EmojiPresentationStyle {
348 ///     fn unicode_extension_key() -> Option<Key> {
349 ///         Some(key!("em"))
350 ///     }
351 ///
352 ///     fn try_from_key_value(
353 ///         key: &Key,
354 ///         value: &Value,
355 ///     ) -> Result<Option<Self>, PreferencesParseError> {
356 ///         if Self::unicode_extension_key() == Some(*key) {
357 ///             let subtag = value.as_single_subtag()
358 ///                               .ok_or(PreferencesParseError::InvalidKeywordValue)?;
359 ///             match subtag.as_str() {
360 ///                 "emoji" => Ok(Some(Self::Emoji)),
361 ///                 "text" => Ok(Some(Self::Text)),
362 ///                 "default" => Ok(Some(Self::Default)),
363 ///                 _ => Err(PreferencesParseError::InvalidKeywordValue)
364 ///             }
365 ///         } else {
366 ///             Ok(None)
367 ///         }
368 ///     }
369 ///
370 ///     fn unicode_extension_value(&self) -> Option<Value> {
371 ///         Some(match self {
372 ///             EmojiPresentationStyle::Emoji => value!("emoji"),
373 ///             EmojiPresentationStyle::Text => value!("text"),
374 ///             EmojiPresentationStyle::Default => value!("default"),
375 ///         })
376 ///     }
377 /// }
378 ///
379 /// #[non_exhaustive]
380 /// #[derive(Debug, Clone, Eq, PartialEq, Hash)]
381 /// pub struct CustomFormat {
382 ///     value: String
383 /// }
384 ///
385 /// impl PreferenceKey for CustomFormat {}
386 ///
387 /// define_preferences!(
388 ///     MyFormatterPreferences,
389 ///     {
390 ///         emoji: EmojiPresentationStyle,
391 ///         custom: CustomFormat
392 ///     }
393 /// );
394 /// ```
395 /// [`ICU4X`]: ../icu/index.html
396 pub trait PreferenceKey: Sized {
397     /// Optional constructor of the given preference. It takes the
398     /// unicode extension key and if the key matches it attemptes to construct
399     /// the preference based on the given value.
400     /// If the value is not a valid value for the given key, the constructor throws.
try_from_key_value( _key: &crate::extensions::unicode::Key, _value: &crate::extensions::unicode::Value, ) -> Result<Option<Self>, crate::preferences::extensions::unicode::errors::PreferencesParseError>401     fn try_from_key_value(
402         _key: &crate::extensions::unicode::Key,
403         _value: &crate::extensions::unicode::Value,
404     ) -> Result<Option<Self>, crate::preferences::extensions::unicode::errors::PreferencesParseError>
405     {
406         Ok(None)
407     }
408 
409     /// Retrieve unicode extension key corresponding to a given preference.
unicode_extension_key() -> Option<crate::extensions::unicode::Key>410     fn unicode_extension_key() -> Option<crate::extensions::unicode::Key> {
411         None
412     }
413 
414     /// Retrieve unicode extension value corresponding to the given instance of the preference.
unicode_extension_value(&self) -> Option<crate::extensions::unicode::Value>415     fn unicode_extension_value(&self) -> Option<crate::extensions::unicode::Value> {
416         None
417     }
418 }
419 
420 /// A macro to facilitate generation of preferences struct.
421 ///
422 ///
423 /// The generated preferences struct provides methods for merging and converting between [`Locale`] and
424 /// the preference bag. See [`preferences`](crate::preferences) for use cases.
425 ///
426 /// In the example below, the input argument is the generated preferences struct which
427 /// can be auto-converted from a Locale, or combined from a Locale and Preferences Bag.
428 ///
429 /// # Examples
430 /// ```
431 /// use icu::locale::{
432 ///     preferences::{
433 ///         define_preferences,
434 ///         extensions::unicode::keywords::HourCycle
435 ///     },
436 ///     locale,
437 /// };
438 ///
439 /// define_preferences!(
440 ///     [Copy]
441 ///     NoCalendarFormatterPreferences,
442 ///     {
443 ///         hour_cycle: HourCycle
444 ///     }
445 /// );
446 ///
447 /// struct NoCalendarFormatter {}
448 ///
449 /// impl NoCalendarFormatter {
450 ///     pub fn try_new(prefs: NoCalendarFormatterPreferences) -> Result<Self, ()> {
451 ///         // load data and set struct fields based on the prefs input
452 ///         Ok(Self {})
453 ///     }
454 /// }
455 ///
456 /// let loc = locale!("en-US");
457 ///
458 /// let tf = NoCalendarFormatter::try_new(loc.into());
459 /// ```
460 ///
461 /// [`Locale`]: crate::Locale
462 #[macro_export]
463 #[doc(hidden)]
464 macro_rules! __define_preferences {
465     (
466         $(#[$doc:meta])*
467         $([$derive_attrs:ty])?
468         $name:ident,
469         {
470             $(
471                 $(#[$key_doc:meta])*
472                 $key:ident: $pref:ty
473             ),*
474         }
475      ) => (
476         $(#[$doc])*
477         #[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
478         $(#[derive($derive_attrs)])?
479         #[non_exhaustive]
480         pub struct $name {
481             /// Locale Preferences for the Preferences structure.
482             pub locale_preferences: $crate::preferences::LocalePreferences,
483 
484             $(
485                 $(#[$key_doc])*
486                 pub $key: Option<$pref>,
487             )*
488         }
489 
490         impl From<$crate::Locale> for $name {
491             fn from(loc: $crate::Locale) -> Self {
492                 $name::from(&loc)
493             }
494         }
495 
496         impl From<&$crate::Locale> for $name {
497             fn from(loc: &$crate::Locale) -> Self {
498                 use $crate::preferences::PreferenceKey;
499 
500                 $(
501                     let mut $key = None;
502                 )*
503 
504                 for (k, v) in loc.extensions.unicode.keywords.iter() {
505                     $(
506                         if let Ok(Some(r)) = <$pref>::try_from_key_value(k, v) {
507                             $key = Some(r);
508                             continue;
509                         }
510                     )*
511                 }
512 
513                 Self {
514                     locale_preferences: loc.into(),
515 
516                     $(
517                         $key,
518                     )*
519                 }
520             }
521         }
522 
523         impl From<$crate::LanguageIdentifier> for $name {
524             fn from(lid: $crate::LanguageIdentifier) -> Self {
525                 $name::from(&lid)
526             }
527         }
528 
529         impl From<&$crate::LanguageIdentifier> for $name {
530             fn from(lid: &$crate::LanguageIdentifier) -> Self {
531                 Self {
532                     locale_preferences: lid.into(),
533 
534                     $(
535                         $key: None,
536                     )*
537                 }
538             }
539         }
540 
541         // impl From<$name> for $crate::Locale {
542         //     fn from(other: $name) -> Self {
543         //         use $crate::preferences::PreferenceKey;
544         //         let mut result = Self::from(other.locale_preferences);
545         //         $(
546         //             if let Some(value) = other.$key {
547         //                 if let Some(ue) = <$pref>::unicode_extension_key() {
548         //                     let val = value.unicode_extension_value().unwrap();
549         //                     result.extensions.unicode.keywords.set(ue, val);
550         //                 }
551         //             }
552         //         )*
553         //         result
554         //     }
555         // }
556 
557         impl $name {
558             /// Extends the preferences with the values from another set of preferences.
559             pub fn extend(&mut self, other: $name) {
560                 self.locale_preferences.extend(other.locale_preferences);
561                 $(
562                     if let Some(value) = other.$key {
563                         self.$key = Some(value);
564                     }
565                 )*
566             }
567         }
568     )
569 }
570 
571 #[macro_export]
572 #[doc(hidden)]
573 macro_rules! __prefs_convert {
574     (
575         $name1:ident,
576         $name2:ident
577     ) => {
578         impl From<&$name1> for $name2 {
579             fn from(other: &$name1) -> Self {
580                 let mut result = Self::default();
581                 result.locale_preferences = other.locale_preferences;
582                 result
583             }
584         }
585     };
586     (
587         $name1:ident,
588         $name2:ident,
589         {
590             $(
591                 $key:ident
592             ),*
593         }
594     ) => {
595         impl From<&$name1> for $name2 {
596             fn from(other: &$name1) -> Self {
597                 let mut result = Self::default();
598                 result.locale_preferences = other.locale_preferences;
599                 $(
600                     result.$key = other.$key;
601                 )*
602                 result
603             }
604         }
605     };
606 }
607 
608 #[doc(inline)]
609 pub use __define_preferences as define_preferences;
610 
611 #[doc(inline)]
612 pub use __prefs_convert as prefs_convert;
613