/// Plural forms — handles language-specific plurality rules. /// /// Different languages have very different plural forms. English has two /// (one / other). Russian has four. Arabic has six. This module maps counts /// to the correct form for a given locale. /// Named plural categories per Unicode CLDR. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum PluralForm { /// Exactly zero (some languages have a special zero form). Zero, /// Exactly one. One, /// Small numbers (e.g. 2–4 in Slavic languages). Two, /// Few (language-specific). Few, /// Many (language-specific). Many, /// The catch-all / default. Other, } /// Determine the plural form for a count in a given language. /// /// Rules are a simplified implementation of CLDR plural rules for the most /// common languages. The full CLDR spec covers hundreds of languages; these /// cover the major cases. pub fn plural_form(language: &str, count: i64) -> PluralForm { let n = count.unsigned_abs(); // absolute value for matching match language { // English and most Western European languages: one / other "en" | "de" | "nl" | "sv" | "da" | "no" | "nb" | "nn" | "fi" | "et" | "hu" | "tr" | "pt" | "it" | "es" | "ca" | "el" | "id" | "ms" | "th" | "zh" | "ja" | "ko" | "vi" | "ur" => { if n == 1 { PluralForm::One } else { PluralForm::Other } } // French: one for 0 and 1, other for rest "fr" => { if n <= 1 { PluralForm::One } else { PluralForm::Other } } // Russian, Ukrainian, Belarusian: complex Slavic rules "ru" | "uk" | "be" => { let n10 = n % 10; let n100 = n % 100; if n10 == 1 && n100 != 11 { PluralForm::One } else if (2..=4).contains(&n10) && !(12..=14).contains(&n100) { PluralForm::Few } else { PluralForm::Many } } // Polish: similar Slavic rules "pl" => { let n10 = n % 10; let n100 = n % 100; if n == 1 { PluralForm::One } else if (2..=4).contains(&n10) && !(12..=14).contains(&n100) { PluralForm::Few } else { PluralForm::Many } } // Czech, Slovak "cs" | "sk" => { if n == 1 { PluralForm::One } else if (2..=4).contains(&n) { PluralForm::Few } else { PluralForm::Other } } // Arabic: 6 forms "ar" => { let n100 = n % 100; if n == 0 { PluralForm::Zero } else if n == 1 { PluralForm::One } else if n == 2 { PluralForm::Two } else if (3..=10).contains(&n100) { PluralForm::Few } else if (11..=99).contains(&n100) { PluralForm::Many } else { PluralForm::Other } } // Hebrew "he" | "iw" => { if n == 1 { PluralForm::One } else if n == 2 { PluralForm::Two } else if n >= 11 && n % 10 == 0 { PluralForm::Many } else { PluralForm::Other } } // Default: one / other _ => { if n == 1 { PluralForm::One } else { PluralForm::Other } } } } #[cfg(test)] mod tests { use super::*; #[test] fn english_one() { assert_eq!(plural_form("en", 1), PluralForm::One); } #[test] fn english_other() { assert_eq!(plural_form("en", 0), PluralForm::Other); assert_eq!(plural_form("en", 2), PluralForm::Other); assert_eq!(plural_form("en", 100), PluralForm::Other); } #[test] fn french_zero_is_one() { assert_eq!(plural_form("fr", 0), PluralForm::One); assert_eq!(plural_form("fr", 1), PluralForm::One); assert_eq!(plural_form("fr", 2), PluralForm::Other); } #[test] fn russian_one() { assert_eq!(plural_form("ru", 1), PluralForm::One); assert_eq!(plural_form("ru", 21), PluralForm::One); assert_eq!(plural_form("ru", 101), PluralForm::One); } #[test] fn russian_few() { assert_eq!(plural_form("ru", 2), PluralForm::Few); assert_eq!(plural_form("ru", 3), PluralForm::Few); assert_eq!(plural_form("ru", 22), PluralForm::Few); } #[test] fn russian_many() { assert_eq!(plural_form("ru", 5), PluralForm::Many); assert_eq!(plural_form("ru", 11), PluralForm::Many); assert_eq!(plural_form("ru", 20), PluralForm::Many); } #[test] fn arabic_zero() { assert_eq!(plural_form("ar", 0), PluralForm::Zero); } #[test] fn arabic_two() { assert_eq!(plural_form("ar", 2), PluralForm::Two); } #[test] fn arabic_few() { assert_eq!(plural_form("ar", 5), PluralForm::Few); assert_eq!(plural_form("ar", 10), PluralForm::Few); } #[test] fn arabic_many() { assert_eq!(plural_form("ar", 15), PluralForm::Many); } #[test] fn hebrew_two() { assert_eq!(plural_form("he", 2), PluralForm::Two); } }