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