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,132 +1,342 @@
1
- import { AbstractLanguage } from '../classes/abstract-language.js'
2
-
3
1
  /**
4
- * Italian language converter.
2
+ * Italian language converter - Functional Implementation
3
+ *
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
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
6
+ * Italian-specific rules:
7
+ * - Concatenation without spaces within segments ("ventotto" not "venti otto")
8
+ * - Phonetic vowel elision: "venti" + "otto" "ventotto"
9
+ * - Accent on final "tre" in compounds: "ventitré"
10
+ * - mille/mila alternation for thousands
11
+ * - Scale words: milione/milioni, miliardo/miliardi, etc.
12
+ * - "e" connector before simple final remainder
10
13
  */
11
- export class Italian extends AbstractLanguage {
12
- negativeWord = 'meno'
13
- decimalSeparatorWord = 'virgola'
14
- zeroWord = 'zero'
15
14
 
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
- ]
15
+ import { parseNumericValue } from '../utils/parse-numeric.js'
21
16
 
22
- tensWords = { 2: 'venti', 3: 'trenta', 4: 'quaranta', 6: 'sessanta' }
17
+ // ============================================================================
18
+ // Vocabulary (module-level constants)
19
+ // ============================================================================
23
20
 
24
- exponentPrefixes = [this.zeroWord, 'm', 'b', 'tr', 'quadr', 'quint', 'sest', 'sett', 'ott', 'nov', 'dec']
21
+ // Base vocabulary
22
+ const ONES = ['', 'uno', 'due', 'tre', 'quattro', 'cinque', 'sei', 'sette', 'otto', 'nove']
23
+ const TEENS = ['dieci', 'undici', 'dodici', 'tredici', 'quattordici', 'quindici', 'sedici', 'diciassette', 'diciotto', 'diciannove']
24
+ const TENS = ['', '', 'venti', 'trenta', 'quaranta', 'cinquanta', 'sessanta', 'settanta', 'ottanta', 'novanta']
25
+ const HUNDREDS = ['', 'cento', 'duecento', 'trecento', 'quattrocento', 'cinquecento', 'seicento', 'settecento', 'ottocento', 'novecento']
25
26
 
26
- /** Adds accent to final "tre" in compound words (ventitré). */
27
- accentuate (string) {
28
- const splittedString = string.split(' ')
27
+ // Pre-elided tens stems (drop final vowel before 'uno'/'otto')
28
+ // vent- (from venti), trent- (from trenta), etc.
29
+ const TENS_STEM = ['', '', 'vent', 'trent', 'quarant', 'cinquant', 'sessant', 'settant', 'ottant', 'novant']
29
30
 
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(' ')
34
- }
31
+ const ZERO = 'zero'
32
+ const NEGATIVE = 'meno'
33
+ const DECIMAL_SEP = 'virgola'
35
34
 
36
- /** Omits word if it represents zero. */
37
- omitIfZero (numberToString) {
38
- return numberToString === this.zeroWord ? '' : numberToString
39
- }
35
+ // Thousands
36
+ const THOUSAND_SINGULAR = 'mille'
37
+ const THOUSAND_PLURAL_SUFFIX = 'mila'
40
38
 
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')
44
- }
39
+ // Scale word generation
40
+ const SCALE_PREFIXES = ['m', 'b', 'tr', 'quadr', 'quint', 'sest', 'sett', 'ott', 'nov', 'dec']
45
41
 
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)
53
- }
42
+ // ============================================================================
43
+ // Segment Building
44
+ // ============================================================================
45
+
46
+ /**
47
+ * Builds the segment word for a number 0-999.
48
+ * Handles Italian phonetic elision inline (no regex).
49
+ *
50
+ * Elision rules:
51
+ * - Tens ending in vowel + uno/otto → drop tens vowel: ventuno, ventotto
52
+ * - Hundreds cento + otto/ottanta → centotto, centottanta (drop 'o')
53
+ * - Final 'tre' in compounds becomes 'tré': ventitré, trentatré
54
+ */
55
+ function buildSegment (n) {
56
+ if (n === 0) return ''
57
+
58
+ const ones = n % 10
59
+ const tens = Math.floor(n / 10) % 10
60
+ const hundreds = Math.floor(n / 100)
54
61
 
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
62
+ let result = ''
63
+
64
+ // Hundreds
65
+ if (hundreds > 0) {
66
+ // Elision: *cento + otto/ottanta → *centotto/centottanta (drop final 'o')
67
+ // Only applies when tens = 8 (ottanta) or tens = 0 and ones = 8 (otto)
68
+ if (tens === 8 || (tens === 0 && ones === 8)) {
69
+ // Remove final 'o' from hundreds: cento→cent, duecento→duecent, etc.
70
+ result = HUNDREDS[hundreds].slice(0, -1)
71
+ } else {
72
+ result = HUNDREDS[hundreds]
61
73
  }
62
- const postfix = this.omitIfZero(this.integerToWords(number % 100))
63
- return this.phoneticContraction(prefix + postfix)
64
74
  }
65
75
 
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
76
+ // Tens and ones
77
+ if (tens === 0 && ones === 0) {
78
+ // Nothing more (just hundreds)
79
+ } else if (tens === 1) {
80
+ // Teens: 10-19
81
+ result += TEENS[ones]
82
+ } else if (tens >= 2) {
83
+ // 20-99: handle elision for uno (1) and otto (8)
84
+ if (ones === 1 || ones === 8) {
85
+ // Use stem form: vent + uno = ventuno, vent + otto = ventotto
86
+ result += TENS_STEM[tens] + ONES[ones]
87
+ } else if (ones === 3) {
88
+ // Final tre becomes tré
89
+ result += TENS[tens] + 'tré'
90
+ } else if (ones > 0) {
91
+ result += TENS[tens] + ONES[ones]
92
+ } else {
93
+ result += TENS[tens]
94
+ }
95
+ } else if (ones > 0) {
96
+ // 1-9 (tens === 0)
97
+ if (ones === 3 && hundreds > 0) {
98
+ // centotré, duecentotré, etc.
99
+ result += 'tré'
100
+ } else {
101
+ result += ONES[ones]
102
+ }
72
103
  }
73
104
 
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'
105
+ return result
106
+ }
107
+
108
+ /**
109
+ * Builds segment word with "un" for scale context (millions, billions).
110
+ * Same as buildSegment but returns "un" for 1 instead of "uno".
111
+ */
112
+ function buildSegmentForScale (n) {
113
+ if (n === 0) return ''
114
+ if (n === 1) return 'un' // "un milione" not "uno milione"
115
+ return buildSegment(n)
116
+ }
117
+
118
+ /**
119
+ * Builds thousands word for 1-999 thousand.
120
+ * Handles elision: tre + mila = tremila (no accent), otto + mila = ottomila
121
+ */
122
+ function buildThousands (n) {
123
+ if (n === 0) return ''
124
+ if (n === 1) return THOUSAND_SINGULAR // "mille"
125
+
126
+ // Build segment and append "mila"
127
+ // Note: elision of segment ending vowel + 'mila' not needed (mila starts with 'm')
128
+ // But we need to handle cases like "ottomila" (no double-o issue since we build directly)
129
+ return buildSegment(n) + THOUSAND_PLURAL_SUFFIX
130
+ }
131
+
132
+ // ============================================================================
133
+ // Scale Word Functions
134
+ // ============================================================================
135
+
136
+ /**
137
+ * Gets singular scale word for index.
138
+ * @param {number} scaleIndex - 2=million, 3=billion, etc.
139
+ */
140
+ function getScaleWordSingular (scaleIndex) {
141
+ if (scaleIndex < 2) return ''
142
+ const prefixIndex = Math.floor((scaleIndex - 2) / 2)
143
+ const isIardo = (scaleIndex - 2) % 2 === 1
144
+ const prefix = SCALE_PREFIXES[prefixIndex]
145
+ if (!prefix) return ''
146
+ return prefix + (isIardo ? 'iliardo' : 'ilione')
147
+ }
148
+
149
+ /**
150
+ * Gets plural scale word for index.
151
+ * @param {number} scaleIndex - 2=million, 3=billion, etc.
152
+ */
153
+ function getScaleWordPlural (scaleIndex) {
154
+ if (scaleIndex < 2) return ''
155
+ const prefixIndex = Math.floor((scaleIndex - 2) / 2)
156
+ const isIardo = (scaleIndex - 2) % 2 === 1
157
+ const prefix = SCALE_PREFIXES[prefixIndex]
158
+ if (!prefix) return ''
159
+ return prefix + (isIardo ? 'iliardi' : 'ilioni')
160
+ }
161
+
162
+ // ============================================================================
163
+ // Conversion Functions
164
+ // ============================================================================
165
+
166
+ /**
167
+ * Converts a non-negative integer to Italian words.
168
+ *
169
+ * @param {bigint} n - Non-negative integer to convert
170
+ * @returns {string} Italian words
171
+ */
172
+ function integerToWords (n) {
173
+ if (n === 0n) return ZERO
174
+
175
+ // Fast path: numbers < 1000
176
+ if (n < 1000n) {
177
+ return buildSegment(Number(n))
78
178
  }
79
179
 
80
- /** Converts large numbers (millions and above) with exponent-based naming. */
81
- bigNumberToCardinal (number) {
82
- const digits = [...number.toString()]
180
+ // Fast path: numbers < 1,000,000 (thousands)
181
+ if (n < 1_000_000n) {
182
+ const thousands = Number(n / 1000n)
183
+ const remainder = Number(n % 1000n)
83
184
 
84
- let preDigits = digits.length % 3
85
- if (preDigits === 0) {
86
- preDigits = 3
185
+ if (remainder === 0) {
186
+ return buildThousands(thousands)
87
187
  }
88
188
 
89
- const multiplier = digits.slice(0, preDigits) // first `preDigits` elements
90
- const exponent = digits.slice(preDigits) // without the first `preDigits` elements
189
+ // Concatenate thousands + remainder
190
+ return buildThousands(thousands) + buildSegment(remainder)
191
+ }
192
+
193
+ // For numbers >= 1,000,000, use scale decomposition
194
+ return buildLargeNumberWords(n)
195
+ }
91
196
 
92
- let prefix, postfix
93
- let infix = this.exponentLengthToString(exponent.length)
197
+ /**
198
+ * Builds words for numbers >= 1,000,000.
199
+ *
200
+ * @param {bigint} n - Number >= 1,000,000
201
+ * @returns {string} Italian words
202
+ */
203
+ function buildLargeNumberWords (n) {
204
+ const parts = []
205
+ let remaining = n
94
206
 
95
- if (multiplier.join('') === '1') {
96
- prefix = 'un '
207
+ // Find the highest scale
208
+ let maxScale = 2
209
+ let testValue = 1_000_000n
210
+ while (testValue * 1000n <= remaining) {
211
+ testValue *= 1000n
212
+ maxScale++
213
+ }
214
+
215
+ // Process from highest scale down
216
+ for (let scaleIndex = maxScale; scaleIndex >= 0; scaleIndex--) {
217
+ const divisor = 1000n ** BigInt(scaleIndex)
218
+ const segment = remaining / divisor
219
+ remaining = remaining % divisor
220
+
221
+ if (segment === 0n) continue
222
+
223
+ const segNum = Number(segment)
224
+
225
+ if (scaleIndex >= 2) {
226
+ // Millions and above: "segment scaleWord"
227
+ const segmentWords = buildSegmentForScale(segNum)
228
+ const scaleWord = segment === 1n
229
+ ? getScaleWordSingular(scaleIndex)
230
+ : getScaleWordPlural(scaleIndex)
231
+ parts.push(segmentWords + ' ' + scaleWord)
232
+ } else if (scaleIndex === 1) {
233
+ // Thousands
234
+ parts.push(buildThousands(segNum))
97
235
  } else {
98
- prefix = this.integerToWords(Math.trunc(Number(multiplier.join(''))))
99
- infix = ' ' + infix.slice(0, -1) + 'i' // without last element
236
+ // Units (scaleIndex === 0): just the segment
237
+ parts.push(buildSegment(segNum))
100
238
  }
239
+ }
101
240
 
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('')))
241
+ return joinPartsWithConnector(parts)
242
+ }
243
+
244
+ /**
245
+ * Joins parts with Italian connector rules.
246
+ * Uses "e" before simple (non-compound) final segment.
247
+ *
248
+ * @param {string[]} parts - Parts to join
249
+ * @returns {string} Joined string
250
+ */
251
+ function joinPartsWithConnector (parts) {
252
+ const len = parts.length
253
+ if (len === 0) return ''
254
+ if (len === 1) return parts[0]
107
255
 
108
- infix += (postfix.includes(' e ') ? ', ' : ' e ')
256
+ // Check if last part is "simple" (no space = no scale word)
257
+ const lastPart = parts[len - 1]
258
+ if (lastPart.indexOf(' ') === -1) {
259
+ // Join all but last with space, then add "e" connector
260
+ let result = parts[0]
261
+ for (let i = 1; i < len - 1; i++) {
262
+ result += ' ' + parts[i]
109
263
  }
264
+ return result + ' e ' + lastPart
265
+ }
110
266
 
111
- return prefix + infix + postfix
267
+ // Join with spaces
268
+ let result = parts[0]
269
+ for (let i = 1; i < len; i++) {
270
+ result += ' ' + parts[i]
112
271
  }
272
+ return result
273
+ }
113
274
 
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
- }
275
+ /**
276
+ * Converts decimal digits to Italian words.
277
+ *
278
+ * @param {string} decimalPart - Decimal digits (without the point)
279
+ * @returns {string} Italian words for decimal part
280
+ */
281
+ function decimalPartToWords (decimalPart) {
282
+ let result = ''
283
+
284
+ // Handle leading zeros
285
+ let i = 0
286
+ while (i < decimalPart.length && decimalPart[i] === '0') {
287
+ if (result) result += ' '
288
+ result += ZERO
289
+ i++
290
+ }
129
291
 
130
- return this.accentuate(words)
292
+ // Convert remainder as a single number
293
+ const remainder = decimalPart.slice(i)
294
+ if (remainder) {
295
+ if (result) result += ' '
296
+ result += integerToWords(BigInt(remainder))
131
297
  }
298
+
299
+ return result
132
300
  }
301
+
302
+ /**
303
+ * Converts a numeric value to Italian words.
304
+ *
305
+ * This is the main public API. It accepts any valid numeric input
306
+ * (number, string, or bigint) and handles parsing internally.
307
+ *
308
+ * @param {number | string | bigint} value - The numeric value to convert
309
+ * @returns {string} The number in Italian words
310
+ * @throws {TypeError} If value is not a valid numeric type
311
+ * @throws {Error} If value is not a valid number format
312
+ *
313
+ * @example
314
+ * toWords(28) // 'ventotto'
315
+ * toWords(23) // 'ventitré'
316
+ * toWords(1000) // 'mille'
317
+ * toWords(2000) // 'duemila'
318
+ * toWords(1000000) // 'un milione'
319
+ */
320
+ function toWords (value) {
321
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
322
+
323
+ let result = ''
324
+
325
+ if (isNegative) {
326
+ result = NEGATIVE + ' '
327
+ }
328
+
329
+ result += integerToWords(integerPart)
330
+
331
+ if (decimalPart) {
332
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
333
+ }
334
+
335
+ return result
336
+ }
337
+
338
+ // ============================================================================
339
+ // Public API
340
+ // ============================================================================
341
+
342
+ 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;