blob: a1029542f9c2ba379b8c09676976cac707acd8ab [file] [log] [blame]
// 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.
use crate::provider::*;
use icu_locale_core::subtags::*;
use icu_provider::prelude::*;
#[doc(inline)]
pub use icu_provider::fallback::{LocaleFallbackConfig, LocaleFallbackPriority};
mod algorithms;
/// Implements the algorithm defined in *[UTS #35: Locale Inheritance and Matching]*.
///
/// Note that this implementation performs some additional steps compared to the *UTS #35*
/// algorithm. See *[the design doc]* for a detailed description and [#2243](
/// https://github.com/unicode-org/icu4x/issues/2243) to track alignment with *UTS #35*.
///
/// If running fallback in a loop, use [`DataLocale::is_default()`] to break from the loop.
///
/// # Examples
///
/// ```
/// use icu::locale::fallback::LocaleFallbacker;
/// use icu::locale::locale;
///
/// // Set up a LocaleFallbacker with data.
/// let fallbacker = LocaleFallbacker::new();
///
/// // Create a LocaleFallbackerIterator with a default configuration.
/// // By default, uses language priority.
/// let mut fallback_iterator = fallbacker
/// .for_config(Default::default())
/// .fallback_for(locale!("hi-Latn-IN").into());
///
/// // Run the algorithm and check the results.
/// assert_eq!(fallback_iterator.get(), &locale!("hi-Latn-IN").into());
/// fallback_iterator.step();
/// assert_eq!(fallback_iterator.get(), &locale!("hi-Latn").into());
/// fallback_iterator.step();
/// assert_eq!(fallback_iterator.get(), &locale!("en-IN").into());
/// fallback_iterator.step();
/// assert_eq!(fallback_iterator.get(), &locale!("en-001").into());
/// fallback_iterator.step();
/// assert_eq!(fallback_iterator.get(), &locale!("en").into());
/// fallback_iterator.step();
/// assert_eq!(fallback_iterator.get(), &locale!("und").into());
/// ```
///
/// [UTS #35: Locale Inheritance and Matching]: https://www.unicode.org/reports/tr35/#Locale_Inheritance
/// [the design doc]: https://docs.google.com/document/d/1Mp7EUyl-sFh_HZYgyeVwj88vJGpCBIWxzlCwGgLCDwM/edit
#[doc(hidden)] // canonical location in super
#[derive(Debug, Clone, PartialEq)]
pub struct LocaleFallbacker {
likely_subtags: DataPayload<LocaleLikelySubtagsLanguageV1>,
parents: DataPayload<LocaleParentsV1>,
}
/// Borrowed version of [`LocaleFallbacker`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LocaleFallbackerBorrowed<'a> {
likely_subtags: &'a LikelySubtagsForLanguage<'a>,
parents: &'a Parents<'a>,
}
/// A [`LocaleFallbackerBorrowed`] with an associated [`LocaleFallbackConfig`].
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LocaleFallbackerWithConfig<'a> {
likely_subtags: &'a LikelySubtagsForLanguage<'a>,
parents: &'a Parents<'a>,
config: LocaleFallbackConfig,
}
/// Inner iteration type. Does not own the item under fallback.
#[derive(Debug)]
struct LocaleFallbackIteratorInner<'a> {
likely_subtags: &'a LikelySubtagsForLanguage<'a>,
parents: &'a Parents<'a>,
config: LocaleFallbackConfig,
backup_subdivision: Option<Subtag>,
backup_variant: Option<Variant>,
backup_region: Option<Region>,
max_script: Option<Script>,
}
/// 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()`.
#[derive(Debug)]
pub struct LocaleFallbackIterator<'a> {
current: DataLocale,
inner: LocaleFallbackIteratorInner<'a>,
}
impl LocaleFallbacker {
/// Creates a [`LocaleFallbacker`] with compiled fallback data (likely subtags and parent locales).
///
/// ✨ *Enabled with the `compiled_data` Cargo feature.*
///
/// [📚 Help choosing a constructor](icu_provider::constructors)
#[cfg(feature = "compiled_data")]
#[allow(clippy::new_ret_no_self)] // keeping constructors together
#[allow(clippy::new_without_default)] // Deliberate choice, see #5554
pub const fn new<'a>() -> LocaleFallbackerBorrowed<'a> {
// Safety: we're transmuting down from LocaleFallbackerBorrowed<'static> to LocaleFallbackerBorrowed<'a>
// ZeroMaps use associated types in a way that confuse the compiler which gives up and marks them
// as invariant. However, they are covariant, and in non-const code this covariance can be safely triggered
// using Yokeable::transform. In const code we must transmute. In the long run we should
// be able to `transform()` in const code, and also we will have hopefully improved map polymorphism (#3128)
unsafe { core::mem::transmute(LocaleFallbackerBorrowed::<'static>::new()) }
}
icu_provider::gen_buffer_data_constructors!(() -> error: DataError,
functions: [
new: skip,
try_new_with_buffer_provider,
try_new_unstable,
Self
]);
#[doc = icu_provider::gen_buffer_unstable_docs!(UNSTABLE, Self::new)]
pub fn try_new_unstable<P>(provider: &P) -> Result<Self, DataError>
where
P: DataProvider<LocaleLikelySubtagsLanguageV1> + DataProvider<LocaleParentsV1> + ?Sized,
{
let likely_subtags = provider.load(Default::default())?.payload;
let parents = provider.load(Default::default())?.payload;
Ok(LocaleFallbacker {
likely_subtags,
parents,
})
}
/// 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(LikelySubtagsForLanguage {
language: Default::default(),
language_region: Default::default(),
language_script: Default::default(),
// Unused
und: (
Default::default(),
crate::subtags::script!("Zzzz"),
crate::subtags::region!("ZZ"),
),
}),
parents: DataPayload::from_owned(Default::default()),
}
}
/// Associates a configuration with this fallbacker.
#[inline]
pub fn for_config(&self, config: LocaleFallbackConfig) -> LocaleFallbackerWithConfig {
self.as_borrowed().for_config(config)
}
/// Creates a borrowed version of this fallbacker for performance.
pub fn as_borrowed(&self) -> LocaleFallbackerBorrowed {
LocaleFallbackerBorrowed {
likely_subtags: self.likely_subtags.get(),
parents: self.parents.get(),
}
}
}
impl<'a> LocaleFallbackerBorrowed<'a> {
/// Associates a configuration with this fallbacker.
#[inline]
pub const fn for_config(self, config: LocaleFallbackConfig) -> LocaleFallbackerWithConfig<'a> {
LocaleFallbackerWithConfig {
likely_subtags: self.likely_subtags,
parents: self.parents,
config,
}
}
}
impl LocaleFallbackerBorrowed<'static> {
/// Creates a [`LocaleFallbackerBorrowed`] with compiled fallback data (likely subtags and parent locales).
///
/// ✨ *Enabled with the `compiled_data` Cargo feature.*
///
/// [📚 Help choosing a constructor](icu_provider::constructors)
#[cfg(feature = "compiled_data")]
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
Self {
likely_subtags: crate::provider::Baked::SINGLETON_LOCALE_LIKELY_SUBTAGS_LANGUAGE_V1,
parents: crate::provider::Baked::SINGLETON_LOCALE_PARENTS_V1,
}
}
/// Cheaply converts a [`LocaleFallbackerBorrowed<'static>`] into a [`LocaleFallbacker`].
///
/// Note: Due to branching and indirection, using [`LocaleFallbacker`] might inhibit some
/// compile-time optimizations that are possible with [`LocaleFallbackerBorrowed`].
pub const fn static_to_owned(self) -> LocaleFallbacker {
LocaleFallbacker {
likely_subtags: DataPayload::from_static_ref(self.likely_subtags),
parents: DataPayload::from_static_ref(self.parents),
}
}
}
impl<'a> LocaleFallbackerWithConfig<'a> {
/// Creates an iterator based on a [`DataLocale`].
///
/// If you have a [`Locale`](icu_locale_core::Locale), call `.into()` to get a [`DataLocale`].
///
/// When first initialized, the locale is normalized according to the fallback algorithm.
pub fn fallback_for(&self, mut locale: DataLocale) -> LocaleFallbackIterator<'a> {
let mut default_script = None;
self.normalize(&mut locale, &mut default_script);
let max_script = locale.script.or(default_script);
LocaleFallbackIterator {
current: locale,
inner: LocaleFallbackIteratorInner {
likely_subtags: self.likely_subtags,
parents: self.parents,
config: self.config,
backup_subdivision: None,
backup_variant: None,
backup_region: None,
max_script,
},
}
}
}
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 [`DataLocale::default()`].
pub fn step(&mut self) -> &mut Self {
self.inner.step(&mut self.current);
self
}
}