n2words 1.24.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +183 -156
  3. package/dist/languages/am-Latn.js +3 -0
  4. package/dist/languages/am-Latn.js.map +1 -0
  5. package/dist/languages/am.js +3 -0
  6. package/dist/languages/am.js.map +1 -0
  7. package/dist/languages/ar.js +3 -2
  8. package/dist/languages/ar.js.map +1 -1
  9. package/dist/languages/az.js +3 -2
  10. package/dist/languages/az.js.map +1 -1
  11. package/dist/languages/bn.js +3 -2
  12. package/dist/languages/bn.js.map +1 -1
  13. package/dist/languages/cs.js +3 -2
  14. package/dist/languages/cs.js.map +1 -1
  15. package/dist/languages/da.js +3 -2
  16. package/dist/languages/da.js.map +1 -1
  17. package/dist/languages/de.js +3 -2
  18. package/dist/languages/de.js.map +1 -1
  19. package/dist/languages/el.js +3 -2
  20. package/dist/languages/el.js.map +1 -1
  21. package/dist/languages/en.js +3 -2
  22. package/dist/languages/en.js.map +1 -1
  23. package/dist/languages/es.js +3 -2
  24. package/dist/languages/es.js.map +1 -1
  25. package/dist/languages/fa.js +3 -2
  26. package/dist/languages/fa.js.map +1 -1
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -2
  30. package/dist/languages/fil.js.map +1 -1
  31. package/dist/languages/fr-BE.js +3 -2
  32. package/dist/languages/fr-BE.js.map +1 -1
  33. package/dist/languages/fr.js +3 -2
  34. package/dist/languages/fr.js.map +1 -1
  35. package/dist/languages/gu.js +3 -2
  36. package/dist/languages/gu.js.map +1 -1
  37. package/dist/languages/ha.js +3 -0
  38. package/dist/languages/ha.js.map +1 -0
  39. package/dist/languages/hbo.js +3 -0
  40. package/dist/languages/hbo.js.map +1 -0
  41. package/dist/languages/he.js +3 -2
  42. package/dist/languages/he.js.map +1 -1
  43. package/dist/languages/hi.js +3 -2
  44. package/dist/languages/hi.js.map +1 -1
  45. package/dist/languages/hr.js +3 -2
  46. package/dist/languages/hr.js.map +1 -1
  47. package/dist/languages/hu.js +3 -2
  48. package/dist/languages/hu.js.map +1 -1
  49. package/dist/languages/id.js +3 -2
  50. package/dist/languages/id.js.map +1 -1
  51. package/dist/languages/it.js +3 -2
  52. package/dist/languages/it.js.map +1 -1
  53. package/dist/languages/ja.js +3 -2
  54. package/dist/languages/ja.js.map +1 -1
  55. package/dist/languages/kn.js +3 -2
  56. package/dist/languages/kn.js.map +1 -1
  57. package/dist/languages/ko.js +3 -2
  58. package/dist/languages/ko.js.map +1 -1
  59. package/dist/languages/lt.js +3 -2
  60. package/dist/languages/lt.js.map +1 -1
  61. package/dist/languages/lv.js +3 -2
  62. package/dist/languages/lv.js.map +1 -1
  63. package/dist/languages/mr.js +3 -2
  64. package/dist/languages/mr.js.map +1 -1
  65. package/dist/languages/ms.js +3 -2
  66. package/dist/languages/ms.js.map +1 -1
  67. package/dist/languages/nb.js +3 -2
  68. package/dist/languages/nb.js.map +1 -1
  69. package/dist/languages/nl.js +3 -2
  70. package/dist/languages/nl.js.map +1 -1
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -2
  74. package/dist/languages/pl.js.map +1 -1
  75. package/dist/languages/pt.js +3 -2
  76. package/dist/languages/pt.js.map +1 -1
  77. package/dist/languages/ro.js +3 -2
  78. package/dist/languages/ro.js.map +1 -1
  79. package/dist/languages/ru.js +3 -2
  80. package/dist/languages/ru.js.map +1 -1
  81. package/dist/languages/sr-Cyrl.js +3 -0
  82. package/dist/languages/sr-Cyrl.js.map +1 -0
  83. package/dist/languages/sr-Latn.js +3 -2
  84. package/dist/languages/sr-Latn.js.map +1 -1
  85. package/dist/languages/sv.js +3 -2
  86. package/dist/languages/sv.js.map +1 -1
  87. package/dist/languages/sw.js +3 -2
  88. package/dist/languages/sw.js.map +1 -1
  89. package/dist/languages/ta.js +3 -2
  90. package/dist/languages/ta.js.map +1 -1
  91. package/dist/languages/te.js +3 -2
  92. package/dist/languages/te.js.map +1 -1
  93. package/dist/languages/th.js +3 -2
  94. package/dist/languages/th.js.map +1 -1
  95. package/dist/languages/tr.js +3 -2
  96. package/dist/languages/tr.js.map +1 -1
  97. package/dist/languages/uk.js +3 -2
  98. package/dist/languages/uk.js.map +1 -1
  99. package/dist/languages/ur.js +3 -2
  100. package/dist/languages/ur.js.map +1 -1
  101. package/dist/languages/vi.js +3 -2
  102. package/dist/languages/vi.js.map +1 -1
  103. package/dist/languages/zh-Hans.js +3 -2
  104. package/dist/languages/zh-Hans.js.map +1 -1
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +3 -2
  108. package/dist/n2words.js.map +1 -1
  109. package/lib/languages/am-Latn.d.ts +7 -0
  110. package/lib/languages/am-Latn.js +164 -0
  111. package/lib/languages/am.d.ts +7 -0
  112. package/lib/languages/am.js +164 -0
  113. package/lib/languages/ar.d.ts +17 -0
  114. package/lib/languages/ar.js +171 -209
  115. package/lib/languages/az.d.ts +7 -0
  116. package/lib/languages/az.js +167 -49
  117. package/lib/languages/bn.d.ts +7 -0
  118. package/lib/languages/bn.js +142 -123
  119. package/lib/languages/cs.d.ts +18 -0
  120. package/lib/languages/cs.js +303 -176
  121. package/lib/languages/da.d.ts +14 -0
  122. package/lib/languages/da.js +267 -139
  123. package/lib/languages/de.d.ts +17 -0
  124. package/lib/languages/de.js +310 -113
  125. package/lib/languages/el.d.ts +14 -0
  126. package/lib/languages/el.js +225 -98
  127. package/lib/languages/en.d.ts +17 -0
  128. package/lib/languages/en.js +235 -102
  129. package/lib/languages/es.d.ts +21 -0
  130. package/lib/languages/es.js +307 -125
  131. package/lib/languages/fa.d.ts +7 -0
  132. package/lib/languages/fa.js +115 -108
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +7 -0
  136. package/lib/languages/fil.js +199 -139
  137. package/lib/languages/fr-BE.d.ts +11 -0
  138. package/lib/languages/fr-BE.js +287 -48
  139. package/lib/languages/fr.d.ts +21 -0
  140. package/lib/languages/fr.js +343 -119
  141. package/lib/languages/gu.d.ts +7 -0
  142. package/lib/languages/gu.js +125 -144
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +13 -0
  146. package/lib/languages/hbo.js +300 -0
  147. package/lib/languages/he.d.ts +13 -0
  148. package/lib/languages/he.js +230 -283
  149. package/lib/languages/hi.d.ts +7 -0
  150. package/lib/languages/hi.js +142 -123
  151. package/lib/languages/hr.d.ts +11 -0
  152. package/lib/languages/hr.js +190 -129
  153. package/lib/languages/hu.d.ts +7 -0
  154. package/lib/languages/hu.js +194 -133
  155. package/lib/languages/id.d.ts +7 -0
  156. package/lib/languages/id.js +167 -140
  157. package/lib/languages/it.d.ts +19 -0
  158. package/lib/languages/it.js +337 -108
  159. package/lib/languages/ja.d.ts +17 -0
  160. package/lib/languages/ja.js +224 -155
  161. package/lib/languages/kn.d.ts +7 -0
  162. package/lib/languages/kn.js +128 -62
  163. package/lib/languages/ko.d.ts +14 -0
  164. package/lib/languages/ko.js +250 -70
  165. package/lib/languages/lt.d.ts +18 -0
  166. package/lib/languages/lt.js +287 -148
  167. package/lib/languages/lv.d.ts +18 -0
  168. package/lib/languages/lv.js +291 -123
  169. package/lib/languages/mr.d.ts +7 -0
  170. package/lib/languages/mr.js +125 -144
  171. package/lib/languages/ms.d.ts +7 -0
  172. package/lib/languages/ms.js +171 -112
  173. package/lib/languages/nb.d.ts +14 -0
  174. package/lib/languages/nb.js +275 -100
  175. package/lib/languages/nl.d.ts +26 -0
  176. package/lib/languages/nl.js +307 -174
  177. package/lib/languages/pa.d.ts +7 -0
  178. package/lib/languages/pa.js +163 -0
  179. package/lib/languages/pl.d.ts +22 -0
  180. package/lib/languages/pl.js +299 -158
  181. package/lib/languages/pt.d.ts +17 -0
  182. package/lib/languages/pt.js +279 -120
  183. package/lib/languages/ro.d.ts +18 -0
  184. package/lib/languages/ro.js +214 -337
  185. package/lib/languages/ru.d.ts +11 -0
  186. package/lib/languages/ru.js +219 -95
  187. package/lib/languages/sr-Cyrl.d.ts +11 -0
  188. package/lib/languages/sr-Cyrl.js +215 -0
  189. package/lib/languages/sr-Latn.d.ts +11 -0
  190. package/lib/languages/sr-Latn.js +190 -132
  191. package/lib/languages/sv.d.ts +14 -0
  192. package/lib/languages/sv.js +280 -103
  193. package/lib/languages/sw.d.ts +7 -0
  194. package/lib/languages/sw.js +135 -103
  195. package/lib/languages/ta.d.ts +7 -0
  196. package/lib/languages/ta.js +133 -205
  197. package/lib/languages/te.d.ts +7 -0
  198. package/lib/languages/te.js +148 -213
  199. package/lib/languages/th.d.ts +7 -0
  200. package/lib/languages/th.js +139 -101
  201. package/lib/languages/tr.d.ts +18 -0
  202. package/lib/languages/tr.js +246 -66
  203. package/lib/languages/uk.d.ts +11 -0
  204. package/lib/languages/uk.js +197 -101
  205. package/lib/languages/ur.d.ts +7 -0
  206. package/lib/languages/ur.js +160 -123
  207. package/lib/languages/vi.d.ts +17 -0
  208. package/lib/languages/vi.js +287 -164
  209. package/lib/languages/zh-Hans.d.ts +11 -0
  210. package/lib/languages/zh-Hans.js +159 -142
  211. package/lib/languages/zh-Hant.d.ts +11 -0
  212. package/lib/languages/zh-Hant.js +202 -0
  213. package/lib/n2words.d.ts +53 -0
  214. package/lib/n2words.js +91 -227
  215. package/lib/utils/is-plain-object.d.ts +13 -0
  216. package/lib/utils/is-plain-object.js +17 -0
  217. package/lib/utils/parse-numeric.d.ts +17 -0
  218. package/lib/utils/parse-numeric.js +108 -0
  219. package/lib/utils/validate-options.d.ts +8 -0
  220. package/lib/utils/validate-options.js +16 -0
  221. package/package.json +118 -67
  222. package/dist/languages/pa-Guru.js +0 -2
  223. package/dist/languages/pa-Guru.js.map +0 -1
  224. package/lib/classes/abstract-language.js +0 -261
  225. package/lib/classes/greedy-scale-language.js +0 -195
  226. package/lib/classes/slavic-language.js +0 -251
  227. package/lib/classes/south-asian-language.js +0 -161
  228. package/lib/classes/turkic-language.js +0 -63
  229. package/lib/languages/pa-Guru.js +0 -126
  230. package/typings/classes/abstract-language.d.ts +0 -144
  231. package/typings/classes/greedy-scale-language.d.ts +0 -148
  232. package/typings/classes/slavic-language.d.ts +0 -145
  233. package/typings/classes/south-asian-language.d.ts +0 -101
  234. package/typings/classes/turkic-language.d.ts +0 -42
  235. package/typings/languages/ar.d.ts +0 -93
  236. package/typings/languages/az.d.ts +0 -25
  237. package/typings/languages/bn.d.ts +0 -1
  238. package/typings/languages/cs.d.ts +0 -120
  239. package/typings/languages/da.d.ts +0 -53
  240. package/typings/languages/de.d.ts +0 -26
  241. package/typings/languages/el.d.ts +0 -11
  242. package/typings/languages/en.d.ts +0 -30
  243. package/typings/languages/es.d.ts +0 -43
  244. package/typings/languages/fa.d.ts +0 -81
  245. package/typings/languages/fil.d.ts +0 -12
  246. package/typings/languages/fr-BE.d.ts +0 -41
  247. package/typings/languages/fr.d.ts +0 -43
  248. package/typings/languages/gu.d.ts +0 -12
  249. package/typings/languages/he.d.ts +0 -197
  250. package/typings/languages/hi.d.ts +0 -1
  251. package/typings/languages/hr.d.ts +0 -110
  252. package/typings/languages/hu.d.ts +0 -37
  253. package/typings/languages/id.d.ts +0 -69
  254. package/typings/languages/it.d.ts +0 -51
  255. package/typings/languages/ja.d.ts +0 -58
  256. package/typings/languages/kn.d.ts +0 -11
  257. package/typings/languages/ko.d.ts +0 -25
  258. package/typings/languages/lt.d.ts +0 -110
  259. package/typings/languages/lv.d.ts +0 -99
  260. package/typings/languages/mr.d.ts +0 -12
  261. package/typings/languages/ms.d.ts +0 -37
  262. package/typings/languages/nb.d.ts +0 -27
  263. package/typings/languages/nl.d.ts +0 -65
  264. package/typings/languages/pa-Guru.d.ts +0 -1
  265. package/typings/languages/pl.d.ts +0 -116
  266. package/typings/languages/pt.d.ts +0 -39
  267. package/typings/languages/ro.d.ts +0 -229
  268. package/typings/languages/ru.d.ts +0 -108
  269. package/typings/languages/sr-Latn.d.ts +0 -98
  270. package/typings/languages/sv.d.ts +0 -30
  271. package/typings/languages/sw.d.ts +0 -1
  272. package/typings/languages/ta.d.ts +0 -1
  273. package/typings/languages/te.d.ts +0 -1
  274. package/typings/languages/th.d.ts +0 -1
  275. package/typings/languages/tr.d.ts +0 -46
  276. package/typings/languages/uk.d.ts +0 -117
  277. package/typings/languages/ur.d.ts +0 -1
  278. package/typings/languages/vi.d.ts +0 -116
  279. package/typings/languages/zh-Hans.d.ts +0 -57
  280. package/typings/n2words.d.ts +0 -177
@@ -1,156 +1,137 @@
1
1
  /**
2
- * Marathi language implementation for n2words
2
+ * Marathi language converter - Functional Implementation
3
3
  *
4
- * Marathi uses Indian-style number grouping (3 digits, then 2-2 from right).
5
- * Numbers: शून्य (0), एक (1), दोन (2), तीन (3), चार (4), पाच (5)...
4
+ * Self-contained converter for South Asian numbering.
6
5
  *
7
- * @module lib/languages/mr
8
- * @example
9
- * import mr from './lib/languages/mr.js'
10
- * mr(42) // 'बेचाळीस'
11
- * mr(1000) // 'एक हजार'
6
+ * Key features:
7
+ * - Indian numbering system (हजार, लाख, कोटी)
8
+ * - Devanagari script
9
+ * - 3-2-2 grouping pattern (last 3 digits, then groups of 2)
10
+ * - Complete word forms for 0-99
11
+ * - Per-digit decimal reading
12
12
  */
13
13
 
14
- import SouthAsianLanguage from '../classes/south-asian-language.js'
14
+ import { parseNumericValue } from '../utils/parse-numeric.js'
15
15
 
16
- /**
17
- * Marathi language implementation
18
- * Extends SouthAsianLanguage for Indian-style grouping
19
- */
20
- class MarathiLanguage extends SouthAsianLanguage {
21
- negativeWord = 'उणे'
22
- decimalSeparatorWord = 'दशांश'
23
- zeroWord = 'शून्य'
24
- hundredWord = 'शंभर'
25
- convertDecimalsPerDigit = true
26
-
27
- belowHundred = [
28
- 'शून्य',
29
- 'एक',
30
- 'दोन',
31
- 'तीन',
32
- 'चार',
33
- 'पाच',
34
- 'सहा',
35
- 'सात',
36
- 'आठ',
37
- 'नऊ',
38
- 'दहा',
39
- 'अकरा',
40
- 'बारा',
41
- 'तेरा',
42
- 'चौदा',
43
- 'पंधरा',
44
- 'सोळा',
45
- 'सतरा',
46
- 'अठरा',
47
- 'एकोणीस',
48
- 'वीस',
49
- 'एकवीस',
50
- 'बावीस',
51
- 'तेवीस',
52
- 'चोवीस',
53
- 'पंचवीस',
54
- 'सव्वीस',
55
- 'सत्तावीस',
56
- 'अठ्ठावीस',
57
- 'एकोणतीस',
58
- 'तीस',
59
- 'एकतीस',
60
- 'बत्तीस',
61
- 'तेहेतीस',
62
- 'चौतीस',
63
- 'पस्तीस',
64
- 'छत्तीस',
65
- 'सदतीस',
66
- 'अडतीस',
67
- 'एकोणचाळीस',
68
- 'चाळीस',
69
- 'एकेचाळीस',
70
- 'बेचाळीस',
71
- 'त्रेचाळीस',
72
- 'चव्वेचाळीस',
73
- 'पंचेचाळीस',
74
- 'सेहेचाळीस',
75
- 'सत्तेचाळीस',
76
- 'अठ्ठेचाळीस',
77
- 'एकोणपन्नास',
78
- 'पन्नास',
79
- 'एक्काव्वन',
80
- 'बावन्न',
81
- 'त्रेपन्न',
82
- 'चोपन्न',
83
- 'पंचाव्वन',
84
- 'छप्पन्न',
85
- 'सत्तावन्न',
86
- 'अठ्ठावन्न',
87
- 'एकोणसाठ',
88
- 'साठ',
89
- 'एकसष्ठ',
90
- 'बासष्ठ',
91
- 'त्रेसष्ठ',
92
- 'चौसष्ठ',
93
- 'पासष्ठ',
94
- 'सहासष्ठ',
95
- 'सदुसष्ठ',
96
- 'अडुसष्ठ',
97
- 'एकोणसत्तर',
98
- 'सत्तर',
99
- 'एकाहत्तर',
100
- 'बाहत्तर',
101
- 'त्र्याहत्तर',
102
- 'चौऱ्याहत्तर',
103
- 'पंच्याहत्तर',
104
- 'शहात्तर',
105
- 'सत्याहत्तर',
106
- 'अठ्ठ्याहत्तर',
107
- 'एकोणऐंशी',
108
- 'ऐंशी',
109
- 'एक्याऐंशी',
110
- 'ब्याऐंशी',
111
- 'त्र्याऐंशी',
112
- 'चौऱ्याऐंशी',
113
- 'पंच्याऐंशी',
114
- 'शहाऐंशी',
115
- 'सत्याऐंशी',
116
- 'अठ्ठ्याऐंशी',
117
- 'एकोणनव्वद',
118
- 'नव्वद',
119
- 'एक्याण्णव',
120
- 'ब्याण्णव',
121
- 'त्र्याण्णव',
122
- 'चौऱ्याण्णव',
123
- 'पंच्याण्णव',
124
- 'शहाण्णव',
125
- 'सत्याण्णव',
126
- 'अठ्ठ्याण्णव',
127
- 'नव्याण्णव'
128
- ]
129
-
130
- scaleWords = [
131
- '',
132
- 'हजार',
133
- 'लाख',
134
- 'कोटी',
135
- 'अब्ज',
136
- 'खर्व',
137
- 'निखर्व',
138
- 'महापद्म',
139
- 'शंकू'
140
- ]
16
+ // ============================================================================
17
+ // Vocabulary
18
+ // ============================================================================
19
+
20
+ const ZERO = 'शून्य'
21
+ const NEGATIVE = 'उणे'
22
+ const DECIMAL_SEP = 'दशांश'
23
+ const HUNDRED = 'शंभर'
24
+
25
+ const BELOW_HUNDRED = [
26
+ 'शून्य', 'एक', 'दोन', 'तीन', 'चार', 'पाच', 'सहा', 'सात', 'आठ', 'नऊ',
27
+ 'दहा', 'अकरा', 'बारा', 'तेरा', 'चौदा', 'पंधरा', 'सोळा', 'सतरा', 'अठरा', 'एकोणीस',
28
+ 'वीस', 'एकवीस', 'बावीस', 'तेवीस', 'चोवीस', 'पंचवीस', 'सव्वीस', 'सत्तावीस', 'अठ्ठावीस', 'एकोणतीस',
29
+ 'तीस', 'एकतीस', 'बत्तीस', 'तेहेतीस', 'चौतीस', 'पस्तीस', 'छत्तीस', 'सदतीस', 'अडतीस', 'एकोणचाळीस',
30
+ 'चाळीस', 'एकेचाळीस', 'बेचाळीस', 'त्रेचाळीस', 'चव्वेचाळीस', 'पंचेचाळीस', 'सेहेचाळीस', 'सत्तेचाळीस', 'अठ्ठेचाळीस', 'एकोणपन्नास',
31
+ 'पन्नास', 'एक्काव्वन', 'बावन्न', 'त्रेपन्न', 'चोपन्न', 'पंचाव्वन', 'छप्पन्न', 'सत्तावन्न', 'अठ्ठावन्न', 'एकोणसाठ',
32
+ 'साठ', 'एकसष्ठ', 'बासष्ठ', 'त्रेसष्ठ', 'चौसष्ठ', 'पासष्ठ', 'सहासष्ठ', 'सदुसष्ठ', 'अडुसष्ठ', 'एकोणसत्तर',
33
+ 'सत्तर', 'एकाहत्तर', 'बाहत्तर', 'त्र्याहत्तर', 'चौऱ्याहत्तर', 'पंच्याहत्तर', 'शहात्तर', 'सत्याहत्तर', 'अठ्ठ्याहत्तर', 'एकोणऐंशी',
34
+ 'ऐंशी', 'एक्याऐंशी', 'ब्याऐंशी', 'त्र्याऐंशी', 'चौऱ्याऐंशी', 'पंच्याऐंशी', 'शहाऐंशी', 'सत्याऐंशी', 'अठ्ठ्याऐंशी', 'एकोणनव्वद',
35
+ 'नव्वद', 'एक्याण्णव', 'ब्याण्णव', 'त्र्याण्णव', 'चौऱ्याण्णव', 'पंच्याण्णव', 'शहाण्णव', 'सत्याण्णव', 'अठ्ठ्याण्णव', 'नव्याण्णव'
36
+ ]
37
+
38
+ // Scale words: index 0 = units (empty), 1 = thousand, 2 = lakh, 3 = crore, etc.
39
+ const SCALE_WORDS = ['', 'हजार', 'लाख', 'कोटी', 'अब्ज', 'खर्व', 'निखर्व', 'महापद्म', 'शंकू']
40
+
41
+ // ============================================================================
42
+ // Segment Splitting (inlined for performance)
43
+ // ============================================================================
44
+
45
+ function groupByThreeThenTwos (n) {
46
+ const numStr = n.toString()
47
+ if (numStr.length <= 3) return [Number(numStr)]
48
+
49
+ const segments = []
50
+ segments.unshift(Number(numStr.slice(-3)))
51
+
52
+ let remaining = numStr.slice(0, -3)
53
+ while (remaining.length > 0) {
54
+ segments.unshift(Number(remaining.slice(-2)))
55
+ remaining = remaining.slice(0, -2)
56
+ }
57
+
58
+ return segments
59
+ }
60
+
61
+ function segmentToWords (n) {
62
+ if (n === 0) return ''
63
+ if (n < 100) return BELOW_HUNDRED[n]
64
+
65
+ const hundreds = Math.trunc(n / 100)
66
+ const remainder = n % 100
67
+
68
+ if (remainder === 0) {
69
+ return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED
70
+ }
71
+ return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED + ' ' + BELOW_HUNDRED[remainder]
72
+ }
73
+
74
+ // ============================================================================
75
+ // Conversion Functions
76
+ // ============================================================================
77
+
78
+ function integerToWords (n) {
79
+ if (n === 0n) return ZERO
80
+
81
+ const segments = groupByThreeThenTwos(n)
82
+ const segmentCount = segments.length
83
+ const words = []
84
+
85
+ for (let i = 0; i < segmentCount; i++) {
86
+ const segmentValue = segments[i]
87
+ if (segmentValue === 0) continue
88
+
89
+ const scaleIndex = segmentCount - i - 1
90
+ words.push(segmentToWords(segmentValue))
91
+ if (scaleIndex > 0 && SCALE_WORDS[scaleIndex]) {
92
+ words.push(SCALE_WORDS[scaleIndex])
93
+ }
94
+ }
95
+
96
+ return words.join(' ').trim()
97
+ }
98
+
99
+ function decimalPartToWords (decimalPart) {
100
+ // Per-digit decimal reading
101
+ const digits = []
102
+ for (const char of decimalPart) {
103
+ const d = parseInt(char, 10)
104
+ digits.push(d === 0 ? ZERO : BELOW_HUNDRED[d])
105
+ }
106
+ return digits.join(' ')
141
107
  }
142
108
 
143
109
  /**
144
- * Convert a number to Marathi words
110
+ * Converts a numeric value to Marathi words.
145
111
  *
146
- * @param {number|string|bigint} value - The number to convert
147
- * @param {Object} [options={}] - Conversion options
148
- * @returns {string} The Marathi word representation
149
- * @example
150
- * convertToWords(42) // 'बेचाळीस'
151
- * convertToWords(1000) // 'एक हजार'
152
- * convertToWords(100000) // 'एक लाख'
112
+ * @param {number | string | bigint} value - The numeric value to convert
113
+ * @returns {string} The number in Marathi words
153
114
  */
154
- export default function convertToWords (value, options = {}) {
155
- return new MarathiLanguage(options).convertToWords(value)
115
+ function toWords (value) {
116
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
117
+
118
+ let result = ''
119
+
120
+ if (isNegative) {
121
+ result = NEGATIVE + ' '
122
+ }
123
+
124
+ result += integerToWords(integerPart)
125
+
126
+ if (decimalPart) {
127
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
128
+ }
129
+
130
+ return result
156
131
  }
132
+
133
+ // ============================================================================
134
+ // Exports
135
+ // ============================================================================
136
+
137
+ export { toWords }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts a numeric value to Malay words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Malay words
6
+ */
7
+ export function toWords(value: number | string | bigint): string;
@@ -1,146 +1,205 @@
1
- import AbstractLanguage from '../classes/abstract-language.js'
2
-
3
1
  /**
4
- * Malay (Bahasa Melayu) language converter.
2
+ * Malay (Bahasa Melayu) language converter - Functional Implementation
3
+ *
4
+ * Self-contained converter with precomputed lookup tables.
5
5
  *
6
- * Conventions:
7
- * - Base-10 structure
8
- * - "Se-" prefix for singular units (seratus, seribu, sejuta, sebilion, setrilion)
9
- * - Space-separated components (no conjunction like "dan" for tens/ones)
10
- * - Grouping by thousands (ribu, juta, bilion, trilion)
6
+ * Key features:
7
+ * - "Se-" prefix for ALL singular scale units (seratus, seribu, sejuta, sebilion)
8
+ * - Regular patterns (puluh for tens, ratus for hundreds)
9
+ * - Teens with "belas" suffix
10
+ * - Note: "lapan" (8) differs from Indonesian "delapan"
11
11
  */
12
- export class Malay extends AbstractLanguage {
13
- negativeWord = 'minus'
14
- decimalSeparatorWord = 'perpuluhan'
15
- zeroWord = 'sifar'
16
- base = {
17
- 0: [],
18
- 1: ['satu'],
19
- 2: ['dua'],
20
- 3: ['tiga'],
21
- 4: ['empat'],
22
- 5: ['lima'],
23
- 6: ['enam'],
24
- 7: ['tujuh'],
25
- 8: ['lapan'],
26
- 9: ['sembilan']
27
- }
28
12
 
29
- thousands = {
30
- 3: 'ribu',
31
- 6: 'juta',
32
- 9: 'bilion',
33
- 12: 'trilion'
34
- }
13
+ import { parseNumericValue } from '../utils/parse-numeric.js'
35
14
 
36
- splitBy3 (number) {
37
- const blocks = []
38
- const stringNumber = number.toString()
39
- const length = stringNumber.length
40
- let firstBlock
15
+ // ============================================================================
16
+ // Vocabulary
17
+ // ============================================================================
41
18
 
42
- if (length < 3) {
43
- blocks.push([stringNumber])
44
- } else {
45
- const firstBlockLength = length % 3
19
+ const ONES = ['', 'satu', 'dua', 'tiga', 'empat', 'lima', 'enam', 'tujuh', 'lapan', 'sembilan']
20
+ const TEENS = ['sepuluh', 'sebelas', 'dua belas', 'tiga belas', 'empat belas', 'lima belas', 'enam belas', 'tujuh belas', 'lapan belas', 'sembilan belas']
21
+ const TENS = ['', '', 'dua puluh', 'tiga puluh', 'empat puluh', 'lima puluh', 'enam puluh', 'tujuh puluh', 'lapan puluh', 'sembilan puluh']
46
22
 
47
- if (firstBlockLength > 0) {
48
- firstBlock = [stringNumber.slice(0, firstBlockLength)]
49
- blocks.push(firstBlock)
50
- }
23
+ const HUNDRED_WORD = 'ratus'
24
+ const THOUSAND_WORD = 'ribu'
25
+ const SCALE_WORDS = ['juta', 'bilion', 'trilion']
51
26
 
52
- for (let index = firstBlockLength; index < length; index += 3) {
53
- const nextBlock = [stringNumber.slice(index, index + 3)]
54
- blocks.push(nextBlock)
55
- }
56
- }
57
- return blocks
58
- }
27
+ const ZERO = 'sifar'
28
+ const NEGATIVE = 'minus'
29
+ const DECIMAL_SEP = 'perpuluhan'
30
+
31
+ // ============================================================================
32
+ // Precomputed Lookup Tables
33
+ // ============================================================================
34
+
35
+ function buildSegment (n) {
36
+ if (n === 0) return ''
37
+
38
+ const onesDigit = n % 10
39
+ const tensDigit = Math.floor(n / 10) % 10
40
+ const hundredsDigit = Math.floor(n / 100)
41
+
42
+ const parts = []
59
43
 
60
- spell (blocks) {
61
- let wordBlocks = []
62
- let spelling
63
- const firstBlock = blocks[0]
64
- if (firstBlock[0].length === 1) {
65
- spelling = firstBlock[0] === '0' ? ['sifar'] : this.base[Math.trunc(firstBlock[0])]
66
- } else if (firstBlock[0].length === 2) {
67
- spelling = this.getTens(firstBlock[0])
44
+ // Hundreds: seratus (100) or N ratus (200-900)
45
+ if (hundredsDigit > 0) {
46
+ if (hundredsDigit === 1) {
47
+ parts.push('se' + HUNDRED_WORD)
68
48
  } else {
69
- spelling = [...this.getHundreds(firstBlock[0][0]), ...this.getTens(firstBlock[0].slice(1, 3))]
49
+ parts.push(ONES[hundredsDigit] + ' ' + HUNDRED_WORD)
70
50
  }
71
- wordBlocks = [...wordBlocks, [firstBlock[0], spelling]]
72
- for (let index = 1; index < blocks.length; index++) {
73
- let block = blocks[index]
74
- spelling = [...this.getHundreds(block[0][0]), ...this.getTens(block[0].slice(1, 3))]
75
- block = [...block, spelling]
76
- wordBlocks = [...wordBlocks, block]
77
- }
78
- return wordBlocks
79
51
  }
80
52
 
81
- getHundreds (number) { // 'ratus'
82
- if (number === '1') {
83
- return ['seratus']
84
- } else if (number === '0') {
85
- return []
86
- } else {
87
- return [...this.base[Math.trunc(number)], 'ratus']
88
- }
53
+ // Tens and ones
54
+ const tensOnes = n % 100
55
+
56
+ if (tensOnes === 0) {
57
+ // Just hundreds
58
+ } else if (tensOnes < 10) {
59
+ parts.push(ONES[tensOnes])
60
+ } else if (tensOnes < 20) {
61
+ parts.push(TEENS[tensOnes - 10])
62
+ } else if (onesDigit === 0) {
63
+ parts.push(TENS[tensDigit])
64
+ } else {
65
+ parts.push(TENS[tensDigit] + ' ' + ONES[onesDigit])
89
66
  }
90
67
 
91
- getTens (number) { // 'puluh' and 'belas'
92
- if (number[0] === '1') {
93
- if (number[1] === '0') {
94
- return ['sepuluh']
95
- } else if (number[1] === '1') {
96
- return ['sebelas']
97
- }
98
- return [...this.base[Math.trunc(number[1])], 'belas']
68
+ return parts.join(' ')
69
+ }
70
+
71
+ const SEGMENTS = new Array(1000)
72
+ for (let i = 0; i < 1000; i++) {
73
+ SEGMENTS[i] = buildSegment(i)
74
+ }
75
+
76
+ // ============================================================================
77
+ // Conversion Functions
78
+ // ============================================================================
79
+
80
+ function integerToWords (n) {
81
+ if (n === 0n) return ZERO
82
+
83
+ if (n < 1000n) {
84
+ return SEGMENTS[Number(n)]
85
+ }
86
+
87
+ if (n < 1_000_000n) {
88
+ const thousands = Number(n / 1000n)
89
+ const remainder = Number(n % 1000n)
90
+
91
+ let result
92
+ if (thousands === 1) {
93
+ result = 'se' + THOUSAND_WORD
94
+ } else {
95
+ result = SEGMENTS[thousands] + ' ' + THOUSAND_WORD
99
96
  }
100
97
 
101
- if (number[0] === '0') {
102
- return this.base[Math.trunc(number[1])]
98
+ if (remainder > 0) {
99
+ result += ' ' + SEGMENTS[remainder]
103
100
  }
104
101
 
105
- return [...this.base[Math.trunc(number[0])], 'puluh', ...this.base[Math.trunc(number[1])]]
102
+ return result
106
103
  }
107
104
 
108
- join (wordBlocks) {
109
- const wordList = []
110
- const length = wordBlocks.length - 1
105
+ return buildLargeNumberWords(n)
106
+ }
111
107
 
112
- for (let index = 0; index <= length; index++) {
113
- const words = wordBlocks[index][1]
114
- const isLast = index === length
115
- const scaleWord = isLast ? null : this.thousands[(length - index) * 3]
108
+ function buildLargeNumberWords (n) {
109
+ const numStr = n.toString()
110
+ const len = numStr.length
116
111
 
117
- if (!isLast && words.length === 1 && words[0] === 'satu') {
118
- // Use se- prefix for singular scale units: seribu, sejuta, sebilion, setrilion
119
- wordList.push('se' + scaleWord)
120
- continue
121
- }
112
+ const segments = []
113
+ const segmentSize = 3
122
114
 
123
- // Add current block words
124
- for (const w of words) wordList.push(w)
115
+ const remainderLen = len % segmentSize
116
+ let pos = 0
117
+ if (remainderLen > 0) {
118
+ segments.push(Number(numStr.slice(0, remainderLen)))
119
+ pos = remainderLen
120
+ }
121
+ while (pos < len) {
122
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
123
+ pos += segmentSize
124
+ }
125
125
 
126
- // Append scale word if applicable and current block contributed words
127
- if (!isLast && words.length > 0) {
128
- wordList.push(scaleWord)
126
+ const parts = []
127
+ let scaleIndex = segments.length - 1
128
+
129
+ for (let i = 0; i < segments.length; i++) {
130
+ const segment = segments[i]
131
+
132
+ if (segment !== 0) {
133
+ if (scaleIndex === 0) {
134
+ parts.push(SEGMENTS[segment])
135
+ } else if (scaleIndex === 1) {
136
+ if (segment === 1) {
137
+ parts.push('se' + THOUSAND_WORD)
138
+ } else {
139
+ parts.push(SEGMENTS[segment] + ' ' + THOUSAND_WORD)
140
+ }
141
+ } else {
142
+ // Malay: "se-" prefix for ALL scale words when segment is 1
143
+ const scaleWord = SCALE_WORDS[scaleIndex - 2]
144
+ if (segment === 1) {
145
+ parts.push('se' + scaleWord)
146
+ } else {
147
+ parts.push(SEGMENTS[segment] + ' ' + scaleWord)
148
+ }
129
149
  }
130
150
  }
131
151
 
132
- return wordList.join(' ')
152
+ scaleIndex--
133
153
  }
134
154
 
135
- convertWholePart (number) {
136
- return this.join(
137
- this.spell(
138
- this.splitBy3(number)
139
- )
140
- ).trim()
155
+ return parts.join(' ')
156
+ }
157
+
158
+ function decimalPartToWords (decimalPart) {
159
+ let result = ''
160
+
161
+ let i = 0
162
+ while (i < decimalPart.length && decimalPart[i] === '0') {
163
+ if (result) result += ' '
164
+ result += ZERO
165
+ i++
141
166
  }
167
+
168
+ const remainder = decimalPart.slice(i)
169
+ if (remainder) {
170
+ if (result) result += ' '
171
+ result += integerToWords(BigInt(remainder))
172
+ }
173
+
174
+ return result
142
175
  }
143
176
 
144
- export default function convertToWords (value, options = {}) {
145
- return new Malay(options).convertToWords(value)
177
+ /**
178
+ * Converts a numeric value to Malay words.
179
+ *
180
+ * @param {number | string | bigint} value - The numeric value to convert
181
+ * @returns {string} The number in Malay words
182
+ */
183
+ function toWords (value) {
184
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
185
+
186
+ let result = ''
187
+
188
+ if (isNegative) {
189
+ result = NEGATIVE + ' '
190
+ }
191
+
192
+ result += integerToWords(integerPart)
193
+
194
+ if (decimalPart) {
195
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
196
+ }
197
+
198
+ return result
146
199
  }
200
+
201
+ // ============================================================================
202
+ // Exports
203
+ // ============================================================================
204
+
205
+ export { toWords }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Converts a numeric value to Norwegian Bokmål words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Norwegian words
6
+ * @throws {TypeError} If value is not a valid numeric type
7
+ * @throws {Error} If value is not a valid number format
8
+ *
9
+ * @example
10
+ * toWords(21) // 'tjue-en'
11
+ * toWords(101) // 'en hundre og en'
12
+ * toWords(1000000) // 'en million'
13
+ */
14
+ export function toWords(value: number | string | bigint): string;