n2words 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +86 -188
  3. package/dist/languages/am-Latn.js +3 -0
  4. package/dist/languages/am-Latn.js.map +1 -0
  5. package/dist/languages/am.js +3 -0
  6. package/dist/languages/am.js.map +1 -0
  7. package/dist/languages/ar.js +3 -0
  8. package/dist/languages/ar.js.map +1 -0
  9. package/dist/languages/az.js +3 -0
  10. package/dist/languages/az.js.map +1 -0
  11. package/dist/languages/bn.js +3 -0
  12. package/dist/languages/bn.js.map +1 -0
  13. package/dist/languages/cs.js +3 -0
  14. package/dist/languages/cs.js.map +1 -0
  15. package/dist/languages/da.js +3 -0
  16. package/dist/languages/da.js.map +1 -0
  17. package/dist/languages/de.js +3 -0
  18. package/dist/languages/de.js.map +1 -0
  19. package/dist/languages/el.js +3 -0
  20. package/dist/languages/el.js.map +1 -0
  21. package/dist/languages/en.js +3 -0
  22. package/dist/languages/en.js.map +1 -0
  23. package/dist/languages/es.js +3 -0
  24. package/dist/languages/es.js.map +1 -0
  25. package/dist/languages/fa.js +3 -0
  26. package/dist/languages/fa.js.map +1 -0
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -0
  30. package/dist/languages/fil.js.map +1 -0
  31. package/dist/languages/fr-BE.js +3 -0
  32. package/dist/languages/fr-BE.js.map +1 -0
  33. package/dist/languages/fr.js +3 -0
  34. package/dist/languages/fr.js.map +1 -0
  35. package/dist/languages/gu.js +3 -0
  36. package/dist/languages/gu.js.map +1 -0
  37. package/dist/languages/ha.js +3 -0
  38. package/dist/languages/ha.js.map +1 -0
  39. package/dist/languages/hbo.js +3 -0
  40. package/dist/languages/hbo.js.map +1 -0
  41. package/dist/languages/he.js +3 -0
  42. package/dist/languages/he.js.map +1 -0
  43. package/dist/languages/hi.js +3 -0
  44. package/dist/languages/hi.js.map +1 -0
  45. package/dist/languages/hr.js +3 -0
  46. package/dist/languages/hr.js.map +1 -0
  47. package/dist/languages/hu.js +3 -0
  48. package/dist/languages/hu.js.map +1 -0
  49. package/dist/languages/id.js +3 -0
  50. package/dist/languages/id.js.map +1 -0
  51. package/dist/languages/it.js +3 -0
  52. package/dist/languages/it.js.map +1 -0
  53. package/dist/languages/ja.js +3 -0
  54. package/dist/languages/ja.js.map +1 -0
  55. package/dist/languages/kn.js +3 -0
  56. package/dist/languages/kn.js.map +1 -0
  57. package/dist/languages/ko.js +3 -0
  58. package/dist/languages/ko.js.map +1 -0
  59. package/dist/languages/lt.js +3 -0
  60. package/dist/languages/lt.js.map +1 -0
  61. package/dist/languages/lv.js +3 -0
  62. package/dist/languages/lv.js.map +1 -0
  63. package/dist/languages/mr.js +3 -0
  64. package/dist/languages/mr.js.map +1 -0
  65. package/dist/languages/ms.js +3 -0
  66. package/dist/languages/ms.js.map +1 -0
  67. package/dist/languages/nb.js +3 -0
  68. package/dist/languages/nb.js.map +1 -0
  69. package/dist/languages/nl.js +3 -0
  70. package/dist/languages/nl.js.map +1 -0
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -0
  74. package/dist/languages/pl.js.map +1 -0
  75. package/dist/languages/pt.js +3 -0
  76. package/dist/languages/pt.js.map +1 -0
  77. package/dist/languages/ro.js +3 -0
  78. package/dist/languages/ro.js.map +1 -0
  79. package/dist/languages/ru.js +3 -0
  80. package/dist/languages/ru.js.map +1 -0
  81. package/dist/languages/sr-Cyrl.js +3 -0
  82. package/dist/languages/sr-Cyrl.js.map +1 -0
  83. package/dist/languages/sr-Latn.js +3 -0
  84. package/dist/languages/sr-Latn.js.map +1 -0
  85. package/dist/languages/sv.js +3 -0
  86. package/dist/languages/sv.js.map +1 -0
  87. package/dist/languages/sw.js +3 -0
  88. package/dist/languages/sw.js.map +1 -0
  89. package/dist/languages/ta.js +3 -0
  90. package/dist/languages/ta.js.map +1 -0
  91. package/dist/languages/te.js +3 -0
  92. package/dist/languages/te.js.map +1 -0
  93. package/dist/languages/th.js +3 -0
  94. package/dist/languages/th.js.map +1 -0
  95. package/dist/languages/tr.js +3 -0
  96. package/dist/languages/tr.js.map +1 -0
  97. package/dist/languages/uk.js +3 -0
  98. package/dist/languages/uk.js.map +1 -0
  99. package/dist/languages/ur.js +3 -0
  100. package/dist/languages/ur.js.map +1 -0
  101. package/dist/languages/vi.js +3 -0
  102. package/dist/languages/vi.js.map +1 -0
  103. package/dist/languages/zh-Hans.js +3 -0
  104. package/dist/languages/zh-Hans.js.map +1 -0
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +2 -2
  108. package/dist/n2words.js.map +1 -1
  109. package/lib/languages/am-Latn.d.ts +7 -0
  110. package/lib/languages/am-Latn.js +164 -0
  111. package/lib/languages/am.d.ts +7 -0
  112. package/lib/languages/am.js +164 -0
  113. package/lib/languages/ar.d.ts +14 -27
  114. package/lib/languages/ar.js +175 -129
  115. package/lib/languages/az.d.ts +4 -9
  116. package/lib/languages/az.js +171 -37
  117. package/lib/languages/bn.d.ts +4 -8
  118. package/lib/languages/bn.js +138 -124
  119. package/lib/languages/cs.d.ts +15 -85
  120. package/lib/languages/cs.js +310 -114
  121. package/lib/languages/da.d.ts +11 -12
  122. package/lib/languages/da.js +276 -101
  123. package/lib/languages/de.d.ts +14 -11
  124. package/lib/languages/de.js +317 -86
  125. package/lib/languages/el.d.ts +11 -11
  126. package/lib/languages/el.js +231 -78
  127. package/lib/languages/en.d.ts +14 -13
  128. package/lib/languages/en.js +242 -72
  129. package/lib/languages/es.d.ts +18 -12
  130. package/lib/languages/es.js +317 -103
  131. package/lib/languages/fa.d.ts +4 -44
  132. package/lib/languages/fa.js +112 -122
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +4 -13
  136. package/lib/languages/fil.js +207 -106
  137. package/lib/languages/fr-BE.d.ts +8 -8
  138. package/lib/languages/fr-BE.js +294 -19
  139. package/lib/languages/fr.d.ts +18 -12
  140. package/lib/languages/fr.js +352 -89
  141. package/lib/languages/gu.d.ts +4 -8
  142. package/lib/languages/gu.js +130 -125
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +10 -110
  146. package/lib/languages/hbo.js +263 -214
  147. package/lib/languages/he.d.ts +10 -77
  148. package/lib/languages/he.js +242 -172
  149. package/lib/languages/hi.d.ts +4 -8
  150. package/lib/languages/hi.js +138 -124
  151. package/lib/languages/hr.d.ts +8 -77
  152. package/lib/languages/hr.js +194 -89
  153. package/lib/languages/hu.d.ts +4 -19
  154. package/lib/languages/hu.js +198 -119
  155. package/lib/languages/id.d.ts +4 -34
  156. package/lib/languages/id.js +171 -129
  157. package/lib/languages/it.d.ts +16 -34
  158. package/lib/languages/it.js +339 -94
  159. package/lib/languages/ja.d.ts +14 -14
  160. package/lib/languages/ja.js +233 -111
  161. package/lib/languages/kn.d.ts +4 -8
  162. package/lib/languages/kn.js +130 -35
  163. package/lib/languages/ko.d.ts +11 -11
  164. package/lib/languages/ko.js +257 -49
  165. package/lib/languages/lt.d.ts +15 -67
  166. package/lib/languages/lt.js +296 -122
  167. package/lib/languages/lv.d.ts +15 -67
  168. package/lib/languages/lv.js +297 -106
  169. package/lib/languages/mr.d.ts +4 -8
  170. package/lib/languages/mr.js +130 -125
  171. package/lib/languages/ms.d.ts +4 -28
  172. package/lib/languages/ms.js +171 -116
  173. package/lib/languages/nb.d.ts +11 -9
  174. package/lib/languages/nb.js +282 -87
  175. package/lib/languages/nl.d.ts +23 -13
  176. package/lib/languages/nl.js +317 -133
  177. package/lib/languages/pa.d.ts +4 -8
  178. package/lib/languages/pa.js +156 -124
  179. package/lib/languages/pl.d.ts +19 -77
  180. package/lib/languages/pl.js +307 -87
  181. package/lib/languages/pt.d.ts +14 -26
  182. package/lib/languages/pt.js +286 -92
  183. package/lib/languages/ro.d.ts +15 -155
  184. package/lib/languages/ro.js +219 -235
  185. package/lib/languages/ru.d.ts +8 -82
  186. package/lib/languages/ru.js +222 -78
  187. package/lib/languages/sr-Cyrl.d.ts +8 -77
  188. package/lib/languages/sr-Cyrl.js +191 -89
  189. package/lib/languages/sr-Latn.d.ts +8 -77
  190. package/lib/languages/sr-Latn.js +191 -89
  191. package/lib/languages/sv.d.ts +11 -11
  192. package/lib/languages/sv.js +288 -74
  193. package/lib/languages/sw.d.ts +4 -36
  194. package/lib/languages/sw.js +133 -106
  195. package/lib/languages/ta.d.ts +4 -17
  196. package/lib/languages/ta.js +129 -201
  197. package/lib/languages/te.d.ts +4 -19
  198. package/lib/languages/te.js +141 -196
  199. package/lib/languages/th.d.ts +4 -14
  200. package/lib/languages/th.js +135 -91
  201. package/lib/languages/tr.d.ts +15 -9
  202. package/lib/languages/tr.js +256 -49
  203. package/lib/languages/uk.d.ts +8 -82
  204. package/lib/languages/uk.js +200 -78
  205. package/lib/languages/ur.d.ts +4 -8
  206. package/lib/languages/ur.js +156 -124
  207. package/lib/languages/vi.d.ts +14 -69
  208. package/lib/languages/vi.js +294 -125
  209. package/lib/languages/zh-Hans.d.ts +8 -18
  210. package/lib/languages/zh-Hans.js +163 -92
  211. package/lib/languages/zh-Hant.d.ts +8 -18
  212. package/lib/languages/zh-Hant.js +181 -90
  213. package/lib/n2words.d.ts +53 -209
  214. package/lib/n2words.js +111 -530
  215. package/lib/utils/is-plain-object.d.ts +13 -0
  216. package/lib/utils/is-plain-object.js +17 -0
  217. package/lib/utils/parse-numeric.d.ts +17 -0
  218. package/lib/utils/parse-numeric.js +108 -0
  219. package/lib/utils/validate-options.d.ts +8 -0
  220. package/lib/utils/validate-options.js +16 -0
  221. package/package.json +26 -14
  222. package/dist/ArabicConverter.js +0 -3
  223. package/dist/ArabicConverter.js.map +0 -1
  224. package/dist/AzerbaijaniConverter.js +0 -3
  225. package/dist/AzerbaijaniConverter.js.map +0 -1
  226. package/dist/BanglaConverter.js +0 -3
  227. package/dist/BanglaConverter.js.map +0 -1
  228. package/dist/BiblicalHebrewConverter.js +0 -3
  229. package/dist/BiblicalHebrewConverter.js.map +0 -1
  230. package/dist/CroatianConverter.js +0 -3
  231. package/dist/CroatianConverter.js.map +0 -1
  232. package/dist/CzechConverter.js +0 -3
  233. package/dist/CzechConverter.js.map +0 -1
  234. package/dist/DanishConverter.js +0 -3
  235. package/dist/DanishConverter.js.map +0 -1
  236. package/dist/DutchConverter.js +0 -3
  237. package/dist/DutchConverter.js.map +0 -1
  238. package/dist/EnglishConverter.js +0 -3
  239. package/dist/EnglishConverter.js.map +0 -1
  240. package/dist/FilipinoConverter.js +0 -3
  241. package/dist/FilipinoConverter.js.map +0 -1
  242. package/dist/FrenchBelgiumConverter.js +0 -3
  243. package/dist/FrenchBelgiumConverter.js.map +0 -1
  244. package/dist/FrenchConverter.js +0 -3
  245. package/dist/FrenchConverter.js.map +0 -1
  246. package/dist/GermanConverter.js +0 -3
  247. package/dist/GermanConverter.js.map +0 -1
  248. package/dist/GreekConverter.js +0 -3
  249. package/dist/GreekConverter.js.map +0 -1
  250. package/dist/GujaratiConverter.js +0 -3
  251. package/dist/GujaratiConverter.js.map +0 -1
  252. package/dist/HebrewConverter.js +0 -3
  253. package/dist/HebrewConverter.js.map +0 -1
  254. package/dist/HindiConverter.js +0 -3
  255. package/dist/HindiConverter.js.map +0 -1
  256. package/dist/HungarianConverter.js +0 -3
  257. package/dist/HungarianConverter.js.map +0 -1
  258. package/dist/IndonesianConverter.js +0 -3
  259. package/dist/IndonesianConverter.js.map +0 -1
  260. package/dist/ItalianConverter.js +0 -3
  261. package/dist/ItalianConverter.js.map +0 -1
  262. package/dist/JapaneseConverter.js +0 -3
  263. package/dist/JapaneseConverter.js.map +0 -1
  264. package/dist/KannadaConverter.js +0 -3
  265. package/dist/KannadaConverter.js.map +0 -1
  266. package/dist/KoreanConverter.js +0 -3
  267. package/dist/KoreanConverter.js.map +0 -1
  268. package/dist/LatvianConverter.js +0 -3
  269. package/dist/LatvianConverter.js.map +0 -1
  270. package/dist/LithuanianConverter.js +0 -3
  271. package/dist/LithuanianConverter.js.map +0 -1
  272. package/dist/MalayConverter.js +0 -3
  273. package/dist/MalayConverter.js.map +0 -1
  274. package/dist/MarathiConverter.js +0 -3
  275. package/dist/MarathiConverter.js.map +0 -1
  276. package/dist/NorwegianBokmalConverter.js +0 -3
  277. package/dist/NorwegianBokmalConverter.js.map +0 -1
  278. package/dist/PersianConverter.js +0 -3
  279. package/dist/PersianConverter.js.map +0 -1
  280. package/dist/PolishConverter.js +0 -3
  281. package/dist/PolishConverter.js.map +0 -1
  282. package/dist/PortugueseConverter.js +0 -3
  283. package/dist/PortugueseConverter.js.map +0 -1
  284. package/dist/PunjabiConverter.js +0 -3
  285. package/dist/PunjabiConverter.js.map +0 -1
  286. package/dist/RomanianConverter.js +0 -3
  287. package/dist/RomanianConverter.js.map +0 -1
  288. package/dist/RussianConverter.js +0 -3
  289. package/dist/RussianConverter.js.map +0 -1
  290. package/dist/SerbianCyrillicConverter.js +0 -3
  291. package/dist/SerbianCyrillicConverter.js.map +0 -1
  292. package/dist/SerbianLatinConverter.js +0 -3
  293. package/dist/SerbianLatinConverter.js.map +0 -1
  294. package/dist/SimplifiedChineseConverter.js +0 -3
  295. package/dist/SimplifiedChineseConverter.js.map +0 -1
  296. package/dist/SpanishConverter.js +0 -3
  297. package/dist/SpanishConverter.js.map +0 -1
  298. package/dist/SwahiliConverter.js +0 -3
  299. package/dist/SwahiliConverter.js.map +0 -1
  300. package/dist/SwedishConverter.js +0 -3
  301. package/dist/SwedishConverter.js.map +0 -1
  302. package/dist/TamilConverter.js +0 -3
  303. package/dist/TamilConverter.js.map +0 -1
  304. package/dist/TeluguConverter.js +0 -3
  305. package/dist/TeluguConverter.js.map +0 -1
  306. package/dist/ThaiConverter.js +0 -3
  307. package/dist/ThaiConverter.js.map +0 -1
  308. package/dist/TraditionalChineseConverter.js +0 -3
  309. package/dist/TraditionalChineseConverter.js.map +0 -1
  310. package/dist/TurkishConverter.js +0 -3
  311. package/dist/TurkishConverter.js.map +0 -1
  312. package/dist/UkrainianConverter.js +0 -3
  313. package/dist/UkrainianConverter.js.map +0 -1
  314. package/dist/UrduConverter.js +0 -3
  315. package/dist/UrduConverter.js.map +0 -1
  316. package/dist/VietnameseConverter.js +0 -3
  317. package/dist/VietnameseConverter.js.map +0 -1
  318. package/lib/classes/abstract-language.d.ts +0 -178
  319. package/lib/classes/abstract-language.js +0 -268
  320. package/lib/classes/greedy-scale-language.d.ts +0 -109
  321. package/lib/classes/greedy-scale-language.js +0 -201
  322. package/lib/classes/slavic-language.d.ts +0 -148
  323. package/lib/classes/slavic-language.js +0 -281
  324. package/lib/classes/south-asian-language.d.ts +0 -70
  325. package/lib/classes/south-asian-language.js +0 -154
  326. package/lib/classes/turkic-language.d.ts +0 -26
  327. package/lib/classes/turkic-language.js +0 -59
@@ -1,132 +1,377 @@
1
- import { AbstractLanguage } from '../classes/abstract-language.js'
2
-
3
1
  /**
4
- * Italian language converter.
2
+ * Italian language converter - Functional Implementation v2
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 1000 segment values (0-999) at module load.
8
+ * This eliminates all per-call string manipulation for segment conversion.
5
9
  *
6
- * Supports:
7
- * - Phonetic contractions (vowel elision: "ventotto" not "ventiotto")
8
- * - Accentuation rules for "tre" in compounds ("ventitré")
9
- * - Custom word construction for irregular patterns
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
10
17
  */
11
- export class Italian extends AbstractLanguage {
12
- negativeWord = 'meno'
13
- decimalSeparatorWord = 'virgola'
14
- zeroWord = 'zero'
15
18
 
16
- onesWords = [
17
- this.zeroWord, 'uno', 'due', 'tre', 'quattro', 'cinque', 'sei', 'sette', 'otto',
18
- 'nove', 'dieci', 'undici', 'dodici', 'tredici', 'quattordici', 'quindici',
19
- 'sedici', 'diciassette', 'diciotto', 'diciannove'
20
- ]
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'
21
34
 
22
- tensWords = { 2: 'venti', 3: 'trenta', 4: 'quaranta', 6: 'sessanta' }
35
+ // Thousands
36
+ const THOUSAND_SINGULAR = 'mille'
37
+ const THOUSAND_PLURAL_SUFFIX = 'mila'
23
38
 
24
- exponentPrefixes = [this.zeroWord, 'm', 'b', 'tr', 'quadr', 'quint', 'sest', 'sett', 'ott', 'nov', 'dec']
39
+ // Scale word generation
40
+ const SCALE_PREFIXES = ['m', 'b', 'tr', 'quadr', 'quint', 'sest', 'sett', 'ott', 'nov', 'dec']
25
41
 
26
- /** Adds accent to final "tre" in compound words (ventitré). */
27
- accentuate (string) {
28
- const splittedString = string.split(' ')
42
+ // ============================================================================
43
+ // Precomputed Lookup Tables (built once at module load)
44
+ // ============================================================================
29
45
 
30
- const result = splittedString.map(word => {
31
- return word.slice(-3) === 'tre' && word.length > 3 ? word.replaceAll('tré', 'tre').slice(0, -3) + 'tré' : word.replaceAll('tré', 'tre')
32
- })
33
- return result.join(' ')
46
+ /**
47
+ * Applies Italian phonetic vowel elision rules.
48
+ * Only used during table construction.
49
+ */
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é'
34
66
  }
67
+ return word
68
+ }
69
+
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 = ''
35
82
 
36
- /** Omits word if it represents zero. */
37
- omitIfZero (numberToString) {
38
- return numberToString === this.zeroWord ? '' : numberToString
83
+ // Hundreds
84
+ if (hundreds > 0) {
85
+ result = HUNDREDS[hundreds]
39
86
  }
40
87
 
41
- /** Removes duplicate vowels for phonetic contractions. */
42
- phoneticContraction (string) {
43
- 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]
44
103
  }
45
104
 
46
- /** Converts tens (20-99) with phonetic contractions. */
47
- tensToCardinal (number) {
48
- const tens = Math.floor(number / 10)
49
- const units = number % 10
50
- const prefix = Object.prototype.hasOwnProperty.call(this.tensWords, tens) ? this.tensWords[tens] : this.onesWords[tens].slice(0, -1) + 'anta'
51
- const postfix = this.omitIfZero(this.onesWords[units])
52
- 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]
53
125
  }
54
126
 
55
- /** Converts hundreds (100-999) with "cento" composition. */
56
- hundredsToCardinal (number) {
57
- const hundreds = Math.floor(number / 100)
58
- let prefix = 'cento'
59
- if (hundreds !== 1) {
60
- prefix = this.onesWords[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]
61
135
  }
62
- const postfix = this.omitIfZero(this.integerToWords(number % 100))
63
- 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]
64
140
  }
65
141
 
66
- /** Converts thousands (1000-999999) with "mille/mila" composition. */
67
- thousandsToCardinal (number) {
68
- const thousands = Math.floor(number / 1000)
69
- const prefix = thousands === 1 ? 'mille' : this.integerToWords(thousands) + 'mila'
70
- const postfix = this.omitIfZero(this.integerToWords(number % 1000))
71
- return prefix + postfix
72
- }
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
73
209
 
74
- /** Converts exponent length to Italian scale word (milione, miliardo, etc.). */
75
- exponentLengthToString (exponentLength) {
76
- const prefix = this.exponentPrefixes[Math.floor(exponentLength / 6)]
77
- return exponentLength % 6 === 0 ? prefix + 'ilione' : prefix + 'iliardo'
210
+ // Fast path: numbers < 1000 (direct lookup)
211
+ if (n < 1000n) {
212
+ return SEGMENTS[Number(n)]
78
213
  }
79
214
 
80
- /** Converts large numbers (millions and above) with exponent-based naming. */
81
- bigNumberToCardinal (number) {
82
- 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)
83
219
 
84
- let preDigits = digits.length % 3
85
- if (preDigits === 0) {
86
- preDigits = 3
220
+ if (remainder === 0) {
221
+ return THOUSANDS[thousands]
87
222
  }
88
223
 
89
- const multiplier = digits.slice(0, preDigits) // first `preDigits` elements
90
- 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
+ }
231
+
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
241
+
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
91
255
 
92
- let prefix, postfix
93
- let infix = this.exponentLengthToString(exponent.length)
256
+ if (segment === 0n) continue
94
257
 
95
- if (multiplier.join('') === '1') {
96
- prefix = 'un '
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])
97
270
  } else {
98
- prefix = this.integerToWords(Math.trunc(Number(multiplier.join(''))))
99
- infix = ' ' + infix.slice(0, -1) + 'i' // without last element
271
+ // Units (scaleIndex === 0): just the segment
272
+ parts.push(SEGMENTS[segNum])
100
273
  }
274
+ }
101
275
 
102
- const isSetsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value))
103
- if (isSetsEqual(new Set(exponent), new Set(['0']))) {
104
- postfix = ''
105
- } else {
106
- postfix = this.integerToWords(Math.trunc(exponent.join('')))
276
+ return joinPartsWithConnector(parts)
277
+ }
107
278
 
108
- 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]
109
298
  }
299
+ return result + ' e ' + lastPart
300
+ }
110
301
 
111
- 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]
112
306
  }
307
+ return result
308
+ }
113
309
 
114
- /** Converts integer part using Italian custom algorithm with accentuation. */
115
- integerToWords (integerPart) {
116
- let words = ''
117
-
118
- if (integerPart < 20) {
119
- words = this.onesWords[integerPart]
120
- } else if (integerPart < 100) {
121
- words = this.tensToCardinal(Number(integerPart))
122
- } else if (integerPart < 1000) {
123
- words = this.hundredsToCardinal(Number(integerPart))
124
- } else if (integerPart < 1_000_000) {
125
- words = this.thousandsToCardinal(Number(integerPart))
126
- } else {
127
- words = this.bigNumberToCardinal(integerPart)
128
- }
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 = ''
318
+
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++
325
+ }
129
326
 
130
- return this.accentuate(words)
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))
131
332
  }
333
+
334
+ return result
132
335
  }
336
+
337
+ /**
338
+ * Converts a numeric value to Italian words.
339
+ *
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'
354
+ */
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
371
+ }
372
+
373
+ // ============================================================================
374
+ // Public API
375
+ // ============================================================================
376
+
377
+ export { toWords }
@@ -1,17 +1,17 @@
1
1
  /**
2
- * Japanese language converter.
2
+ * Converts a numeric value to Japanese words.
3
3
  *
4
- * Supports:
5
- * - Kanji numerals (一, 二, 三, etc.)
6
- * - Grouping by 万 (10,000) instead of 1,000
7
- * - Special (one) omission rules (十, 百, but 一万, 一億)
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) // '一億'
8
16
  */
9
- export class Japanese extends AbstractLanguage {
10
- onesWords: string[];
11
- scaleWords: string[];
12
- /** Converts a segment of up to 4 digits to Japanese kanji with 一 omission rules. */
13
- segmentToWords(num: any): string;
14
- /** Converts integer part using Japanese 万-based grouping system. */
15
- integerToWords(integerPart: any): string;
16
- }
17
- import { AbstractLanguage } from '../classes/abstract-language.js';
17
+ export function toWords(value: number | string | bigint): string;