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;