use icu_locale_core::Locale;
use icu_plurals::{PluralCategory as IcuCategory, PluralOperands, PluralRuleType, PluralRules};
use crate::CollateError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum PluralCategory {
Zero,
One,
Two,
Few,
Many,
Other,
}
impl PluralCategory {
fn from_icu(cat: IcuCategory) -> Self {
match cat {
IcuCategory::Zero => PluralCategory::Zero,
IcuCategory::One => PluralCategory::One,
IcuCategory::Two => PluralCategory::Two,
IcuCategory::Few => PluralCategory::Few,
IcuCategory::Many => PluralCategory::Many,
IcuCategory::Other => PluralCategory::Other,
}
}
}
pub struct IcuPluralRules {
cardinal: PluralRules,
ordinal: PluralRules,
}
impl IcuPluralRules {
pub fn new(locale: &str) -> Result<Self, CollateError> {
let loc: Locale = locale
.parse()
.map_err(|e| CollateError::InvalidLocale(format!("{e}")))?;
let prefs = icu_plurals::PluralRulesPreferences::from(loc);
let cardinal_opts: icu_plurals::PluralRulesOptions = PluralRuleType::Cardinal.into();
let ordinal_opts: icu_plurals::PluralRulesOptions = PluralRuleType::Ordinal.into();
let cardinal = PluralRules::try_new(prefs, cardinal_opts)
.map_err(|e| CollateError::Icu(format!("{e}")))?;
let ordinal = PluralRules::try_new(prefs, ordinal_opts)
.map_err(|e| CollateError::Icu(format!("{e}")))?;
Ok(Self { cardinal, ordinal })
}
pub fn category_for(&self, count: u64) -> PluralCategory {
let operands: PluralOperands = count.into();
PluralCategory::from_icu(self.cardinal.category_for(operands))
}
pub fn ordinal_category_for(&self, count: u64) -> PluralCategory {
let operands: PluralOperands = count.into();
PluralCategory::from_icu(self.ordinal.category_for(operands))
}
pub fn select<'a>(&self, count: u64, one: &'a str, other: &'a str) -> &'a str {
match self.category_for(count) {
PluralCategory::One => one,
_ => other,
}
}
pub fn categories(&self) -> Vec<IcuCategory> {
self.cardinal.categories().collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plural_english_cardinal() {
let rules = IcuPluralRules::new("en").expect("English plural rules");
assert_eq!(rules.category_for(0), PluralCategory::Other);
assert_eq!(rules.category_for(1), PluralCategory::One);
assert_eq!(rules.category_for(2), PluralCategory::Other);
assert_eq!(rules.category_for(5), PluralCategory::Other);
assert_eq!(rules.category_for(100), PluralCategory::Other);
}
#[test]
fn plural_english_select() {
let rules = IcuPluralRules::new("en").expect("rules");
assert_eq!(rules.select(1, "item", "items"), "item");
assert_eq!(rules.select(2, "item", "items"), "items");
assert_eq!(rules.select(0, "item", "items"), "items");
}
#[test]
fn plural_russian_has_few_category() {
let rules = IcuPluralRules::new("ru").expect("Russian plural rules");
assert_eq!(rules.category_for(1), PluralCategory::One);
assert_eq!(rules.category_for(2), PluralCategory::Few);
assert_eq!(rules.category_for(5), PluralCategory::Many);
assert_eq!(rules.category_for(11), PluralCategory::Many);
assert_eq!(rules.category_for(21), PluralCategory::One);
}
#[test]
fn plural_arabic_zero_category() {
let rules = IcuPluralRules::new("ar").expect("Arabic plural rules");
assert_eq!(rules.category_for(0), PluralCategory::Zero);
assert_eq!(rules.category_for(1), PluralCategory::One);
assert_eq!(rules.category_for(2), PluralCategory::Two);
}
#[test]
fn plural_english_ordinal() {
let rules = IcuPluralRules::new("en").expect("rules");
assert_eq!(rules.ordinal_category_for(1), PluralCategory::One);
assert_eq!(rules.ordinal_category_for(2), PluralCategory::Two);
assert_eq!(rules.ordinal_category_for(3), PluralCategory::Few);
assert_eq!(rules.ordinal_category_for(4), PluralCategory::Other);
}
#[test]
fn categories_returns_vec() {
let rules = IcuPluralRules::new("en").expect("rules");
let cats = rules.categories();
assert!(!cats.is_empty(), "categories should not be empty");
}
#[test]
fn invalid_locale_returns_error() {
let result = IcuPluralRules::new("not-a-locale!!!");
assert!(result.is_err(), "invalid locale should produce error");
}
}