usecrate::{DataError, DataErrorKind}; use core::cmp::Ordering; use core::default::Default; use core::fmt; use core::fmt::Debug; use core::hash::Hash; use core::str::FromStr; use icu_locid::extensions::unicode as unicode_ext; use icu_locid::subtags::{Language, Region, Script, Variants}; use icu_locid::{LanguageIdentifier, Locale}; use writeable::{LengthHint, Writeable};
#[cfg(feature = "experimental")] use alloc::string::String; #[cfg(feature = "experimental")] use core::ops::Deref; #[cfg(feature = "experimental")] use icu_locid::extensions::private::Subtag; #[cfg(feature = "experimental")] use tinystr::TinyAsciiStr;
#[cfg(doc)] use icu_locid::subtags::Variant;
/// The request type passed into all data provider implementations. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] #[allow(clippy::exhaustive_structs)] // this type is stable pubstruct DataRequest<'a> { /// The locale for which to load data. /// /// If locale fallback is enabled, the resulting data may be from a different locale /// than the one requested here. pub locale: &'a DataLocale, /// Metadata that may affect the behavior of the data provider. pub metadata: DataRequestMetadata,
}
/// Metadata for data requests. This is currently empty, but it may be extended with options /// for tuning locale fallback, buffer layout, and so forth. #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[non_exhaustive] pubstruct DataRequestMetadata { /// Silent requests do not log errors. This can be used for exploratory querying, such as fallbacks. pub silent: bool,
}
/// A locale type optimized for use in fallbacking and the ICU4X data pipeline. /// /// [`DataLocale`] contains less functionality than [`Locale`] but more than /// [`LanguageIdentifier`] for better size and performance while still meeting /// the needs of the ICU4X data pipeline. /// /// # Examples /// /// Convert a [`Locale`] to a [`DataLocale`] and back: /// /// ``` /// use icu_locid::locale; /// use icu_provider::DataLocale; /// /// let locale = locale!("en-u-ca-buddhist"); /// let data_locale = DataLocale::from(locale); /// let locale = data_locale.into_locale(); /// /// assert_eq!(locale, locale!("en-u-ca-buddhist")); /// ``` /// /// You can alternatively create a [`DataLocale`] from a borrowed [`Locale`], which is more /// efficient than cloning the [`Locale`], but less efficient than converting an owned /// [`Locale`]: /// /// ``` /// use icu_locid::locale; /// use icu_provider::DataLocale; /// /// let locale1 = locale!("en-u-ca-buddhist"); /// let data_locale = DataLocale::from(&locale1); /// let locale2 = data_locale.into_locale(); /// /// assert_eq!(locale1, locale2); /// ``` /// /// If you are sure that you have no Unicode keywords, start with [`LanguageIdentifier`]: /// /// ``` /// use icu_locid::langid; /// use icu_provider::DataLocale; /// /// let langid = langid!("es-CA-valencia"); /// let data_locale = DataLocale::from(langid); /// let langid = data_locale.get_langid(); /// /// assert_eq!(langid, langid!("es-CA-valencia")); /// ``` /// /// [`DataLocale`] only supports `-u` keywords, to reflect the current state of CLDR data /// lookup and fallback. This may change in the future. /// /// ``` /// use icu_locid::{locale, Locale}; /// use icu_provider::DataLocale; /// /// let locale = "hi-t-en-h0-hybrid-u-attr-ca-buddhist" /// .parse::<Locale>() /// .unwrap(); /// let data_locale = DataLocale::from(locale); /// /// assert_eq!(data_locale.into_locale(), locale!("hi-u-ca-buddhist")); /// ``` #[derive(PartialEq, Clone, Default, Eq, Hash)] pubstruct DataLocale {
langid: LanguageIdentifier,
keywords: unicode_ext::Keywords, #[cfg(feature = "experimental")]
aux: Option<AuxiliaryKeys>,
}
/// Returns an ordering suitable for use in [`BTreeSet`]. /// /// The ordering may or may not be equivalent to string ordering, and it /// may or may not be stable across ICU4X releases. /// /// [`BTreeSet`]: alloc::collections::BTreeSet pubfn total_cmp(&self, other: &Self) -> Ordering { self.langid
.total_cmp(&other.langid)
.then_with(|| self.keywords.cmp(&other.keywords))
.then_with(|| { #[cfg(feature = "experimental")] returnself.aux.cmp(&other.aux); #[cfg(not(feature = "experimental"))] return Ordering::Equal;
})
}
/// Returns whether this [`DataLocale`] is `und` in the locale and extensions portion. /// /// This ignores auxiliary keys. /// /// See also: /// /// - [`DataLocale::is_empty()`] /// - [`DataLocale::is_langid_und()`] /// /// # Examples /// /// ``` /// use icu_provider::DataLocale; /// /// assert!("und".parse::<DataLocale>().unwrap().is_und()); /// assert!(!"und-u-ca-buddhist".parse::<DataLocale>().unwrap().is_und()); /// assert!("und-x-aux".parse::<DataLocale>().unwrap().is_und()); /// assert!(!"ca-ES".parse::<DataLocale>().unwrap().is_und()); /// ``` pubfn is_und(&self) -> bool { self.langid == LanguageIdentifier::UND && self.keywords.is_empty()
}
/// Returns whether the [`LanguageIdentifier`] associated with this request is `und`. /// /// This ignores extension keywords and auxiliary keys. /// /// See also: /// /// - [`DataLocale::is_empty()`] /// - [`DataLocale::is_und()`] /// /// # Examples /// /// ``` /// use icu_provider::DataLocale; /// /// assert!("und".parse::<DataLocale>().unwrap().is_langid_und()); /// assert!("und-u-ca-buddhist" /// .parse::<DataLocale>() /// .unwrap() /// .is_langid_und()); /// assert!("und-x-aux".parse::<DataLocale>().unwrap().is_langid_und()); /// assert!(!"ca-ES".parse::<DataLocale>().unwrap().is_langid_und()); /// ``` pubfn is_langid_und(&self) -> bool { self.langid == LanguageIdentifier::UND
}
/// Gets the [`LanguageIdentifier`] for this [`DataLocale`]. /// /// This may allocate memory if there are variant subtags. If you need only the language, /// script, and/or region subtag, use the specific getters for those subtags: /// /// - [`DataLocale::language()`] /// - [`DataLocale::script()`] /// - [`DataLocale::region()`] /// /// If you have ownership over the `DataLocale`, use [`DataLocale::into_locale()`] /// and then access the `id` field. /// /// # Examples /// /// ``` /// use icu_locid::langid; /// use icu_provider::prelude::*; /// /// const FOO_BAR: DataKey = icu_provider::data_key!("foo/bar@1"); /// /// let req_no_langid = DataRequest { /// locale: &Default::default(), /// metadata: Default::default(), /// }; /// /// let req_with_langid = DataRequest { /// locale: &langid!("ar-EG").into(), /// metadata: Default::default(), /// }; /// /// assert_eq!(req_no_langid.locale.get_langid(), langid!("und")); /// assert_eq!(req_with_langid.locale.get_langid(), langid!("ar-EG")); /// ``` pubfn get_langid(&self) -> LanguageIdentifier { self.langid.clone()
}
/// Overrides the entire [`LanguageIdentifier`] portion of this [`DataLocale`]. #[inline] pubfn set_langid(&mutself, lid: LanguageIdentifier) { self.langid = lid;
}
/// Converts this [`DataLocale`] into a [`Locale`]. /// /// See also [`DataLocale::get_langid()`]. /// /// # Examples /// /// ``` /// use icu_locid::{ /// langid, locale, /// subtags::{language, region}, /// }; /// use icu_provider::prelude::*; /// /// let locale: DataLocale = locale!("it-IT-u-ca-coptic").into(); /// /// assert_eq!(locale.get_langid(), langid!("it-IT")); /// assert_eq!(locale.language(), language!("it")); /// assert_eq!(locale.script(), None); /// assert_eq!(locale.region(), Some(region!("IT"))); /// /// let locale = locale.into_locale(); /// assert_eq!(locale, locale!("it-IT-u-ca-coptic")); /// ``` /// /// Auxiliary keys are retained: /// /// ``` /// use icu_provider::prelude::*; /// use writeable::assert_writeable_eq; /// /// let data_locale: DataLocale = "und-u-nu-arab-x-gbp".parse().unwrap(); /// assert_writeable_eq!(data_locale, "und-u-nu-arab-x-gbp"); /// /// let recovered_locale = data_locale.into_locale(); /// assert_writeable_eq!(recovered_locale, "und-u-nu-arab-x-gbp"); /// ``` pubfn into_locale(self) -> Locale { letmut loc = Locale {
id: self.langid,
..Default::default()
};
loc.extensions.unicode.keywords = self.keywords; #[cfg(feature = "experimental")] iflet Some(aux) = self.aux {
loc.extensions.private =
icu_locid::extensions::private::Private::from_vec_unchecked(aux.iter().collect());
}
loc
}
/// Returns the [`Language`] for this [`DataLocale`]. #[inline] pubfn language(&self) -> Language { self.langid.language
}
/// Returns the [`Language`] for this [`DataLocale`]. #[inline] pubfn set_language(&mutself, language: Language) { self.langid.language = language;
}
/// Returns the [`Script`] for this [`DataLocale`]. #[inline] pubfn script(&self) -> Option<Script> { self.langid.script
}
/// Sets the [`Script`] for this [`DataLocale`]. #[inline] pubfn set_script(&mutself, script: Option<Script>) { self.langid.script = script;
}
/// Returns the [`Region`] for this [`DataLocale`]. #[inline] pubfn region(&self) -> Option<Region> { self.langid.region
}
/// Sets the [`Region`] for this [`DataLocale`]. #[inline] pubfn set_region(&mutself, region: Option<Region>) { self.langid.region = region;
}
/// Returns whether there are any [`Variant`] subtags in this [`DataLocale`]. #[inline] pubfn has_variants(&self) -> bool {
!self.langid.variants.is_empty()
}
/// Sets all [`Variants`] on this [`DataLocale`], overwriting any that were there previously. #[inline] pubfn set_variants(&mutself, variants: Variants) { self.langid.variants = variants;
}
/// Removes all [`Variant`] subtags in this [`DataLocale`]. #[inline] pubfn clear_variants(&mutself) -> Variants { self.langid.variants.clear()
}
/// Gets the value of the specified Unicode extension keyword for this [`DataLocale`]. #[inline] pubfn get_unicode_ext(&self, key: &unicode_ext::Key) -> Option<unicode_ext::Value> { self.keywords.get(key).cloned()
}
/// Returns whether there are any Unicode extension keywords in this [`DataLocale`]. #[inline] pubfn has_unicode_ext(&self) -> bool {
!self.keywords.is_empty()
}
/// Returns whether a specific Unicode extension keyword is present in this [`DataLocale`]. #[inline] pubfn contains_unicode_ext(&self, key: &unicode_ext::Key) -> bool { self.keywords.contains_key(key)
}
/// Returns whether this [`DataLocale`] contains a Unicode extension keyword /// with the specified key and value. /// /// # Examples /// /// ``` /// use icu_locid::extensions::unicode::{key, value}; /// use icu_provider::prelude::*; /// /// let locale: DataLocale = "it-IT-u-ca-coptic".parse().expect("Valid BCP-47"); /// /// assert_eq!(locale.get_unicode_ext(&key!("hc")), None); /// assert_eq!(locale.get_unicode_ext(&key!("ca")), Some(value!("coptic"))); /// assert!(locale.matches_unicode_ext(&key!("ca"), &value!("coptic"),)); /// ``` #[inline] pubfn matches_unicode_ext(&self, key: &unicode_ext::Key, value: &unicode_ext::Value) -> bool { self.keywords.get(key) == Some(value)
}
/// Sets the value for a specific Unicode extension keyword on this [`DataLocale`]. #[inline] pubfn set_unicode_ext(
&mutself,
key: unicode_ext::Key,
value: unicode_ext::Value,
) -> Option<unicode_ext::Value> { self.keywords.set(key, value)
}
/// Removes a specific Unicode extension keyword from this [`DataLocale`], returning /// the value if it was present. #[inline] pubfn remove_unicode_ext(&mutself, key: &unicode_ext::Key) -> Option<unicode_ext::Value> { self.keywords.remove(key)
}
/// Retains a subset of keywords as specified by the predicate function. #[inline] pubfn retain_unicode_ext<F>(&mutself, predicate: F) where
F: FnMut(&unicode_ext::Key) -> bool,
{ self.keywords.retain_by_key(predicate)
}
/// Gets the auxiliary key for this [`DataLocale`]. /// /// For more information and examples, see [`AuxiliaryKeys`]. #[cfg(feature = "experimental")] pubfn get_aux(&self) -> Option<&AuxiliaryKeys> { self.aux.as_ref()
}
/// Returns whether this [`DataLocale`] has an auxiliary key. /// /// For more information and examples, see [`AuxiliaryKeys`]. #[cfg(feature = "experimental")] pubfn has_aux(&self) -> bool { self.aux.is_some()
}
/// Sets an auxiliary key on this [`DataLocale`]. /// /// Returns the previous auxiliary key if present. /// /// For more information and examples, see [`AuxiliaryKeys`]. #[cfg(feature = "experimental")] pubfn set_aux(&mutself, value: AuxiliaryKeys) -> Option<AuxiliaryKeys> { self.aux.replace(value)
}
/// Remove an auxiliary key, if present. Returns the removed auxiliary key. /// /// # Examples /// /// ``` /// use icu_locid::langid; /// use icu_provider::prelude::*; /// use writeable::assert_writeable_eq; /// /// let mut data_locale: DataLocale = langid!("ar-EG").into(); /// let aux = "gbp" /// .parse::<AuxiliaryKeys>() /// .expect("contains valid characters"); /// data_locale.set_aux(aux); /// assert_writeable_eq!(data_locale, "ar-EG-x-gbp"); /// /// let maybe_aux = data_locale.remove_aux(); /// assert_writeable_eq!(data_locale, "ar-EG"); /// assert_writeable_eq!(maybe_aux.unwrap(), "gbp"); /// ``` #[cfg(feature = "experimental")] pubfn remove_aux(&mutself) -> Option<AuxiliaryKeys> { self.aux.take()
}
}
/// The "auxiliary key" is an annotation on [`DataLocale`] that can contain an arbitrary /// information that does not fit into the [`LanguageIdentifier`] or [`Keywords`]. /// /// A [`DataLocale`] can have multiple auxiliary keys, represented by this struct. The auxiliary /// keys are stored as private use subtags following `-x-`. /// /// An auxiliary key currently allows 1-8 lowercase alphanumerics. /// /// <div class="stab unstable"> /// This code is experimental; it may change at any time, in breaking or non-breaking ways, /// including in SemVer minor releases. It can be enabled with the "experimental" Cargo feature /// of the `icu_provider` crate. Use with caution. /// <a href="https://github.com/unicode-org/icu4x/issues/3632">#3632</a> /// </div> /// /// # Examples /// /// ``` /// use icu_locid::langid; /// use icu_provider::prelude::*; /// use writeable::assert_writeable_eq; /// /// let mut data_locale: DataLocale = langid!("ar-EG").into(); /// assert_writeable_eq!(data_locale, "ar-EG"); /// assert!(!data_locale.has_aux()); /// assert_eq!(data_locale.get_aux(), None); /// /// let aux = "gbp" /// .parse::<AuxiliaryKeys>() /// .expect("contains valid characters"); /// /// data_locale.set_aux(aux); /// assert_writeable_eq!(data_locale, "ar-EG-x-gbp"); /// assert!(data_locale.has_aux()); /// assert_eq!(data_locale.get_aux(), Some(&"gbp".parse().unwrap())); /// ``` /// /// Multiple auxiliary keys are allowed: /// /// ``` /// use icu_provider::prelude::*; /// use writeable::assert_writeable_eq; /// /// let data_locale = "ar-EG-x-gbp-long".parse::<DataLocale>().unwrap(); /// assert_writeable_eq!(data_locale, "ar-EG-x-gbp-long"); /// assert_eq!(data_locale.get_aux().unwrap().iter().count(), 2); /// ``` /// /// Not all strings are valid auxiliary keys. /// The string must be well-formed and case-normalized: /// /// ``` /// use icu_provider::prelude::*; /// /// assert!("abcdefg".parse::<AuxiliaryKeys>().is_ok()); /// assert!("abc-xyz".parse::<AuxiliaryKeys>().is_ok()); /// /// assert!("".parse::<AuxiliaryKeys>().is_err()); /// assert!("!@#$%".parse::<AuxiliaryKeys>().is_err()); /// assert!("abc_xyz".parse::<AuxiliaryKeys>().is_err()); /// assert!("ABC123".parse::<AuxiliaryKeys>().is_err()); /// ``` /// /// [`Keywords`]: unicode_ext::Keywords #[derive(Debug, PartialEq, Clone, Eq, Hash, PartialOrd, Ord)] #[cfg(feature = "experimental")] pubstruct AuxiliaryKeys {
value: AuxiliaryKeysInner,
}
#[cfg(feature = "experimental")] #[derive(Clone)] enum AuxiliaryKeysInner {
Boxed(alloc::boxed::Box<str>),
Stack(TinyAsciiStr<23>), // NOTE: In the future, a `Static` variant could be added to allow `data_locale!("...")` // Static(&'static str),
}
#[cfg(feature = "experimental")] impl FromStr for AuxiliaryKeys { type Err = DataError;
fn from_str(s: &str) -> Result<Self, Self::Err> { if !s.is_empty()
&& s.split(Self::separator()).all(|b| { iflet Ok(subtag) = Subtag::from_str(b) { // Enforces normalization:
b == subtag.as_str()
} else { false
}
})
{ if s.len() <= 23 { #[allow(clippy::unwrap_used)] // we just checked that the string is ascii
Ok(Self {
value: AuxiliaryKeysInner::Stack(s.parse().unwrap()),
})
} else {
Ok(Self {
value: AuxiliaryKeysInner::Boxed(s.into()),
})
}
} else {
Err(DataErrorKind::KeyLocaleSyntax
.into_error()
.with_display_context(s))
}
}
}
#[cfg(feature = "experimental")] impl AuxiliaryKeys { /// Creates an [`AuxiliaryKeys`] from an iterator of individual keys. /// /// # Examples /// /// ``` /// use icu_locid::extensions::private::subtag; /// use icu_provider::prelude::*; /// /// // Single auxiliary key: /// let a = AuxiliaryKeys::try_from_iter([subtag!("abc")]).unwrap(); /// let b = "abc".parse::<AuxiliaryKeys>().unwrap(); /// assert_eq!(a, b); /// /// // Multiple auxiliary keys: /// let a = AuxiliaryKeys::try_from_iter([subtag!("abc"), subtag!("defg")]) /// .unwrap(); /// let b = "abc-defg".parse::<AuxiliaryKeys>().unwrap(); /// assert_eq!(a, b); /// ``` /// /// The iterator can't be empty: /// /// ``` /// use icu_provider::prelude::*; /// /// assert!(AuxiliaryKeys::try_from_iter([]).is_err()); /// ``` pubfn try_from_iter(iter: impl IntoIterator<Item = Subtag>) -> Result<Self, DataError> { // TODO: Avoid the allocation when possible letmut builder = String::new(); for item in iter { if !builder.is_empty() {
builder.push(AuxiliaryKeys::separator());
}
builder.push_str(item.as_str())
} if builder.is_empty() { return Err(DataErrorKind::KeyLocaleSyntax.with_str_context("empty aux iterator"));
} if builder.len() <= 23 { #[allow(clippy::unwrap_used)] // we just checked that the string is ascii
Ok(Self {
value: AuxiliaryKeysInner::Stack(builder.parse().unwrap()),
})
} else {
Ok(Self {
value: AuxiliaryKeysInner::Boxed(builder.into()),
})
}
}
/// Creates an [`AuxiliaryKeys`] from a single subtag. /// /// # Examples /// /// ``` /// use icu_locid::extensions::private::subtag; /// use icu_provider::prelude::*; /// /// // Single auxiliary key: /// let a = AuxiliaryKeys::from_subtag(subtag!("abc")); /// let b = "abc".parse::<AuxiliaryKeys>().unwrap(); /// assert_eq!(a, b); /// ``` pubconstfn from_subtag(input: Subtag) -> Self { Self {
value: AuxiliaryKeysInner::Stack(input.into_tinystr().resize()),
}
}
/// Iterates over the components of the auxiliary key. /// /// # Example /// /// ``` /// use icu_locid::extensions::private::subtag; /// use icu_provider::AuxiliaryKeys; /// /// let aux: AuxiliaryKeys = "abc-defg".parse().unwrap(); /// assert_eq!( /// aux.iter().collect::<Vec<_>>(), /// vec![subtag!("abc"), subtag!("defg")] /// ); /// ``` pubfn iter(&self) -> impl Iterator<Item = Subtag> + '_ { self.value
.split(Self::separator())
.filter_map(|x| match x.parse() {
Ok(x) => Some(x),
Err(_) => {
debug_assert!(false, "failed to convert to subtag: {x}");
None
}
})
}
/// Returns the internal separator byte used for auxiliary keys in data locales. /// /// This is, according to BCP-47, an ASCII hyphen. #[inline] pub(crate) constfn separator() -> char { '-'
}
}
#[cfg(feature = "experimental")] impl From<Subtag> for AuxiliaryKeys { fn from(subtag: Subtag) -> Self { #[allow(clippy::expect_used)] // subtags definitely fit within auxiliary keys Self {
value: AuxiliaryKeysInner::Stack(
TinyAsciiStr::from_bytes(subtag.as_str().as_bytes())
.expect("Subtags are capped to 8 elements, AuxiliaryKeys supports up to 23"),
),
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.