use icu_locid::LanguageIdentifier;
#[cfg(not(feature = "cldr"))]
mod likely_subtags;
#[cfg(feature = "cldr")]
use icu_locid_transform::{LocaleExpander, TransformResult};
#[cfg(not(feature = "cldr"))]
use likely_subtags::{LocaleExpander, TransformResult};
#[derive(PartialEq, Debug, Clone, Copy)]
pub enum NegotiationStrategy {
Filtering,
Matching,
Lookup,
}
fn subtag_matches<P: PartialEq>(
subtag1: &Option<P>,
subtag2: &Option<P>,
as_range1: bool,
as_range2: bool,
) -> bool {
(as_range1 && subtag1.is_none()) || (as_range2 && subtag2.is_none()) || subtag1 == subtag2
}
#[inline(always)]
fn matches(
lid1: &LanguageIdentifier,
lid2: &LanguageIdentifier,
range1: bool,
range2: bool,
) -> bool {
((range1 && lid1.language.is_empty())
|| (range2 && lid2.language.is_empty())
|| lid1.language == lid2.language)
&& subtag_matches(&lid1.script, &lid2.script, range1, range2)
&& subtag_matches(&lid1.region, &lid2.region, range1, range2)
&& ((range1 && lid1.variants.is_empty())
|| (range2 && lid2.variants.is_empty())
|| lid1.variants == lid2.variants)
}
pub fn filter_matches<'a, R: 'a + AsRef<LanguageIdentifier>, A: 'a + AsRef<LanguageIdentifier>>(
requested: &[R],
available: &'a [A],
strategy: NegotiationStrategy,
) -> Vec<&'a A> {
let mut lc: Option<LocaleExpander> = None;
let mut supported_locales = vec![];
let mut available_locales: Vec<&A> = available.iter().collect();
macro_rules! test_strategy {
($req:ident, $self_as_range:expr, $other_as_range:expr) => {{
let mut match_found = false;
available_locales.retain(|locale| {
if strategy != NegotiationStrategy::Filtering && match_found {
return true;
}
if matches(locale.as_ref(), &$req, $self_as_range, $other_as_range) {
match_found = true;
supported_locales.push(*locale);
return false;
}
true
});
if match_found {
match strategy {
NegotiationStrategy::Filtering => {}
NegotiationStrategy::Matching => continue,
NegotiationStrategy::Lookup => break,
}
}
}};
}
for req in requested {
let req = req.as_ref();
test_strategy!(req, false, false);
test_strategy!(req, true, false);
if req.language.is_empty() {
continue;
}
let mut req = req.to_owned();
let lc = lc.get_or_insert_with(LocaleExpander::new);
if lc.maximize(&mut req) == TransformResult::Modified {
test_strategy!(req, true, false);
}
req.variants.clear();
test_strategy!(req, true, true);
req.region = None;
if lc.maximize(&mut req) == TransformResult::Modified {
test_strategy!(req, true, false);
}
req.region = None;
test_strategy!(req, true, true);
}
supported_locales
}
pub fn negotiate_languages<
'a,
R: 'a + AsRef<LanguageIdentifier>,
A: 'a + AsRef<LanguageIdentifier> + PartialEq,
>(
requested: &[R],
available: &'a [A],
default: Option<&'a A>,
strategy: NegotiationStrategy,
) -> Vec<&'a A> {
let mut supported = filter_matches(requested, available, strategy);
if let Some(default) = default {
if strategy == NegotiationStrategy::Lookup {
if supported.is_empty() {
supported.push(default);
}
} else if !supported.contains(&default) {
supported.push(default);
}
}
supported
}