n2words 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +86 -188
  3. package/dist/languages/am-Latn.js +3 -0
  4. package/dist/languages/am-Latn.js.map +1 -0
  5. package/dist/languages/am.js +3 -0
  6. package/dist/languages/am.js.map +1 -0
  7. package/dist/languages/ar.js +3 -0
  8. package/dist/languages/ar.js.map +1 -0
  9. package/dist/languages/az.js +3 -0
  10. package/dist/languages/az.js.map +1 -0
  11. package/dist/languages/bn.js +3 -0
  12. package/dist/languages/bn.js.map +1 -0
  13. package/dist/languages/cs.js +3 -0
  14. package/dist/languages/cs.js.map +1 -0
  15. package/dist/languages/da.js +3 -0
  16. package/dist/languages/da.js.map +1 -0
  17. package/dist/languages/de.js +3 -0
  18. package/dist/languages/de.js.map +1 -0
  19. package/dist/languages/el.js +3 -0
  20. package/dist/languages/el.js.map +1 -0
  21. package/dist/languages/en.js +3 -0
  22. package/dist/languages/en.js.map +1 -0
  23. package/dist/languages/es.js +3 -0
  24. package/dist/languages/es.js.map +1 -0
  25. package/dist/languages/fa.js +3 -0
  26. package/dist/languages/fa.js.map +1 -0
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -0
  30. package/dist/languages/fil.js.map +1 -0
  31. package/dist/languages/fr-BE.js +3 -0
  32. package/dist/languages/fr-BE.js.map +1 -0
  33. package/dist/languages/fr.js +3 -0
  34. package/dist/languages/fr.js.map +1 -0
  35. package/dist/languages/gu.js +3 -0
  36. package/dist/languages/gu.js.map +1 -0
  37. package/dist/languages/ha.js +3 -0
  38. package/dist/languages/ha.js.map +1 -0
  39. package/dist/languages/hbo.js +3 -0
  40. package/dist/languages/hbo.js.map +1 -0
  41. package/dist/languages/he.js +3 -0
  42. package/dist/languages/he.js.map +1 -0
  43. package/dist/languages/hi.js +3 -0
  44. package/dist/languages/hi.js.map +1 -0
  45. package/dist/languages/hr.js +3 -0
  46. package/dist/languages/hr.js.map +1 -0
  47. package/dist/languages/hu.js +3 -0
  48. package/dist/languages/hu.js.map +1 -0
  49. package/dist/languages/id.js +3 -0
  50. package/dist/languages/id.js.map +1 -0
  51. package/dist/languages/it.js +3 -0
  52. package/dist/languages/it.js.map +1 -0
  53. package/dist/languages/ja.js +3 -0
  54. package/dist/languages/ja.js.map +1 -0
  55. package/dist/languages/kn.js +3 -0
  56. package/dist/languages/kn.js.map +1 -0
  57. package/dist/languages/ko.js +3 -0
  58. package/dist/languages/ko.js.map +1 -0
  59. package/dist/languages/lt.js +3 -0
  60. package/dist/languages/lt.js.map +1 -0
  61. package/dist/languages/lv.js +3 -0
  62. package/dist/languages/lv.js.map +1 -0
  63. package/dist/languages/mr.js +3 -0
  64. package/dist/languages/mr.js.map +1 -0
  65. package/dist/languages/ms.js +3 -0
  66. package/dist/languages/ms.js.map +1 -0
  67. package/dist/languages/nb.js +3 -0
  68. package/dist/languages/nb.js.map +1 -0
  69. package/dist/languages/nl.js +3 -0
  70. package/dist/languages/nl.js.map +1 -0
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -0
  74. package/dist/languages/pl.js.map +1 -0
  75. package/dist/languages/pt.js +3 -0
  76. package/dist/languages/pt.js.map +1 -0
  77. package/dist/languages/ro.js +3 -0
  78. package/dist/languages/ro.js.map +1 -0
  79. package/dist/languages/ru.js +3 -0
  80. package/dist/languages/ru.js.map +1 -0
  81. package/dist/languages/sr-Cyrl.js +3 -0
  82. package/dist/languages/sr-Cyrl.js.map +1 -0
  83. package/dist/languages/sr-Latn.js +3 -0
  84. package/dist/languages/sr-Latn.js.map +1 -0
  85. package/dist/languages/sv.js +3 -0
  86. package/dist/languages/sv.js.map +1 -0
  87. package/dist/languages/sw.js +3 -0
  88. package/dist/languages/sw.js.map +1 -0
  89. package/dist/languages/ta.js +3 -0
  90. package/dist/languages/ta.js.map +1 -0
  91. package/dist/languages/te.js +3 -0
  92. package/dist/languages/te.js.map +1 -0
  93. package/dist/languages/th.js +3 -0
  94. package/dist/languages/th.js.map +1 -0
  95. package/dist/languages/tr.js +3 -0
  96. package/dist/languages/tr.js.map +1 -0
  97. package/dist/languages/uk.js +3 -0
  98. package/dist/languages/uk.js.map +1 -0
  99. package/dist/languages/ur.js +3 -0
  100. package/dist/languages/ur.js.map +1 -0
  101. package/dist/languages/vi.js +3 -0
  102. package/dist/languages/vi.js.map +1 -0
  103. package/dist/languages/zh-Hans.js +3 -0
  104. package/dist/languages/zh-Hans.js.map +1 -0
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +2 -2
  108. package/dist/n2words.js.map +1 -1
  109. package/lib/languages/am-Latn.d.ts +7 -0
  110. package/lib/languages/am-Latn.js +164 -0
  111. package/lib/languages/am.d.ts +7 -0
  112. package/lib/languages/am.js +164 -0
  113. package/lib/languages/ar.d.ts +14 -27
  114. package/lib/languages/ar.js +175 -129
  115. package/lib/languages/az.d.ts +4 -9
  116. package/lib/languages/az.js +171 -37
  117. package/lib/languages/bn.d.ts +4 -8
  118. package/lib/languages/bn.js +138 -124
  119. package/lib/languages/cs.d.ts +15 -85
  120. package/lib/languages/cs.js +310 -114
  121. package/lib/languages/da.d.ts +11 -12
  122. package/lib/languages/da.js +276 -101
  123. package/lib/languages/de.d.ts +14 -11
  124. package/lib/languages/de.js +317 -86
  125. package/lib/languages/el.d.ts +11 -11
  126. package/lib/languages/el.js +231 -78
  127. package/lib/languages/en.d.ts +14 -13
  128. package/lib/languages/en.js +242 -72
  129. package/lib/languages/es.d.ts +18 -12
  130. package/lib/languages/es.js +317 -103
  131. package/lib/languages/fa.d.ts +4 -44
  132. package/lib/languages/fa.js +112 -122
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +4 -13
  136. package/lib/languages/fil.js +207 -106
  137. package/lib/languages/fr-BE.d.ts +8 -8
  138. package/lib/languages/fr-BE.js +294 -19
  139. package/lib/languages/fr.d.ts +18 -12
  140. package/lib/languages/fr.js +352 -89
  141. package/lib/languages/gu.d.ts +4 -8
  142. package/lib/languages/gu.js +130 -125
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +10 -110
  146. package/lib/languages/hbo.js +263 -214
  147. package/lib/languages/he.d.ts +10 -77
  148. package/lib/languages/he.js +242 -172
  149. package/lib/languages/hi.d.ts +4 -8
  150. package/lib/languages/hi.js +138 -124
  151. package/lib/languages/hr.d.ts +8 -77
  152. package/lib/languages/hr.js +194 -89
  153. package/lib/languages/hu.d.ts +4 -19
  154. package/lib/languages/hu.js +198 -119
  155. package/lib/languages/id.d.ts +4 -34
  156. package/lib/languages/id.js +171 -129
  157. package/lib/languages/it.d.ts +16 -34
  158. package/lib/languages/it.js +339 -94
  159. package/lib/languages/ja.d.ts +14 -14
  160. package/lib/languages/ja.js +233 -111
  161. package/lib/languages/kn.d.ts +4 -8
  162. package/lib/languages/kn.js +130 -35
  163. package/lib/languages/ko.d.ts +11 -11
  164. package/lib/languages/ko.js +257 -49
  165. package/lib/languages/lt.d.ts +15 -67
  166. package/lib/languages/lt.js +296 -122
  167. package/lib/languages/lv.d.ts +15 -67
  168. package/lib/languages/lv.js +297 -106
  169. package/lib/languages/mr.d.ts +4 -8
  170. package/lib/languages/mr.js +130 -125
  171. package/lib/languages/ms.d.ts +4 -28
  172. package/lib/languages/ms.js +171 -116
  173. package/lib/languages/nb.d.ts +11 -9
  174. package/lib/languages/nb.js +282 -87
  175. package/lib/languages/nl.d.ts +23 -13
  176. package/lib/languages/nl.js +317 -133
  177. package/lib/languages/pa.d.ts +4 -8
  178. package/lib/languages/pa.js +156 -124
  179. package/lib/languages/pl.d.ts +19 -77
  180. package/lib/languages/pl.js +307 -87
  181. package/lib/languages/pt.d.ts +14 -26
  182. package/lib/languages/pt.js +286 -92
  183. package/lib/languages/ro.d.ts +15 -155
  184. package/lib/languages/ro.js +219 -235
  185. package/lib/languages/ru.d.ts +8 -82
  186. package/lib/languages/ru.js +222 -78
  187. package/lib/languages/sr-Cyrl.d.ts +8 -77
  188. package/lib/languages/sr-Cyrl.js +191 -89
  189. package/lib/languages/sr-Latn.d.ts +8 -77
  190. package/lib/languages/sr-Latn.js +191 -89
  191. package/lib/languages/sv.d.ts +11 -11
  192. package/lib/languages/sv.js +288 -74
  193. package/lib/languages/sw.d.ts +4 -36
  194. package/lib/languages/sw.js +133 -106
  195. package/lib/languages/ta.d.ts +4 -17
  196. package/lib/languages/ta.js +129 -201
  197. package/lib/languages/te.d.ts +4 -19
  198. package/lib/languages/te.js +141 -196
  199. package/lib/languages/th.d.ts +4 -14
  200. package/lib/languages/th.js +135 -91
  201. package/lib/languages/tr.d.ts +15 -9
  202. package/lib/languages/tr.js +256 -49
  203. package/lib/languages/uk.d.ts +8 -82
  204. package/lib/languages/uk.js +200 -78
  205. package/lib/languages/ur.d.ts +4 -8
  206. package/lib/languages/ur.js +156 -124
  207. package/lib/languages/vi.d.ts +14 -69
  208. package/lib/languages/vi.js +294 -125
  209. package/lib/languages/zh-Hans.d.ts +8 -18
  210. package/lib/languages/zh-Hans.js +163 -92
  211. package/lib/languages/zh-Hant.d.ts +8 -18
  212. package/lib/languages/zh-Hant.js +181 -90
  213. package/lib/n2words.d.ts +53 -209
  214. package/lib/n2words.js +111 -530
  215. package/lib/utils/is-plain-object.d.ts +13 -0
  216. package/lib/utils/is-plain-object.js +17 -0
  217. package/lib/utils/parse-numeric.d.ts +17 -0
  218. package/lib/utils/parse-numeric.js +108 -0
  219. package/lib/utils/validate-options.d.ts +8 -0
  220. package/lib/utils/validate-options.js +16 -0
  221. package/package.json +26 -14
  222. package/dist/ArabicConverter.js +0 -3
  223. package/dist/ArabicConverter.js.map +0 -1
  224. package/dist/AzerbaijaniConverter.js +0 -3
  225. package/dist/AzerbaijaniConverter.js.map +0 -1
  226. package/dist/BanglaConverter.js +0 -3
  227. package/dist/BanglaConverter.js.map +0 -1
  228. package/dist/BiblicalHebrewConverter.js +0 -3
  229. package/dist/BiblicalHebrewConverter.js.map +0 -1
  230. package/dist/CroatianConverter.js +0 -3
  231. package/dist/CroatianConverter.js.map +0 -1
  232. package/dist/CzechConverter.js +0 -3
  233. package/dist/CzechConverter.js.map +0 -1
  234. package/dist/DanishConverter.js +0 -3
  235. package/dist/DanishConverter.js.map +0 -1
  236. package/dist/DutchConverter.js +0 -3
  237. package/dist/DutchConverter.js.map +0 -1
  238. package/dist/EnglishConverter.js +0 -3
  239. package/dist/EnglishConverter.js.map +0 -1
  240. package/dist/FilipinoConverter.js +0 -3
  241. package/dist/FilipinoConverter.js.map +0 -1
  242. package/dist/FrenchBelgiumConverter.js +0 -3
  243. package/dist/FrenchBelgiumConverter.js.map +0 -1
  244. package/dist/FrenchConverter.js +0 -3
  245. package/dist/FrenchConverter.js.map +0 -1
  246. package/dist/GermanConverter.js +0 -3
  247. package/dist/GermanConverter.js.map +0 -1
  248. package/dist/GreekConverter.js +0 -3
  249. package/dist/GreekConverter.js.map +0 -1
  250. package/dist/GujaratiConverter.js +0 -3
  251. package/dist/GujaratiConverter.js.map +0 -1
  252. package/dist/HebrewConverter.js +0 -3
  253. package/dist/HebrewConverter.js.map +0 -1
  254. package/dist/HindiConverter.js +0 -3
  255. package/dist/HindiConverter.js.map +0 -1
  256. package/dist/HungarianConverter.js +0 -3
  257. package/dist/HungarianConverter.js.map +0 -1
  258. package/dist/IndonesianConverter.js +0 -3
  259. package/dist/IndonesianConverter.js.map +0 -1
  260. package/dist/ItalianConverter.js +0 -3
  261. package/dist/ItalianConverter.js.map +0 -1
  262. package/dist/JapaneseConverter.js +0 -3
  263. package/dist/JapaneseConverter.js.map +0 -1
  264. package/dist/KannadaConverter.js +0 -3
  265. package/dist/KannadaConverter.js.map +0 -1
  266. package/dist/KoreanConverter.js +0 -3
  267. package/dist/KoreanConverter.js.map +0 -1
  268. package/dist/LatvianConverter.js +0 -3
  269. package/dist/LatvianConverter.js.map +0 -1
  270. package/dist/LithuanianConverter.js +0 -3
  271. package/dist/LithuanianConverter.js.map +0 -1
  272. package/dist/MalayConverter.js +0 -3
  273. package/dist/MalayConverter.js.map +0 -1
  274. package/dist/MarathiConverter.js +0 -3
  275. package/dist/MarathiConverter.js.map +0 -1
  276. package/dist/NorwegianBokmalConverter.js +0 -3
  277. package/dist/NorwegianBokmalConverter.js.map +0 -1
  278. package/dist/PersianConverter.js +0 -3
  279. package/dist/PersianConverter.js.map +0 -1
  280. package/dist/PolishConverter.js +0 -3
  281. package/dist/PolishConverter.js.map +0 -1
  282. package/dist/PortugueseConverter.js +0 -3
  283. package/dist/PortugueseConverter.js.map +0 -1
  284. package/dist/PunjabiConverter.js +0 -3
  285. package/dist/PunjabiConverter.js.map +0 -1
  286. package/dist/RomanianConverter.js +0 -3
  287. package/dist/RomanianConverter.js.map +0 -1
  288. package/dist/RussianConverter.js +0 -3
  289. package/dist/RussianConverter.js.map +0 -1
  290. package/dist/SerbianCyrillicConverter.js +0 -3
  291. package/dist/SerbianCyrillicConverter.js.map +0 -1
  292. package/dist/SerbianLatinConverter.js +0 -3
  293. package/dist/SerbianLatinConverter.js.map +0 -1
  294. package/dist/SimplifiedChineseConverter.js +0 -3
  295. package/dist/SimplifiedChineseConverter.js.map +0 -1
  296. package/dist/SpanishConverter.js +0 -3
  297. package/dist/SpanishConverter.js.map +0 -1
  298. package/dist/SwahiliConverter.js +0 -3
  299. package/dist/SwahiliConverter.js.map +0 -1
  300. package/dist/SwedishConverter.js +0 -3
  301. package/dist/SwedishConverter.js.map +0 -1
  302. package/dist/TamilConverter.js +0 -3
  303. package/dist/TamilConverter.js.map +0 -1
  304. package/dist/TeluguConverter.js +0 -3
  305. package/dist/TeluguConverter.js.map +0 -1
  306. package/dist/ThaiConverter.js +0 -3
  307. package/dist/ThaiConverter.js.map +0 -1
  308. package/dist/TraditionalChineseConverter.js +0 -3
  309. package/dist/TraditionalChineseConverter.js.map +0 -1
  310. package/dist/TurkishConverter.js +0 -3
  311. package/dist/TurkishConverter.js.map +0 -1
  312. package/dist/UkrainianConverter.js +0 -3
  313. package/dist/UkrainianConverter.js.map +0 -1
  314. package/dist/UrduConverter.js +0 -3
  315. package/dist/UrduConverter.js.map +0 -1
  316. package/dist/VietnameseConverter.js +0 -3
  317. package/dist/VietnameseConverter.js.map +0 -1
  318. package/lib/classes/abstract-language.d.ts +0 -178
  319. package/lib/classes/abstract-language.js +0 -268
  320. package/lib/classes/greedy-scale-language.d.ts +0 -109
  321. package/lib/classes/greedy-scale-language.js +0 -201
  322. package/lib/classes/slavic-language.d.ts +0 -148
  323. package/lib/classes/slavic-language.js +0 -281
  324. package/lib/classes/south-asian-language.d.ts +0 -70
  325. package/lib/classes/south-asian-language.js +0 -154
  326. package/lib/classes/turkic-language.d.ts +0 -26
  327. package/lib/classes/turkic-language.js +0 -59
@@ -1,15 +1,21 @@
1
1
  /**
2
- * Spanish language converter.
2
+ * Converts a numeric value to Spanish words.
3
3
  *
4
- * Supports:
5
- * - Gender agreement (masculine/feminine via genderStem option)
6
- * - "y" conjunction between tens and units
7
- * - Special hundred forms (cien/ciento)
4
+ * This is the main public API. It accepts any valid numeric input
5
+ * (number, string, or bigint) and handles parsing internally.
6
+ *
7
+ * @param {number | string | bigint} value - The numeric value to convert
8
+ * @param {Object} [options] - Optional configuration
9
+ * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
10
+ * @returns {string} The number in Spanish words
11
+ * @throws {TypeError} If value is not a valid numeric type
12
+ * @throws {Error} If value is not a valid number format
13
+ *
14
+ * @example
15
+ * toWords(21) // 'veintiuno'
16
+ * toWords(21, {gender: 'feminine'}) // 'veintiuna'
17
+ * toWords(1000000) // 'un millón'
8
18
  */
9
- export class Spanish extends GreedyScaleLanguage {
10
- constructor(options?: {});
11
- scaleWords: (string | bigint)[][];
12
- /** Combines two word-sets with Spanish gender and conjunction rules. */
13
- combineWordSets(preceding: any, following: any): any;
14
- }
15
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js';
19
+ export function toWords(value: number | string | bigint, options?: {
20
+ gender?: "masculine" | "feminine" | undefined;
21
+ }): string;
@@ -1,121 +1,335 @@
1
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js'
2
-
3
1
  /**
4
- * Spanish language converter.
2
+ * Spanish language converter - Functional Implementation
3
+ *
4
+ * A performance-optimized number-to-words converter using precomputed lookup tables.
5
+ * Self-contained module with its own input validation, ready for subpath exports.
6
+ *
7
+ * Key optimization: Precompute all segment values (0-999) at module load.
8
+ * This eliminates all per-call string manipulation for segment conversion.
5
9
  *
6
- * Supports:
7
- * - Gender agreement (masculine/feminine via genderStem option)
8
- * - "y" conjunction between tens and units
9
- * - Special hundred forms (cien/ciento)
10
+ * Spanish-specific rules (handled in precomputation):
11
+ * - Gender agreement: uno/una, veintiuno/veintiuna, hundreds
12
+ * - Special twenties: veinte, veintiuno, veintidós, ... veintinueve
13
+ * - "y" conjunction: treinta y uno (only 30-99 with ones)
14
+ * - "cien" for exact 100, "ciento/cienta" otherwise
15
+ * - Irregular hundreds: quinientos, setecientos, novecientos
16
+ * - Compound long scale: millón, mil millones, billón, mil billones
17
+ * - "un" before millón (not "uno"), omit before mil
10
18
  */
11
- export class Spanish extends GreedyScaleLanguage {
12
- negativeWord = 'menos'
13
- decimalSeparatorWord = 'punto'
14
- zeroWord = 'cero'
15
-
16
- scaleWords = [
17
- [1_000_000_000_000_000_000_000_000n, 'cuatrillón'],
18
- [1_000_000_000_000_000_000n, 'trillón'],
19
- [1_000_000_000_000n, 'billón'],
20
- [1_000_000n, 'millón'],
21
- [1000n, 'mil'],
22
- [100n, 'cien'],
23
- [90n, 'noventa'],
24
- [80n, 'ochenta'],
25
- [70n, 'setenta'],
26
- [60n, 'sesenta'],
27
- [50n, 'cincuenta'],
28
- [40n, 'cuarenta'],
29
- [30n, 'treinta'],
30
- [29n, 'veintinueve'],
31
- [28n, 'veintiocho'],
32
- [27n, 'veintisiete'],
33
- [26n, 'veintiséis'],
34
- [25n, 'veinticinco'],
35
- [24n, 'veinticuatro'],
36
- [23n, 'veintitrés'],
37
- [22n, 'veintidós'],
38
- [21n, 'veintiuno'],
39
- [20n, 'veinte'],
40
- [19n, 'diecinueve'],
41
- [18n, 'dieciocho'],
42
- [17n, 'diecisiete'],
43
- [16n, 'dieciseis'],
44
- [15n, 'quince'],
45
- [14n, 'catorce'],
46
- [13n, 'trece'],
47
- [12n, 'doce'],
48
- [11n, 'once'],
49
- [10n, 'diez'],
50
- [9n, 'nueve'],
51
- [8n, 'ocho'],
52
- [7n, 'siete'],
53
- [6n, 'seis'],
54
- [5n, 'cinco'],
55
- [4n, 'cuatro'],
56
- [3n, 'tres'],
57
- [2n, 'dos'],
58
- [1n, 'uno'],
59
- [0n, 'cero']
60
- ]
61
-
62
- constructor (options = {}) {
63
- super()
64
-
65
- this.setOptions({
66
- gender: 'masculine'
67
- }, options)
68
-
69
- // Apply gender agreement to scale words if feminine
70
- if (this.options.gender === 'feminine') {
71
- this.scaleWords = this.scaleWords.map(([value, word]) => {
72
- if (word === 'veintiuno') return [value, 'veintiuna']
73
- if (word === 'uno') return [value, 'una']
74
- return [value, word]
75
- })
76
- }
19
+
20
+ import { parseNumericValue } from '../utils/parse-numeric.js'
21
+ import { validateOptions } from '../utils/validate-options.js'
22
+
23
+ // ============================================================================
24
+ // Vocabulary (module-level constants)
25
+ // ============================================================================
26
+
27
+ const ONES_MASC = ['', 'uno', 'dos', 'tres', 'cuatro', 'cinco', 'seis', 'siete', 'ocho', 'nueve']
28
+ const ONES_FEM = ['', 'una', 'dos', 'tres', 'cuatro', 'cinco', 'seis', 'siete', 'ocho', 'nueve']
29
+
30
+ const TEENS = ['diez', 'once', 'doce', 'trece', 'catorce', 'quince', 'dieciseis', 'diecisiete', 'dieciocho', 'diecinueve']
31
+
32
+ // 20-29 have special compound forms
33
+ const TWENTIES_MASC = ['veinte', 'veintiuno', 'veintidós', 'veintitrés', 'veinticuatro', 'veinticinco', 'veintiséis', 'veintisiete', 'veintiocho', 'veintinueve']
34
+ const TWENTIES_FEM = ['veinte', 'veintiuna', 'veintidós', 'veintitrés', 'veinticuatro', 'veinticinco', 'veintiséis', 'veintisiete', 'veintiocho', 'veintinueve']
35
+
36
+ const TENS = ['', '', '', 'treinta', 'cuarenta', 'cincuenta', 'sesenta', 'setenta', 'ochenta', 'noventa']
37
+
38
+ // Irregular hundreds
39
+ const HUNDREDS_MASC = ['', 'ciento', 'doscientos', 'trescientos', 'cuatrocientos', 'quinientos', 'seiscientos', 'setecientos', 'ochocientos', 'novecientos']
40
+ const HUNDREDS_FEM = ['', 'cienta', 'doscientas', 'trescientas', 'cuatrocientas', 'quinientas', 'seiscientas', 'setecientas', 'ochocientas', 'novecientas']
41
+
42
+ // Scale words (compound long scale)
43
+ const SCALES = ['millón', 'billón', 'trillón', 'cuatrillón']
44
+ const SCALES_PLURAL = ['millones', 'billones', 'trillones', 'cuatrillones']
45
+
46
+ const THOUSAND = 'mil'
47
+ const ZERO = 'cero'
48
+ const NEGATIVE = 'menos'
49
+ const DECIMAL_SEP = 'punto'
50
+
51
+ // ============================================================================
52
+ // Precomputed Lookup Tables (built once at module load)
53
+ // ============================================================================
54
+
55
+ /**
56
+ * Builds segment word for 0-999.
57
+ * @param {number} n - Segment value
58
+ * @param {boolean} feminine - Use feminine forms
59
+ * @returns {string} Spanish word
60
+ */
61
+ function buildSegment (n, feminine) {
62
+ if (n === 0) return ''
63
+
64
+ // Special case: exact 100 is "cien" (no gender)
65
+ if (n === 100) return 'cien'
66
+
67
+ const ones = n % 10
68
+ const tens = Math.floor(n / 10) % 10
69
+ const hundreds = Math.floor(n / 100)
70
+ const tensOnes = n % 100
71
+
72
+ const parts = []
73
+
74
+ // Hundreds
75
+ if (hundreds > 0) {
76
+ const hundredsArr = feminine ? HUNDREDS_FEM : HUNDREDS_MASC
77
+ parts.push(hundredsArr[hundreds])
77
78
  }
78
79
 
79
- /** Combines two word-sets with Spanish gender and conjunction rules. */
80
- combineWordSets (preceding, following) {
81
- let precedingWord = Object.keys(preceding)[0]
82
- let followingWord = Object.keys(following)[0]
83
- const precedingValue = Object.values(preceding)[0]
84
- const followingValue = Object.values(following)[0]
85
- const genderStem = this.options.gender === 'feminine' ? 'a' : 'o'
86
-
87
- if (precedingValue === 1n) {
88
- if (followingValue < 1_000_000n) return following
89
- precedingWord = 'un'
90
- } else if (precedingValue === 100n && followingValue % 1000n !== 0n) {
91
- precedingWord += 't' + genderStem
80
+ // Tens and ones
81
+ if (tensOnes === 0) {
82
+ // Just hundreds
83
+ } else if (tensOnes < 10) {
84
+ // Single digit
85
+ const onesArr = feminine ? ONES_FEM : ONES_MASC
86
+ parts.push(onesArr[tensOnes])
87
+ } else if (tensOnes < 20) {
88
+ // 10-19: teens
89
+ parts.push(TEENS[ones])
90
+ } else if (tensOnes < 30) {
91
+ // 20-29: special twenties
92
+ const twentiesArr = feminine ? TWENTIES_FEM : TWENTIES_MASC
93
+ parts.push(twentiesArr[ones])
94
+ } else {
95
+ // 30-99: tens y ones
96
+ if (ones === 0) {
97
+ parts.push(TENS[tens])
98
+ } else {
99
+ const onesArr = feminine ? ONES_FEM : ONES_MASC
100
+ parts.push(TENS[tens] + ' y ' + onesArr[ones])
92
101
  }
102
+ }
103
+
104
+ return parts.join(' ')
105
+ }
106
+
107
+ // Precompute all 1000 segment words (0-999) for masculine
108
+ const SEGMENTS_MASC = new Array(1000)
109
+ for (let i = 0; i < 1000; i++) {
110
+ SEGMENTS_MASC[i] = buildSegment(i, false)
111
+ }
112
+
113
+ // Precompute all 1000 segment words (0-999) for feminine
114
+ const SEGMENTS_FEM = new Array(1000)
115
+ for (let i = 0; i < 1000; i++) {
116
+ SEGMENTS_FEM[i] = buildSegment(i, true)
117
+ }
118
+
119
+ // ============================================================================
120
+ // Helper Functions
121
+ // ============================================================================
122
+
123
+ /**
124
+ * Gets scale word for Spanish compound long scale.
125
+ *
126
+ * @param {number} scaleIndex - Scale level (1 = thousand, 2 = million, etc.)
127
+ * @param {bigint} segment - Segment value for pluralization
128
+ * @returns {string} Scale word
129
+ */
130
+ function getScaleWord (scaleIndex, segment) {
131
+ if (scaleIndex === 1) return THOUSAND
132
+
133
+ // Even indices (2, 4, 6, 8): millón, billón, trillón, cuatrillón
134
+ // Odd indices > 1 (3, 5, 7): mil millones, mil billones, mil trillones
135
+ if (scaleIndex % 2 === 0) {
136
+ const arrayIndex = (scaleIndex / 2) - 1
137
+ const baseWord = SCALES[arrayIndex]
138
+ if (!baseWord) return ''
139
+ return segment > 1n ? SCALES_PLURAL[arrayIndex] : baseWord
140
+ } else {
141
+ // Compound: "mil millones" pattern
142
+ const arrayIndex = ((scaleIndex - 1) / 2) - 1
143
+ const pluralWord = SCALES_PLURAL[arrayIndex]
144
+ if (!pluralWord) return THOUSAND
145
+ return THOUSAND + ' ' + pluralWord
146
+ }
147
+ }
148
+
149
+ // ============================================================================
150
+ // Conversion Functions
151
+ // ============================================================================
152
+
153
+ /**
154
+ * Converts a non-negative integer to Spanish words.
155
+ *
156
+ * @param {bigint} n - Non-negative integer to convert
157
+ * @param {boolean} feminine - Use feminine forms
158
+ * @returns {string} Spanish words
159
+ */
160
+ function integerToWords (n, feminine) {
161
+ if (n === 0n) return ZERO
162
+
163
+ const segments = feminine ? SEGMENTS_FEM : SEGMENTS_MASC
164
+
165
+ // Fast path: numbers < 1000 (direct lookup)
166
+ if (n < 1000n) {
167
+ return segments[Number(n)]
168
+ }
93
169
 
94
- if (followingValue < precedingValue) {
95
- if (precedingValue < 100n) {
96
- return { [`${precedingWord} y ${followingWord}`]: precedingValue + followingValue }
170
+ // Fast path: numbers < 1,000,000 (thousands)
171
+ if (n < 1_000_000n) {
172
+ const thousands = Number(n / 1000n)
173
+ const remainder = Number(n % 1000n)
174
+
175
+ let result
176
+ if (thousands === 1) {
177
+ // "mil" not "uno mil"
178
+ result = THOUSAND
179
+ } else {
180
+ // Use masculine for thousands segment, but check for "uno" → omit before mil
181
+ const thousandsWord = SEGMENTS_MASC[thousands]
182
+ // "uno mil" → "mil" (handled in joinSegments equivalent)
183
+ if (thousandsWord === 'uno' || thousandsWord === 'una') {
184
+ result = THOUSAND
185
+ } else {
186
+ result = thousandsWord + ' ' + THOUSAND
97
187
  }
98
- return { [`${precedingWord} ${followingWord}`]: precedingValue + followingValue }
99
188
  }
100
189
 
101
- if (followingValue % 1_000_000n === 0n && precedingValue > 1n) {
102
- followingWord = followingWord.slice(0, -3) + 'lones'
190
+ if (remainder > 0) {
191
+ result += ' ' + segments[remainder]
103
192
  }
104
193
 
105
- if (followingValue === 100n) {
106
- if (precedingValue === 5n) {
107
- precedingWord = 'quinien'
108
- followingWord = ''
109
- } else if (precedingValue === 7n) {
110
- precedingWord = 'sete'
111
- } else if (precedingValue === 9n) {
112
- precedingWord = 'nove'
194
+ return result
195
+ }
196
+
197
+ // For numbers >= 1,000,000, use scale decomposition
198
+ return buildLargeNumberWords(n, feminine)
199
+ }
200
+
201
+ /**
202
+ * Builds words for numbers >= 1,000,000.
203
+ * Uses BigInt division for faster segment extraction.
204
+ *
205
+ * @param {bigint} n - Number >= 1,000,000
206
+ * @param {boolean} feminine - Use feminine forms
207
+ * @returns {string} Spanish words
208
+ */
209
+ function buildLargeNumberWords (n, feminine) {
210
+ // Extract segments using BigInt division (faster than string slicing)
211
+ // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
212
+ const segmentValues = []
213
+ let temp = n
214
+ while (temp > 0n) {
215
+ segmentValues.push(temp % 1000n)
216
+ temp = temp / 1000n
217
+ }
218
+
219
+ const segments = feminine ? SEGMENTS_FEM : SEGMENTS_MASC
220
+
221
+ // Build result string directly
222
+ let result = ''
223
+
224
+ for (let i = segmentValues.length - 1; i >= 0; i--) {
225
+ const segment = segmentValues[i]
226
+ if (segment === 0n) continue
227
+
228
+ const scaleWord = i > 0 ? getScaleWord(i, segment) : ''
229
+
230
+ if (result) result += ' '
231
+
232
+ if (i === 0) {
233
+ // Units segment
234
+ result += segments[Number(segment)]
235
+ } else if (i === 1) {
236
+ // Thousands: omit "uno" before mil
237
+ if (segment === 1n) {
238
+ result += THOUSAND
239
+ } else {
240
+ result += SEGMENTS_MASC[Number(segment)] + ' ' + scaleWord
241
+ }
242
+ } else if (i % 2 === 1) {
243
+ // Odd scale indices (3, 5, 7): "mil millones", "mil billones", etc.
244
+ // Omit "uno" before these compound scales
245
+ if (segment === 1n) {
246
+ result += scaleWord
247
+ } else {
248
+ result += SEGMENTS_MASC[Number(segment)] + ' ' + scaleWord
113
249
  }
114
- followingWord += 't' + genderStem + 's'
115
250
  } else {
116
- followingWord = ' ' + followingWord
251
+ // Even scale indices (2, 4, 6): millón, billón, trillón
252
+ if (segment === 1n) {
253
+ // "un millón" not "uno millón"
254
+ result += 'un ' + scaleWord
255
+ } else {
256
+ // Use masculine for scale segment
257
+ result += SEGMENTS_MASC[Number(segment)] + ' ' + scaleWord
258
+ }
117
259
  }
260
+ }
261
+
262
+ return result
263
+ }
264
+
265
+ /**
266
+ * Converts decimal digits to Spanish words.
267
+ *
268
+ * @param {string} decimalPart - Decimal digits (without the point)
269
+ * @param {boolean} feminine - Use feminine forms
270
+ * @returns {string} Spanish words for decimal part
271
+ */
272
+ function decimalPartToWords (decimalPart, feminine) {
273
+ let result = ''
274
+
275
+ // Handle leading zeros
276
+ let i = 0
277
+ while (i < decimalPart.length && decimalPart[i] === '0') {
278
+ if (result) result += ' '
279
+ result += ZERO
280
+ i++
281
+ }
282
+
283
+ // Convert remainder as a single number
284
+ const remainder = decimalPart.slice(i)
285
+ if (remainder) {
286
+ if (result) result += ' '
287
+ result += integerToWords(BigInt(remainder), feminine)
288
+ }
289
+
290
+ return result
291
+ }
292
+
293
+ /**
294
+ * Converts a numeric value to Spanish words.
295
+ *
296
+ * This is the main public API. It accepts any valid numeric input
297
+ * (number, string, or bigint) and handles parsing internally.
298
+ *
299
+ * @param {number | string | bigint} value - The numeric value to convert
300
+ * @param {Object} [options] - Optional configuration
301
+ * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender
302
+ * @returns {string} The number in Spanish words
303
+ * @throws {TypeError} If value is not a valid numeric type
304
+ * @throws {Error} If value is not a valid number format
305
+ *
306
+ * @example
307
+ * toWords(21) // 'veintiuno'
308
+ * toWords(21, {gender: 'feminine'}) // 'veintiuna'
309
+ * toWords(1000000) // 'un millón'
310
+ */
311
+ function toWords (value, options) {
312
+ options = validateOptions(options)
313
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
314
+ const feminine = options.gender === 'feminine'
315
+
316
+ let result = ''
118
317
 
119
- return { [`${precedingWord}${followingWord}`]: precedingValue * followingValue }
318
+ if (isNegative) {
319
+ result = NEGATIVE + ' '
120
320
  }
321
+
322
+ result += integerToWords(integerPart, feminine)
323
+
324
+ if (decimalPart) {
325
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, feminine)
326
+ }
327
+
328
+ return result
121
329
  }
330
+
331
+ // ============================================================================
332
+ // Public API
333
+ // ============================================================================
334
+
335
+ export { toWords }
@@ -1,47 +1,7 @@
1
1
  /**
2
- * Persian language converter.
2
+ * Converts a numeric value to Persian words.
3
3
  *
4
- * Supports:
5
- * - "و" (and) conjunction for compound numbers
6
- * - Recursive conversion for larger numbers
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Persian words
7
6
  */
8
- export class Persian extends AbstractLanguage {
9
- /**
10
- * Words for digits 1-9.
11
- * @type {Object.<number, string>}
12
- */
13
- onesWords: {
14
- [x: number]: string;
15
- };
16
- /**
17
- * Words for teen numbers (10-19).
18
- * @type {Object.<number, string>}
19
- */
20
- teensWords: {
21
- [x: number]: string;
22
- };
23
- /**
24
- * Words for multiples of ten (20-90).
25
- * @type {Object.<number, string>}
26
- */
27
- tensWords: {
28
- [x: number]: string;
29
- };
30
- /**
31
- * Words for hundreds (100-900).
32
- * @type {Object.<number, string>}
33
- */
34
- hundredsWords: {
35
- [x: number]: string;
36
- };
37
- /**
38
- * Scale magnitude words.
39
- * @type {Object.<number, string>}
40
- */
41
- scaleWords: {
42
- [x: number]: string;
43
- };
44
- /** Converts integer part using categorized word tables. */
45
- integerToWords(integerPart: any): any;
46
- }
47
- import { AbstractLanguage } from '../classes/abstract-language.js';
7
+ export function toWords(value: number | string | bigint): string;