// lang_ru.mbt — Russian number spelling (cardinal, year).
//
// Russian quirks handled here:
// * every scale word has three plural forms selected by the group value:
// 1 -> "тысяча" / "миллион"
// 2..4 -> "тысячи" / "миллиона" (but not 11..14)
// 0, 5+ -> "тысяч" / "миллионов"
// so 1000 -> "одна тысяча", 2000 -> "две тысячи", 5000 -> "пять тысяч";
// * "тысяча" is feminine, so the preceding number uses "одна" / "две"
// instead of "один" / "два"; scale words from million up are masculine;
// * groups are separated by a single space and no commas are written.
// Scale words for a group value of 1. Index = group power.
///|
let ru_scale_one : Array[String] = [
"", "тысяча", "миллион", "миллиард", "триллион", "квадриллион",
"квинтиллион", "секстиллион", "септиллион", "октиллион",
"нониллион", "дециллион",
]
// Scale words for a group value of 2..4 (but not 11..14).
///|
let ru_scale_few : Array[String] = [
"", "тысячи", "миллиона", "миллиарда", "триллиона",
"квадриллиона", "квинтиллиона", "секстиллиона",
"септиллиона", "октиллиона", "нониллиона", "дециллиона",
]
// Scale words for every other group value (0, 5..20, 25..30, ...).
///|
let ru_scale_many : Array[String] = [
"", "тысяч", "миллионов", "миллиардов", "триллионов",
"квадриллионов", "квинтиллионов", "секстиллионов",
"септиллионов", "октиллионов", "нониллионов",
"дециллионов",
]
// Hundreds: 1 -> "сто", 2 -> "двести", ... 9 -> "девятьсот".
///|
let ru_hundreds : Array[String] = [
"", "сто", "двести", "триста", "четыреста", "пятьсот",
"шестьсот", "семьсот", "восемьсот", "девятьсот",
]
///|
pub fn lang_ru() -> Lang {
{
variant: Ru,
zero: "ноль",
ones: [
"ноль", "один", "два", "три", "четыре", "пять", "шесть",
"семь", "восемь", "девять",
],
teens: [
"десять", "одиннадцать", "двенадцать", "тринадцать",
"четырнадцать", "пятнадцать", "шестнадцать",
"семнадцать", "восемнадцать", "девятнадцать",
],
tens: [
"", "десять", "двадцать", "тридцать", "сорок", "пятьдесят",
"шестьдесят", "семьдесят", "восемьдесят", "девяносто",
],
hundred: "сто",
thousands: ru_scale_many,
negative: "минус",
decimal: "запятая",
comma_sep: false,
}
}
///|
/// The unit word, honouring grammatical gender: "один"/"одна",
/// "два"/"две"; 3..9 are gender-neutral.
fn ru_unit(l : Lang, u : Int, feminine : Bool) -> String {
if feminine {
if u == 1 {
return "одна"
}
if u == 2 {
return "две"
}
}
l.ones[u]
}
///|
fn Lang::below100_ru(self : Lang, r : Int, feminine : Bool) -> String {
if r < 10 {
return ru_unit(self, r, feminine)
}
if r < 20 {
return self.teens[r - 10]
}
let d = r / 10
let u = r % 10
if u == 0 {
return self.tens[d]
}
self.tens[d] + " " + ru_unit(self, u, feminine)
}
///|
fn Lang::triple_ru(self : Lang, t : Int, feminine : Bool) -> String {
if t == 0 {
return ""
}
let h = t / 100
let r = t % 100
if h == 0 {
return self.below100_ru(r, feminine)
}
if r == 0 {
return ru_hundreds[h]
}
ru_hundreds[h] + " " + self.below100_ru(r, feminine)
}
///|
/// Pick the plural form of a scale word: 1 = one, 2 = few (2..4),
/// 3 = many (everything else, including 11..14).
fn ru_form(n : Int) -> Int {
let m100 = n % 100
if m100 >= 11 && m100 <= 14 {
return 3
}
let m10 = n % 10
if m10 == 1 {
return 1
}
if m10 >= 2 && m10 <= 4 {
return 2
}
3
}
///|
fn ru_scale(power : Int, form : Int) -> String {
if form == 1 {
ru_scale_one[power]
} else if form == 2 {
ru_scale_few[power]
} else {
ru_scale_many[power]
}
}
///|
/// Cardinal with a gender override for every group's unit word. Currency mode
/// uses this when the currency noun is feminine (гривна, копейка): upstream
/// applies the feminine form to each group, so `1000000` becomes
/// "одна миллион" (grammatically odd, but that is what upstream emits).
fn Lang::cardinal_ru_gendered(
self : Lang,
n : BigInt,
feminine_units : Bool,
) -> String {
if n == BigInt::from_int(0) {
return self.zero
}
if !self.supports(n) {
return ""
}
let neg = n < BigInt::from_int(0)
let n = if neg { -n } else { n }
let groups = group3(n)
let parts : Array[String] = []
for i = 0; i < groups.length(); i = i + 1 {
let (triple, power) = groups[i]
let t = triple.to_int()
if t == 0 {
continue
}
// "тысяча" is feminine: 1 -> "одна тысяча", 2 -> "две тысячи".
let feminine = feminine_units || power == 1
let mut s = self.triple_ru(t, feminine)
if power > 0 {
s = s + " " + ru_scale(power, ru_form(t))
}
parts.push(s)
}
let joined = join_parts(parts, " ")
if neg {
self.negative + " " + joined
} else {
joined
}
}
///|
fn Lang::cardinal_ru(self : Lang, n : BigInt) -> String {
self.cardinal_ru_gendered(n, false)
}
///|
/// Russian years are read as a plain cardinal ("2024" ->
/// "две тысячи двадцать четыре"), so this delegates to `cardinal_ru`.
pub fn year_ru(n : BigInt) -> String {
lang_ru().cardinal_ru(n)
}