///|
/// CLDR-compatible plural categories used by message selection.
pub(all) enum PluralCategory {
Zero
One
Two
Few
Many
Other
} derive(Eq, Debug)
///|
pub fn PluralCategory::name(self : PluralCategory) -> String {
match self {
Zero => "zero"
One => "one"
Two => "two"
Few => "few"
Many => "many"
Other => "other"
}
}
///|
/// The kind of plural rule requested by an application.
pub(all) enum PluralKind {
Cardinal
Ordinal
} derive(Eq, Debug)
///|
/// Reusable plural selector bound to a locale.
pub struct PluralRules {
locale : Locale
kind : PluralKind
} derive(Eq, Debug)
///|
pub fn PluralRules::new(
locale : Locale,
kind? : PluralKind = Cardinal,
) -> PluralRules {
{ locale, kind }
}
///|
/// Creates rules for ordinal values such as `1st` and `23rd`.
pub fn PluralRules::ordinal(locale : Locale) -> PluralRules {
{ locale, kind: Ordinal }
}
///|
pub fn PluralRules::locale(self : PluralRules) -> Locale {
self.locale
}
///|
pub fn PluralRules::kind(self : PluralRules) -> PluralKind {
self.kind
}
///|
pub fn PluralRules::select(self : PluralRules, value : Int) -> PluralCategory {
match self.kind {
Cardinal => cardinal_plural(self.locale, value)
Ordinal => ordinal_plural(self.locale, value)
}
}
///|
fn absolute(value : Int) -> Int {
if value < 0 {
-value
} else {
value
}
}
///|
fn between(value : Int, lower : Int, upper : Int) -> Bool {
value >= lower && value <= upper
}
///|
fn is_language_in(language : String, languages : Array[String]) -> Bool {
languages.any(fn(candidate) { candidate == language })
}
///|
fn cardinal_arabic(value : Int) -> PluralCategory {
let n = absolute(value)
let mod100 = n % 100
if n == 0 {
Zero
} else if n == 1 {
One
} else if n == 2 {
Two
} else if between(mod100, 3, 10) {
Few
} else if between(mod100, 11, 99) {
Many
} else {
Other
}
}
///|
fn cardinal_east_slavic(value : Int) -> PluralCategory {
let n = absolute(value)
let mod10 = n % 10
let mod100 = n % 100
if mod10 == 1 && mod100 != 11 {
One
} else if between(mod10, 2, 4) && !between(mod100, 12, 14) {
Few
} else if mod10 == 0 || between(mod10, 5, 9) || between(mod100, 11, 14) {
Many
} else {
Other
}
}
///|
fn cardinal_polish(value : Int) -> PluralCategory {
let n = absolute(value)
let mod10 = n % 10
let mod100 = n % 100
if n == 1 {
One
} else if between(mod10, 2, 4) && !between(mod100, 12, 14) {
Few
} else if n != 1 &&
(between(mod10, 0, 1) || between(mod10, 5, 9) || between(mod100, 12, 14)) {
Many
} else {
Other
}
}
///|
fn cardinal_lithuanian(value : Int) -> PluralCategory {
let n = absolute(value)
let mod10 = n % 10
let mod100 = n % 100
if mod10 == 1 && !between(mod100, 11, 19) {
One
} else if between(mod10, 2, 9) && !between(mod100, 11, 19) {
Few
} else if mod10 == 0 || between(mod100, 11, 19) {
Many
} else {
Other
}
}
///|
fn cardinal_latvian(value : Int) -> PluralCategory {
let n = absolute(value)
let mod10 = n % 10
let mod100 = n % 100
if mod10 == 0 || between(mod100, 11, 19) {
Zero
} else if mod10 == 1 && mod100 != 11 {
One
} else {
Other
}
}
///|
fn cardinal_slovenian(value : Int) -> PluralCategory {
match absolute(value) % 100 {
1 => One
2 => Two
3 | 4 => Few
_ => Other
}
}
///|
fn cardinal_romanian(value : Int) -> PluralCategory {
let n = absolute(value)
let mod100 = n % 100
if n == 1 {
One
} else if n == 0 || between(mod100, 1, 19) {
Few
} else {
Other
}
}
///|
fn cardinal_hebrew(value : Int) -> PluralCategory {
let n = absolute(value)
if n == 1 {
One
} else if n == 2 {
Two
} else if n != 0 && n % 10 == 0 {
Many
} else {
Other
}
}
///|
/// Selects a cardinal category for an integer.
///
/// The implementation covers the major CLDR integer rule families. Languages
/// without grammatical integer plurals safely return `other`.
pub fn cardinal_plural(locale : Locale, value : Int) -> PluralCategory {
let language = locale.language()
if language == "ar" {
cardinal_arabic(value)
} else if is_language_in(language, ["ru", "uk", "be", "sr", "hr"]) {
cardinal_east_slavic(value)
} else if language == "pl" {
cardinal_polish(value)
} else if language == "lt" {
cardinal_lithuanian(value)
} else if language == "lv" {
cardinal_latvian(value)
} else if language == "sl" {
cardinal_slovenian(value)
} else if language == "ro" {
cardinal_romanian(value)
} else if language == "he" {
cardinal_hebrew(value)
} else if is_language_in(language, ["cs", "sk"]) {
if value == 1 {
One
} else if between(absolute(value), 2, 4) {
Few
} else {
Other
}
} else if is_language_in(language, ["fr", "pt"]) {
if value == 0 || value == 1 {
One
} else {
Other
}
} else if is_language_in(language, [
"en", "de", "es", "it", "nl", "sv", "no", "da", "fi", "et", "el",
]) {
if value == 1 {
One
} else {
Other
}
} else {
Other
}
}
///|
fn ordinal_english(value : Int) -> PluralCategory {
let n = absolute(value)
let mod10 = n % 10
let mod100 = n % 100
if mod10 == 1 && mod100 != 11 {
One
} else if mod10 == 2 && mod100 != 12 {
Two
} else if mod10 == 3 && mod100 != 13 {
Few
} else {
Other
}
}
///|
fn ordinal_welsh(value : Int) -> PluralCategory {
match absolute(value) {
0 | 7 | 8 | 9 => Zero
1 => One
2 => Two
3 | 4 => Few
5 | 6 => Many
_ => Other
}
}
///|
/// Selects an ordinal category for an integer.
pub fn ordinal_plural(locale : Locale, value : Int) -> PluralCategory {
match locale.language() {
"en" => ordinal_english(value)
"cy" => ordinal_welsh(value)
"fr" => if value == 1 { One } else { Other }
"it" =>
if value == 8 || value == 11 || value == 80 || value == 800 {
Many
} else {
Other
}
_ => Other
}
}