Archived
190 lines
5.3 KiB
Rust
190 lines
5.3 KiB
Rust
/// 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);
|
||
}
|
||
}
|