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