Archived
feat: port el-ui vessels — rename crates→vessels, add El source + manifests
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user