// This file is part of ICU4X. For terms of use, please see the file // called LICENSE at the top level of the ICU4X source tree // (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). //! A data provider wrapper that performs locale fallback. use crate::helpers::result_is_err_missing_locale; use icu_locid_transform::provider::*; use icu_provider::prelude::*; #[doc(hidden)] // moved pub use icu_locid_transform::fallback::{ LocaleFallbackIterator, LocaleFallbacker, LocaleFallbackerWithConfig, }; #[doc(hidden)] // moved pub use icu_provider::fallback::LocaleFallbackConfig; /// A data provider wrapper that performs locale fallback. This enables arbitrary locales to be /// handled at runtime. /// /// # Examples /// /// ``` /// use icu_locid::locale; /// use icu_provider::prelude::*; /// use icu_provider::hello_world::*; /// use icu_provider_adapters::fallback::LocaleFallbackProvider; /// /// # let provider = icu_provider_blob::BlobDataProvider::try_new_from_static_blob(include_bytes!("../../tests/data/blob.postcard")).unwrap(); /// # let provider = provider.as_deserializing(); /// /// let req = DataRequest { /// locale: &locale!("ja-JP").into(), /// metadata: Default::default(), /// }; /// /// // The provider does not have data for "ja-JP": /// DataProvider::::load(&provider, req).expect_err("No fallback"); /// /// // But if we wrap the provider in a fallback provider... /// let provider = LocaleFallbackProvider::try_new_unstable(provider) /// .expect("Fallback data present"); /// /// // ...then we can load "ja-JP" based on "ja" data /// let response = /// DataProvider::::load(&provider, req).expect("successful with vertical fallback"); /// /// assert_eq!( /// response.metadata.locale.unwrap(), /// locale!("ja").into(), /// ); /// assert_eq!( /// response.payload.unwrap().get().message, /// "こんにちは世界", /// ); /// ``` #[derive(Clone, Debug)] pub struct LocaleFallbackProvider

{ inner: P, fallbacker: LocaleFallbacker, } impl

LocaleFallbackProvider

where P: DataProvider + DataProvider + DataProvider, { /// Create a [`LocaleFallbackProvider`] by wrapping another data provider and then loading /// fallback data from it. /// /// If the data provider being wrapped does not contain fallback data, use /// [`LocaleFallbackProvider::new_with_fallbacker`]. pub fn try_new_unstable(provider: P) -> Result { let fallbacker = LocaleFallbacker::try_new_unstable(&provider)?; Ok(Self { inner: provider, fallbacker, }) } } impl

LocaleFallbackProvider

where P: AnyProvider, { /// Create a [`LocaleFallbackProvider`] by wrapping another data provider and then loading /// fallback data from it. /// /// If the data provider being wrapped does not contain fallback data, use /// [`LocaleFallbackProvider::new_with_fallbacker`]. pub fn try_new_with_any_provider(provider: P) -> Result { let fallbacker = LocaleFallbacker::try_new_with_any_provider(&provider)?; Ok(Self { inner: provider, fallbacker, }) } } #[cfg(feature = "serde")] impl

LocaleFallbackProvider

where P: BufferProvider, { /// Create a [`LocaleFallbackProvider`] by wrapping another data provider and then loading /// fallback data from it. /// /// If the data provider being wrapped does not contain fallback data, use /// [`LocaleFallbackProvider::new_with_fallbacker`]. pub fn try_new_with_buffer_provider(provider: P) -> Result { let fallbacker = LocaleFallbacker::try_new_with_buffer_provider(&provider)?; Ok(Self { inner: provider, fallbacker, }) } } impl

LocaleFallbackProvider

{ /// Wrap a provider with an arbitrary fallback engine. /// /// This relaxes the requirement that the wrapped provider contains its own fallback data. /// /// # Examples /// /// ``` /// use icu_locid::locale; /// use icu_locid_transform::LocaleFallbacker; /// use icu_provider::hello_world::*; /// use icu_provider::prelude::*; /// use icu_provider_adapters::fallback::LocaleFallbackProvider; /// /// let provider = HelloWorldProvider; /// /// let req = DataRequest { /// locale: &locale!("de-CH").into(), /// metadata: Default::default(), /// }; /// /// // There is no "de-CH" data in the `HelloWorldProvider` /// DataProvider::::load(&provider, req) /// .expect_err("No data for de-CH"); /// /// // `HelloWorldProvider` does not contain fallback data, /// // but we can construct a fallbacker with `icu_locid_transform`'s /// // compiled data. /// let provider = LocaleFallbackProvider::new_with_fallbacker( /// provider, /// LocaleFallbacker::new().static_to_owned(), /// ); /// /// // Now we can load the "de-CH" data via fallback to "de". /// let german_hello_world: DataPayload = provider /// .load(req) /// .expect("Loading should succeed") /// .take_payload() /// .expect("Data should be present"); /// /// assert_eq!("Hallo Welt", german_hello_world.get().message); /// ``` pub fn new_with_fallbacker(provider: P, fallbacker: LocaleFallbacker) -> Self { Self { inner: provider, fallbacker, } } /// Returns a reference to the inner provider, bypassing fallback. pub fn inner(&self) -> &P { &self.inner } /// Returns a mutable reference to the inner provider. pub fn inner_mut(&mut self) -> &mut P { &mut self.inner } /// Returns ownership of the inner provider to the caller. pub fn into_inner(self) -> P { self.inner } /// Run the fallback algorithm with the data request using the inner data provider. /// Internal function; external clients should use one of the trait impls below. /// /// Function arguments: /// /// - F1 should perform a data load for a single DataRequest and return the result of it /// - F2 should map from the provider-specific response type to DataResponseMetadata fn run_fallback( &self, key: DataKey, mut base_req: DataRequest, mut f1: F1, mut f2: F2, ) -> Result where F1: FnMut(DataRequest) -> Result, F2: FnMut(&mut R) -> &mut DataResponseMetadata, { if key.metadata().singleton { return f1(base_req); } let mut fallback_iterator = self .fallbacker .for_config(key.fallback_config()) .fallback_for(base_req.locale.clone()); let base_silent = core::mem::replace(&mut base_req.metadata.silent, true); loop { let result = f1(DataRequest { locale: fallback_iterator.get(), metadata: base_req.metadata, }); if !result_is_err_missing_locale(&result) { return result .map(|mut res| { f2(&mut res).locale = Some(fallback_iterator.take()); res }) // Log the original request rather than the fallback request .map_err(|e| { base_req.metadata.silent = base_silent; e.with_req(key, base_req) }); } // If we just checked und, break out of the loop. if fallback_iterator.get().is_und() { break; } fallback_iterator.step(); } base_req.metadata.silent = base_silent; Err(DataErrorKind::MissingLocale.with_req(key, base_req)) } } impl

AnyProvider for LocaleFallbackProvider

where P: AnyProvider, { fn load_any(&self, key: DataKey, base_req: DataRequest) -> Result { self.run_fallback( key, base_req, |req| self.inner.load_any(key, req), |res| &mut res.metadata, ) } } impl

BufferProvider for LocaleFallbackProvider

where P: BufferProvider, { fn load_buffer( &self, key: DataKey, base_req: DataRequest, ) -> Result, DataError> { self.run_fallback( key, base_req, |req| self.inner.load_buffer(key, req), |res| &mut res.metadata, ) } } impl DynamicDataProvider for LocaleFallbackProvider

where P: DynamicDataProvider, M: DataMarker, { fn load_data(&self, key: DataKey, base_req: DataRequest) -> Result, DataError> { self.run_fallback( key, base_req, |req| self.inner.load_data(key, req), |res| &mut res.metadata, ) } } impl DataProvider for LocaleFallbackProvider

where P: DataProvider, M: KeyedDataMarker, { fn load(&self, base_req: DataRequest) -> Result, DataError> { self.run_fallback( M::KEY, base_req, |req| self.inner.load(req), |res| &mut res.metadata, ) } }