use std::cell::RefCell;
use std::cmp::Ordering;
use std::collections::HashMap;
use std::rc::Rc;
use colligo::{CaseMode, Collator};
pub const CLDR_VERSION: &str = "48";
thread_local! {
static COLLATORS: RefCell<HashMap<String, Option<Rc<Collator>>>> =
RefCell::new(HashMap::new());
}
fn cached(tag: &str) -> Option<Rc<Collator>> {
COLLATORS.with(|cache| {
cache
.borrow_mut()
.entry(tag.to_string())
.or_insert_with(|| build(tag).map(Rc::new))
.clone()
})
}
fn build(tag: &str) -> Option<Collator> {
let mut parts = tag.split('-').skip(1);
if parts.any(|p| p.len() == 1 && !p.eq_ignore_ascii_case("x")) {
return None;
}
let collator = Collator::builder(tag)
.case_mode(CaseMode::LowerFirst) .build()
.ok()?;
Some(collator)
}
pub fn resolves(tag: &str) -> bool {
cached(tag).is_some()
}
pub fn compare(tag: &str, a: &str, b: &str) -> Option<Ordering> {
Some(cached(tag)?.compare(a, b))
}
pub(crate) fn range_ok(tag: &str, value: &str, lo: Option<&str>, hi: Option<&str>) -> Option<bool> {
let coll = cached(tag)?;
let above = lo.is_none_or(|l| coll.compare(l, value) != Ordering::Greater);
let below = hi.is_none_or(|h| coll.compare(value, h) != Ordering::Greater);
Some(above && below)
}
pub(crate) fn enum_has(tag: &str, value: &str, vs: &[String]) -> Option<bool> {
let coll = cached(tag)?;
Some(vs.iter().any(|v| coll.compare(v, value) == Ordering::Equal))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tertiary_default_orders_accents_with_base() {
assert_eq!(compare("fr", "étude", "zebra"), Some(Ordering::Less));
assert_eq!(compare("fr", "café", "cafe"), Some(Ordering::Greater));
assert_ne!(compare("fr", "Cafe", "cafe"), Some(Ordering::Equal));
}
#[test]
fn equality_crosses_normalization_forms() {
assert_eq!(
compare("fr", "caf\u{e9}", "cafe\u{301}"),
Some(Ordering::Equal)
);
}
#[test]
fn non_exact_tiers_and_extensions_reject() {
assert!(!resolves("fr-CA"));
assert!(!resolves("ja"));
assert!(!resolves("de-u-ks-level1"));
assert!(!resolves("en-u-kc-true"));
assert!(resolves("ru-x-icu"));
assert!(resolves("de"));
}
}