1use crate::base::{
51 default_to_cardinal, set_low_numwords, set_mid_numwords, Cards, Kwargs, Lang, N2WError,
52 Result,
53};
54use crate::currency::{CurrencyForms, CurrencyValue};
55use crate::floatpath::FloatValue;
56use crate::strnum::{python_decimal_parse, python_int_parse, ParsedNumber};
57use num_bigint::BigInt;
58use num_traits::{FromPrimitive, One, Signed, ToPrimitive, Zero};
59use std::collections::HashMap;
60
61const GENDER_STEM: &str = "o";
64
65const ORDS: &[(i64, &str)] = &[
69 (1, "primer"),
70 (2, "segund"),
71 (3, "tercer"),
72 (4, "cuart"),
73 (5, "quint"),
74 (6, "sext"),
75 (7, "séptim"),
76 (8, "octav"),
77 (9, "noven"),
78 (10, "décim"),
79 (20, "vigésim"),
80 (30, "trigésim"),
81 (40, "cuadragésim"),
82 (50, "quincuagésim"),
83 (60, "sexagésim"),
84 (70, "septuagésim"),
85 (80, "octogésim"),
86 (90, "nonagésim"),
87 (100, "centésim"),
88 (200, "ducentésim"),
89 (300, "tricentésim"),
90 (400, "cuadrigentésim"),
91 (500, "quingentésim"),
92 (600, "sexcentésim"),
93 (700, "septigentésim"),
94 (800, "octigentésim"),
95 (900, "noningentésim"),
96 (1_000, "milésim"),
97 (1_000_000, "millonésim"),
98 (1_000_000_000, "billonésim"),
99 (1_000_000_000_000, "trillonésim"),
100 (1_000_000_000_000_000, "cuadrillonésim"),
101];
102
103const CURRENCY_FORMS: &[(&str, &[&str], &[&str])] = &[
121 ("EUR", &["euro", "euros"], &["céntimo", "céntimos"]),
122 ("ESP", &["peseta", "pesetas"], &["céntimo", "céntimos"]),
123 ("USD", &["dólar", "dólares"], &["centavo", "centavos"]),
124 ("PEN", &["sol", "soles"], &["céntimo", "céntimos"]),
125 ("CRC", &["colón", "colones"], &["céntimo", "céntimos"]),
126 ("AUD", &["dólar", "dólares"], &["centavo", "centavos"]),
127 ("CAD", &["dólar", "dólares"], &["centavo", "centavos"]),
128 ("GBP", &["libra", "libras"], &["penique", "peniques"]),
129 ("RUB", &["rublo", "rublos"], &["kopeyka", "kopeykas"]),
130 ("SEK", &["corona", "coronas"], &["öre", "öre"]),
131 ("NOK", &["corona", "coronas"], &["øre", "øre"]),
132 ("PLN", &["zloty", "zlotys"], &["grosz", "groszy"]),
133 ("MXN", &["peso", "pesos"], &["centavo", "centavos"]),
134 ("RON", &["leu", "leus"], &["ban", "bani"]),
135 ("INR", &["rupia", "rupias"], &["paisa", "paisas"]),
136 ("HUF", &["florín", "florines"], &["fillér", "fillér"]),
137 ("FRF", &["franco", "francos"], &["céntimo", "céntimos"]),
138 ("CNY", &["yuan", "yuanes"], &["fen", "jiaos"]),
139 ("CZK", &["corona", "coronas"], &["haléř", "haléř"]),
140 ("NIO", &["córdoba", "córdobas"], &["centavo", "centavos"]),
141 ("VES", &["bolívar", "bolívares"], &["céntimo", "céntimos"]),
142 ("BRL", &["real", "reales"], &["centavo", "centavos"]),
143 ("CHF", &["franco", "francos"], &["céntimo", "céntimos"]),
144 ("JPY", &["yen", "yenes"], &["sen", "sen"]),
145 ("KRW", &["won", "wones"], &["jeon", "jeon"]),
146 ("KPW", &["won", "wones"], &["chon", "chon"]),
147 ("TRY", &["lira", "liras"], &["kuruş", "kuruş"]),
148 ("ZAR", &["rand", "rands"], &["céntimo", "céntimos"]),
149 ("KZT", &["tenge", "tenges"], &["tïın", "tïın"]),
150 ("UAH", &["hryvnia", "hryvnias"], &["kopiyka", "kopiykas"]),
151 ("THB", &["baht", "bahts"], &["satang", "satang"]),
152 ("AED", &["dirham", "dirhams"], &["fils", "fils"]),
153 ("AFN", &["afghani", "afghanis"], &["pul", "puls"]),
154 ("ALL", &["lek ", "leke"], &["qindarkë", "qindarka"]),
155 ("AMD", &["dram", "drams"], &["luma", "lumas"]),
156 ("ANG", &["florín", "florines"], &["centavo", "centavos"]),
157 ("AOA", &["kwanza", "kwanzas"], &["céntimo", "céntimos"]),
158 ("ARS", &["peso", "pesos"], &["centavo", "centavos"]),
159 ("AWG", &["florín", "florines"], &["centavo", "centavos"]),
160 ("AZN", &["manat", "manat"], &["qəpik", "qəpik"]),
161 ("BBD", &["dólar", "dólares"], &["centavo", "centavos"]),
162 ("BDT", &["taka", "takas"], &["paisa", "paisas"]),
163 ("BGN", &["lev", "leva"], &["stotinka", "stotinki"]),
164 ("BHD", &["dinar", "dinares"], &["fils", "fils"]),
165 ("BIF", &["franco", "francos"], &["céntimo", "céntimos"]),
166 ("BMD", &["dólar", "dólares"], &["centavo", "centavos"]),
167 ("BND", &["dólar", "dólares"], &["centavo", "centavos"]),
168 ("BOB", &["boliviano", "bolivianos"], &["centavo", "centavos"]),
169 ("BSD", &["dólar", "dólares"], &["centavo", "centavos"]),
170 ("BTN", &["ngultrum", "ngultrum"], &["chetrum", "chetrum"]),
171 ("BWP", &["pula", "pulas"], &["thebe", "thebes"]),
172 ("BYN", &["rublo", "rublos"], &["kópek", "kópeks"]),
173 ("BYR", &["rublo", "rublos"], &["kópek", "kópeks"]),
174 ("BZD", &["dólar", "dólares"], &["céntimo", "céntimos"]),
175 ("CDF", &["franco", "francos"], &["céntimo", "céntimos"]),
176 ("CLP", &["peso", "pesos"], &["centavo", "centavos"]),
177 ("COP", &["peso", "pesos"], &["centavo", "centavos"]),
178 ("CUP", &["peso", "pesos"], &["centavo", "centavos"]),
179 ("CVE", &["escudo", "escudos"], &["centavo", "centavos"]),
180 ("CYP", &["libra", "libras"], &["céntimo", "céntimos"]),
181 ("DJF", &["franco", "francos"], &["céntimo", "céntimos"]),
182 ("DKK", &["corona", "coronas"], &["øre", "øre"]),
183 ("DOP", &["peso", "pesos"], &["centavo", "centavos"]),
184 ("DZD", &["dinar", "dinares"], &["céntimo", "céntimos"]),
185 ("ECS", &["sucre", "sucres"], &["centavo", "centavos"]),
186 ("EGP", &["libra", "libras"], &["piastra", "piastras"]),
187 ("ERN", &["nakfa", "nakfas"], &["céntimo", "céntimos"]),
188 ("ETB", &["birr", "birrs"], &["céntimo", "céntimos"]),
189 ("FJD", &["dólar", "dólares"], &["centavo", "centavos"]),
190 ("FKP", &["libra", "libras"], &["penique", "peniques"]),
191 ("GEL", &["lari", "laris"], &["tetri", "tetris"]),
192 ("GHS", &["cedi", "cedis"], &["pesewa", "pesewas"]),
193 ("GIP", &["libra", "libras"], &["penique", "peniques"]),
194 ("GMD", &["dalasi", "dalasis"], &["butut", "bututs"]),
195 ("GNF", &["franco", "francos"], &["céntimo", "céntimos"]),
196 ("GTQ", &["quetzal", "quetzales"], &["centavo", "centavos"]),
197 ("GYD", &["dólar", "dólares"], &["centavo", "centavos"]),
198 ("HKD", &["dólar", "dólares"], &["centavo", "centavos"]),
199 ("HNL", &["lempira", "lempiras"], &["centavo", "centavos"]),
200 ("HRK", &["kuna", "kunas"], &["lipa", "lipas"]),
201 ("HTG", &["gourde", "gourdes"], &["céntimo", "céntimos"]),
202 ("IDR", &["rupia", "rupias"], &["céntimo", "céntimos"]),
203 ("ILS", &["séquel", "séqueles"], &["agora", "agoras"]),
204 ("IQD", &["dinar", "dinares"], &["fils", "fils"]),
205 ("IRR", &["rial", "riales"], &["dinar", "dinares"]),
206 ("ISK", &["corona", "coronas"], &["eyrir", "aurar"]),
207 ("ITL", &["lira", "liras"], &["céntimo", "céntimos"]),
208 ("JMD", &["dólar", "dólares"], &["céntimo", "céntimos"]),
209 ("JOD", &["dinar", "dinares"], &["piastra", "piastras"]),
210 ("KES", &["chelín", "chelines"], &["céntimo", "céntimos"]),
211 ("KGS", &["som", "som"], &["tyiyn", "tyiyn"]),
212 ("KHR", &["riel", "rieles"], &["céntimo", "céntimos"]),
213 ("KMF", &["franco", "francos"], &["céntimo", "céntimos"]),
214 ("KWD", &["dinar", "dinares"], &["fils", "fils"]),
215 ("KYD", &["dólar", "dólares"], &["céntimo", "céntimos"]),
216 ("LAK", &["kip", "kips"], &["att", "att"]),
217 ("LBP", &["libra", "libras"], &["piastra", "piastras"]),
218 ("LKR", &["rupia", "rupias"], &["céntimo", "céntimos"]),
219 ("LRD", &["dólar", "dólares"], &["céntimo", "céntimos"]),
220 ("LSL", &["loti", "lotis"], &["céntimo", "céntimos"]),
221 ("LTL", &["lita", "litas"], &["céntimo", "céntimos"]),
222 ("LVL", &["lat", "lats"], &["céntimo", "céntimos"]),
223 ("LYD", &["dinar", "dinares"], &["dírham", "dírhams"]),
224 ("MAD", &["dírham", "dirhams"], &["céntimo", "céntimos"]),
225 ("MDL", &["leu", "lei"], &["ban", "bani"]),
226 ("MGA", &["ariary", "ariaris"], &["iraimbilanja", "iraimbilanja"]),
227 ("MKD", &["denar", "denares"], &["deni", "denis"]),
228 ("MMK", &["kiat", "kiats"], &["pya", "pyas"]),
229 ("MNT", &["tugrik", "tugriks"], &["möngö", "möngö"]),
230 ("MOP", &["pataca", "patacas"], &["avo", "avos"]),
231 ("MRO", &["ouguiya", "ouguiyas"], &["khoums", "khoums"]),
232 ("MRU", &["ouguiya", "ouguiyas"], &["khoums", "khoums"]),
233 ("MUR", &["rupia", "rupias"], &["céntimo", "céntimos"]),
234 ("MVR", &["rufiyaa", "rufiyaas"], &["laari", "laari"]),
235 ("MWK", &["kuacha", "kuachas"], &["tambala", "tambalas"]),
236 ("MYR", &["ringgit", "ringgit"], &["céntimo", "céntimos"]),
237 ("MZN", &["metical", "metical"], &["centavo", "centavos"]),
238 ("NAD", &["dólar", "dólares"], &["céntimo", "céntimos"]),
239 ("NGN", &["naira", "nairas"], &["kobo", "kobo"]),
240 ("NPR", &["rupia", "rupias"], &["paisa", "paisas"]),
241 ("NZD", &["dólar", "dólares"], &["centavo", "centavos"]),
242 ("OMR", &["rial", "riales"], &["baisa", "baisa"]),
243 ("PAB", &["balboa", "balboas"], &["centésimo", "centésimos"]),
244 ("PGK", &["kina", "kinas"], &["toea", "toea"]),
245 ("PHP", &["peso", "pesos"], &["centavo", "centavos"]),
246 ("PKR", &["rupia", "rupias"], &["paisa", "paisas"]),
247 ("PLZ", &["zloty", "zlotys"], &["grosz", "groszy"]),
248 ("PYG", &["guaraní", "guaranís"], &["céntimo", "céntimos"]),
249 ("QAR", &["rial", "riales"], &["dírham", "dírhams"]),
250 ("QTQ", &["quetzal", "quetzales"], &["centavo", "centavos"]),
251 ("RSD", &["dinar", "dinares"], &["para", "para"]),
252 ("RUR", &["rublo", "rublos"], &["kopek", "kopeks"]),
253 ("RWF", &["franco", "francos"], &["céntimo", "céntimos"]),
254 ("SAR", &["riyal", "riales"], &["halala", "halalas"]),
255 ("SBD", &["dólar", "dólares"], &["céntimo", "céntimos"]),
256 ("SCR", &["rupia", "rupias"], &["céntimo", "céntimos"]),
257 ("SDG", &["libra", "libras"], &["piastra", "piastras"]),
258 ("SGD", &["dólar", "dólares"], &["céntimo", "céntimos"]),
259 ("SHP", &["libra", "libras"], &["penique", "peniques"]),
260 ("SKK", &["corona", "coronas"], &["halier", "haliers"]),
261 ("SLL", &["leona", "leonas"], &["céntimo", "céntimos"]),
262 ("SRD", &["dólar", "dólares"], &["céntimo", "céntimos"]),
263 ("SSP", &["libra", "libras"], &["piastra", "piastras"]),
264 ("STD", &["dobra", "dobras"], &["céntimo", "céntimos"]),
265 ("SVC", &["colón", "colones"], &["centavo", "centavos"]),
266 ("SYP", &["libra", "libras"], &["piastra", "piastras"]),
267 ("SZL", &["lilangeni", "emalangeni"], &["céntimo", "céntimos"]),
268 ("TJS", &["somoni", "somonis"], &["dirame", "dirames"]),
269 ("TMT", &["manat", "manat"], &["tenge", "tenge"]),
270 ("TND", &["dinar", "dinares"], &["milésimo", "milésimos"]),
271 ("TOP", &["paanga", "paangas"], &["céntimo", "céntimos"]),
272 ("TTD", &["dólar", "dólares"], &["céntimo", "céntimos"]),
273 ("TWD", &["nuevo dólar", "nuevos dólares"], &["céntimo", "céntimos"]),
274 ("TZS", &["chelín", "chelines"], &["céntimo", "céntimos"]),
275 ("UAG", &["hryvnia", "hryvnias"], &["kopiyka", "kopiykas"]),
276 ("UGX", &["chelín", "chelines"], &["céntimo", "céntimos"]),
277 ("UYU", &["peso", "pesos"], &["centésimo", "centésimos"]),
278 ("UZS", &["sum", "sum"], &["tiyin", "tiyin"]),
279 ("VEF", &["bolívar fuerte", "bolívares fuertes"], &["céntimo", "céntimos"]),
280 ("VND", &["dong", "dongs"], &["xu", "xu"]),
281 ("VUV", &["vatu", "vatu"], &["nenhum", "nenhum"]),
282 ("WST", &["tala", "tala"], &["centavo", "centavos"]),
283 ("XAF", &["franco CFA", "francos CFA"], &["céntimo", "céntimos"]),
284 ("XCD", &["dólar", "dólares"], &["céntimo", "céntimos"]),
285 ("XOF", &["franco CFA", "francos CFA"], &["céntimo", "céntimos"]),
286 ("XPF", &["franco CFP", "francos CFP"], &["céntimo", "céntimos"]),
287 ("YER", &["rial", "riales"], &["fils", "fils"]),
288 ("YUM", &["dinar", "dinares"], &["para", "para"]),
289 ("ZMW", &["kwacha", "kwachas"], &["ngwee", "ngwee"]),
290 ("ZRZ", &["zaire", "zaires"], &["likuta", "makuta"]),
291 ("ZWL", &["dólar", "dólares"], &["céntimo", "céntimos"]),
292];
293
294const CURRENCY_ADJECTIVES: &[(&str, &str)] = &[
301 ("AUD", "Australian"),
302 ("BYN", "Belarusian"),
303 ("CAD", "Canadian"),
304 ("EEK", "Estonian"),
305 ("USD", "US"),
306 ("RUB", "Russian"),
307 ("NOK", "Norwegian"),
308 ("MXN", "Mexican"),
309 ("RON", "Romanian"),
310 ("INR", "Indian"),
311 ("HUF", "Hungarian"),
312 ("ISK", "íslenskar"),
313 ("UZS", "Uzbekistan"),
314 ("SAR", "Saudi"),
315 ("JPY", "Japanese"),
316 ("KRW", "Korean"),
317];
318
319fn drop_last_chars(s: &str, n: usize) -> String {
323 let count = s.chars().count();
324 if count <= n {
325 return String::new();
326 }
327 s.chars().take(count - n).collect()
328}
329
330fn float_ordinal_reject(value: &FloatValue) -> N2WError {
335 if let FloatValue::Float { value: f, .. } = value {
336 if f.is_nan() {
337 return N2WError::Value("cannot convert float NaN to integer".into());
338 }
339 if f.is_infinite() {
340 return N2WError::Overflow("cannot convert float infinity to integer".into());
341 }
342 }
343 let shown = match value {
344 FloatValue::Float { value: f, .. } => format!("{}", f),
345 FloatValue::Decimal { value: d, .. } => crate::strnum::python_decimal_str(d),
346 };
347 N2WError::Type(format!(
348 "El float {} no puede ser tratado como un ordinal.",
349 shown
350 ))
351}
352
353fn float_trunc_int(value: &FloatValue) -> Result<BigInt> {
356 match value {
357 FloatValue::Float { value: f, .. } => {
358 if f.is_nan() {
359 return Err(N2WError::Value(
360 "cannot convert float NaN to integer".into(),
361 ));
362 }
363 if f.is_infinite() {
364 return Err(N2WError::Overflow(
365 "cannot convert float infinity to integer".into(),
366 ));
367 }
368 Ok(BigInt::from_f64(f.trunc()).expect("finite whole f64 converts exactly"))
370 }
371 FloatValue::Decimal { value: d, .. } => {
372 Ok(d.with_scale(0).as_bigint_and_exponent().0)
375 }
376 }
377}
378
379pub fn es_str_to_number(s: &str) -> Result<ParsedNumber> {
387 let t = s.trim();
388 if t.chars().count() >= 3 {
390 let suffix: String = t.chars().rev().take(2).collect::<Vec<_>>()
391 .into_iter().rev().collect();
392 let gender = match suffix.as_str() {
393 "ro" | "do" | "mo" | "to" | "no" | "vo" => Some('m'),
394 "ra" | "da" | "ma" | "ta" | "na" | "va" => Some('f'),
395 _ => None,
396 };
397 if let Some(g) = gender {
398 let stem: String = {
399 let mut c: Vec<char> = t.chars().collect();
400 c.truncate(c.len() - 2);
401 c.into_iter().collect()
402 };
403 if !stem.is_empty()
407 && stem.chars().all(|ch| crate::strnum::unicode_digit(ch).is_some())
408 {
409 if let Some(n) = python_int_parse(&stem) {
410 return Ok(ParsedNumber::EsOrdinal { n, gender: g });
411 }
412 }
413 }
414 }
415 python_decimal_parse(t)
416}
417
418pub struct LangEs {
419 cards: Cards,
420 maxval: BigInt,
421 exclude_title: Vec<String>,
422 currency_forms: HashMap<&'static str, CurrencyForms>,
426 currency_adjectives: HashMap<&'static str, &'static str>,
427}
428
429impl Default for LangEs {
430 fn default() -> Self {
431 Self::new()
432 }
433}
434
435impl LangEs {
436 pub fn new() -> Self {
437 let high = ["non", "oct", "sept", "sext", "quint", "cuatr", "tr", "b", "m"];
441
442 let mut cards = Cards::new();
443
444 let cap: i64 = 3 + 6 * high.len() as i64;
447 let mut n = cap;
448 for word in high.iter() {
449 if n <= 3 {
450 break; }
452 cards.insert(
453 BigInt::from(10u8).pow((n - 3) as u32),
454 format!("{}illón", word),
455 );
456 n -= 6;
457 }
458
459 set_mid_numwords(
460 &mut cards,
461 &[
462 (1000, "mil"),
463 (100, "cien"),
464 (90, "noventa"),
465 (80, "ochenta"),
466 (70, "setenta"),
467 (60, "sesenta"),
468 (50, "cincuenta"),
469 (40, "cuarenta"),
470 (30, "treinta"),
471 ],
472 );
473 set_low_numwords(
474 &mut cards,
475 &[
476 "veintinueve",
477 "veintiocho",
478 "veintisiete",
479 "veintiséis",
480 "veinticinco",
481 "veinticuatro",
482 "veintitrés",
483 "veintidós",
484 "veintiuno",
485 "veinte",
486 "diecinueve",
487 "dieciocho",
488 "diecisiete",
489 "dieciséis",
490 "quince",
491 "catorce",
492 "trece",
493 "doce",
494 "once",
495 "diez",
496 "nueve",
497 "ocho",
498 "siete",
499 "seis",
500 "cinco",
501 "cuatro",
502 "tres",
503 "dos",
504 "uno",
505 "cero",
506 ],
507 );
508
509 let maxval = cards.highest().cloned().unwrap_or_else(BigInt::zero) * BigInt::from(1000);
511
512 LangEs {
513 cards,
514 maxval,
515 exclude_title: vec!["y".into(), "menos".into(), "punto".into()],
516 currency_forms: CURRENCY_FORMS
517 .iter()
518 .map(|(code, unit, sub)| (*code, CurrencyForms::new(unit, sub)))
519 .collect(),
520 currency_adjectives: CURRENCY_ADJECTIVES.iter().copied().collect(),
521 }
522 }
523
524 fn verify_ordinal(&self, value: &BigInt) -> Result<()> {
527 if value.is_negative() {
528 return Err(N2WError::Type(format!(
529 "El número negativo {} no puede ser tratado como un ordinal.",
530 value
531 )));
532 }
533 Ok(())
534 }
535
536 fn ord_stem(&self, key: i64) -> Result<&'static str> {
538 ORDS.iter()
539 .find(|(k, _)| *k == key)
540 .map(|(_, v)| *v)
541 .ok_or_else(|| N2WError::Key(key.to_string()))
542 }
543
544 fn to_ordinal_small(&self, value: i64, gender: &str) -> Result<String> {
547 let mut gender_stem = if gender == "f" { "a" } else { "o" };
548
549 let text: String = if value == 0 {
550 String::new()
551 } else if value <= 10 {
552 format!("{}{}", self.ord_stem(value)?, gender_stem)
553 } else if value <= 29 {
554 gender_stem = "o";
558 let dec = (value / 10) * 10;
559 format!(
560 "{}{}{}",
561 self.ord_stem(dec)?.replace('é', "e"),
562 gender_stem,
563 self.to_ordinal_small(value % 10, gender)?
564 )
565 } else if value <= 100 {
566 let dec = (value / 10) * 10;
567 format!(
568 "{}{} {}",
569 self.ord_stem(dec)?,
570 gender_stem,
571 self.to_ordinal_small(value - dec, gender)?
572 )
573 } else if value <= 1000 {
574 let cen = (value / 100) * 100;
575 format!(
576 "{}{} {}",
577 self.ord_stem(cen)?,
578 gender_stem,
579 self.to_ordinal_small(value - cen, gender)?
580 )
581 } else {
582 let k = ((value as f64).ln() / 1000f64.ln()).trunc();
596 let dec = 1000i64
597 .checked_pow(k as u32)
598 .ok_or_else(|| N2WError::Key(format!("1000^{}", k)))?;
599
600 let (high_part, low_part) = (value / dec, value % dec);
601 let cardinal = if high_part != 1 {
602 self.to_cardinal(&BigInt::from(high_part))?
603 } else {
604 String::new()
605 };
606
607 let stem = self.ord_stem(dec)?;
610
611 if high_part == 0 {
612 return Err(N2WError::NotImplemented(format!(
616 "RecursionError: to_ordinal({}) recurses forever in Python",
617 value
618 )));
619 }
620
621 format!(
622 "{}{}{} {}",
623 cardinal,
624 stem,
625 gender_stem,
626 self.to_ordinal_small(low_part, gender)?
627 )
628 };
629
630 Ok(text.trim().replace("oo", "o"))
633 }
634
635 fn to_ordinal_gender(&self, value: &BigInt, gender: &str) -> Result<String> {
639 self.verify_ordinal(value)?;
640
641 let limit = BigInt::from(10u8).pow(18u32);
643 if value >= &limit {
644 return Ok(self.to_cardinal(value)?.trim().replace("oo", "o"));
645 }
646
647 let v = value
649 .to_i64()
650 .ok_or_else(|| N2WError::Type(format!("valor fuera de rango: {}", value)))?;
651 self.to_ordinal_small(v, gender)
652 }
653}
654
655impl Lang for LangEs {
656
657 fn str_to_number(&self, s: &str) -> crate::base::Result<crate::strnum::ParsedNumber> {
658 crate::lang_es::es_str_to_number(s)
659 }
660 fn default_currency(&self) -> &str {
663 "EUR"
664 }
665
666 fn default_separator(&self) -> &str {
671 " con"
672 }
673
674 fn cards(&self) -> &Cards {
675 &self.cards
676 }
677 fn maxval(&self) -> &BigInt {
678 &self.maxval
679 }
680 fn negword(&self) -> &str {
681 "menos "
682 }
683 fn pointword(&self) -> &str {
684 "punto"
685 }
686 fn exclude_title(&self) -> &[String] {
687 &self.exclude_title
688 }
689
690 fn merge(&self, l: (&str, &BigInt), r: (&str, &BigInt)) -> (String, BigInt) {
691 let (ctext0, cnum) = l;
692 let (ntext0, nnum) = r;
693 let mut ctext = ctext0.to_string();
694 let mut ntext = ntext0.to_string();
695
696 let hundred = BigInt::from(100);
697 let thousand = BigInt::from(1000);
698 let million = BigInt::from(1_000_000);
699
700 if cnum.is_one() {
701 if nnum < &million {
702 return (ntext, nnum.clone());
703 }
704 ctext = "un".to_string();
705 } else if cnum == &hundred && !(nnum % &thousand).is_zero() {
706 ctext.push('t');
709 ctext.push_str(GENDER_STEM);
710 }
711
712 if nnum < cnum {
713 if cnum < &hundred {
714 return (format!("{} y {}", ctext, ntext), cnum + nnum);
715 }
716 return (format!("{} {}", ctext, ntext), cnum + nnum);
717 } else if (nnum % &million).is_zero() && !cnum.is_one() {
718 ntext = format!("{}lones", drop_last_chars(&ntext, 3));
720 }
721
722 if nnum == &hundred {
723 if cnum == &BigInt::from(5) {
724 ctext = "quinien".to_string();
725 ntext = String::new();
726 } else if cnum == &BigInt::from(7) {
727 ctext = "sete".to_string();
728 } else if cnum == &BigInt::from(9) {
729 ctext = "nove".to_string();
730 }
731 ntext = format!("{}t{}s", ntext, GENDER_STEM);
732 } else {
733 if nnum >= &thousand {
736 if ctext.ends_with("veintiuno") {
737 ctext = format!("{}ún", drop_last_chars(&ctext, 3));
738 } else if ctext.ends_with("uno") {
739 ctext = drop_last_chars(&ctext, 1);
740 }
741 }
742 ntext = format!(" {}", ntext);
743 }
744
745 (format!("{}{}", ctext, ntext), cnum * nnum)
746 }
747
748 fn to_cardinal(&self, value: &BigInt) -> Result<String> {
749 let v = value.abs();
756 if v >= self.maxval {
757 return Err(N2WError::Overflow(format!(
758 "abs({}) deber ser inferior a {}.",
759 v, self.maxval
760 )));
761 }
762 default_to_cardinal(self, value)
763 }
764
765 fn to_ordinal(&self, value: &BigInt) -> Result<String> {
766 self.to_ordinal_gender(value, "m")
767 }
768
769 fn to_ordinal_num(&self, value: &BigInt) -> Result<String> {
770 self.verify_ordinal(value)?;
771 Ok(format!("{}º", value))
772 }
773
774 fn to_year(&self, value: &BigInt) -> Result<String> {
775 self.to_cardinal(value)
777 }
778
779 fn ordinal_float_entry(&self, value: &FloatValue) -> Result<String> {
789 match value.as_whole_int() {
790 Some(i) => self.to_ordinal(&i),
794 None => Err(float_ordinal_reject(value)),
795 }
796 }
797
798 fn ordinal_num_float_entry(&self, value: &FloatValue, repr_str: &str) -> Result<String> {
799 match value.as_whole_int() {
800 Some(i) if !i.is_negative() => Ok(format!("{}º", repr_str)),
803 Some(_) => Err(N2WError::Type(format!(
804 "El número negativo {} no puede ser tratado como un ordinal.",
805 repr_str
806 ))),
807 None => Err(float_ordinal_reject(value)),
808 }
809 }
810
811 fn year_float_entry(&self, value: &FloatValue) -> Result<String> {
812 self.to_cardinal(&float_trunc_int(value)?)
815 }
816
817 fn to_ordinal_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
825 if !kw.only(&["gender"]) {
826 return Err(N2WError::Fallback("kwargs".into()));
827 }
828 let gender = if kw.str("gender") == Some("f") { "f" } else { "m" };
829 self.to_ordinal_gender(value, gender)
830 }
831
832 fn to_ordinal_num_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
834 if !kw.only(&["gender"]) {
835 return Err(N2WError::Fallback("kwargs".into()));
836 }
837 self.verify_ordinal(value)?;
838 let marker = if kw.str("gender") == Some("f") { "ª" } else { "º" };
839 Ok(format!("{}{}", value, marker))
840 }
841
842 fn to_year_kw(&self, value: &BigInt, kw: &Kwargs) -> Result<String> {
845 if !kw.only(&["suffix", "longval"]) {
846 return Err(N2WError::Fallback("kwargs".into()));
847 }
848 self.to_year(value)
849 }
850
851 fn to_fraction(&self, numerator: &BigInt, denominator: &BigInt) -> Result<String> {
857 if denominator.is_zero() {
858 return Err(N2WError::ZeroDivision("denominator must not be zero".into()));
859 }
860 if denominator.is_one() || numerator.is_zero() {
861 return self.to_cardinal(numerator);
862 }
863 let is_negative = numerator.is_negative() ^ denominator.is_negative();
864 let abs_n = numerator.abs();
865 let abs_d = denominator.abs();
866
867 let den_word = if abs_d == BigInt::from(2) {
868 (if abs_n.is_one() { "medio" } else { "medios" }).to_string()
869 } else if abs_d == BigInt::from(3) {
870 (if abs_n.is_one() { "tercio" } else { "tercios" }).to_string()
871 } else if abs_d == BigInt::from(4) {
872 (if abs_n.is_one() { "cuarto" } else { "cuartos" }).to_string()
873 } else {
874 let mut w = self.to_ordinal(&abs_d)?;
875 if !abs_n.is_one() && !w.ends_with('s') {
876 w.push('s');
877 }
878 w
879 };
880
881 let num_word = if abs_n.is_one() {
884 "un".to_string()
885 } else {
886 self.to_cardinal(&abs_n)?
887 };
888 let sign = if is_negative { "menos " } else { "" };
889 Ok(format!("{}{} {}", sign, num_word, den_word))
890 }
891
892 fn lang_name(&self) -> &str {
906 "Num2Word_ES"
907 }
908
909 fn currency_forms(&self, code: &str) -> Option<&CurrencyForms> {
910 self.currency_forms.get(code)
911 }
912
913 fn currency_adjective(&self, code: &str) -> Option<&str> {
914 self.currency_adjectives.get(code).copied()
915 }
916
917 fn pluralize(&self, n: &BigInt, forms: &[String]) -> Result<String> {
919 let form = if n.is_one() { 0 } else { 1 };
920 forms
921 .get(form)
922 .cloned()
923 .ok_or_else(|| N2WError::Index("tuple index out of range".into()))
924 }
925
926 fn to_currency(
932 &self,
933 val: &CurrencyValue,
934 currency: &str,
935 cents: bool,
936 separator: Option<&str>,
937 adjective: bool,
938 ) -> Result<String> {
939 let separator = separator.unwrap_or(self.default_separator());
942 let separator = if separator == "," { " con" } else { separator };
952
953 if let CurrencyValue::Int(v) = val {
955 let forms = match self.currency_forms.get(currency) {
956 Some(f) => f,
957 None => {
961 return Err(N2WError::NotImplemented(format!(
962 "Currency code \"{}\" not implemented for \"{}\"",
963 currency,
964 self.lang_name()
965 )))
966 }
967 };
968
969 let _ = adjective;
973
974 let minus_str = if v.is_negative() { self.negword() } else { "" };
979 let abs_val = v.abs();
980
981 let mut money_str = self.to_cardinal(&abs_val)?;
984 let currency_str = if abs_val.is_one() {
985 money_str = "un".to_string();
988 forms.unit.first()
989 } else {
990 forms.unit.get(1).or_else(|| forms.unit.first())
991 }
992 .ok_or_else(|| N2WError::Index("tuple index out of range".into()))?;
993
994 return Ok(format!("{} {} {}", minus_str, money_str, currency_str)
995 .trim()
996 .to_string());
997 }
998
999 let result =
1001 crate::currency::default_to_currency(self, val, currency, cents, separator, adjective)?;
1002
1003 let result = result.replace("veintiuno euro", "veintiún euro");
1009 let result = result.replace("veintiuno céntimo", "veintiún céntimo");
1010 let result = result.replace("uno euro", "un euro");
1011 let result = result.replace("uno céntimo", "un céntimo");
1012 let result = result.replace("uno centavo", "un centavo");
1013 let result = result.replace("uno dólar", "un dólar");
1014 Ok(result)
1015 }
1016}