This repository has been archived on 2026-08-20. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
el-retired/ui/vessels/el-i18n/src/plural.rs
T

190 lines
5.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// 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. 24 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);
}
}