n2words 1.23.0 → 1.24.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 (317) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +182 -53
  3. package/dist/languages/ar.js +2 -0
  4. package/dist/languages/ar.js.map +1 -0
  5. package/dist/languages/az.js +2 -0
  6. package/dist/languages/az.js.map +1 -0
  7. package/dist/languages/bn.js +2 -0
  8. package/dist/languages/bn.js.map +1 -0
  9. package/dist/languages/cs.js +2 -0
  10. package/dist/languages/cs.js.map +1 -0
  11. package/dist/languages/da.js +2 -0
  12. package/dist/languages/da.js.map +1 -0
  13. package/dist/languages/de.js +2 -0
  14. package/dist/languages/de.js.map +1 -0
  15. package/dist/languages/el.js +2 -0
  16. package/dist/languages/el.js.map +1 -0
  17. package/dist/languages/en.js +2 -0
  18. package/dist/languages/en.js.map +1 -0
  19. package/dist/languages/es.js +2 -0
  20. package/dist/languages/es.js.map +1 -0
  21. package/dist/languages/fa.js +2 -0
  22. package/dist/languages/fa.js.map +1 -0
  23. package/dist/languages/fil.js +2 -0
  24. package/dist/languages/fil.js.map +1 -0
  25. package/dist/languages/fr-BE.js +2 -0
  26. package/dist/languages/fr-BE.js.map +1 -0
  27. package/dist/languages/fr.js +2 -0
  28. package/dist/languages/fr.js.map +1 -0
  29. package/dist/languages/gu.js +2 -0
  30. package/dist/languages/gu.js.map +1 -0
  31. package/dist/languages/he.js +2 -0
  32. package/dist/languages/he.js.map +1 -0
  33. package/dist/languages/hi.js +2 -0
  34. package/dist/languages/hi.js.map +1 -0
  35. package/dist/languages/hr.js +2 -0
  36. package/dist/languages/hr.js.map +1 -0
  37. package/dist/languages/hu.js +2 -0
  38. package/dist/languages/hu.js.map +1 -0
  39. package/dist/languages/id.js +2 -0
  40. package/dist/languages/id.js.map +1 -0
  41. package/dist/languages/it.js +2 -0
  42. package/dist/languages/it.js.map +1 -0
  43. package/dist/languages/ja.js +2 -0
  44. package/dist/languages/ja.js.map +1 -0
  45. package/dist/languages/kn.js +2 -0
  46. package/dist/languages/kn.js.map +1 -0
  47. package/dist/languages/ko.js +2 -0
  48. package/dist/languages/ko.js.map +1 -0
  49. package/dist/languages/lt.js +2 -0
  50. package/dist/languages/lt.js.map +1 -0
  51. package/dist/languages/lv.js +2 -0
  52. package/dist/languages/lv.js.map +1 -0
  53. package/dist/languages/mr.js +2 -0
  54. package/dist/languages/mr.js.map +1 -0
  55. package/dist/languages/ms.js +2 -0
  56. package/dist/languages/ms.js.map +1 -0
  57. package/dist/languages/nb.js +2 -0
  58. package/dist/languages/nb.js.map +1 -0
  59. package/dist/languages/nl.js +2 -0
  60. package/dist/languages/nl.js.map +1 -0
  61. package/dist/languages/pa-Guru.js +2 -0
  62. package/dist/languages/pa-Guru.js.map +1 -0
  63. package/dist/languages/pl.js +2 -0
  64. package/dist/languages/pl.js.map +1 -0
  65. package/dist/languages/pt.js +2 -0
  66. package/dist/languages/pt.js.map +1 -0
  67. package/dist/languages/ro.js +2 -0
  68. package/dist/languages/ro.js.map +1 -0
  69. package/dist/languages/ru.js +2 -0
  70. package/dist/languages/ru.js.map +1 -0
  71. package/dist/languages/sr-Latn.js +2 -0
  72. package/dist/languages/sr-Latn.js.map +1 -0
  73. package/dist/languages/sv.js +2 -0
  74. package/dist/languages/sv.js.map +1 -0
  75. package/dist/languages/sw.js +2 -0
  76. package/dist/languages/sw.js.map +1 -0
  77. package/dist/languages/ta.js +2 -0
  78. package/dist/languages/ta.js.map +1 -0
  79. package/dist/languages/te.js +2 -0
  80. package/dist/languages/te.js.map +1 -0
  81. package/dist/languages/th.js +2 -0
  82. package/dist/languages/th.js.map +1 -0
  83. package/dist/languages/tr.js +2 -0
  84. package/dist/languages/tr.js.map +1 -0
  85. package/dist/languages/uk.js +2 -0
  86. package/dist/languages/uk.js.map +1 -0
  87. package/dist/languages/ur.js +2 -0
  88. package/dist/languages/ur.js.map +1 -0
  89. package/dist/languages/vi.js +2 -0
  90. package/dist/languages/vi.js.map +1 -0
  91. package/dist/languages/zh-Hans.js +2 -0
  92. package/dist/languages/zh-Hans.js.map +1 -0
  93. package/dist/n2words.js +1 -1
  94. package/dist/n2words.js.map +1 -1
  95. package/lib/classes/abstract-language.js +211 -110
  96. package/lib/classes/greedy-scale-language.js +195 -0
  97. package/lib/classes/slavic-language.js +251 -0
  98. package/lib/classes/south-asian-language.js +161 -0
  99. package/lib/classes/turkic-language.js +63 -0
  100. package/lib/languages/ar.js +243 -0
  101. package/lib/languages/az.js +58 -0
  102. package/lib/languages/bn.js +126 -0
  103. package/lib/languages/cs.js +212 -0
  104. package/lib/languages/da.js +167 -0
  105. package/lib/languages/de.js +135 -0
  106. package/lib/languages/el.js +116 -0
  107. package/lib/languages/en.js +123 -0
  108. package/lib/languages/es.js +153 -0
  109. package/lib/languages/fa.js +127 -0
  110. package/lib/languages/fil.js +162 -0
  111. package/lib/languages/fr-BE.js +61 -0
  112. package/lib/languages/fr.js +145 -0
  113. package/lib/languages/gu.js +156 -0
  114. package/lib/languages/he.js +329 -0
  115. package/lib/languages/hi.js +126 -0
  116. package/lib/languages/hr.js +157 -0
  117. package/lib/languages/hu.js +155 -0
  118. package/lib/languages/id.js +174 -0
  119. package/lib/languages/it.js +148 -0
  120. package/lib/languages/ja.js +190 -0
  121. package/lib/languages/kn.js +71 -0
  122. package/lib/languages/ko.js +83 -0
  123. package/lib/languages/lt.js +171 -0
  124. package/lib/languages/lv.js +153 -0
  125. package/lib/languages/mr.js +156 -0
  126. package/lib/languages/ms.js +146 -0
  127. package/lib/languages/nb.js +120 -0
  128. package/lib/languages/nl.js +206 -0
  129. package/lib/languages/pa-Guru.js +126 -0
  130. package/lib/languages/pl.js +189 -0
  131. package/lib/languages/pt.js +147 -0
  132. package/lib/languages/ro.js +380 -0
  133. package/lib/languages/ru.js +116 -0
  134. package/lib/languages/sr-Latn.js +157 -0
  135. package/lib/languages/sv.js +127 -0
  136. package/lib/languages/sw.js +121 -0
  137. package/lib/languages/ta.js +226 -0
  138. package/lib/languages/te.js +229 -0
  139. package/lib/languages/th.js +123 -0
  140. package/lib/languages/tr.js +83 -0
  141. package/lib/{i18n → languages}/uk.js +50 -23
  142. package/lib/languages/ur.js +126 -0
  143. package/lib/languages/vi.js +193 -0
  144. package/lib/languages/zh-Hans.js +165 -0
  145. package/lib/n2words.js +246 -75
  146. package/package.json +80 -72
  147. package/typings/classes/abstract-language.d.ts +144 -0
  148. package/typings/classes/greedy-scale-language.d.ts +148 -0
  149. package/typings/classes/slavic-language.d.ts +145 -0
  150. package/typings/classes/south-asian-language.d.ts +101 -0
  151. package/typings/classes/turkic-language.d.ts +42 -0
  152. package/typings/languages/ar.d.ts +93 -0
  153. package/typings/languages/az.d.ts +25 -0
  154. package/typings/languages/bn.d.ts +1 -0
  155. package/typings/languages/cs.d.ts +120 -0
  156. package/typings/languages/da.d.ts +53 -0
  157. package/typings/languages/de.d.ts +26 -0
  158. package/typings/languages/el.d.ts +11 -0
  159. package/typings/languages/en.d.ts +30 -0
  160. package/typings/languages/es.d.ts +43 -0
  161. package/typings/languages/fa.d.ts +81 -0
  162. package/typings/languages/fil.d.ts +12 -0
  163. package/typings/languages/fr-BE.d.ts +41 -0
  164. package/typings/languages/fr.d.ts +43 -0
  165. package/typings/languages/gu.d.ts +12 -0
  166. package/typings/languages/he.d.ts +197 -0
  167. package/typings/languages/hi.d.ts +1 -0
  168. package/typings/languages/hr.d.ts +110 -0
  169. package/typings/languages/hu.d.ts +37 -0
  170. package/typings/languages/id.d.ts +69 -0
  171. package/typings/languages/it.d.ts +51 -0
  172. package/typings/languages/ja.d.ts +58 -0
  173. package/typings/languages/kn.d.ts +11 -0
  174. package/typings/languages/ko.d.ts +25 -0
  175. package/typings/languages/lt.d.ts +110 -0
  176. package/typings/languages/lv.d.ts +99 -0
  177. package/typings/languages/mr.d.ts +12 -0
  178. package/typings/languages/ms.d.ts +37 -0
  179. package/typings/languages/nb.d.ts +27 -0
  180. package/typings/languages/nl.d.ts +65 -0
  181. package/typings/languages/pa-Guru.d.ts +1 -0
  182. package/typings/languages/pl.d.ts +116 -0
  183. package/typings/languages/pt.d.ts +39 -0
  184. package/typings/languages/ro.d.ts +229 -0
  185. package/typings/languages/ru.d.ts +108 -0
  186. package/typings/languages/sr-Latn.d.ts +98 -0
  187. package/typings/languages/sv.d.ts +30 -0
  188. package/typings/languages/sw.d.ts +1 -0
  189. package/typings/languages/ta.d.ts +1 -0
  190. package/typings/languages/te.d.ts +1 -0
  191. package/typings/languages/th.d.ts +1 -0
  192. package/typings/languages/tr.d.ts +46 -0
  193. package/typings/languages/uk.d.ts +117 -0
  194. package/typings/languages/ur.d.ts +1 -0
  195. package/typings/languages/vi.d.ts +116 -0
  196. package/typings/languages/zh-Hans.d.ts +57 -0
  197. package/typings/n2words.d.ts +177 -0
  198. package/dist/ar.js +0 -2
  199. package/dist/ar.js.map +0 -1
  200. package/dist/az.js +0 -2
  201. package/dist/az.js.map +0 -1
  202. package/dist/cz.js +0 -2
  203. package/dist/cz.js.map +0 -1
  204. package/dist/de.js +0 -2
  205. package/dist/de.js.map +0 -1
  206. package/dist/dk.js +0 -2
  207. package/dist/dk.js.map +0 -1
  208. package/dist/en.js +0 -2
  209. package/dist/en.js.map +0 -1
  210. package/dist/es.js +0 -2
  211. package/dist/es.js.map +0 -1
  212. package/dist/fa.js +0 -2
  213. package/dist/fa.js.map +0 -1
  214. package/dist/fr-BE.js +0 -2
  215. package/dist/fr-BE.js.map +0 -1
  216. package/dist/fr.js +0 -2
  217. package/dist/fr.js.map +0 -1
  218. package/dist/he.js +0 -2
  219. package/dist/he.js.map +0 -1
  220. package/dist/hr.js +0 -2
  221. package/dist/hr.js.map +0 -1
  222. package/dist/hu.js +0 -2
  223. package/dist/hu.js.map +0 -1
  224. package/dist/id.js +0 -2
  225. package/dist/id.js.map +0 -1
  226. package/dist/it.js +0 -2
  227. package/dist/it.js.map +0 -1
  228. package/dist/ko.js +0 -2
  229. package/dist/ko.js.map +0 -1
  230. package/dist/lt.js +0 -2
  231. package/dist/lt.js.map +0 -1
  232. package/dist/lv.js +0 -2
  233. package/dist/lv.js.map +0 -1
  234. package/dist/n2words.d.ts +0 -2
  235. package/dist/nl.js +0 -2
  236. package/dist/nl.js.map +0 -1
  237. package/dist/no.js +0 -2
  238. package/dist/no.js.map +0 -1
  239. package/dist/pl.js +0 -2
  240. package/dist/pl.js.map +0 -1
  241. package/dist/pt.js +0 -2
  242. package/dist/pt.js.map +0 -1
  243. package/dist/ro.js +0 -2
  244. package/dist/ro.js.map +0 -1
  245. package/dist/ru.js +0 -2
  246. package/dist/ru.js.map +0 -1
  247. package/dist/sr.js +0 -2
  248. package/dist/sr.js.map +0 -1
  249. package/dist/tr.js +0 -2
  250. package/dist/tr.js.map +0 -1
  251. package/dist/uk.js +0 -2
  252. package/dist/uk.js.map +0 -1
  253. package/dist/vi.js +0 -2
  254. package/dist/vi.js.map +0 -1
  255. package/dist/zh.js +0 -2
  256. package/dist/zh.js.map +0 -1
  257. package/lib/classes/abstract-language.d.ts +0 -54
  258. package/lib/classes/base-language.d.ts +0 -58
  259. package/lib/classes/base-language.js +0 -172
  260. package/lib/i18n/ar.d.ts +0 -41
  261. package/lib/i18n/ar.js +0 -209
  262. package/lib/i18n/az.d.ts +0 -15
  263. package/lib/i18n/az.js +0 -66
  264. package/lib/i18n/cz.d.ts +0 -68
  265. package/lib/i18n/cz.js +0 -135
  266. package/lib/i18n/de.d.ts +0 -17
  267. package/lib/i18n/de.js +0 -103
  268. package/lib/i18n/dk.d.ts +0 -14
  269. package/lib/i18n/dk.js +0 -110
  270. package/lib/i18n/en.d.ts +0 -22
  271. package/lib/i18n/en.js +0 -86
  272. package/lib/i18n/es.d.ts +0 -16
  273. package/lib/i18n/es.js +0 -110
  274. package/lib/i18n/fa.d.ts +0 -54
  275. package/lib/i18n/fa.js +0 -106
  276. package/lib/i18n/fr-BE.d.ts +0 -11
  277. package/lib/i18n/fr-BE.js +0 -20
  278. package/lib/i18n/fr.d.ts +0 -15
  279. package/lib/i18n/fr.js +0 -99
  280. package/lib/i18n/he.d.ts +0 -61
  281. package/lib/i18n/he.js +0 -132
  282. package/lib/i18n/hr.d.ts +0 -68
  283. package/lib/i18n/hr.js +0 -129
  284. package/lib/i18n/hu.d.ts +0 -17
  285. package/lib/i18n/hu.js +0 -135
  286. package/lib/i18n/id.d.ts +0 -43
  287. package/lib/i18n/id.js +0 -156
  288. package/lib/i18n/it.d.ts +0 -29
  289. package/lib/i18n/it.js +0 -137
  290. package/lib/i18n/ko.d.ts +0 -15
  291. package/lib/i18n/ko.js +0 -56
  292. package/lib/i18n/lt.d.ts +0 -68
  293. package/lib/i18n/lt.js +0 -138
  294. package/lib/i18n/lv.d.ts +0 -57
  295. package/lib/i18n/lv.js +0 -120
  296. package/lib/i18n/nl.d.ts +0 -20
  297. package/lib/i18n/nl.js +0 -125
  298. package/lib/i18n/no.d.ts +0 -15
  299. package/lib/i18n/no.js +0 -77
  300. package/lib/i18n/pl.d.ts +0 -67
  301. package/lib/i18n/pl.js +0 -126
  302. package/lib/i18n/pt.d.ts +0 -26
  303. package/lib/i18n/pt.js +0 -118
  304. package/lib/i18n/ro.d.ts +0 -109
  305. package/lib/i18n/ro.js +0 -360
  306. package/lib/i18n/ru.d.ts +0 -30
  307. package/lib/i18n/ru.js +0 -198
  308. package/lib/i18n/sr.d.ts +0 -56
  309. package/lib/i18n/sr.js +0 -127
  310. package/lib/i18n/tr.d.ts +0 -15
  311. package/lib/i18n/tr.js +0 -64
  312. package/lib/i18n/uk.d.ts +0 -78
  313. package/lib/i18n/vi.d.ts +0 -70
  314. package/lib/i18n/vi.js +0 -151
  315. package/lib/i18n/zh.d.ts +0 -18
  316. package/lib/i18n/zh.js +0 -78
  317. package/lib/n2words.d.ts +0 -9
@@ -0,0 +1,155 @@
1
+ import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
2
+
3
+ /**
4
+ * Hungarian language converter.
5
+ *
6
+ * Converts numbers to Hungarian words following Hungarian conventions:
7
+ * - Agglutinative structure for compound numbers
8
+ * - No spaces between tens and units (e.g., "huszonegy" = twenty-one)
9
+ * - Special handling for "egy" (one) - often omitted as multiplier
10
+ * - Vowel harmony in compound words
11
+ *
12
+ * Features:
13
+ * - Compact number representations (húsz, harminc, negyven)
14
+ * - Pre-composed twenties (huszonegy through huszonkilenc)
15
+ * - "egész" as decimal separator (meaning "whole")
16
+ * - Support for very large numbers (up to quadrilliards)
17
+ */
18
+ export class Hungarian extends GreedyScaleLanguage {
19
+ negativeWord = 'mínusz'
20
+ decimalSeparatorWord = 'egész'
21
+ zeroWord = 'nulla'
22
+ scaleWordPairs = [
23
+ [1_000_000_000_000_000_000_000_000_000n, 'quadrilliárd'],
24
+ [1_000_000_000_000_000_000_000_000n, 'quadrillió'],
25
+ [1_000_000_000_000_000_000_000n, 'trilliárd'],
26
+ [1_000_000_000_000_000_000n, 'trillió'],
27
+ [1_000_000_000_000_000n, 'billiárd'],
28
+ [1_000_000_000_000n, 'billió'],
29
+ [1_000_000_000n, 'milliárd'],
30
+ [1_000_000n, 'millió'],
31
+ [1000n, 'ezer'],
32
+ [100n, 'száz'],
33
+ [90n, 'kilencven'],
34
+ [80n, 'nyolcvan'],
35
+ [70n, 'hetven'],
36
+ [60n, 'hatvan'],
37
+ [50n, 'ötven'],
38
+ [40n, 'negyven'],
39
+ [30n, 'harminc'],
40
+ [29n, 'huszonkilenc'],
41
+ [28n, 'huszonnyolc'],
42
+ [27n, 'huszonhét'],
43
+ [26n, 'huszonhat'],
44
+ [25n, 'huszonöt'],
45
+ [24n, 'huszonnégy'],
46
+ [23n, 'huszonhárom'],
47
+ [22n, 'huszonkettő'],
48
+ [21n, 'huszonegy'],
49
+ [20n, 'húsz'],
50
+ [19n, 'tizenkilenc'],
51
+ [18n, 'tizennyolc'],
52
+ [17n, 'tizenhét'],
53
+ [16n, 'tizenhat'],
54
+ [15n, 'tizenöt'],
55
+ [14n, 'tizennégy'],
56
+ [13n, 'tizenhárom'],
57
+ [12n, 'tizenkettő'],
58
+ [11n, 'tizenegy'],
59
+ [10n, 'tíz'],
60
+ [9n, 'kilenc'],
61
+ [8n, 'nyolc'],
62
+ [7n, 'hét'],
63
+ [6n, 'hat'],
64
+ [5n, 'öt'],
65
+ [4n, 'négy'],
66
+ [3n, 'három'],
67
+ [2n, 'kettő'],
68
+ [1n, 'egy'],
69
+ [0n, 'nulla']
70
+ ]
71
+
72
+ tensToCardinal (number) {
73
+ // Expecting `number` as bigint when called from convertWholePart
74
+ if (this.getScaleWord(number)) {
75
+ return this.getScaleWord(number)
76
+ } else {
77
+ const tens = number / 10n
78
+ const units = number % 10n
79
+ return this.getScaleWord(tens * 10n) + this.convertWholePart(units)
80
+ }
81
+ }
82
+
83
+ hundredsToCardinal (number) {
84
+ const hundreds = number / 100n
85
+ let prefix = 'száz'
86
+ if (hundreds !== 1n) {
87
+ prefix = this.convertWholePart(hundreds, '') + prefix
88
+ }
89
+ const postfix = this.convertWholePart(number % 100n, '')
90
+ return prefix + postfix
91
+ }
92
+
93
+ thousandsToCardinal (number) {
94
+ const thousands = number / 1000n
95
+ let prefix = 'ezer'
96
+ if (thousands !== 1n) {
97
+ prefix = this.convertWholePart(thousands, '') + prefix
98
+ }
99
+ const postfix = this.convertWholePart(number % 1000n, '')
100
+ const middle = (number <= 2000n || postfix === '') ? '' : '-'
101
+ return prefix + middle + postfix
102
+ }
103
+
104
+ bigNumberToCardinal (number) {
105
+ const numberLength = number.toString().length
106
+ const digits = (numberLength % 3 === 0) ? numberLength - 2 : numberLength
107
+ const exp = 10 ** (Math.floor(digits / 3) * 3)
108
+ const prefix = this.convertWholePart(number / BigInt(exp), '')
109
+ const rest = this.convertWholePart(number % BigInt(exp), '')
110
+ const postfix = (rest === '') ? '' : ('-' + rest)
111
+ return prefix + this.getScaleWord(BigInt(exp)) + postfix
112
+ }
113
+
114
+ convertWholePart (number, zeroWord = this.zeroWord) {
115
+ let words = ''
116
+
117
+ // Normalize to BigInt for consistent comparisons
118
+ if (typeof number !== 'bigint') number = BigInt(number)
119
+
120
+ if (number === 0n) {
121
+ words = zeroWord
122
+ } else if (zeroWord === '' && number === 2n) {
123
+ words = 'két'
124
+ } else if (number < 30n) {
125
+ words = this.getScaleWord(number)
126
+ } else if (number < 100n) {
127
+ words = this.tensToCardinal(number)
128
+ } else if (number < 1000n) {
129
+ words = this.hundredsToCardinal(number)
130
+ } else if (number < 1_000_000n) {
131
+ words = this.thousandsToCardinal(number)
132
+ } else {
133
+ words = this.bigNumberToCardinal(number)
134
+ }
135
+
136
+ return words
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Converts a number to Hungarian cardinal (written) form.
142
+ *
143
+ * @param {number|string|bigint} value The number to convert.
144
+ * @param {Object} [options] Conversion options (see Hungarian class options).
145
+ * @returns {string} The number expressed in Hungarian words.
146
+ * @throws {TypeError} If value is NaN or invalid type.
147
+ * @throws {Error} If value is an invalid number string.
148
+ *
149
+ * @example
150
+ * convertToWords(42); // 'negyvenkettő'
151
+ * convertToWords(21); // 'huszonegy'
152
+ */
153
+ export default function convertToWords (value, options = {}) {
154
+ return new Hungarian(options).convertToWords(value)
155
+ }
@@ -0,0 +1,174 @@
1
+ import AbstractLanguage from '../classes/abstract-language.js'
2
+
3
+ /**
4
+ * Indonesian language converter.
5
+ *
6
+ * Converts numbers to Indonesian words following Indonesian conventions:
7
+ * - Simple base-10 structure
8
+ * - "Se-" prefix for one (e.g., "seratus" = one hundred, "seribu" = one thousand)
9
+ * - Space-separated number components
10
+ * - Straightforward grouping by thousands
11
+ *
12
+ * Key Features:
13
+ * - Base number mapping (base) for single digits 0-9
14
+ * - Magnitude scale (thousands) mapping powers of 10 to Indonesian words
15
+ * - Group-based algorithm:
16
+ * 1. Split number into groups of 3 digits
17
+ * 2. For each group, convert ones/tens/hundreds using base and naming rules
18
+ * 3. Apply "se-" prefix for 1 (seratus, seribu, sejuta)
19
+ * 4. Combine with magnitude words
20
+ * 5. Join all parts with spaces
21
+ * - Regular patterns (puluh for tens, ratus for hundreds, ribu for thousands)
22
+ * - Clear grouping: ribu (10³), juta (10⁶), miliar (10⁹), triliun (10¹²)
23
+ *
24
+ * Features:
25
+ * - "Se-" prefix usage for singular units
26
+ * - Support for very large numbers (up to decillions)
27
+ */
28
+ export class Indonesian extends AbstractLanguage {
29
+ negativeWord = 'min'
30
+ decimalSeparatorWord = 'koma'
31
+ zeroWord = 'nol'
32
+ base = {
33
+ 0: [],
34
+ 1: ['satu'],
35
+ 2: ['dua'],
36
+ 3: ['tiga'],
37
+ 4: ['empat'],
38
+ 5: ['lima'],
39
+ 6: ['enam'],
40
+ 7: ['tujuh'],
41
+ 8: ['delapan'],
42
+ 9: ['sembilan']
43
+ }
44
+
45
+ thousands = {
46
+ 3: 'ribu', // 10^3
47
+ 6: 'juta', // 10^6
48
+ 9: 'miliar', // 10^9
49
+ 12: 'triliun',
50
+ 15: 'kuadriliun',
51
+ 18: 'kuantiliun',
52
+ 21: 'sekstiliun',
53
+ 24: 'septiliun',
54
+ 27: 'oktiliun',
55
+ 30: 'noniliun',
56
+ 33: 'desiliun'
57
+ }
58
+
59
+ splitBy3 (number) {
60
+ // Split to groups of 3 numbers: 1234567 -> [['1'], ['234'], ['567']]
61
+ const blocks = []
62
+ const stringNumber = number.toString()
63
+ const length = stringNumber.length
64
+ let firstBlock
65
+
66
+ if (length < 3) {
67
+ blocks.push([stringNumber])
68
+ } else {
69
+ const firstBlockLength = length % 3
70
+
71
+ if (firstBlockLength > 0) {
72
+ firstBlock = [stringNumber.slice(0, firstBlockLength)]
73
+ blocks.push(firstBlock)
74
+ }
75
+
76
+ for (let index = firstBlockLength; index < length; index += 3) {
77
+ const nextBlock = [stringNumber.slice(index, index + 3)]
78
+ blocks.push(nextBlock)
79
+ }
80
+ }
81
+ return blocks
82
+ }
83
+
84
+ spell (blocks) {
85
+ let wordBlocks = []
86
+ let spelling
87
+ const firstBlock = blocks[0]
88
+ if (firstBlock[0].length === 1) {
89
+ spelling = firstBlock[0] === '0' ? ['nol'] : this.base[Math.trunc(firstBlock[0])]
90
+ } else if (firstBlock[0].length === 2) {
91
+ spelling = this.getTens(firstBlock[0])
92
+ } else {
93
+ spelling = [...this.getHundreds(firstBlock[0][0]), ...this.getTens(firstBlock[0].slice(1, 3))]
94
+ }
95
+ wordBlocks = [...wordBlocks, [firstBlock[0], spelling]]
96
+ for (let index = 1; index < blocks.length; index++) {
97
+ let block = blocks[index]
98
+ spelling = [...this.getHundreds(block[0][0]), ...this.getTens(block[0].slice(1, 3))]
99
+ block = [...block, spelling]
100
+ wordBlocks = [...wordBlocks, block]
101
+ }
102
+ return wordBlocks
103
+ }
104
+
105
+ getHundreds (number) {
106
+ if (number === '1') {
107
+ return ['seratus']
108
+ } else if (number === '0') {
109
+ return []
110
+ } else {
111
+ return [...this.base[Math.trunc(number)], 'ratus']
112
+ }
113
+ }
114
+
115
+ getTens (number) {
116
+ if (number[0] === '1') {
117
+ if (number[1] === '0') {
118
+ return ['sepuluh']
119
+ } else if (number[1] === '1') {
120
+ return ['sebelas']
121
+ }
122
+ return [...this.base[Math.trunc(number[1])], 'belas']
123
+ }
124
+
125
+ if (number[0] === '0') {
126
+ return this.base[Math.trunc(number[1])]
127
+ }
128
+
129
+ return [...this.base[Math.trunc(number[0])], 'puluh', ...this.base[Math.trunc(number[1])]]
130
+ }
131
+
132
+ join (wordBlocks) {
133
+ let wordList = []
134
+ const length = wordBlocks.length - 1
135
+ const firstBlock = [wordBlocks[0]]
136
+ let start = 0
137
+ if (length === 1 && firstBlock[0][0] === '1') {
138
+ wordList.push('seribu')
139
+ start = 1
140
+ }
141
+ for (let index = start; index < length + 1; index++) {
142
+ wordList = [...wordList, ...wordBlocks[index][1]]
143
+ if (wordBlocks[index][1].length === 0) {
144
+ continue
145
+ }
146
+ if (index === length) {
147
+ break
148
+ }
149
+ wordList = [...wordList, this.thousands[(length - index) * 3]]
150
+ }
151
+ return wordList.join(' ')
152
+ }
153
+
154
+ convertWholePart (number) {
155
+ return this.join(
156
+ this.spell(
157
+ this.splitBy3(number)
158
+ )
159
+ ).trim()
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Converts a number to Indonesian cardinal (written) form.
165
+ *
166
+ * @param {number|string|bigint} value The number to convert.
167
+ * @param {Object} [options={}] Configuration options.
168
+ * @returns {string} The number expressed in Indonesian words.
169
+ * @throws {TypeError} If value is NaN or invalid type.
170
+ * @throws {Error} If value is an invalid number string.
171
+ */
172
+ export default function convertToWords (value, options = {}) {
173
+ return new Indonesian(options).convertToWords(value)
174
+ }
@@ -0,0 +1,148 @@
1
+ import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
2
+
3
+ /**
4
+ * Italian language converter.
5
+ *
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
11
+ *
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().
17
+ *
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
23
+ */
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(' ')
45
+ }
46
+
47
+ omitIfZero (numberToString) {
48
+ return numberToString === this.zeroWord ? '' : numberToString
49
+ }
50
+
51
+ phoneticContraction (string) {
52
+ return string.replaceAll('oo', 'o').replaceAll('ao', 'o').replaceAll('io', 'o').replaceAll('au', 'u').replaceAll('iu', 'u')
53
+ }
54
+
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)
61
+ }
62
+
63
+ hundredsToCardinal (number) {
64
+ const hundreds = Math.floor(number / 100)
65
+ let prefix = 'cento'
66
+ if (hundreds !== 1) {
67
+ prefix = this.cardinalWords[hundreds] + prefix
68
+ }
69
+ const postfix = this.omitIfZero(this.convertWholePart(number % 100))
70
+ return this.phoneticContraction(prefix + postfix)
71
+ }
72
+
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
+ }
79
+
80
+ exponentLengthToString (exponentLength) {
81
+ const prefix = this.exponentPrefixes[Math.floor(exponentLength / 6)]
82
+ return exponentLength % 6 === 0 ? prefix + 'ilione' : prefix + 'iliardo'
83
+ }
84
+
85
+ bigNumberToCardinal (number) {
86
+ const digits = [...number.toString()]
87
+
88
+ let preDigits = digits.length % 3
89
+ if (preDigits === 0) {
90
+ preDigits = 3
91
+ }
92
+
93
+ const multiplier = digits.slice(0, preDigits) // first `preDigits` elements
94
+ const exponent = digits.slice(preDigits) // without the first `preDigits` elements
95
+
96
+ let prefix, postfix
97
+ let infix = this.exponentLengthToString(exponent.length)
98
+
99
+ if (multiplier.join('') === '1') {
100
+ prefix = 'un '
101
+ } else {
102
+ prefix = this.convertWholePart(Math.trunc(Number(multiplier.join(''))))
103
+ infix = ' ' + infix.slice(0, -1) + 'i' // without last element
104
+ }
105
+
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('')))
111
+
112
+ infix += (postfix.includes(' e ') ? ', ' : ' e ')
113
+ }
114
+
115
+ return prefix + infix + postfix
116
+ }
117
+
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
+ }
132
+
133
+ return this.accentuate(words)
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Converts a number to Italian cardinal (written) form.
139
+ *
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.
145
+ */
146
+ export default function convertToWords (value, options = {}) {
147
+ return new Italian(options).convertToWords(value)
148
+ }
@@ -0,0 +1,190 @@
1
+ import AbstractLanguage from '../classes/abstract-language.js'
2
+
3
+ /**
4
+ * Japanese language converter.
5
+ *
6
+ * Converts numbers to Japanese kanji numerals using the Sino-Japanese system:
7
+ * - Uses kanji characters (一, 二, 三, etc.)
8
+ * - Grouping by 万 (man, 10,000) and 億 (oku, 100,000,000)
9
+ * - Unique scale units: 兆 (chō, trillion), 京 (kei, 10^16)
10
+ * - Special rules for 1: omitted before 十 (10), 百 (100), 千 (1000), but kept for 万 and above
11
+ *
12
+ * Key Features:
13
+ * - Sino-Japanese number system (standard for general counting)
14
+ * - Grouping by powers of 10,000 (万-based system, not 1,000)
15
+ * - Scale units: 万 (10^4), 億 (10^8), 兆 (10^12), 京 (10^16)
16
+ * - Special handling of 一 (one) prefix
17
+ * - Support for very large numbers up to 無量大数 (10^68)
18
+ *
19
+ * Algorithm:
20
+ * 1. Split number into groups of 4 digits (万-based, not 3-digit thousand-based)
21
+ * 2. Convert each group to kanji using special rules
22
+ * 3. Append appropriate scale word (万, 億, 兆, etc.)
23
+ * 4. Join all groups
24
+ *
25
+ * Special Rules:
26
+ * - 10, 100, 1000: Don't use 一 prefix (十, 百, 千 not 一十, 一百, 一千)
27
+ * - 10,000+: Keep 一 prefix (一万, 一億, 一兆)
28
+ * - Zero: 零 or 〇 (both acceptable, 零 is more formal)
29
+ */
30
+ export class Japanese extends AbstractLanguage {
31
+ negativeWord = 'マイナス'
32
+ decimalSeparatorWord = '点'
33
+ zeroWord = '零'
34
+ wordSeparator = '' // Japanese doesn't use spaces between characters
35
+ convertDecimalsPerDigit = true // Enable digit-by-digit decimal conversion
36
+
37
+ // Digits used for group conversion (1-9)
38
+ digits = ['一', '二', '三', '四', '五', '六', '七', '八', '九']
39
+
40
+ // Scale words for grouping by 10^4
41
+ scales = [
42
+ '', // 10^0 (ones)
43
+ '万', // 10^4 (man)
44
+ '億', // 10^8 (oku)
45
+ '兆', // 10^12 (chō)
46
+ '京', // 10^16 (kei)
47
+ '垓', // 10^20 (gai)
48
+ '秭', // 10^24 (jo/shi)
49
+ '穣', // 10^28 (jō)
50
+ '溝', // 10^32 (kō)
51
+ '澗', // 10^36 (kan)
52
+ '正', // 10^40 (sei)
53
+ '載', // 10^44 (sai)
54
+ '極', // 10^48 (goku)
55
+ '恒河沙', // 10^52 (gōgasha)
56
+ '阿僧祇', // 10^56 (asōgi)
57
+ '那由他', // 10^60 (nayuta)
58
+ '不可思議', // 10^64 (fukashigi)
59
+ '無量大数' // 10^68 (muryōtaisū)
60
+ ]
61
+
62
+ /**
63
+ * Converts a group of up to 4 digits to Japanese kanji.
64
+ * Handles special rules for omitting 一 before 十, 百, 千.
65
+ *
66
+ * Rule: Within a group, omit 一 before 十/百/千 EXCEPT when:
67
+ * - It's a lower group (not isTopGroup) AND
68
+ * - It would be the only character in that position
69
+ *
70
+ * @param {bigint} num - Number from 0 to 9999
71
+ * @param {boolean} isTopGroup - Whether this is the highest non-zero group
72
+ * @returns {string} Japanese kanji representation
73
+ */
74
+ convertGroup (num, isTopGroup = false) {
75
+ if (num === 0n) return ''
76
+
77
+ const thousands = num / 1000n
78
+ const hundreds = (num % 1000n) / 100n
79
+ const tens = (num % 100n) / 10n
80
+ const ones = num % 10n
81
+
82
+ let result = ''
83
+
84
+ // Thousands (千)
85
+ if (thousands > 0n) {
86
+ // Always omit 一 before 千 when thousands === 1
87
+ if (thousands === 1n) {
88
+ result += '千'
89
+ } else {
90
+ result += this.digits[Number(thousands) - 1] + '千'
91
+ }
92
+ }
93
+
94
+ // Hundreds (百)
95
+ if (hundreds > 0n) {
96
+ // Always omit 一 before 百 when hundreds === 1
97
+ if (hundreds === 1n) {
98
+ result += '百'
99
+ } else {
100
+ result += this.digits[Number(hundreds) - 1] + '百'
101
+ }
102
+ }
103
+
104
+ // Tens (十)
105
+ if (tens > 0n) {
106
+ // Always omit 一 before 十 when tens === 1
107
+ if (tens === 1n) {
108
+ result += '十'
109
+ } else {
110
+ result += this.digits[Number(tens) - 1] + '十'
111
+ }
112
+ }
113
+
114
+ // Ones
115
+ if (ones > 0n) {
116
+ result += this.digits[Number(ones) - 1]
117
+ }
118
+
119
+ return result
120
+ }
121
+
122
+ /**
123
+ * Converts a BigInt number to Japanese cardinal form.
124
+ *
125
+ * @param {bigint} number - The number to convert
126
+ * @returns {string} Japanese kanji representation
127
+ */
128
+ convertWholePart (number) {
129
+ if (number === 0n) {
130
+ return this.zeroWord
131
+ }
132
+
133
+ let temp = number
134
+ let scaleIndex = 0
135
+ const groups = []
136
+
137
+ // Split into groups of 4 digits (万-based system)
138
+ while (temp > 0n) {
139
+ const group = temp % 10000n
140
+ if (group > 0n) {
141
+ groups.push({ value: group, scale: scaleIndex })
142
+ }
143
+ temp = temp / 10000n
144
+ scaleIndex++
145
+ }
146
+
147
+ // Reverse to process from highest to lowest
148
+ groups.reverse()
149
+
150
+ let result = ''
151
+
152
+ for (let i = 0; i < groups.length; i++) {
153
+ const { value, scale } = groups[i]
154
+ const isTopGroup = (i === 0)
155
+
156
+ const groupStr = this.convertGroup(value, isTopGroup)
157
+
158
+ // For scales >= 1 (万 and above), always add the scale word
159
+ if (scale >= 1) {
160
+ // Special case: if group is 1 and scale >= 1, we need 一 before the scale
161
+ if (value === 1n) {
162
+ result += '一' + this.scales[scale]
163
+ } else {
164
+ result += groupStr + this.scales[scale]
165
+ }
166
+ } else {
167
+ result += groupStr
168
+ }
169
+ }
170
+
171
+ return result
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Converts a value to cardinal (written) form in Japanese.
177
+ *
178
+ * @param {number|string|bigint} value - Number to convert.
179
+ * @param {Object} [options] - Options for the converter.
180
+ * @returns {string} Value in Japanese kanji numerals.
181
+ * @throws {Error} If value is invalid.
182
+ *
183
+ * @example
184
+ * convertToWords(42, { lang: 'ja' }); // '四十二'
185
+ * convertToWords(1000, { lang: 'ja' }); // '千'
186
+ * convertToWords(10000, { lang: 'ja' }); // '一万'
187
+ */
188
+ export default function convertToWords (value, options = {}) {
189
+ return new Japanese(options).convertToWords(value)
190
+ }