summaryrefslogtreecommitdiffstats
path: root/vendor/icu_provider_adapters/src/fallback
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/icu_provider_adapters/src/fallback')
-rw-r--r--vendor/icu_provider_adapters/src/fallback/adapter.rs273
-rw-r--r--vendor/icu_provider_adapters/src/fallback/algorithms.rs419
-rw-r--r--vendor/icu_provider_adapters/src/fallback/mod.rs390
-rw-r--r--vendor/icu_provider_adapters/src/fallback/provider.rs111
4 files changed, 1193 insertions, 0 deletions
diff --git a/vendor/icu_provider_adapters/src/fallback/adapter.rs b/vendor/icu_provider_adapters/src/fallback/adapter.rs
new file mode 100644
index 000000000..4d1f79255
--- /dev/null
+++ b/vendor/icu_provider_adapters/src/fallback/adapter.rs
@@ -0,0 +1,273 @@
+// 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 ).
+
+use super::*;
+use crate::helpers::result_is_err_missing_data_options;
+
+/// 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_testdata::unstable_no_fallback();
+///
+/// let req = DataRequest {
+/// locale: &locale!("ja-JP").into(),
+/// metadata: Default::default(),
+/// };
+///
+/// // The provider does not have data for "ja-JP":
+/// DataProvider::<HelloWorldV1Marker>::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::<HelloWorldV1Marker>::load(&provider, req).expect("successful with vertical fallback");
+///
+/// assert_eq!(
+/// "ja",
+/// response.metadata.locale.unwrap().to_string()
+/// );
+/// assert_eq!(
+/// "こんにちは世界",
+/// response.payload.unwrap().get().message
+/// );
+/// ```
+pub struct LocaleFallbackProvider<P> {
+ inner: P,
+ fallbacker: LocaleFallbacker,
+}
+
+impl<P> LocaleFallbackProvider<P>
+where
+ P: DataProvider<LocaleFallbackLikelySubtagsV1Marker>
+ + DataProvider<LocaleFallbackParentsV1Marker>
+ + DataProvider<CollationFallbackSupplementV1Marker>,
+{
+ /// 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<Self, DataError> {
+ let fallbacker = LocaleFallbacker::try_new_unstable(&provider)?;
+ Ok(Self {
+ inner: provider,
+ fallbacker,
+ })
+ }
+}
+
+impl<P> LocaleFallbackProvider<P>
+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<Self, DataError> {
+ let fallbacker = LocaleFallbacker::try_new_with_any_provider(&provider)?;
+ Ok(Self {
+ inner: provider,
+ fallbacker,
+ })
+ }
+}
+
+#[cfg(feature = "serde")]
+impl<P> LocaleFallbackProvider<P>
+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<Self, DataError> {
+ let fallbacker = LocaleFallbacker::try_new_with_buffer_provider(&provider)?;
+ Ok(Self {
+ inner: provider,
+ fallbacker,
+ })
+ }
+}
+
+impl<P> LocaleFallbackProvider<P> {
+ /// 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_provider::hello_world::*;
+ /// use icu_provider::prelude::*;
+ /// use icu_provider_adapters::fallback::{
+ /// LocaleFallbackProvider, LocaleFallbacker,
+ /// };
+ ///
+ /// let provider = HelloWorldProvider;
+ ///
+ /// let req = DataRequest {
+ /// locale: &locale!("de-CH").into(),
+ /// metadata: Default::default(),
+ /// };
+ ///
+ /// // There is no "de-CH" data in the `HelloWorldProvider`
+ /// DataProvider::<HelloWorldV1Marker>::load(&provider, req)
+ /// .expect_err("No data for de-CH");
+ ///
+ /// // `HelloWorldProvider` does not contain fallback data,
+ /// // but we can fetch it from `icu_testdata`, and then
+ /// // use it to create the fallbacking data provider.
+ /// let fallbacker =
+ /// LocaleFallbacker::try_new_unstable(&icu_testdata::unstable())
+ /// .expect("Fallback data present");
+ /// let provider =
+ /// LocaleFallbackProvider::new_with_fallbacker(provider, fallbacker);
+ ///
+ /// // Now we can load the "de-CH" data via fallback to "de".
+ /// let german_hello_world: DataPayload<HelloWorldV1Marker> = 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 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<F1, F2, R>(
+ &self,
+ key: DataKey,
+ base_req: DataRequest,
+ mut f1: F1,
+ mut f2: F2,
+ ) -> Result<R, DataError>
+ where
+ F1: FnMut(DataRequest) -> Result<R, DataError>,
+ F2: FnMut(&mut R) -> &mut DataResponseMetadata,
+ {
+ let key_fallbacker = self.fallbacker.for_key(key);
+ let mut fallback_iterator = key_fallbacker.fallback_for(base_req.locale.clone());
+ loop {
+ let result = f1(DataRequest {
+ locale: fallback_iterator.get(),
+ metadata: Default::default(),
+ });
+ if !result_is_err_missing_data_options(&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| e.with_req(key, base_req));
+ }
+ // If we just checked und, break out of the loop.
+ if fallback_iterator.get().is_empty() {
+ break;
+ }
+ fallback_iterator.step();
+ }
+ Err(DataErrorKind::MissingLocale.with_req(key, base_req))
+ }
+}
+
+impl<P> AnyProvider for LocaleFallbackProvider<P>
+where
+ P: AnyProvider,
+{
+ fn load_any(&self, key: DataKey, base_req: DataRequest) -> Result<AnyResponse, DataError> {
+ self.run_fallback(
+ key,
+ base_req,
+ |req| self.inner.load_any(key, req),
+ |res| &mut res.metadata,
+ )
+ }
+}
+
+impl<P> BufferProvider for LocaleFallbackProvider<P>
+where
+ P: BufferProvider,
+{
+ fn load_buffer(
+ &self,
+ key: DataKey,
+ base_req: DataRequest,
+ ) -> Result<DataResponse<BufferMarker>, DataError> {
+ self.run_fallback(
+ key,
+ base_req,
+ |req| self.inner.load_buffer(key, req),
+ |res| &mut res.metadata,
+ )
+ }
+}
+
+impl<P, M> DynamicDataProvider<M> for LocaleFallbackProvider<P>
+where
+ P: DynamicDataProvider<M>,
+ M: DataMarker,
+{
+ fn load_data(&self, key: DataKey, base_req: DataRequest) -> Result<DataResponse<M>, DataError> {
+ self.run_fallback(
+ key,
+ base_req,
+ |req| self.inner.load_data(key, req),
+ |res| &mut res.metadata,
+ )
+ }
+}
+
+impl<P, M> DataProvider<M> for LocaleFallbackProvider<P>
+where
+ P: DataProvider<M>,
+ M: KeyedDataMarker,
+{
+ fn load(&self, base_req: DataRequest) -> Result<DataResponse<M>, DataError> {
+ self.run_fallback(
+ M::KEY,
+ base_req,
+ |req| self.inner.load(req),
+ |res| &mut res.metadata,
+ )
+ }
+}
diff --git a/vendor/icu_provider_adapters/src/fallback/algorithms.rs b/vendor/icu_provider_adapters/src/fallback/algorithms.rs
new file mode 100644
index 000000000..9af52ef6d
--- /dev/null
+++ b/vendor/icu_provider_adapters/src/fallback/algorithms.rs
@@ -0,0 +1,419 @@
+// 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 ).
+
+use icu_locid::extensions::unicode::Key;
+use icu_locid::extensions_unicode_key as key;
+use icu_locid::subtags::Language;
+use icu_locid::LanguageIdentifier;
+use icu_provider::FallbackPriority;
+
+use super::*;
+
+const SUBDIVISION_KEY: Key = key!("sd");
+
+impl<'a> LocaleFallbackerWithConfig<'a> {
+ pub(crate) fn normalize(&self, locale: &mut DataLocale) {
+ let language = locale.language();
+ // 1. Populate the region (required for region fallback only)
+ if self.config.priority == FallbackPriority::Region && locale.region().is_none() {
+ // 1a. First look for region based on language+script
+ if let Some(script) = locale.script() {
+ locale.set_region(
+ self.likely_subtags
+ .ls2r
+ .get_2d(&language.into(), &script.into())
+ .copied(),
+ );
+ }
+ // 1b. If that fails, try language only
+ if locale.region().is_none() {
+ locale.set_region(self.likely_subtags.l2r.get(&language.into()).copied());
+ }
+ }
+ // 2. Remove the script if it is implied by the other subtags
+ if let Some(script) = locale.script() {
+ let default_script = self
+ .likely_subtags
+ .l2s
+ .get_copied(&language.into())
+ .unwrap_or(DEFAULT_SCRIPT);
+ if let Some(region) = locale.region() {
+ if script
+ == self
+ .likely_subtags
+ .lr2s
+ .get_copied_2d(&language.into(), &region.into())
+ .unwrap_or(default_script)
+ {
+ locale.set_script(None);
+ }
+ } else if script == default_script {
+ locale.set_script(None);
+ }
+ }
+ // 3. Remove irrelevant extension subtags
+ locale.retain_unicode_ext(|key| {
+ match *key {
+ // Always retain -u-sd
+ SUBDIVISION_KEY => true,
+ // Retain the query-specific keyword
+ _ if Some(*key) == self.config.extension_key => true,
+ // Drop all others
+ _ => false,
+ }
+ });
+ // 4. If there is an invalid "sd" subtag, drop it
+ // For now, ignore it, and let fallback do it for us
+ }
+}
+
+impl<'a, 'b> LocaleFallbackIteratorInner<'a, 'b> {
+ pub fn step(&mut self, locale: &mut DataLocale) {
+ match self.config.priority {
+ FallbackPriority::Language => self.step_language(locale),
+ FallbackPriority::Region => self.step_region(locale),
+ // TODO(#1964): Change the collation fallback rules to be different
+ // from the language fallback fules.
+ FallbackPriority::Collation => self.step_language(locale),
+ // This case should not normally happen, but `FallbackPriority` is non_exhaustive.
+ // Make it go directly to `und`.
+ _ => {
+ debug_assert!(
+ false,
+ "Unknown FallbackPriority: {:?}",
+ self.config.priority
+ );
+ *locale = Default::default()
+ }
+ }
+ }
+
+ fn step_language(&mut self, locale: &mut DataLocale) {
+ // 1. Remove the extension fallback keyword
+ if let Some(extension_key) = self.config.extension_key {
+ if let Some(value) = locale.remove_unicode_ext(&extension_key) {
+ self.backup_extension = Some(value);
+ return;
+ }
+ }
+ // 2. Remove the subdivision keyword
+ if let Some(value) = locale.remove_unicode_ext(&SUBDIVISION_KEY) {
+ self.backup_subdivision = Some(value);
+ return;
+ }
+ // 3. Assert that the locale is a language identifier
+ debug_assert!(!locale.has_unicode_ext());
+ // 4. Remove variants
+ if locale.has_variants() {
+ self.backup_variants = Some(locale.clear_variants());
+ return;
+ }
+ // 5. Check for parent override
+ if let Some(parent) = self.get_explicit_parent(locale) {
+ locale.set_langid(parent);
+ self.restore_extensions_variants(locale);
+ return;
+ }
+ // 6. Add the script subtag if necessary
+ if locale.script().is_none() {
+ if let Some(region) = locale.region() {
+ let language = locale.language();
+ if let Some(script) = self
+ .likely_subtags
+ .lr2s
+ .get_copied_2d(&language.into(), &region.into())
+ {
+ locale.set_script(Some(script));
+ self.restore_extensions_variants(locale);
+ return;
+ }
+ }
+ }
+ // 7. Remove region
+ if locale.region().is_some() {
+ locale.set_region(None);
+ return;
+ }
+ // 8. Remove language+script
+ debug_assert!(!locale.language().is_empty()); // don't call .step() on und
+ locale.set_script(None);
+ locale.set_language(Language::UND);
+ }
+
+ fn step_region(&mut self, locale: &mut DataLocale) {
+ // 1. Remove the extension fallback keyword
+ if let Some(extension_key) = self.config.extension_key {
+ if let Some(value) = locale.remove_unicode_ext(&extension_key) {
+ self.backup_extension = Some(value);
+ return;
+ }
+ }
+ // 2. Remove the subdivision keyword
+ if let Some(value) = locale.remove_unicode_ext(&SUBDIVISION_KEY) {
+ self.backup_subdivision = Some(value);
+ return;
+ }
+ // 3. Assert that the locale is a language identifier
+ debug_assert!(!locale.has_unicode_ext());
+ // 4. Remove variants
+ if locale.has_variants() {
+ self.backup_variants = Some(locale.clear_variants());
+ return;
+ }
+ // 5. Remove language+script
+ if !locale.language().is_empty() || locale.script().is_some() {
+ locale.set_script(None);
+ locale.set_language(Language::UND);
+ self.restore_extensions_variants(locale);
+ return;
+ }
+ // 6. Remove region
+ debug_assert!(locale.region().is_some()); // don't call .step() on und
+ locale.set_region(None);
+ }
+
+ fn restore_extensions_variants(&mut self, locale: &mut DataLocale) {
+ if let Some(value) = self.backup_extension.take() {
+ #[allow(clippy::unwrap_used)] // not reachable unless extension_key is present
+ locale.set_unicode_ext(self.config.extension_key.unwrap(), value);
+ }
+ if let Some(value) = self.backup_subdivision.take() {
+ locale.set_unicode_ext(SUBDIVISION_KEY, value);
+ }
+ if let Some(variants) = self.backup_variants.take() {
+ locale.set_variants(variants);
+ }
+ }
+
+ fn get_explicit_parent(&self, locale: &DataLocale) -> Option<LanguageIdentifier> {
+ self.supplement
+ .and_then(|supplement| {
+ supplement
+ .parents
+ .get_copied_by(|uvstr| locale.strict_cmp(uvstr).reverse())
+ })
+ .or_else(|| {
+ self.parents
+ .parents
+ .get_copied_by(|uvstr| locale.strict_cmp(uvstr).reverse())
+ })
+ .map(LanguageIdentifier::from)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use icu_locid::Locale;
+ use std::str::FromStr;
+
+ struct TestCase {
+ input: &'static str,
+ requires_data: bool,
+ extension_key: Option<Key>,
+ fallback_supplement: Option<FallbackSupplement>,
+ // Note: The first entry in the chain is the normalized locale
+ expected_language_chain: &'static [&'static str],
+ expected_region_chain: &'static [&'static str],
+ }
+
+ // TODO: Consider loading these from a JSON file
+ const TEST_CASES: &[TestCase] = &[
+ TestCase {
+ input: "en-u-hc-h12-sd-usca",
+ requires_data: false,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &["en-u-sd-usca", "en"],
+ expected_region_chain: &["en-u-sd-usca", "en", "und-u-sd-usca"],
+ },
+ TestCase {
+ input: "en-US-u-hc-h12-sd-usca",
+ requires_data: false,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &["en-US-u-sd-usca", "en-US", "en"],
+ expected_region_chain: &["en-US-u-sd-usca", "en-US", "und-US-u-sd-usca", "und-US"],
+ },
+ TestCase {
+ input: "en-US-fonipa-u-hc-h12-sd-usca",
+ requires_data: false,
+ extension_key: Some(key!("hc")),
+ fallback_supplement: None,
+ expected_language_chain: &[
+ "en-US-fonipa-u-hc-h12-sd-usca",
+ "en-US-fonipa-u-sd-usca",
+ "en-US-fonipa",
+ "en-US",
+ "en",
+ ],
+ expected_region_chain: &[
+ "en-US-fonipa-u-hc-h12-sd-usca",
+ "en-US-fonipa-u-sd-usca",
+ "en-US-fonipa",
+ "en-US",
+ "und-US-fonipa-u-hc-h12-sd-usca",
+ "und-US-fonipa-u-sd-usca",
+ "und-US-fonipa",
+ "und-US",
+ ],
+ },
+ TestCase {
+ input: "en-u-hc-h12-sd-usca",
+ requires_data: true,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &["en-u-sd-usca", "en"],
+ expected_region_chain: &["en-US-u-sd-usca", "en-US", "und-US-u-sd-usca", "und-US"],
+ },
+ TestCase {
+ input: "en-Latn-u-sd-usca",
+ requires_data: true,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &["en-u-sd-usca", "en"],
+ expected_region_chain: &["en-US-u-sd-usca", "en-US", "und-US-u-sd-usca", "und-US"],
+ },
+ TestCase {
+ input: "en-Latn-US-u-sd-usca",
+ requires_data: true,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &["en-US-u-sd-usca", "en-US", "en"],
+ expected_region_chain: &["en-US-u-sd-usca", "en-US", "und-US-u-sd-usca", "und-US"],
+ },
+ TestCase {
+ // NOTE: -u-rg is not yet supported; when it is, this test should be updated
+ input: "en-u-rg-gbxxxx",
+ requires_data: false,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &["en"],
+ expected_region_chain: &["en"],
+ },
+ TestCase {
+ input: "sr-ME",
+ requires_data: true,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &["sr-ME", "sr-Latn-ME", "sr-Latn"],
+ expected_region_chain: &["sr-ME", "und-ME"],
+ },
+ TestCase {
+ input: "sr-ME-fonipa",
+ requires_data: true,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &[
+ "sr-ME-fonipa",
+ "sr-ME",
+ "sr-Latn-ME-fonipa",
+ "sr-Latn-ME",
+ "sr-Latn",
+ ],
+ expected_region_chain: &["sr-ME-fonipa", "sr-ME", "und-ME-fonipa", "und-ME"],
+ },
+ TestCase {
+ input: "de-Latn-LI",
+ requires_data: true,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &["de-LI", "de"],
+ expected_region_chain: &["de-LI", "und-LI"],
+ },
+ TestCase {
+ input: "ca-ES-valencia",
+ requires_data: true,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &["ca-ES-valencia", "ca-ES", "ca"],
+ expected_region_chain: &["ca-ES-valencia", "ca-ES", "und-ES-valencia", "und-ES"],
+ },
+ TestCase {
+ input: "es-AR",
+ requires_data: true,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &["es-AR", "es-419", "es"],
+ expected_region_chain: &["es-AR", "und-AR"],
+ },
+ TestCase {
+ input: "hi-IN",
+ requires_data: true,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &["hi-IN", "hi"],
+ expected_region_chain: &["hi-IN", "und-IN"],
+ },
+ TestCase {
+ input: "hi-Latn-IN",
+ requires_data: true,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &["hi-Latn-IN", "hi-Latn", "en-IN", "en-001", "en"],
+ expected_region_chain: &["hi-Latn-IN", "und-IN"],
+ },
+ TestCase {
+ input: "yue-HK",
+ requires_data: true,
+ extension_key: None,
+ fallback_supplement: None,
+ expected_language_chain: &["yue-HK", "yue"],
+ expected_region_chain: &["yue-HK", "und-HK"],
+ },
+ TestCase {
+ input: "yue-HK",
+ requires_data: true,
+ extension_key: None,
+ fallback_supplement: Some(FallbackSupplement::Collation),
+ // TODO(#1964): add "zh" as a target.
+ expected_language_chain: &["yue-HK", "yue", "zh-Hant"],
+ expected_region_chain: &["yue-HK", "und-HK"],
+ },
+ ];
+
+ #[test]
+ #[cfg(feature = "serde")]
+ fn test_fallback() {
+ let fallbacker_no_data = LocaleFallbacker::new_without_data();
+ let fallbacker_with_data =
+ LocaleFallbacker::try_new_with_buffer_provider(&icu_testdata::buffer()).unwrap();
+ for cas in TEST_CASES {
+ for (priority, expected_chain) in [
+ (FallbackPriority::Language, cas.expected_language_chain),
+ (FallbackPriority::Region, cas.expected_region_chain),
+ ] {
+ let config = LocaleFallbackConfig {
+ priority,
+ extension_key: cas.extension_key,
+ fallback_supplement: cas.fallback_supplement,
+ };
+ let key_fallbacker = if cas.requires_data {
+ fallbacker_with_data.for_config(config)
+ } else {
+ fallbacker_no_data.for_config(config)
+ };
+ let locale = DataLocale::from(Locale::from_str(cas.input).unwrap());
+ let mut it = key_fallbacker.fallback_for(locale);
+ for expected in expected_chain {
+ assert_eq!(
+ expected,
+ &it.get().to_string(),
+ "{:?} ({:?})",
+ cas.input,
+ priority
+ );
+ it.step();
+ }
+ assert_eq!(
+ "und",
+ it.get().to_string(),
+ "{:?} ({:?})",
+ cas.input,
+ priority
+ );
+ }
+ }
+ }
+}
diff --git a/vendor/icu_provider_adapters/src/fallback/mod.rs b/vendor/icu_provider_adapters/src/fallback/mod.rs
new file mode 100644
index 000000000..6ec636a09
--- /dev/null
+++ b/vendor/icu_provider_adapters/src/fallback/mod.rs
@@ -0,0 +1,390 @@
+// 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 ).
+
+//! Tools for locale fallback, enabling arbitrary input locales to be mapped into the nearest
+//! locale with data.
+//!
+//! The algorithm implemented in this module is called [Flexible Vertical Fallback](
+//! https://docs.google.com/document/d/1Mp7EUyl-sFh_HZYgyeVwj88vJGpCBIWxzlCwGgLCDwM/edit).
+//! Watch [#2243](https://github.com/unicode-org/icu4x/issues/2243) to track improvements to
+//! this algorithm and steps to enshrine the algorithm in CLDR.
+//!
+//! # Examples
+//!
+//! Run the locale fallback algorithm:
+//!
+//! ```
+//! use icu_provider_adapters::fallback::LocaleFallbacker;
+//! use icu_provider::prelude::*;
+//!
+//! // Set up a LocaleFallbacker with data.
+//! let fallbacker = LocaleFallbacker::try_new_unstable(&icu_testdata::unstable()).expect("data");
+//!
+//! // Create a LocaleFallbackerWithConfig with a configuration for a specific key.
+//! // By default, uses language priority with no additional extension keywords.
+//! let key_fallbacker = fallbacker.for_config(Default::default());
+//!
+//! // Set up the fallback iterator.
+//! let mut fallback_iterator = key_fallbacker.fallback_for(icu_locid::locale!("hi-Latn-IN").into());
+//!
+//! // Run the algorithm and check the results.
+//! assert_eq!(fallback_iterator.get().to_string(), "hi-Latn-IN");
+//! fallback_iterator.step();
+//! assert_eq!(fallback_iterator.get().to_string(), "hi-Latn");
+//! fallback_iterator.step();
+//! assert_eq!(fallback_iterator.get().to_string(), "en-IN");
+//! fallback_iterator.step();
+//! assert_eq!(fallback_iterator.get().to_string(), "en-001");
+//! fallback_iterator.step();
+//! assert_eq!(fallback_iterator.get().to_string(), "en");
+//! fallback_iterator.step();
+//! assert_eq!(fallback_iterator.get().to_string(), "und");
+//! ```
+
+use icu_locid::extensions::unicode::{Key, Value};
+use icu_locid::subtags::Variants;
+use icu_provider::prelude::*;
+use icu_provider::FallbackPriority;
+use icu_provider::FallbackSupplement;
+
+mod adapter;
+mod algorithms;
+pub mod provider;
+
+pub use adapter::LocaleFallbackProvider;
+
+use provider::*;
+
+/// Configuration settings for a particular fallback operation.
+#[derive(Debug, Clone, PartialEq, Eq, Default)]
+#[non_exhaustive]
+pub struct LocaleFallbackConfig {
+ /// Strategy for choosing which subtags to drop during locale fallback.
+ ///
+ /// # Examples
+ ///
+ /// Retain the language and script subtags until the final step:
+ ///
+ /// ```
+ /// use icu_provider::prelude::*;
+ /// use icu_provider::FallbackPriority;
+ /// use icu_provider_adapters::fallback::LocaleFallbackConfig;
+ /// use icu_provider_adapters::fallback::LocaleFallbacker;
+ ///
+ /// // Set up the fallback iterator.
+ /// let fallbacker =
+ /// LocaleFallbacker::try_new_unstable(&icu_testdata::unstable())
+ /// .expect("data");
+ /// let mut config = LocaleFallbackConfig::default();
+ /// config.priority = FallbackPriority::Language;
+ /// let key_fallbacker = fallbacker.for_config(config);
+ /// let mut fallback_iterator = key_fallbacker
+ /// .fallback_for(icu_locid::locale!("ca-ES-valencia").into());
+ ///
+ /// // Run the algorithm and check the results.
+ /// assert_eq!(fallback_iterator.get().to_string(), "ca-ES-valencia");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "ca-ES");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "ca");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "und");
+ /// ```
+ ///
+ /// Retain the region subtag until the final step:
+ ///
+ /// ```
+ /// use icu_provider::prelude::*;
+ /// use icu_provider::FallbackPriority;
+ /// use icu_provider_adapters::fallback::LocaleFallbackConfig;
+ /// use icu_provider_adapters::fallback::LocaleFallbacker;
+ ///
+ /// // Set up the fallback iterator.
+ /// let fallbacker =
+ /// LocaleFallbacker::try_new_unstable(&icu_testdata::unstable())
+ /// .expect("data");
+ /// let mut config = LocaleFallbackConfig::default();
+ /// config.priority = FallbackPriority::Region;
+ /// let key_fallbacker = fallbacker.for_config(config);
+ /// let mut fallback_iterator = key_fallbacker
+ /// .fallback_for(icu_locid::locale!("ca-ES-valencia").into());
+ ///
+ /// // Run the algorithm and check the results.
+ /// assert_eq!(fallback_iterator.get().to_string(), "ca-ES-valencia");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "ca-ES");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "und-ES-valencia");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "und-ES");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "und");
+ /// ```
+ pub priority: FallbackPriority,
+ /// An extension keyword to retain during locale fallback.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use icu_provider::prelude::*;
+ /// use icu_provider_adapters::fallback::LocaleFallbackConfig;
+ /// use icu_provider_adapters::fallback::LocaleFallbacker;
+ ///
+ /// // Set up the fallback iterator.
+ /// let fallbacker =
+ /// LocaleFallbacker::try_new_unstable(&icu_testdata::unstable())
+ /// .expect("data");
+ /// let mut config = LocaleFallbackConfig::default();
+ /// config.extension_key = Some(icu_locid::extensions_unicode_key!("nu"));
+ /// let key_fallbacker = fallbacker.for_config(config);
+ /// let mut fallback_iterator = key_fallbacker
+ /// .fallback_for(icu_locid::locale!("ar-EG-u-nu-latn").into());
+ ///
+ /// // Run the algorithm and check the results.
+ /// assert_eq!(fallback_iterator.get().to_string(), "ar-EG-u-nu-latn");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "ar-EG");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "ar");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "und");
+ /// ```
+ pub extension_key: Option<Key>,
+ /// Fallback supplement data key to customize fallback rules.
+ ///
+ /// For example, most data keys for collation add additional parent locales, such as
+ /// "yue" to "zh-Hant", and data used for the `"-u-co"` extension keyword fallback.
+ ///
+ /// Currently the only supported fallback supplement is `FallbackSupplement::Collation`, but more may be
+ /// added in the future.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use icu_provider::prelude::*;
+ /// use icu_provider::FallbackPriority;
+ /// use icu_provider::FallbackSupplement;
+ /// use icu_provider_adapters::fallback::LocaleFallbackConfig;
+ /// use icu_provider_adapters::fallback::LocaleFallbacker;
+ /// use tinystr::tinystr;
+ ///
+ /// // Set up the fallback iterator.
+ /// let fallbacker =
+ /// LocaleFallbacker::try_new_unstable(&icu_testdata::unstable())
+ /// .expect("data");
+ /// let mut config = LocaleFallbackConfig::default();
+ /// config.priority = FallbackPriority::Collation;
+ /// config.fallback_supplement = Some(FallbackSupplement::Collation);
+ /// let key_fallbacker = fallbacker.for_config(config);
+ /// let mut fallback_iterator =
+ /// key_fallbacker.fallback_for(icu_locid::locale!("yue-HK").into());
+ ///
+ /// // Run the algorithm and check the results.
+ /// // TODO(#1964): add "zh" as a target.
+ /// assert_eq!(fallback_iterator.get().to_string(), "yue-HK");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "yue");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "zh-Hant");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "und");
+ /// ```
+ pub fallback_supplement: Option<FallbackSupplement>,
+}
+
+/// Entry type for locale fallbacking.
+///
+/// See the module-level documentation for an example.
+#[derive(Debug, Clone, PartialEq)]
+pub struct LocaleFallbacker {
+ likely_subtags: DataPayload<LocaleFallbackLikelySubtagsV1Marker>,
+ parents: DataPayload<LocaleFallbackParentsV1Marker>,
+ collation_supplement: Option<DataPayload<CollationFallbackSupplementV1Marker>>,
+}
+
+/// Intermediate type for spawning locale fallback iterators based on a specific configuration.
+///
+/// See the module-level documentation for an example.
+#[derive(Debug, Clone, PartialEq)]
+pub struct LocaleFallbackerWithConfig<'a> {
+ likely_subtags: &'a LocaleFallbackLikelySubtagsV1<'a>,
+ parents: &'a LocaleFallbackParentsV1<'a>,
+ supplement: Option<&'a LocaleFallbackSupplementV1<'a>>,
+ config: LocaleFallbackConfig,
+}
+
+/// Inner iteration type. Does not own the item under fallback.
+struct LocaleFallbackIteratorInner<'a, 'b> {
+ likely_subtags: &'a LocaleFallbackLikelySubtagsV1<'a>,
+ parents: &'a LocaleFallbackParentsV1<'a>,
+ supplement: Option<&'a LocaleFallbackSupplementV1<'a>>,
+ config: &'b LocaleFallbackConfig,
+ backup_extension: Option<Value>,
+ backup_subdivision: Option<Value>,
+ backup_variants: Option<Variants>,
+}
+
+/// Iteration type for locale fallback operations.
+///
+/// Because the `Iterator` trait does not allow items to borrow from the iterator, this class does
+/// not implement that trait. Instead, use `.step()` and `.get()`.
+pub struct LocaleFallbackIterator<'a, 'b> {
+ current: DataLocale,
+ inner: LocaleFallbackIteratorInner<'a, 'b>,
+}
+
+impl LocaleFallbacker {
+ /// Creates a [`LocaleFallbacker`] with fallback data (likely subtags and parent locales).
+ ///
+ /// [📚 Help choosing a constructor](icu_provider::constructors)
+ /// <div class="stab unstable">
+ /// ⚠️ The bounds on this function may change over time, including in SemVer minor releases.
+ /// </div>
+ pub fn try_new_unstable<P>(provider: &P) -> Result<Self, DataError>
+ where
+ P: DataProvider<LocaleFallbackLikelySubtagsV1Marker>
+ + DataProvider<LocaleFallbackParentsV1Marker>
+ + DataProvider<CollationFallbackSupplementV1Marker>
+ + ?Sized,
+ {
+ let likely_subtags = provider.load(Default::default())?.take_payload()?;
+ let parents = provider.load(Default::default())?.take_payload()?;
+ let collation_supplement = match DataProvider::<CollationFallbackSupplementV1Marker>::load(
+ provider,
+ Default::default(),
+ ) {
+ Ok(response) => Some(response.take_payload()?),
+ // It is expected that not all keys are present
+ Err(DataError {
+ kind: DataErrorKind::MissingDataKey,
+ ..
+ }) => None,
+ Err(e) => return Err(e),
+ };
+ Ok(LocaleFallbacker {
+ likely_subtags,
+ parents,
+ collation_supplement,
+ })
+ }
+
+ icu_provider::gen_any_buffer_constructors!(locale: skip, options: skip, error: DataError);
+
+ /// Creates a [`LocaleFallbacker`] without fallback data. Using this constructor may result in
+ /// surprising behavior, especially in multi-script languages.
+ pub fn new_without_data() -> Self {
+ LocaleFallbacker {
+ likely_subtags: DataPayload::from_owned(Default::default()),
+ parents: DataPayload::from_owned(Default::default()),
+ collation_supplement: None,
+ }
+ }
+
+ /// Creates the intermediate [`LocaleFallbackerWithConfig`] with configuration options.
+ pub fn for_config(&self, config: LocaleFallbackConfig) -> LocaleFallbackerWithConfig {
+ let supplement = match config.fallback_supplement {
+ Some(FallbackSupplement::Collation) => {
+ self.collation_supplement.as_ref().map(|p| p.get())
+ }
+ _ => None,
+ };
+ LocaleFallbackerWithConfig {
+ likely_subtags: self.likely_subtags.get(),
+ parents: self.parents.get(),
+ supplement,
+ config,
+ }
+ }
+
+ /// Creates the intermediate [`LocaleFallbackerWithConfig`] based on a
+ /// [`DataKey`] and a [`DataRequestMetadata`].
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use icu_provider::prelude::*;
+ /// use icu_provider_adapters::fallback::LocaleFallbacker;
+ /// use std::borrow::Cow;
+ ///
+ /// // Define the data struct with key.
+ /// #[icu_provider::data_struct(marker(
+ /// FooV1Marker,
+ /// "demo/foo@1",
+ /// fallback_by = "region"
+ /// ))]
+ /// pub struct FooV1<'data> {
+ /// message: Cow<'data, str>,
+ /// };
+ ///
+ /// // Set up the fallback iterator.
+ /// let fallbacker =
+ /// LocaleFallbacker::try_new_unstable(&icu_testdata::unstable())
+ /// .expect("data");
+ /// let key_fallbacker = fallbacker.for_key(FooV1Marker::KEY);
+ /// let mut fallback_iterator =
+ /// key_fallbacker.fallback_for(icu_locid::locale!("en-GB").into());
+ ///
+ /// // Run the algorithm and check the results.
+ /// assert_eq!(fallback_iterator.get().to_string(), "en-GB");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "und-GB");
+ /// fallback_iterator.step();
+ /// assert_eq!(fallback_iterator.get().to_string(), "und");
+ /// ```
+ ///
+ /// [`DataRequestMetadata`]: icu_provider::DataRequestMetadata
+ pub fn for_key(&self, data_key: DataKey) -> LocaleFallbackerWithConfig {
+ let priority = data_key.metadata().fallback_priority;
+ let extension_key = data_key.metadata().extension_key;
+ let fallback_supplement = data_key.metadata().fallback_supplement;
+ self.for_config(LocaleFallbackConfig {
+ priority,
+ extension_key,
+ fallback_supplement,
+ })
+ }
+}
+
+impl<'a> LocaleFallbackerWithConfig<'a> {
+ /// Creates an iterator based on a [`DataLocale`] (which can be created from [`Locale`]).
+ ///
+ /// When first initialized, the locale is normalized according to the fallback algorithm.
+ ///
+ /// [`Locale`]: icu_locid::Locale
+ pub fn fallback_for<'b>(&'b self, mut locale: DataLocale) -> LocaleFallbackIterator<'a, 'b> {
+ self.normalize(&mut locale);
+ LocaleFallbackIterator {
+ current: locale,
+ inner: LocaleFallbackIteratorInner {
+ likely_subtags: self.likely_subtags,
+ parents: self.parents,
+ supplement: self.supplement,
+ config: &self.config,
+ backup_extension: None,
+ backup_subdivision: None,
+ backup_variants: None,
+ },
+ }
+ }
+}
+
+impl LocaleFallbackIterator<'_, '_> {
+ /// Borrows the current [`DataLocale`] under fallback.
+ pub fn get(&self) -> &DataLocale {
+ &self.current
+ }
+
+ /// Takes the current [`DataLocale`] under fallback.
+ pub fn take(self) -> DataLocale {
+ self.current
+ }
+
+ /// Performs one step of the locale fallback algorithm.
+ ///
+ /// The fallback is completed once the inner [`DataLocale`] becomes `und`.
+ pub fn step(&mut self) -> &mut Self {
+ self.inner.step(&mut self.current);
+ self
+ }
+}
diff --git a/vendor/icu_provider_adapters/src/fallback/provider.rs b/vendor/icu_provider_adapters/src/fallback/provider.rs
new file mode 100644
index 000000000..edfa01f58
--- /dev/null
+++ b/vendor/icu_provider_adapters/src/fallback/provider.rs
@@ -0,0 +1,111 @@
+// 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 ).
+
+//! Data provider struct definitions for vertical fallback.
+//!
+//! Read more about data providers: [`icu_provider`]
+
+// Provider structs must be stable
+#![allow(clippy::exhaustive_structs)]
+
+use icu_locid::extensions::unicode::Key;
+use icu_locid::subtags::{Language, Region, Script};
+use icu_locid::{subtags_region as region, subtags_script as script};
+use tinystr::TinyAsciiStr;
+
+use icu_provider::prelude::*;
+
+use zerovec::ule::UnvalidatedStr;
+use zerovec::ZeroMap;
+use zerovec::ZeroMap2d;
+
+// We use raw TinyAsciiStrs for map keys, as we then don't have to
+// validate them as subtags on deserialization. Map lookup can be
+// done even if they are not valid tags (an invalid key will just
+// become inaccessible).
+type UnvalidatedLanguage = TinyAsciiStr<3>;
+type UnvalidatedScript = TinyAsciiStr<4>;
+type UnvalidatedRegion = TinyAsciiStr<3>;
+
+/// Locale fallback rules derived from likely subtags data.
+#[icu_provider::data_struct(LocaleFallbackLikelySubtagsV1Marker = "fallback/likelysubtags@1")]
+#[derive(Default, Clone, PartialEq, Debug)]
+#[cfg_attr(
+ feature = "datagen",
+ derive(serde::Serialize, databake::Bake),
+ databake(path = icu_provider_adapters::fallback::provider),
+)]
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[yoke(prove_covariance_manually)]
+pub struct LocaleFallbackLikelySubtagsV1<'data> {
+ /// Map from language to the default script in that language. Languages whose default script
+ /// is `Latn` are not included in the map for data size savings.
+ ///
+ /// Example: "zh" defaults to "Hans", which is in this map.
+ #[cfg_attr(feature = "serde", serde(borrow))]
+ pub l2s: ZeroMap<'data, UnvalidatedLanguage, Script>,
+ /// Map from language-region pairs to a script. Only populated if the script is different
+ /// from the one in `l2s` for that language.
+ ///
+ /// Example: "zh-TW" defaults to "Hant", which is in this map.
+ #[cfg_attr(feature = "serde", serde(borrow))]
+ pub lr2s: ZeroMap2d<'data, UnvalidatedLanguage, UnvalidatedRegion, Script>,
+ /// Map from language to the default region in that language. Languages whose default region
+ /// is `ZZ` are not included in the map for data size savings.
+ ///
+ /// Example: "zh" defaults to "CN".
+ #[cfg_attr(feature = "serde", serde(borrow))]
+ pub l2r: ZeroMap<'data, UnvalidatedLanguage, Region>,
+ /// Map from language-script pairs to a region. Only populated if the region is different
+ /// from the one in `l2r` for that language.
+ ///
+ /// Example: "zh-Hant" defaults to "TW".
+ #[cfg_attr(feature = "serde", serde(borrow))]
+ pub ls2r: ZeroMap2d<'data, UnvalidatedLanguage, UnvalidatedScript, Region>,
+}
+
+/// `Latn` is the most common script, so it is defaulted for data size savings.
+pub const DEFAULT_SCRIPT: Script = script!("Latn");
+
+/// `ZZ` is the most common region, so it is defaulted for data size savings.
+pub const DEFAULT_REGION: Region = region!("ZZ");
+
+/// Locale fallback rules derived from CLDR parent locales data.
+#[icu_provider::data_struct(LocaleFallbackParentsV1Marker = "fallback/parents@1")]
+#[derive(Default, Clone, PartialEq, Debug)]
+#[cfg_attr(
+ feature = "datagen",
+ derive(serde::Serialize, databake::Bake),
+ databake(path = icu_provider_adapters::fallback::provider),
+)]
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[yoke(prove_covariance_manually)]
+pub struct LocaleFallbackParentsV1<'data> {
+ /// Map from language identifier to language identifier, indicating that the language on the
+ /// left should inherit from the language on the right.
+ #[cfg_attr(feature = "serde", serde(borrow))]
+ pub parents: ZeroMap<'data, UnvalidatedStr, (Language, Option<Script>, Option<Region>)>,
+}
+
+/// Key-specific supplemental fallback data.
+#[icu_provider::data_struct(marker(
+ CollationFallbackSupplementV1Marker,
+ "fallback/supplement/co@1"
+))]
+#[derive(Default, Clone, PartialEq, Debug)]
+#[cfg_attr(
+ feature = "datagen",
+ derive(serde::Serialize, databake::Bake),
+ databake(path = icu_provider_adapters::fallback::provider),
+)]
+#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
+#[yoke(prove_covariance_manually)]
+pub struct LocaleFallbackSupplementV1<'data> {
+ /// Additional parent locales to supplement the common ones.
+ #[cfg_attr(feature = "serde", serde(borrow))]
+ pub parents: ZeroMap<'data, UnvalidatedStr, (Language, Option<Script>, Option<Region>)>,
+ /// Default values for Unicode extension keywords.
+ #[cfg_attr(feature = "serde", serde(borrow))]
+ pub unicode_extension_defaults: ZeroMap2d<'data, Key, UnvalidatedStr, UnvalidatedStr>,
+}