icu_locale_core/preferences/
mod.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
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::H23,
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");
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: None,
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::H23));
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-h23");
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::H23));
302//!
303//! let tf = ExampleComponent::new(combined_prefs);
304//! ```
305//!
306//! [`ICU4X`]: ../icu/index.html
307//! [`Locale`]: crate::Locale
308
309pub mod extensions;
310mod locale;
311pub 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
396pub 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.
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.
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.
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)]
464macro_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)]
573macro_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)]
609pub use __define_preferences as define_preferences;
610
611#[doc(inline)]
612pub use __prefs_convert as prefs_convert;