use crate::base::{KwVal, Kwargs, Lang, N2WError, Result};
use crate::currency::CurrencyForms;
use crate::floatpath::FloatValue;
use crate::strnum::{python_decimal_parse, ParsedNumber};
use bigdecimal::BigDecimal;
use num_bigint::BigInt;
use num_integer::Integer;
use num_traits::{Signed, Zero};
use std::collections::HashMap;
const ZERO: &str = "нуль";
const NEGWORD: &str = "мінус";
const POINTWORD: &str = "коска";
const ONES_F: [&str; 10] = [
"",
"адна",
"дзве",
"тры",
"чатыры",
"пяць",
"шэсць",
"сем",
"восем",
"дзевяць",
];
const ONES_M: [&str; 10] = [
"",
"адзін",
"два",
"тры",
"чатыры",
"пяць",
"шэсць",
"сем",
"восем",
"дзевяць",
];
const ONES_N: [&str; 10] = [
"",
"адно",
"два",
"тры",
"чатыры",
"пяць",
"шэсць",
"сем",
"восем",
"дзевяць",
];
const TENS: [&str; 10] = [
"дзесяць",
"адзінаццаць",
"дванаццаць",
"трынаццаць",
"чатырнаццаць",
"пятнаццаць",
"шаснаццаць",
"сямнаццаць",
"васямнаццаць",
"дзевятнаццаць",
];
const TWENTIES: [&str; 10] = [
"",
"",
"дваццаць",
"трыццаць",
"сорак",
"пяцьдзясят",
"шэсцьдзясят",
"семдзесят",
"восемдзесят",
"дзевяноста",
];
const TWENTIES_ORD: [(&str, &str); 8] = [
("дваццаць", "дваццаці"),
("трыццаць", "трыццаці"),
("сорак", "сарака"),
("пяцьдзясят", "пяцідзясяці"),
("шэсцьдзясят", "ш\u{0061}сцідзясяці"), ("семдзесят", "сямідзесяці"),
("восемдзесят", "васьмідзесяці"),
("дзевяноста", "дзевяноста"),
];
const HUNDREDS: [&str; 10] = [
"",
"сто",
"дзвесце",
"трыста",
"чатырыста",
"пяцьсот",
"шэсцьсот",
"семсот",
"восемсот",
"дзевяцьсот",
];
const THOUSANDS: [[&str; 3]; 11] = [
["", "", ""], ["тысяча", "тысячы", "тысяч"], ["мільён", "мільёны", "мільёнаў"], ["мільярд", "мільярды", "мільярдаў"], ["трыльён", "трыльёны", "трыльёнаў"], ["квадрыльён", "квадрыльёны", "квадрыльёнаў"], ["квінтыльён", "квінтыльёны", "квінтыльёнаў"], ["секстыльён", "секстыльёны", "секстыльёнаў"], ["сэптыльён", "сэптыльёны", "сэптыльёнаў"], ["актыльён", "актыльёны", "актыльёнаў"], ["нанільён", "нанільёны", "нанільёнаў"], ];
fn build_currency_forms() -> HashMap<&'static str, CurrencyForms> {
const KAPEIKA: [&str; 3] = ["капейка", "капейкі", "капеек"];
const CENT: [&str; 3] = ["цэнт", "цэнты", "цэнтаў"];
const TYIIN: [&str; 3] = ["тыйін", "тыйіны", "тыйінаў"];
let mut m: HashMap<&'static str, CurrencyForms> = HashMap::new();
m.insert(
"RUB",
CurrencyForms::new(
&["расійскі рубель", "расійскія рублі", "расійскіх рублёў"],
&KAPEIKA,
),
);
m.insert("EUR", CurrencyForms::new(&["еўра", "еўра", "еўра"], &CENT));
m.insert(
"USD",
CurrencyForms::new(&["долар", "долары", "долараў"], &CENT),
);
m.insert(
"UAH",
CurrencyForms::new(&["грыўна", "грыўны", "грыўнаў"], &KAPEIKA),
);
m.insert(
"KZT",
CurrencyForms::new(&["тэнге", "тэнге", "тэнге"], &TYIIN),
);
m.insert(
"BYN",
CurrencyForms::new(
&["беларускі рубель", "беларускія рублі", "беларускіх рублёў"],
&KAPEIKA,
),
);
m.insert("UZS", CurrencyForms::new(&["сум", "сумы", "сумаў"], &TYIIN));
m.insert(
"PLN",
CurrencyForms::new(&["злоты", "злотых", "злотых"], &["грош", "грошы", "грошаў"]),
);
m
}
fn plural_form_index(n: &BigInt) -> usize {
let m10 = n.mod_floor(&BigInt::from(10));
let m100 = n.mod_floor(&BigInt::from(100));
if m100 < BigInt::from(10) || m100 > BigInt::from(20) {
if m10 == BigInt::from(1) {
0
} else if m10 < BigInt::from(5) && m10 > BigInt::from(1) {
1
} else {
2
}
} else {
2
}
}
#[derive(Debug, Clone, PartialEq)]
enum Gender {
M,
F,
N,
Bad(String),
}
fn gender_from_kw(kw: &Kwargs, ordinal_bool_handshake: bool) -> Result<Gender> {
let v = match kw.get("gender") {
Option::None => return Ok(Gender::M), Some(v) => v,
};
Ok(match v {
KwVal::Str(s) => match s.as_str() {
"m" => Gender::M,
"f" => Gender::F,
"n" => Gender::N,
other => Gender::Bad(format!("'{}'", other)),
},
KwVal::Bool(true) if ordinal_bool_handshake => Gender::F,
KwVal::Bool(b) => Gender::Bad(if *b { "True" } else { "False" }.to_string()),
KwVal::Int(i) => Gender::Bad(i.to_string()),
KwVal::None => Gender::Bad("None".to_string()),
KwVal::List(_) => return Err(N2WError::Fallback("kwargs".into())),
})
}
fn key_error(key: String) -> N2WError {
N2WError::Key(key)
}
fn index_error(msg: &str) -> N2WError {
N2WError::Index(msg.to_string())
}
fn type_error(msg: String) -> N2WError {
N2WError::Type(msg)
}
fn value_error(msg: String) -> N2WError {
N2WError::Value(msg)
}
fn py_int(s: &str) -> Result<BigInt> {
BigInt::parse_bytes(s.as_bytes(), 10)
.ok_or_else(|| value_error(format!("invalid literal for int() with base 10: '{}'", s)))
}
fn is_exact_tie_below(f: f64, d: &BigInt, scale: i32) -> bool {
let bits = f.abs().to_bits();
let biased = ((bits >> 52) & 0x7ff) as i32;
let frac = bits & ((1u64 << 52) - 1);
let (m, e) = if biased == 0 {
(BigInt::from(frac), -1074i32)
} else {
(BigInt::from(frac | (1u64 << 52)), biased - 1075)
};
let (mut ln, mut ld) = (m, BigInt::from(1));
let p = e + 1;
if p >= 0 {
ln <<= p as usize;
} else {
ld <<= (-p) as usize;
}
let (mut rn, mut rd) = (d * 2 - 1, BigInt::from(1));
if scale >= 0 {
rn *= BigInt::from(10).pow(scale as u32);
} else {
rd *= BigInt::from(10).pow((-scale) as u32);
}
ln * rd == rn * ld
}
fn roundtrips(digits: &str, scale: i32, f: f64) -> bool {
match format!("{}e{}", digits, scale).parse::<f64>() {
Ok(v) => v == f.abs(),
Err(_) => false,
}
}
fn py_repr_float(f: f64) -> String {
if f.is_nan() {
return "nan".to_string();
}
if f.is_infinite() {
return if f.is_sign_negative() {
"-inf".to_string()
} else {
"inf".to_string()
};
}
let s = format!("{:e}", f);
let (mant, exp) = match s.split_once('e') {
Some(p) => p,
None => return s,
};
let exp: i32 = match exp.parse() {
Ok(e) => e,
Err(_) => return s,
};
let (sign, mant) = match mant.strip_prefix('-') {
Some(m) => ("-", m),
None => ("", mant),
};
let mut digits: String = mant.chars().filter(|c| *c != '.').collect();
let mut dlen = digits.len() as i32;
let decpt = exp + 1;
let dint = BigInt::parse_bytes(digits.as_bytes(), 10).unwrap_or_else(BigInt::zero);
if dint.is_odd() && is_exact_tie_below(f, &dint, decpt - dlen) {
let cand: BigInt = &dint - 1;
let cs = cand.to_string();
if !cand.is_zero() && cs.len() as i32 == dlen && roundtrips(&cs, decpt - dlen, f) {
digits = cs;
dlen = digits.len() as i32;
}
}
if decpt <= -4 || decpt > 16 {
let e = decpt - 1;
let mut out = String::from(sign);
out.push_str(&digits[..1]);
if dlen > 1 {
out.push('.');
out.push_str(&digits[1..]);
}
out.push('e');
out.push(if e < 0 { '-' } else { '+' });
out.push_str(&format!("{:02}", e.abs()));
out
} else if decpt <= 0 {
format!("{}0.{}{}", sign, "0".repeat((-decpt) as usize), digits)
} else if decpt >= dlen {
format!(
"{}{}{}.0",
sign,
digits,
"0".repeat((decpt - dlen) as usize)
)
} else {
format!(
"{}{}.{}",
sign,
&digits[..decpt as usize],
&digits[decpt as usize..]
)
}
}
fn py_str_decimal(d: &BigDecimal) -> String {
let (int_val, scale) = d.as_bigint_and_exponent();
let exp: i64 = -scale;
let sign = if int_val.is_negative() { "-" } else { "" };
let int_digits = int_val.abs().to_string();
let dlen = int_digits.len() as i64;
let leftdigits = exp + dlen;
let dotplace: i64 = if exp <= 0 && leftdigits > -6 {
leftdigits
} else {
1
};
let (intpart, fracpart) = if dotplace <= 0 {
(
"0".to_string(),
format!(".{}{}", "0".repeat((-dotplace) as usize), int_digits),
)
} else if dotplace >= dlen {
(
format!("{}{}", int_digits, "0".repeat((dotplace - dlen) as usize)),
String::new(),
)
} else {
(
int_digits[..dotplace as usize].to_string(),
format!(".{}", &int_digits[dotplace as usize..]),
)
};
let expo = if leftdigits == dotplace {
String::new()
} else {
let e = leftdigits - dotplace;
format!("E{}{}", if e < 0 { "-" } else { "+" }, e.abs())
};
format!("{}{}{}{}", sign, intpart, fracpart, expo)
}
fn char_from_end(chars: &[char], k: usize) -> Result<char> {
if chars.len() < k || k == 0 {
return Err(index_error("string index out of range"));
}
Ok(chars[chars.len() - k])
}
fn drop_last(chars: &[char], k: usize) -> String {
if chars.len() <= k {
String::new()
} else {
chars[..chars.len() - k].iter().collect()
}
}
fn last_n(chars: &[char], k: usize) -> String {
chars[chars.len().saturating_sub(k)..].iter().collect()
}
fn keep_through_last(chars: &[char], c: char) -> String {
match chars.iter().rposition(|&x| x == c) {
Some(i) => chars[..=i].iter().collect(),
None => String::new(),
}
}
fn splitbyx(n: &str, x: usize) -> Vec<BigInt> {
let chars: Vec<char> = n.chars().collect();
let length = chars.len();
let parse = |i: usize, j: usize| -> BigInt {
let s: String = chars[i..j.min(length)].iter().collect();
BigInt::parse_bytes(s.as_bytes(), 10).unwrap_or_else(BigInt::zero)
};
let mut out: Vec<BigInt> = Vec::new();
if length > x {
let start = length % x;
if start > 0 {
out.push(parse(0, start));
}
let mut i = start;
while i < length {
out.push(parse(i, i + x));
i += x;
}
} else {
out.push(parse(0, length));
}
out
}
fn get_digits(n: &BigInt) -> [usize; 3] {
let mag = n.abs().to_string();
let s = format!("{}{}", "0".repeat(3usize.saturating_sub(mag.len())), mag);
let chars: Vec<char> = s.chars().collect();
let tail = &chars[chars.len() - 3..];
let mut a = [0usize; 3];
for (k, c) in tail.iter().rev().enumerate() {
a[k] = c.to_digit(10).unwrap_or(0) as usize;
}
a
}
pub struct LangBe {
ords: HashMap<&'static str, &'static str>,
ords_adjective: HashMap<&'static str, &'static str>,
currency_forms: HashMap<&'static str, CurrencyForms>,
}
impl Default for LangBe {
fn default() -> Self {
Self::new()
}
}
impl LangBe {
pub fn new() -> Self {
let mut ords: HashMap<&'static str, &'static str> = HashMap::new();
for (k, v) in [
("нуль", "нулявы"),
("адзін", "першы"),
("два", "другі"),
("тры", "трэці"),
("чатыры", "чацвёрты"),
("пяць", "пяты"),
("шэсць", "шосты"),
("сем", "сёмы"),
("восем", "восьмы"),
("дзевяць", "дзявяты"),
("сто", "соты"),
("тысяча", "тысячны"),
] {
ords.insert(k, v);
}
let mut ords_adjective: HashMap<&'static str, &'static str> = HashMap::new();
for (k, v) in [
("адзін", "адна"),
("адна", "адна"),
("дзве", "двух"),
("тры", "трох"),
("чатыры", "чатырох"),
("пяць", "пяці"),
("шэсць", "шасці"),
("сем", "сямі"),
("восем", "васьмі"),
("дзевяць", "дзевяц\u{0069}"),
("сто", "ста"),
] {
ords_adjective.insert(k, v);
}
LangBe {
ords,
ords_adjective,
currency_forms: build_currency_forms(),
}
}
fn thousands_at(&self, i: usize) -> Result<&'static [&'static str; 3]> {
if i == 0 || i > 10 {
return Err(key_error(i.to_string()));
}
Ok(&THOUSANDS[i])
}
fn ones(gender: &Gender) -> Result<&'static [&'static str; 10]> {
match gender {
Gender::M => Ok(&ONES_M),
Gender::F => Ok(&ONES_F),
Gender::N => Ok(&ONES_N),
Gender::Bad(repr) => Err(key_error(repr.clone())),
}
}
fn pluralize(&self, n: &BigInt, forms: &[&'static str; 3]) -> String {
forms[plural_form_index(n)].to_string()
}
fn int2word(&self, n: &BigInt, gender: &Gender) -> Result<String> {
if n.is_negative() {
return Ok(format!("{} {}", NEGWORD, self.int2word(&n.abs(), gender)?));
}
if n.is_zero() {
return Ok(ZERO.to_string());
}
let mut words: Vec<String> = Vec::new();
let chunks = splitbyx(&n.to_string(), 3);
let mut i = chunks.len();
for x in chunks.iter() {
i -= 1;
if x.is_zero() {
continue;
}
let [n1, n2, n3] = get_digits(x);
if n3 > 0 {
words.push(HUNDREDS[n3].to_string());
}
if n2 > 1 {
words.push(TWENTIES[n2].to_string());
}
if n2 == 1 {
words.push(TENS[n1].to_string());
} else if n1 > 0 {
let ones = match i {
0 => Self::ones(gender)?,
1 => &ONES_F,
_ => &ONES_M,
};
words.push(ones[n1].to_string());
}
if i > 0 {
let forms = self.thousands_at(i)?;
words.push(self.pluralize(x, forms));
}
}
Ok(words.join(" "))
}
fn to_cardinal_str(&self, number_str: &str, gender: &Gender) -> Result<String> {
let n = number_str.replace(',', ".");
if !n.contains('.') {
return self.int2word(&py_int(&n)?, gender);
}
let is_negative = n.starts_with('-');
let abs_n: String = if is_negative {
n.chars().skip(1).collect()
} else {
n.clone()
};
let parts: Vec<&str> = abs_n.split('.').collect();
if parts.len() != 2 {
return Err(value_error(
"too many values to unpack (expected 2)".to_string(),
));
}
let (left, right) = (parts[0], parts[1]);
let leading_zero_count = if !right.is_empty() && right.chars().all(|c| c == '0') {
0
} else {
right.chars().count() - right.trim_start_matches('0').chars().count()
};
let decimal_part = format!(
"{}{}",
format!("{} ", ZERO).repeat(leading_zero_count),
self.int2word(&py_int(right)?, gender)?
);
let mut result = format!(
"{} {} {}",
self.int2word(&py_int(left)?, gender)?,
POINTWORD,
decimal_part
);
if is_negative {
result = format!("{} {}", NEGWORD, result);
}
Ok(result)
}
fn ordinal_fallback(&self, lastword: &str) -> Result<String> {
let lw: Vec<char> = lastword.chars().collect();
let head = drop_last(&lw, 3);
if let Some(stem) = self.ords_adjective.get(head.as_str()) {
return Ok(format!("{}соты", stem));
}
if last_n(&lw, 7) == "дзесяць" {
return Ok("дзясяты".to_string());
}
if last_n(&lw, 9) == "семдзесят" {
return Ok("сямідзясяты".to_string());
}
let m1 = char_from_end(&lw, 1)?;
let m2 = char_from_end(&lw, 2)?;
if m1 == 'ь' || m2 == 'ц' {
return Ok(format!("{}ты", drop_last(&lw, 2)));
}
if m1 == 'к' {
return Ok(format!("{}авы", lastword.replace('о', "а")));
}
if m2 == 'ч' || m1 == 'ч' {
let mut lw2 = lastword.to_string();
if char_from_end(&lw2.chars().collect::<Vec<_>>(), 2)? == 'ч' {
let c: Vec<char> = lw2.chars().collect();
lw2 = format!("{}ны", drop_last(&c, 1));
}
if char_from_end(&lw2.chars().collect::<Vec<_>>(), 1)? == 'ч' {
lw2 = format!("{}ны", lw2);
}
return Ok(lw2);
}
if m1 == 'н' || m2 == 'н' {
return Ok(format!("{}ны", keep_through_last(&lw, 'н')));
}
if last_n(&lw, 3) == "наў" {
return Ok(format!("{}ны", keep_through_last(&lw, 'н')));
}
if m1 == 'д' || m2 == 'д' {
return Ok(format!("{}ны", keep_through_last(&lw, 'д')));
}
Ok(lastword.to_string())
}
fn to_ordinal_gender(&self, number: &BigInt, gender: &Gender) -> Result<String> {
if number.is_negative() {
return Err(type_error(format!(
"Cannot treat negative num {} as ordinal.",
number
)));
}
let cardinal = self.int2word(number, gender)?;
self.ordinal_words(&cardinal, gender)
}
fn ordinal_words(&self, cardinal: &str, gender: &Gender) -> Result<String> {
let mut outwords: Vec<String> = cardinal.split(' ').map(|s| s.to_string()).collect();
let lastword = outwords[outwords.len() - 1].to_lowercase();
if outwords.len() > 1 {
let i2 = outwords.len() - 2;
if let Some(adj) = self.ords_adjective.get(outwords[i2].as_str()) {
outwords[i2] = adj.to_string();
} else if outwords[i2] == "дзесяць" {
let c: Vec<char> = outwords[i2].chars().collect();
outwords[i2] = format!("{}і", drop_last(&c, 1));
}
}
if outwords.len() == 3 && (outwords[0] == "адзін" || outwords[0] == "адна") {
outwords[0] = String::new();
}
let mut lastword = match self.ords.get(lastword.as_str()) {
Some(o) => o.to_string(),
None => self.ordinal_fallback(&lastword)?,
};
if *gender == Gender::F {
let c: Vec<char> = lastword.chars().collect();
if last_n(&c, 2) == "ці" {
lastword = format!("{}цяя", drop_last(&c, 2));
} else {
lastword = format!("{}ая", drop_last(&c, 1));
}
}
if *gender == Gender::N {
let c: Vec<char> = lastword.chars().collect();
let t = last_n(&c, 2);
if t == "ці" || t == "ца" {
lastword = format!("{}цяе", drop_last(&c, 2));
} else {
lastword = format!("{}ае", drop_last(&c, 1));
}
}
let n = outwords.len();
outwords[n - 1] = self.title(&lastword);
if outwords.len() == 2 && outwords[0].contains("адна") {
outwords[0] = outwords[1].clone();
outwords.pop();
}
let last = outwords[outwords.len() - 1].clone();
let scale_tail = (1..=10).any(|i| last.contains(THOUSANDS[i][0])) || last.contains("тысяч");
if outwords.len() > 1 && scale_tail {
let mut new_outwords: Vec<String> = Vec::new();
for w in outwords.iter() {
let replacement = TWENTIES_ORD.iter().find(|x| w.contains(x.0));
match replacement {
Some((from, to)) => new_outwords.push(w.replace(from, to)),
None => new_outwords.push(w.clone()),
}
}
outwords = vec![new_outwords.concat()];
}
Ok(outwords.join(" ").trim().to_string())
}
}
impl Lang for LangBe {
fn cardinal_float_entry(
&self,
value: &crate::floatpath::FloatValue,
precision_override: Option<u32>,
) -> crate::base::Result<String> {
self.to_cardinal_float(value, precision_override)
}
fn default_currency(&self) -> &str {
"EUR"
}
fn default_separator(&self) -> &str {
","
}
fn negword(&self) -> &str {
NEGWORD
}
fn pointword(&self) -> &str {
"коска"
}
fn to_cardinal(&self, value: &BigInt) -> Result<String> {
self.int2word(value, &Gender::M)
}
fn to_ordinal(&self, value: &BigInt) -> Result<String> {
self.to_ordinal_gender(value, &Gender::M)
}
fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
match value {
FloatValue::Float { value: f, .. } => {
if f.is_nan() {
return Err(value_error(
"cannot convert float NaN to integer".to_string(),
));
}
if f.is_infinite() {
return Err(N2WError::Overflow(
"cannot convert float infinity to integer".to_string(),
));
}
if f.fract() != 0.0 {
return Err(type_error(format!(
"Cannot treat float {} as ordinal.",
py_repr_float(*f)
)));
}
if *f < 0.0 {
return Err(type_error(format!(
"Cannot treat negative num {} as ordinal.",
py_repr_float(*f)
)));
}
}
FloatValue::Decimal { value: d, .. } => {
if !d.is_integer() {
return Err(type_error(format!(
"Cannot treat float {} as ordinal.",
py_str_decimal(d)
)));
}
if d.is_negative() {
return Err(type_error(format!(
"Cannot treat negative num {} as ordinal.",
py_str_decimal(d)
)));
}
}
}
let n = match value {
FloatValue::Float { value, .. } => py_repr_float(*value),
FloatValue::Decimal { value, .. } => py_str_decimal(value),
};
let cardinal = self.to_cardinal_str(&n, &Gender::M)?;
self.ordinal_words(&cardinal, &Gender::M)
}
fn str_to_number(&self, s: &str) -> Result<ParsedNumber> {
python_decimal_parse(s)
}
fn inf_result(&self, negative: bool, to: &str) -> Result<String> {
let token = if negative { "-Infinity" } else { "Infinity" };
match to {
"ordinal" => Err(N2WError::Overflow(
"cannot convert Infinity to integer".to_string(),
)),
"ordinal_num" => Ok(token.to_string()),
_ => Err(value_error(format!(
"invalid literal for int() with base 10: '{}'",
token
))),
}
}
fn nan_result(&self, to: &str) -> Result<String> {
match to {
"ordinal" => Err(value_error("cannot convert NaN to integer".to_string())),
"ordinal_num" => Ok("NaN".to_string()),
_ => Err(value_error(
"invalid literal for int() with base 10: 'NaN'".to_string(),
)),
}
}
fn to_cardinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["gender"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let gender = gender_from_kw(kw, false)?;
self.int2word(value, &gender)
}
fn to_ordinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
if !kw.only(&["gender"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let gender = gender_from_kw(kw, true)?;
self.to_ordinal_gender(value, &gender)
}
fn to_year_kw(&self, value: &BigInt, _kw: &Kwargs) -> Result<String> {
self.int2word(value, &Gender::M)
}
fn to_cardinal_float_kw(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
kw: &Kwargs,
) -> Result<String> {
if !kw.only(&["gender"]) {
return Err(N2WError::Fallback("kwargs".into()));
}
let gender = gender_from_kw(kw, false)?;
let n = match value {
FloatValue::Float { value, .. } => py_repr_float(*value),
FloatValue::Decimal { value, .. } => py_str_decimal(value),
};
self.to_cardinal_str(&n, &gender)
}
fn to_cardinal_float(
&self,
value: &FloatValue,
_precision_override: Option<u32>,
) -> Result<String> {
let n = match value {
FloatValue::Float { value, .. } => py_repr_float(*value),
FloatValue::Decimal { value, .. } => py_str_decimal(value),
};
self.to_cardinal_str(&n, &Gender::M)
}
fn lang_name(&self) -> &str {
"Num2Word_BE"
}
fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
self.currency_forms.get(code)
}
fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
forms
.get(plural_form_index(n))
.cloned()
.ok_or_else(|| index_error("tuple index out of range"))
}
fn money_verbose(&self, number: &BigInt, currency: &str) -> Result<String> {
let gender = if currency == "UAH" {
Gender::F
} else {
Gender::M
};
self.int2word(number, &gender)
}
fn cents_verbose(&self, number: &BigInt, currency: &str) -> Result<String> {
let gender = match currency {
"UAH" | "RUB" | "BYN" => Gender::F,
_ => Gender::M,
};
self.int2word(number, &gender)
}
}