n2words 1.24.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 (280) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +183 -156
  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 -2
  8. package/dist/languages/ar.js.map +1 -1
  9. package/dist/languages/az.js +3 -2
  10. package/dist/languages/az.js.map +1 -1
  11. package/dist/languages/bn.js +3 -2
  12. package/dist/languages/bn.js.map +1 -1
  13. package/dist/languages/cs.js +3 -2
  14. package/dist/languages/cs.js.map +1 -1
  15. package/dist/languages/da.js +3 -2
  16. package/dist/languages/da.js.map +1 -1
  17. package/dist/languages/de.js +3 -2
  18. package/dist/languages/de.js.map +1 -1
  19. package/dist/languages/el.js +3 -2
  20. package/dist/languages/el.js.map +1 -1
  21. package/dist/languages/en.js +3 -2
  22. package/dist/languages/en.js.map +1 -1
  23. package/dist/languages/es.js +3 -2
  24. package/dist/languages/es.js.map +1 -1
  25. package/dist/languages/fa.js +3 -2
  26. package/dist/languages/fa.js.map +1 -1
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -2
  30. package/dist/languages/fil.js.map +1 -1
  31. package/dist/languages/fr-BE.js +3 -2
  32. package/dist/languages/fr-BE.js.map +1 -1
  33. package/dist/languages/fr.js +3 -2
  34. package/dist/languages/fr.js.map +1 -1
  35. package/dist/languages/gu.js +3 -2
  36. package/dist/languages/gu.js.map +1 -1
  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 -2
  42. package/dist/languages/he.js.map +1 -1
  43. package/dist/languages/hi.js +3 -2
  44. package/dist/languages/hi.js.map +1 -1
  45. package/dist/languages/hr.js +3 -2
  46. package/dist/languages/hr.js.map +1 -1
  47. package/dist/languages/hu.js +3 -2
  48. package/dist/languages/hu.js.map +1 -1
  49. package/dist/languages/id.js +3 -2
  50. package/dist/languages/id.js.map +1 -1
  51. package/dist/languages/it.js +3 -2
  52. package/dist/languages/it.js.map +1 -1
  53. package/dist/languages/ja.js +3 -2
  54. package/dist/languages/ja.js.map +1 -1
  55. package/dist/languages/kn.js +3 -2
  56. package/dist/languages/kn.js.map +1 -1
  57. package/dist/languages/ko.js +3 -2
  58. package/dist/languages/ko.js.map +1 -1
  59. package/dist/languages/lt.js +3 -2
  60. package/dist/languages/lt.js.map +1 -1
  61. package/dist/languages/lv.js +3 -2
  62. package/dist/languages/lv.js.map +1 -1
  63. package/dist/languages/mr.js +3 -2
  64. package/dist/languages/mr.js.map +1 -1
  65. package/dist/languages/ms.js +3 -2
  66. package/dist/languages/ms.js.map +1 -1
  67. package/dist/languages/nb.js +3 -2
  68. package/dist/languages/nb.js.map +1 -1
  69. package/dist/languages/nl.js +3 -2
  70. package/dist/languages/nl.js.map +1 -1
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -2
  74. package/dist/languages/pl.js.map +1 -1
  75. package/dist/languages/pt.js +3 -2
  76. package/dist/languages/pt.js.map +1 -1
  77. package/dist/languages/ro.js +3 -2
  78. package/dist/languages/ro.js.map +1 -1
  79. package/dist/languages/ru.js +3 -2
  80. package/dist/languages/ru.js.map +1 -1
  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 -2
  84. package/dist/languages/sr-Latn.js.map +1 -1
  85. package/dist/languages/sv.js +3 -2
  86. package/dist/languages/sv.js.map +1 -1
  87. package/dist/languages/sw.js +3 -2
  88. package/dist/languages/sw.js.map +1 -1
  89. package/dist/languages/ta.js +3 -2
  90. package/dist/languages/ta.js.map +1 -1
  91. package/dist/languages/te.js +3 -2
  92. package/dist/languages/te.js.map +1 -1
  93. package/dist/languages/th.js +3 -2
  94. package/dist/languages/th.js.map +1 -1
  95. package/dist/languages/tr.js +3 -2
  96. package/dist/languages/tr.js.map +1 -1
  97. package/dist/languages/uk.js +3 -2
  98. package/dist/languages/uk.js.map +1 -1
  99. package/dist/languages/ur.js +3 -2
  100. package/dist/languages/ur.js.map +1 -1
  101. package/dist/languages/vi.js +3 -2
  102. package/dist/languages/vi.js.map +1 -1
  103. package/dist/languages/zh-Hans.js +3 -2
  104. package/dist/languages/zh-Hans.js.map +1 -1
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +3 -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 +17 -0
  114. package/lib/languages/ar.js +171 -209
  115. package/lib/languages/az.d.ts +7 -0
  116. package/lib/languages/az.js +167 -49
  117. package/lib/languages/bn.d.ts +7 -0
  118. package/lib/languages/bn.js +142 -123
  119. package/lib/languages/cs.d.ts +18 -0
  120. package/lib/languages/cs.js +303 -176
  121. package/lib/languages/da.d.ts +14 -0
  122. package/lib/languages/da.js +267 -139
  123. package/lib/languages/de.d.ts +17 -0
  124. package/lib/languages/de.js +310 -113
  125. package/lib/languages/el.d.ts +14 -0
  126. package/lib/languages/el.js +225 -98
  127. package/lib/languages/en.d.ts +17 -0
  128. package/lib/languages/en.js +235 -102
  129. package/lib/languages/es.d.ts +21 -0
  130. package/lib/languages/es.js +307 -125
  131. package/lib/languages/fa.d.ts +7 -0
  132. package/lib/languages/fa.js +115 -108
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +7 -0
  136. package/lib/languages/fil.js +199 -139
  137. package/lib/languages/fr-BE.d.ts +11 -0
  138. package/lib/languages/fr-BE.js +287 -48
  139. package/lib/languages/fr.d.ts +21 -0
  140. package/lib/languages/fr.js +343 -119
  141. package/lib/languages/gu.d.ts +7 -0
  142. package/lib/languages/gu.js +125 -144
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +13 -0
  146. package/lib/languages/hbo.js +300 -0
  147. package/lib/languages/he.d.ts +13 -0
  148. package/lib/languages/he.js +230 -283
  149. package/lib/languages/hi.d.ts +7 -0
  150. package/lib/languages/hi.js +142 -123
  151. package/lib/languages/hr.d.ts +11 -0
  152. package/lib/languages/hr.js +190 -129
  153. package/lib/languages/hu.d.ts +7 -0
  154. package/lib/languages/hu.js +194 -133
  155. package/lib/languages/id.d.ts +7 -0
  156. package/lib/languages/id.js +167 -140
  157. package/lib/languages/it.d.ts +19 -0
  158. package/lib/languages/it.js +337 -108
  159. package/lib/languages/ja.d.ts +17 -0
  160. package/lib/languages/ja.js +224 -155
  161. package/lib/languages/kn.d.ts +7 -0
  162. package/lib/languages/kn.js +128 -62
  163. package/lib/languages/ko.d.ts +14 -0
  164. package/lib/languages/ko.js +250 -70
  165. package/lib/languages/lt.d.ts +18 -0
  166. package/lib/languages/lt.js +287 -148
  167. package/lib/languages/lv.d.ts +18 -0
  168. package/lib/languages/lv.js +291 -123
  169. package/lib/languages/mr.d.ts +7 -0
  170. package/lib/languages/mr.js +125 -144
  171. package/lib/languages/ms.d.ts +7 -0
  172. package/lib/languages/ms.js +171 -112
  173. package/lib/languages/nb.d.ts +14 -0
  174. package/lib/languages/nb.js +275 -100
  175. package/lib/languages/nl.d.ts +26 -0
  176. package/lib/languages/nl.js +307 -174
  177. package/lib/languages/pa.d.ts +7 -0
  178. package/lib/languages/pa.js +163 -0
  179. package/lib/languages/pl.d.ts +22 -0
  180. package/lib/languages/pl.js +299 -158
  181. package/lib/languages/pt.d.ts +17 -0
  182. package/lib/languages/pt.js +279 -120
  183. package/lib/languages/ro.d.ts +18 -0
  184. package/lib/languages/ro.js +214 -337
  185. package/lib/languages/ru.d.ts +11 -0
  186. package/lib/languages/ru.js +219 -95
  187. package/lib/languages/sr-Cyrl.d.ts +11 -0
  188. package/lib/languages/sr-Cyrl.js +215 -0
  189. package/lib/languages/sr-Latn.d.ts +11 -0
  190. package/lib/languages/sr-Latn.js +190 -132
  191. package/lib/languages/sv.d.ts +14 -0
  192. package/lib/languages/sv.js +280 -103
  193. package/lib/languages/sw.d.ts +7 -0
  194. package/lib/languages/sw.js +135 -103
  195. package/lib/languages/ta.d.ts +7 -0
  196. package/lib/languages/ta.js +133 -205
  197. package/lib/languages/te.d.ts +7 -0
  198. package/lib/languages/te.js +148 -213
  199. package/lib/languages/th.d.ts +7 -0
  200. package/lib/languages/th.js +139 -101
  201. package/lib/languages/tr.d.ts +18 -0
  202. package/lib/languages/tr.js +246 -66
  203. package/lib/languages/uk.d.ts +11 -0
  204. package/lib/languages/uk.js +197 -101
  205. package/lib/languages/ur.d.ts +7 -0
  206. package/lib/languages/ur.js +160 -123
  207. package/lib/languages/vi.d.ts +17 -0
  208. package/lib/languages/vi.js +287 -164
  209. package/lib/languages/zh-Hans.d.ts +11 -0
  210. package/lib/languages/zh-Hans.js +159 -142
  211. package/lib/languages/zh-Hant.d.ts +11 -0
  212. package/lib/languages/zh-Hant.js +202 -0
  213. package/lib/n2words.d.ts +53 -0
  214. package/lib/n2words.js +91 -227
  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 +118 -67
  222. package/dist/languages/pa-Guru.js +0 -2
  223. package/dist/languages/pa-Guru.js.map +0 -1
  224. package/lib/classes/abstract-language.js +0 -261
  225. package/lib/classes/greedy-scale-language.js +0 -195
  226. package/lib/classes/slavic-language.js +0 -251
  227. package/lib/classes/south-asian-language.js +0 -161
  228. package/lib/classes/turkic-language.js +0 -63
  229. package/lib/languages/pa-Guru.js +0 -126
  230. package/typings/classes/abstract-language.d.ts +0 -144
  231. package/typings/classes/greedy-scale-language.d.ts +0 -148
  232. package/typings/classes/slavic-language.d.ts +0 -145
  233. package/typings/classes/south-asian-language.d.ts +0 -101
  234. package/typings/classes/turkic-language.d.ts +0 -42
  235. package/typings/languages/ar.d.ts +0 -93
  236. package/typings/languages/az.d.ts +0 -25
  237. package/typings/languages/bn.d.ts +0 -1
  238. package/typings/languages/cs.d.ts +0 -120
  239. package/typings/languages/da.d.ts +0 -53
  240. package/typings/languages/de.d.ts +0 -26
  241. package/typings/languages/el.d.ts +0 -11
  242. package/typings/languages/en.d.ts +0 -30
  243. package/typings/languages/es.d.ts +0 -43
  244. package/typings/languages/fa.d.ts +0 -81
  245. package/typings/languages/fil.d.ts +0 -12
  246. package/typings/languages/fr-BE.d.ts +0 -41
  247. package/typings/languages/fr.d.ts +0 -43
  248. package/typings/languages/gu.d.ts +0 -12
  249. package/typings/languages/he.d.ts +0 -197
  250. package/typings/languages/hi.d.ts +0 -1
  251. package/typings/languages/hr.d.ts +0 -110
  252. package/typings/languages/hu.d.ts +0 -37
  253. package/typings/languages/id.d.ts +0 -69
  254. package/typings/languages/it.d.ts +0 -51
  255. package/typings/languages/ja.d.ts +0 -58
  256. package/typings/languages/kn.d.ts +0 -11
  257. package/typings/languages/ko.d.ts +0 -25
  258. package/typings/languages/lt.d.ts +0 -110
  259. package/typings/languages/lv.d.ts +0 -99
  260. package/typings/languages/mr.d.ts +0 -12
  261. package/typings/languages/ms.d.ts +0 -37
  262. package/typings/languages/nb.d.ts +0 -27
  263. package/typings/languages/nl.d.ts +0 -65
  264. package/typings/languages/pa-Guru.d.ts +0 -1
  265. package/typings/languages/pl.d.ts +0 -116
  266. package/typings/languages/pt.d.ts +0 -39
  267. package/typings/languages/ro.d.ts +0 -229
  268. package/typings/languages/ru.d.ts +0 -108
  269. package/typings/languages/sr-Latn.d.ts +0 -98
  270. package/typings/languages/sv.d.ts +0 -30
  271. package/typings/languages/sw.d.ts +0 -1
  272. package/typings/languages/ta.d.ts +0 -1
  273. package/typings/languages/te.d.ts +0 -1
  274. package/typings/languages/th.d.ts +0 -1
  275. package/typings/languages/tr.d.ts +0 -46
  276. package/typings/languages/uk.d.ts +0 -117
  277. package/typings/languages/ur.d.ts +0 -1
  278. package/typings/languages/vi.d.ts +0 -116
  279. package/typings/languages/zh-Hans.d.ts +0 -57
  280. package/typings/n2words.d.ts +0 -177
@@ -1,153 +1,335 @@
1
- import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
1
+ /**
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.
9
+ *
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
18
+ */
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
+ // ============================================================================
2
54
 
3
55
  /**
4
- * @typedef {Object} SpanishOptions
5
- * @property {string} [genderStem='o'] Masculine 'o' or feminine 'a' ending.
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
6
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])
78
+ }
79
+
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])
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
+ // ============================================================================
7
122
 
8
123
  /**
9
- * Spanish language converter.
124
+ * Gets scale word for Spanish compound long scale.
10
125
  *
11
- * Handles Spanish grammatical features:
12
- * - Gender agreement for numbers (masculine by default, feminine via `genderStem`)
13
- * - "y" (and) between tens and units (e.g., "veinte y uno")
14
- * - Special forms for hundreds (e.g., "cien", "ciento", "doscientos")
15
- * - Million pluralization
126
+ * @param {number} scaleIndex - Scale level (1 = thousand, 2 = million, etc.)
127
+ * @param {bigint} segment - Segment value for pluralization
128
+ * @returns {string} Scale word
16
129
  */
17
- export class Spanish extends GreedyScaleLanguage {
18
- negativeWord = 'menos'
19
- decimalSeparatorWord = 'punto'
20
- zeroWord = 'cero'
21
- scaleWordPairs = [
22
- [1_000_000_000_000_000_000_000_000n, 'cuatrillón'],
23
- [1_000_000_000_000_000_000n, 'trillón'],
24
- [1_000_000_000_000n, 'billón'],
25
- [1_000_000n, 'millón'],
26
- [1000n, 'mil'],
27
- [100n, 'cien'],
28
- [90n, 'noventa'],
29
- [80n, 'ochenta'],
30
- [70n, 'setenta'],
31
- [60n, 'sesenta'],
32
- [50n, 'cincuenta'],
33
- [40n, 'cuarenta'],
34
- [30n, 'treinta'],
35
- [29n, 'veintinueve'],
36
- [28n, 'veintiocho'],
37
- [27n, 'veintisiete'],
38
- [26n, 'veintiséis'],
39
- [25n, 'veinticinco'],
40
- [24n, 'veinticuatro'],
41
- [23n, 'veintitrés'],
42
- [22n, 'veintidós'],
43
- [21n, 'veintiuno'],
44
- [20n, 'veinte'],
45
- [19n, 'diecinueve'],
46
- [18n, 'dieciocho'],
47
- [17n, 'diecisiete'],
48
- [16n, 'dieciseis'],
49
- [15n, 'quince'],
50
- [14n, 'catorce'],
51
- [13n, 'trece'],
52
- [12n, 'doce'],
53
- [11n, 'once'],
54
- [10n, 'diez'],
55
- [9n, 'nueve'],
56
- [8n, 'ocho'],
57
- [7n, 'siete'],
58
- [6n, 'seis'],
59
- [5n, 'cinco'],
60
- [4n, 'cuatro'],
61
- [3n, 'tres'],
62
- [2n, 'dos'],
63
- [1n, 'uno'],
64
- [0n, 'cero']
65
- ]
66
-
67
- /**
68
- * Initializes the Spanish converter.
69
- *
70
- * @param {SpanishOptions} [options={}] Configuration options.
71
- */
72
- constructor ({ genderStem = 'o' } = {}) {
73
- super()
74
-
75
- this.genderStem = genderStem
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
76
146
  }
147
+ }
77
148
 
78
- /**
79
- * Merges two adjacent word-number pairs according to Spanish grammar rules.
80
- *
81
- * Spanish-specific rules:
82
- * - Implicit "uno": `mergeScales({ 'uno': 1n }, { 'mil': 1000n })` → `{ 'mil': 1000n }`
83
- * - "y" (and) between tens and units: `mergeScales({ 'veinte': 20n }, { 'uno': 1n })` → `{ 'veinte y uno': 21n }`
84
- * - Gender agreement for hundreds: "cien" + suffix based on genderStem
85
- * - Special forms for hundreds (cien/ciento/quinientos/setecientos/novecientos)
86
- * - Million pluralization when coefficient > 1: "millones" instead of "millón"
87
- *
88
- * @param {Object} currentPair The left operand as `{ word: BigInt }`.
89
- * @param {Object} nextPair The right operand as `{ word: BigInt }`.
90
- * @returns {Object} Merged pair with combined word and resulting numeric value.
91
- *
92
- * @example
93
- * mergeScales({ 'uno': 1n }, { 'mil': 1000n }); // { 'mil': 1000n }
94
- * mergeScales({ 'veinte': 20n }, { 'tres': 3n }); // { 'veinte y tres': 23n }
95
- */
96
- mergeScales (currentPair, nextPair) {
97
- let currentWord = Object.keys(currentPair)[0]
98
- let nextWord = Object.keys(nextPair)[0]
99
- const currentNumber = Object.values(currentPair)[0]
100
- const nextNumber = Object.values(nextPair)[0]
101
-
102
- if (currentNumber === 1n) {
103
- if (nextNumber < 1_000_000n) return nextPair
104
- currentWord = 'un'
105
- } else if (currentNumber === 100n && nextNumber % 1000n !== 0n) {
106
- currentWord += 't' + this.genderStem
107
- }
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
108
162
 
109
- if (nextNumber < currentNumber) {
110
- if (currentNumber < 100n) {
111
- return { [`${currentWord} y ${nextWord}`]: currentNumber + nextNumber }
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
+ }
169
+
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
112
187
  }
113
- return { [`${currentWord} ${nextWord}`]: currentNumber + nextNumber }
114
188
  }
115
189
 
116
- if (nextNumber % 1_000_000n === 0n && currentNumber > 1n) {
117
- nextWord = nextWord.slice(0, -3) + 'lones'
190
+ if (remainder > 0) {
191
+ result += ' ' + segments[remainder]
118
192
  }
119
193
 
120
- if (nextNumber === 100n) {
121
- if (currentNumber === 5n) {
122
- currentWord = 'quinien'
123
- nextWord = ''
124
- } else if (currentNumber === 7n) {
125
- currentWord = 'sete'
126
- } else if (currentNumber === 9n) {
127
- currentWord = '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
128
249
  }
129
- nextWord += 't' + this.genderStem + 's'
130
250
  } else {
131
- nextWord = ' ' + nextWord
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
+ }
132
259
  }
260
+ }
133
261
 
134
- return { [`${currentWord}${nextWord}`]: currentNumber * nextNumber }
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)
135
288
  }
289
+
290
+ return result
136
291
  }
137
292
 
138
293
  /**
139
- * Converts a number to Spanish cardinal (written) form.
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.
140
298
  *
141
- * @param {number|string|bigint} value The number to convert.
142
- * @param {Object} [options] Conversion options (see ES class).
143
- * @returns {string} The number expressed in Spanish words.
144
- * @throws {TypeError} If value is NaN or invalid type.
145
- * @throws {Error} If value is an invalid number string.
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
146
305
  *
147
306
  * @example
148
- * convertToWords(42, { lang: 'es' }); // 'cuarenta y dos'
149
- * convertToWords(100, { lang: 'es' }); // 'cien'
307
+ * toWords(21) // 'veintiuno'
308
+ * toWords(21, {gender: 'feminine'}) // 'veintiuna'
309
+ * toWords(1000000) // 'un millón'
150
310
  */
151
- export default function convertToWords (value, options = {}) {
152
- return new Spanish(options).convertToWords(value)
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 = ''
317
+
318
+ if (isNegative) {
319
+ result = NEGATIVE + ' '
320
+ }
321
+
322
+ result += integerToWords(integerPart, feminine)
323
+
324
+ if (decimalPart) {
325
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, feminine)
326
+ }
327
+
328
+ return result
153
329
  }
330
+
331
+ // ============================================================================
332
+ // Public API
333
+ // ============================================================================
334
+
335
+ export { toWords }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts a numeric value to Persian words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Persian words
6
+ */
7
+ export function toWords(value: number | string | bigint): string;