use super::Locale;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum PluralCategory {
Zero,
One,
Two,
Few,
Many,
Other,
}
impl PluralCategory {
pub fn as_str(self) -> &'static str {
match self {
Self::Zero => "zero",
Self::One => "one",
Self::Two => "two",
Self::Few => "few",
Self::Many => "many",
Self::Other => "other",
}
}
}
impl std::fmt::Display for PluralCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl Locale {
pub fn plural_categories(self) -> &'static [PluralCategory] {
use PluralCategory as P;
match self {
Locale::En | Locale::De => &[P::One, P::Other],
Locale::Vi => &[P::Other],
Locale::Fr => &[P::One, P::Many, P::Other],
Locale::Ru => &[P::Few, P::Many, P::One, P::Other],
}
}
pub fn select_plural(self, n: f64) -> PluralCategory {
use PluralCategory as P;
if !n.is_finite() {
return P::Other;
}
if n.fract() != 0.0 {
return P::Other;
}
let i = n.abs() as u64;
match self {
Locale::En | Locale::De =>
if i == 1 {
P::One
} else {
P::Other
},
Locale::Vi => P::Other,
Locale::Fr => {
if i == 0 || i == 1 {
P::One
} else if i != 0 && i.is_multiple_of(1_000_000) {
P::Many
} else {
P::Other
}
}
Locale::Ru => {
let m10 = i % 10;
let m100 = i % 100;
if m10 == 1 && m100 != 11 {
P::One
} else if (2..=4).contains(&m10) && !(12..=14).contains(&m100) {
P::Few
} else {
P::Many
}
}
}
}
fn separators(self) -> (&'static str, &'static str) {
match self {
Locale::En => (",", "."),
Locale::De | Locale::Vi => (".", ","),
Locale::Fr => ("\u{202f}", ","),
Locale::Ru => ("\u{a0}", ","),
}
}
pub fn format_number(self, n: f64) -> String {
if !n.is_finite() {
return n.to_string();
}
let (group, decimal) = self.separators();
let negative = n.is_sign_negative() && n != 0.0;
let plain = format!("{}", n.abs());
let (int_part, frac_part) = match plain.split_once('.') {
Some((i, f)) => (i, Some(f)),
None => (plain.as_str(), None),
};
let digits: Vec<char> = int_part.chars().collect();
let mut grouped = String::new();
for (idx, ch) in digits.iter().enumerate() {
if idx > 0 && (digits.len() - idx).is_multiple_of(3) {
grouped.push_str(group);
}
grouped.push(*ch);
}
let mut out = String::new();
if negative {
out.push('-');
}
out.push_str(&grouped);
if let Some(frac) = frac_part {
out.push_str(decimal);
out.push_str(frac);
}
out
}
}