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,148 +1,377 @@
1
- import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
2
-
3
1
  /**
4
- * Italian language converter.
2
+ * Italian language converter - Functional Implementation v2
5
3
  *
6
- * Converts numbers to Italian words following Italian conventions:
7
- * - Phonetic contractions (removes duplicate vowels: "ventotto" not "ventiotto")
8
- * - Accentuation rules for "tre" in compounds ("ventitré" not "ventitre")
9
- * - Special handling for "uno" and vowel agreement
10
- * - Complex composition patterns for large numbers
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.
11
6
  *
12
- * Architecture Note:
13
- * Unlike other GreedyScaleLanguage subclasses, Italian uses a custom algorithm
14
- * rather than the standard highest-matching-scale approach. This is necessary
15
- * because Italian's word formation rules are irregular and context-dependent.
16
- * See tensToCardinal(), hundredsToCardinal(), and bigNumberToCardinal().
7
+ * Key optimization: Precompute all 1000 segment values (0-999) at module load.
8
+ * This eliminates all per-call string manipulation for segment conversion.
17
9
  *
18
- * Features:
19
- * - Vowel elision (e.g., "ventotto" not "ventiotto")
20
- * - Accentuation of final "tre" (ventitré, trentacinque - note: accent on compound tres)
21
- * - Exponent-based large number naming (milione, miliardo, trilione)
22
- * - Custom word construction for hundreds and thousands
10
+ * Italian-specific rules (handled in precomputation):
11
+ * - Concatenation without spaces within segments ("ventotto" not "venti otto")
12
+ * - Phonetic vowel elision: "venti" + "otto" "ventotto"
13
+ * - Accent on final "tre" in compounds: "ventitré"
14
+ * - mille/mila alternation for thousands
15
+ * - Scale words: milione/milioni, miliardo/miliardi, etc.
16
+ * - "e" connector before simple final remainder
17
+ */
18
+
19
+ import { parseNumericValue } from '../utils/parse-numeric.js'
20
+
21
+ // ============================================================================
22
+ // Vocabulary (module-level constants)
23
+ // ============================================================================
24
+
25
+ // Base vocabulary for building lookup tables
26
+ const ONES = ['', 'uno', 'due', 'tre', 'quattro', 'cinque', 'sei', 'sette', 'otto', 'nove']
27
+ const TEENS = ['dieci', 'undici', 'dodici', 'tredici', 'quattordici', 'quindici', 'sedici', 'diciassette', 'diciotto', 'diciannove']
28
+ const TENS = ['', '', 'venti', 'trenta', 'quaranta', 'cinquanta', 'sessanta', 'settanta', 'ottanta', 'novanta']
29
+ const HUNDREDS = ['', 'cento', 'duecento', 'trecento', 'quattrocento', 'cinquecento', 'seicento', 'settecento', 'ottocento', 'novecento']
30
+
31
+ const ZERO = 'zero'
32
+ const NEGATIVE = 'meno'
33
+ const DECIMAL_SEP = 'virgola'
34
+
35
+ // Thousands
36
+ const THOUSAND_SINGULAR = 'mille'
37
+ const THOUSAND_PLURAL_SUFFIX = 'mila'
38
+
39
+ // Scale word generation
40
+ const SCALE_PREFIXES = ['m', 'b', 'tr', 'quadr', 'quint', 'sest', 'sett', 'ott', 'nov', 'dec']
41
+
42
+ // ============================================================================
43
+ // Precomputed Lookup Tables (built once at module load)
44
+ // ============================================================================
45
+
46
+ /**
47
+ * Applies Italian phonetic vowel elision rules.
48
+ * Only used during table construction.
23
49
  */
24
- export class Italian extends GreedyScaleLanguage {
25
- negativeWord = 'meno'
26
- decimalSeparatorWord = 'virgola'
27
- zeroWord = 'zero'
28
- cardinalWords = [
29
- this.zeroWord, 'uno', 'due', 'tre', 'quattro', 'cinque', 'sei', 'sette', 'otto',
30
- 'nove', 'dieci', 'undici', 'dodici', 'tredici', 'quattordici', 'quindici',
31
- 'sedici', 'diciassette', 'diciotto', 'diciannove'
32
- ]
33
-
34
- strTens = { 2: 'venti', 3: 'trenta', 4: 'quaranta', 6: 'sessanta' }
35
-
36
- exponentPrefixes = [this.zeroWord, 'm', 'b', 'tr', 'quadr', 'quint', 'sest', 'sett', 'ott', 'nov', 'dec']
37
-
38
- accentuate (string) {
39
- const splittedString = string.split(' ')
40
-
41
- const result = splittedString.map(word => {
42
- return word.slice(-3) === 'tre' && word.length > 3 ? word.replaceAll('tré', 'tre').slice(0, -3) + 'tré' : word.replaceAll('tré', 'tre')
43
- })
44
- return result.join(' ')
50
+ function applyPhoneticRules (str) {
51
+ return str
52
+ .replace(/io/g, 'o')
53
+ .replace(/ao/g, 'o')
54
+ .replace(/oo/g, 'o')
55
+ .replace(/iu/g, 'u')
56
+ .replace(/au/g, 'u')
57
+ }
58
+
59
+ /**
60
+ * Adds accent to final "tre" in a word.
61
+ * Only used during table construction.
62
+ */
63
+ function accentuateTre (word) {
64
+ if (word.length > 3 && word.slice(-3) === 'tre') {
65
+ return word.slice(0, -3) + 'tré'
45
66
  }
67
+ return word
68
+ }
46
69
 
47
- omitIfZero (numberToString) {
48
- return numberToString === this.zeroWord ? '' : numberToString
70
+ /**
71
+ * Builds the segment word for a number 0-999.
72
+ * Only used during table construction.
73
+ */
74
+ function buildSegmentWord (n) {
75
+ if (n === 0) return ''
76
+
77
+ const ones = n % 10
78
+ const tens = Math.floor(n / 10) % 10
79
+ const hundreds = Math.floor(n / 100)
80
+
81
+ let result = ''
82
+
83
+ // Hundreds
84
+ if (hundreds > 0) {
85
+ result = HUNDREDS[hundreds]
49
86
  }
50
87
 
51
- phoneticContraction (string) {
52
- return string.replaceAll('oo', 'o').replaceAll('ao', 'o').replaceAll('io', 'o').replaceAll('au', 'u').replaceAll('iu', 'u')
88
+ // Tens and ones
89
+ if (tens === 0 && ones === 0) {
90
+ // Nothing more
91
+ } else if (tens === 1) {
92
+ // Teens: 10-19
93
+ result += TEENS[ones]
94
+ } else if (tens >= 2) {
95
+ // 20-99
96
+ result += TENS[tens]
97
+ if (ones > 0) {
98
+ result += ONES[ones]
99
+ }
100
+ } else if (ones > 0) {
101
+ // 1-9 (tens === 0)
102
+ result += ONES[ones]
53
103
  }
54
104
 
55
- tensToCardinal (number) {
56
- const tens = Math.floor(number / 10)
57
- const units = number % 10
58
- const prefix = Object.prototype.hasOwnProperty.call(this.strTens, tens) ? this.strTens[tens] : this.cardinalWords[tens].slice(0, -1) + 'anta'
59
- const postfix = this.omitIfZero(this.cardinalWords[units])
60
- return this.phoneticContraction(prefix + postfix)
105
+ // Apply phonetic rules and accent
106
+ return accentuateTre(applyPhoneticRules(result))
107
+ }
108
+
109
+ /**
110
+ * Builds segment word with "un" for scale context (millions, billions).
111
+ * Only used during table construction.
112
+ */
113
+ function buildSegmentWordForScale (n) {
114
+ if (n === 0) return ''
115
+ if (n === 1) return 'un' // "un milione" not "uno milione"
116
+
117
+ const ones = n % 10
118
+ const tens = Math.floor(n / 10) % 10
119
+ const hundreds = Math.floor(n / 100)
120
+
121
+ let result = ''
122
+
123
+ if (hundreds > 0) {
124
+ result = HUNDREDS[hundreds]
61
125
  }
62
126
 
63
- hundredsToCardinal (number) {
64
- const hundreds = Math.floor(number / 100)
65
- let prefix = 'cento'
66
- if (hundreds !== 1) {
67
- prefix = this.cardinalWords[hundreds] + prefix
127
+ if (tens === 0 && ones === 0) {
128
+ // Nothing more
129
+ } else if (tens === 1) {
130
+ result += TEENS[ones]
131
+ } else if (tens >= 2) {
132
+ result += TENS[tens]
133
+ if (ones > 0) {
134
+ result += ONES[ones]
68
135
  }
69
- const postfix = this.omitIfZero(this.convertWholePart(number % 100))
70
- return this.phoneticContraction(prefix + postfix)
136
+ } else if (ones > 0) {
137
+ // 1-9 with tens === 0
138
+ // "un" only for exactly 1, others normal
139
+ result += ONES[ones]
71
140
  }
72
141
 
73
- thousandsToCardinal (number) {
74
- const thousands = Math.floor(number / 1000)
75
- const prefix = thousands === 1 ? 'mille' : this.convertWholePart(thousands) + 'mila'
76
- const postfix = this.omitIfZero(this.convertWholePart(number % 1000))
77
- return prefix + postfix
78
- }
142
+ return accentuateTre(applyPhoneticRules(result))
143
+ }
144
+
145
+ // Precompute all 1000 segment words (0-999)
146
+ // SEGMENTS[n] gives the Italian word for n within a segment
147
+ const SEGMENTS = new Array(1000)
148
+ for (let i = 0; i < 1000; i++) {
149
+ SEGMENTS[i] = buildSegmentWord(i)
150
+ }
151
+
152
+ // Precompute segment words for scale context (uses "un" for 1)
153
+ const SEGMENTS_SCALE = new Array(1000)
154
+ for (let i = 0; i < 1000; i++) {
155
+ SEGMENTS_SCALE[i] = buildSegmentWordForScale(i)
156
+ }
157
+
158
+ // Precompute thousands words (1-999) + "mila" suffix
159
+ // THOUSANDS[n] gives the word for n thousand (e.g., THOUSANDS[2] = "duemila")
160
+ const THOUSANDS = new Array(1000)
161
+ THOUSANDS[0] = ''
162
+ THOUSANDS[1] = THOUSAND_SINGULAR // "mille"
163
+ for (let i = 2; i < 1000; i++) {
164
+ THOUSANDS[i] = applyPhoneticRules(SEGMENTS[i] + THOUSAND_PLURAL_SUFFIX)
165
+ }
166
+
167
+ // ============================================================================
168
+ // Scale Word Functions
169
+ // ============================================================================
170
+
171
+ /**
172
+ * Gets singular scale word for index.
173
+ * @param {number} scaleIndex - 2=million, 3=billion, etc.
174
+ */
175
+ function getScaleWordSingular (scaleIndex) {
176
+ if (scaleIndex < 2) return ''
177
+ const prefixIndex = Math.floor((scaleIndex - 2) / 2)
178
+ const isIardo = (scaleIndex - 2) % 2 === 1
179
+ const prefix = SCALE_PREFIXES[prefixIndex]
180
+ if (!prefix) return ''
181
+ return prefix + (isIardo ? 'iliardo' : 'ilione')
182
+ }
183
+
184
+ /**
185
+ * Gets plural scale word for index.
186
+ * @param {number} scaleIndex - 2=million, 3=billion, etc.
187
+ */
188
+ function getScaleWordPlural (scaleIndex) {
189
+ if (scaleIndex < 2) return ''
190
+ const prefixIndex = Math.floor((scaleIndex - 2) / 2)
191
+ const isIardo = (scaleIndex - 2) % 2 === 1
192
+ const prefix = SCALE_PREFIXES[prefixIndex]
193
+ if (!prefix) return ''
194
+ return prefix + (isIardo ? 'iliardi' : 'ilioni')
195
+ }
196
+
197
+ // ============================================================================
198
+ // Conversion Functions
199
+ // ============================================================================
200
+
201
+ /**
202
+ * Converts a non-negative integer to Italian words.
203
+ *
204
+ * @param {bigint} n - Non-negative integer to convert
205
+ * @returns {string} Italian words
206
+ */
207
+ function integerToWords (n) {
208
+ if (n === 0n) return ZERO
79
209
 
80
- exponentLengthToString (exponentLength) {
81
- const prefix = this.exponentPrefixes[Math.floor(exponentLength / 6)]
82
- return exponentLength % 6 === 0 ? prefix + 'ilione' : prefix + 'iliardo'
210
+ // Fast path: numbers < 1000 (direct lookup)
211
+ if (n < 1000n) {
212
+ return SEGMENTS[Number(n)]
83
213
  }
84
214
 
85
- bigNumberToCardinal (number) {
86
- const digits = [...number.toString()]
215
+ // Fast path: numbers < 1,000,000 (thousands)
216
+ if (n < 1_000_000n) {
217
+ const thousands = Number(n / 1000n)
218
+ const remainder = Number(n % 1000n)
87
219
 
88
- let preDigits = digits.length % 3
89
- if (preDigits === 0) {
90
- preDigits = 3
220
+ if (remainder === 0) {
221
+ return THOUSANDS[thousands]
91
222
  }
92
223
 
93
- const multiplier = digits.slice(0, preDigits) // first `preDigits` elements
94
- const exponent = digits.slice(preDigits) // without the first `preDigits` elements
224
+ // Concatenate thousands + remainder
225
+ return THOUSANDS[thousands] + SEGMENTS[remainder]
226
+ }
227
+
228
+ // For numbers >= 1,000,000, use scale decomposition
229
+ return buildLargeNumberWords(n)
230
+ }
95
231
 
96
- let prefix, postfix
97
- let infix = this.exponentLengthToString(exponent.length)
232
+ /**
233
+ * Builds words for numbers >= 1,000,000.
234
+ *
235
+ * @param {bigint} n - Number >= 1,000,000
236
+ * @returns {string} Italian words
237
+ */
238
+ function buildLargeNumberWords (n) {
239
+ const parts = []
240
+ let remaining = n
98
241
 
99
- if (multiplier.join('') === '1') {
100
- prefix = 'un '
242
+ // Find the highest scale
243
+ let maxScale = 2
244
+ let testValue = 1_000_000n
245
+ while (testValue * 1000n <= remaining) {
246
+ testValue *= 1000n
247
+ maxScale++
248
+ }
249
+
250
+ // Process from highest scale down
251
+ for (let scaleIndex = maxScale; scaleIndex >= 0; scaleIndex--) {
252
+ const divisor = 1000n ** BigInt(scaleIndex)
253
+ const segment = remaining / divisor
254
+ remaining = remaining % divisor
255
+
256
+ if (segment === 0n) continue
257
+
258
+ const segNum = Number(segment)
259
+
260
+ if (scaleIndex >= 2) {
261
+ // Millions and above: "segment scaleWord"
262
+ const segmentWords = SEGMENTS_SCALE[segNum]
263
+ const scaleWord = segment === 1n
264
+ ? getScaleWordSingular(scaleIndex)
265
+ : getScaleWordPlural(scaleIndex)
266
+ parts.push(segmentWords + ' ' + scaleWord)
267
+ } else if (scaleIndex === 1) {
268
+ // Thousands: use precomputed table
269
+ parts.push(THOUSANDS[segNum])
101
270
  } else {
102
- prefix = this.convertWholePart(Math.trunc(Number(multiplier.join(''))))
103
- infix = ' ' + infix.slice(0, -1) + 'i' // without last element
271
+ // Units (scaleIndex === 0): just the segment
272
+ parts.push(SEGMENTS[segNum])
104
273
  }
274
+ }
105
275
 
106
- const isSetsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value))
107
- if (isSetsEqual(new Set(exponent), new Set(['0']))) {
108
- postfix = ''
109
- } else {
110
- postfix = this.convertWholePart(Math.trunc(exponent.join('')))
276
+ return joinPartsWithConnector(parts)
277
+ }
111
278
 
112
- infix += (postfix.includes(' e ') ? ', ' : ' e ')
279
+ /**
280
+ * Joins parts with Italian connector rules.
281
+ * Uses "e" before simple (non-compound) final segment.
282
+ *
283
+ * @param {string[]} parts - Parts to join
284
+ * @returns {string} Joined string
285
+ */
286
+ function joinPartsWithConnector (parts) {
287
+ const len = parts.length
288
+ if (len === 0) return ''
289
+ if (len === 1) return parts[0]
290
+
291
+ // Check if last part is "simple" (no space = no scale word)
292
+ const lastPart = parts[len - 1]
293
+ if (lastPart.indexOf(' ') === -1) {
294
+ // Join all but last with space, then add "e" connector
295
+ let result = parts[0]
296
+ for (let i = 1; i < len - 1; i++) {
297
+ result += ' ' + parts[i]
113
298
  }
299
+ return result + ' e ' + lastPart
300
+ }
114
301
 
115
- return prefix + infix + postfix
302
+ // Join with spaces
303
+ let result = parts[0]
304
+ for (let i = 1; i < len; i++) {
305
+ result += ' ' + parts[i]
116
306
  }
307
+ return result
308
+ }
117
309
 
118
- convertWholePart (number) {
119
- let words = ''
120
-
121
- if (number < 20) {
122
- words = this.cardinalWords[number]
123
- } else if (number < 100) {
124
- words = this.tensToCardinal(Number(number))
125
- } else if (number < 1000) {
126
- words = this.hundredsToCardinal(Number(number))
127
- } else if (number < 1_000_000) {
128
- words = this.thousandsToCardinal(Number(number))
129
- } else {
130
- words = this.bigNumberToCardinal(number)
131
- }
310
+ /**
311
+ * Converts decimal digits to Italian words.
312
+ *
313
+ * @param {string} decimalPart - Decimal digits (without the point)
314
+ * @returns {string} Italian words for decimal part
315
+ */
316
+ function decimalPartToWords (decimalPart) {
317
+ let result = ''
132
318
 
133
- return this.accentuate(words)
319
+ // Handle leading zeros
320
+ let i = 0
321
+ while (i < decimalPart.length && decimalPart[i] === '0') {
322
+ if (result) result += ' '
323
+ result += ZERO
324
+ i++
134
325
  }
326
+
327
+ // Convert remainder as a single number
328
+ const remainder = decimalPart.slice(i)
329
+ if (remainder) {
330
+ if (result) result += ' '
331
+ result += integerToWords(BigInt(remainder))
332
+ }
333
+
334
+ return result
135
335
  }
136
336
 
137
337
  /**
138
- * Converts a number to Italian cardinal (written) form.
338
+ * Converts a numeric value to Italian words.
139
339
  *
140
- * @param {number|string|bigint} value The number to convert.
141
- * @param {Object} [options={}] Configuration options.
142
- * @returns {string} The number expressed in Italian words.
143
- * @throws {TypeError} If value is NaN or invalid type.
144
- * @throws {Error} If value is an invalid number string.
340
+ * This is the main public API. It accepts any valid numeric input
341
+ * (number, string, or bigint) and handles parsing internally.
342
+ *
343
+ * @param {number | string | bigint} value - The numeric value to convert
344
+ * @returns {string} The number in Italian words
345
+ * @throws {TypeError} If value is not a valid numeric type
346
+ * @throws {Error} If value is not a valid number format
347
+ *
348
+ * @example
349
+ * toWords(28) // 'ventotto'
350
+ * toWords(23) // 'ventitré'
351
+ * toWords(1000) // 'mille'
352
+ * toWords(2000) // 'duemila'
353
+ * toWords(1000000) // 'un milione'
145
354
  */
146
- export default function convertToWords (value, options = {}) {
147
- return new Italian(options).convertToWords(value)
355
+ function toWords (value) {
356
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
357
+
358
+ let result = ''
359
+
360
+ if (isNegative) {
361
+ result = NEGATIVE + ' '
362
+ }
363
+
364
+ result += integerToWords(integerPart)
365
+
366
+ if (decimalPart) {
367
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
368
+ }
369
+
370
+ return result
148
371
  }
372
+
373
+ // ============================================================================
374
+ // Public API
375
+ // ============================================================================
376
+
377
+ export { toWords }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Converts a numeric value to Japanese words.
3
+ *
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
+ * @returns {string} The number in Japanese kanji words
9
+ * @throws {TypeError} If value is not a valid numeric type
10
+ * @throws {Error} If value is not a valid number format
11
+ *
12
+ * @example
13
+ * toWords(42) // '四十二'
14
+ * toWords(10000) // '一万'
15
+ * toWords(100000000) // '一億'
16
+ */
17
+ export function toWords(value: number | string | bigint): string;