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,193 +1,316 @@
1
- import AbstractLanguage from '../classes/abstract-language.js'
2
-
3
1
  /**
4
- * Vietnamese language converter.
2
+ * Vietnamese language converter - Functional Implementation v2
5
3
  *
6
- * Converts numbers to Vietnamese words following Vietnamese number naming:
7
- * - Base-10 grouping system (trăm = hundred, nghìn = thousand, triệu = million, tỷ = billion)
8
- * - Special pronunciation rules for 5 and 15 (lăm instead of năm)
9
- * - Special pronunciation for final 1 in compound numbers (mốt instead of một)
10
- * - "Lẻ" (odd/extra) used when tens place is zero but units exist
4
+ * A performance-optimized number-to-words converter using precomputed lookup tables.
5
+ * Self-contained module with its own input validation, ready for subpath exports.
11
6
  *
12
- * Key Features:
13
- * - Base number mapping for 0-19 (with special forms)
14
- * - Tens mapping (hai mươi, ba mươi, etc.)
15
- * - Group-based algorithm:
16
- * 1. Split number into groups of 3 digits
17
- * 2. For each group, build words using special pronunciation rules:
18
- * - "Mốt" instead of "một" for final 1 (e.g., 21 → hai mươi mốt)
19
- * - "Lăm" instead of "năm" for 15 and mid-group 5 (e.g., 15, 105)
20
- * - "Lẻ" prefix when tens=0 but units>0 (e.g., 101 → một trăm lẻ một)
21
- * 3. Append magnitude word (nghìn/triệu/tỷ)
22
- * 4. Join all groups with spaces
23
- * - Support for large numbers up to vigintillions
7
+ * Key optimization: Precompute all segment values (0-999) at module load.
8
+ * This eliminates all per-call string manipulation for segment conversion.
24
9
  *
25
- * Features:
26
- * - Natural Vietnamese number flow
27
- * - Proper handling of special cases (mốt, lăm)
28
- * - Contextual pronunciation adjustments
10
+ * Vietnamese-specific rules (handled in precomputation):
11
+ * - Special pronunciation: "lăm" for 5 in tens position, "mốt" for final 1
12
+ * - "Lẻ" (odd/extra) marker when tens place is zero after hundreds/scales
13
+ * - Short scale system with Vietnamese words (nghìn, triệu, tỷ)
29
14
  */
30
- export class Vietnamese extends AbstractLanguage {
31
- negativeWord = 'âm'
32
- decimalSeparatorWord = 'phẩy'
33
- zeroWord = 'không'
34
- base = {
35
- 0: 'không',
36
- 1: 'một',
37
- 2: 'hai',
38
- 3: 'ba',
39
- 4: 'bốn',
40
- 5: 'năm',
41
- 6: 'sáu',
42
- 7: 'bảy',
43
- 8: 'tám',
44
- 9: 'chín',
45
- 10: 'mười',
46
- 11: 'mười một',
47
- 12: 'mười hai',
48
- 13: 'mười ba',
49
- 14: 'mười bốn',
50
- 15: 'mười lăm',
51
- 16: 'mười sáu',
52
- 17: 'mười bảy',
53
- 18: 'mười tám',
54
- 19: 'mười chín'
55
- }
56
15
 
57
- tens = {
58
- 20: 'hai mươi',
59
- 30: 'ba mươi',
60
- 40: 'bốn mươi',
61
- 50: 'năm mươi',
62
- 60: 'sáu mươi',
63
- 70: 'bảy mươi',
64
- 80: 'tám mươi',
65
- 90: 'chín mươi'
66
- }
16
+ import { parseNumericValue } from '../utils/parse-numeric.js'
17
+
18
+ // ============================================================================
19
+ // Vocabulary (module-level constants)
20
+ // ============================================================================
21
+
22
+ // Base vocabulary for building lookup tables
23
+ const ONES = ['không', 'một', 'hai', 'ba', 'bốn', 'năm', 'sáu', 'bảy', 'tám', 'chín']
24
+
25
+ // Scale words indexed by scale level (0 = units, 1 = thousands, etc.)
26
+ const SCALES = [
27
+ '', 'nghìn', 'triệu', 'tỷ', 'nghìn tỷ', 'trăm nghìn tỷ',
28
+ 'Quintillion', 'Sextillion', 'Septillion', 'Octillion',
29
+ 'Nonillion', 'Decillion', 'Undecillion', 'Duodecillion',
30
+ 'Tredecillion', 'Quattuordecillion', 'Sexdecillion',
31
+ 'Septendecillion', 'Octodecillion', 'Novemdecillion', 'Vigintillion'
32
+ ]
33
+
34
+ const HUNDRED = 'trăm'
35
+ const ZERO = 'không'
36
+ const NEGATIVE = 'âm'
37
+ const DECIMAL_SEP = 'phẩy'
38
+ const LE = 'lẻ' // "odd/extra" marker for gaps
39
+
40
+ // Special forms
41
+ const MOT_FINAL = 'mốt' // 1 in tens position (21, 31, etc.)
42
+ const LAM = 'lăm' // 5 in tens position (25, 35, etc.)
43
+
44
+ // ============================================================================
45
+ // Precomputed Lookup Tables (built once at module load)
46
+ // ============================================================================
47
+
48
+ /**
49
+ * Builds word for 0-99 with special forms (mốt, lăm).
50
+ * Only used during table construction.
51
+ */
52
+ function buildBelowHundred (n) {
53
+ if (n === 0) return ONES[0]
54
+ if (n < 10) return ONES[n]
67
55
 
68
- thousands = {
69
- 1: 'nghìn', // 10^1
70
- 2: 'triệu', // 10^2
71
- 3: 'tỷ', // 10^3
72
- 4: 'nghìn tỷ',
73
- 5: 'trăm nghìn tỷ',
74
- 6: 'Quintillion',
75
- 7: 'Sextillion',
76
- 8: 'Septillion',
77
- 9: 'Octillion',
78
- 10: 'Nonillion',
79
- 11: 'Decillion',
80
- 12: 'Undecillion',
81
- 13: 'Duodecillion',
82
- 14: 'Tredecillion',
83
- 15: 'Quattuordecillion',
84
- 16: 'Sexdecillion',
85
- 17: 'Septendecillion',
86
- 18: 'Octodecillion',
87
- 19: 'Novemdecillion',
88
- 20: 'Vigintillion'
56
+ // Teens: 10-19
57
+ if (n < 20) {
58
+ const ones = n - 10
59
+ if (ones === 0) return 'mười'
60
+ if (ones === 5) return 'mười lăm'
61
+ return 'mười ' + ONES[ones]
89
62
  }
90
63
 
91
- /**
92
- * Convert numbers less than 100 to Vietnamese words.
93
- *
94
- * @param {number} number The number to convert (0-99).
95
- * @returns {string} The Vietnamese representation.
96
- */
97
- convertLess100 (number) {
98
- const unitsPart = number % 10
99
- const tensPart = number - unitsPart
100
- const tensPartText = this.tens[tensPart]
101
- if (unitsPart === 0) {
102
- return tensPartText
103
- }
104
- const unitsPartText = this.base[unitsPart]
105
- let suffix = unitsPartText
106
- if (unitsPart === 1) {
107
- suffix = 'mốt'
108
- }
109
- if (unitsPart === 5) {
110
- suffix = 'lăm'
111
- }
112
- return tensPartText + ' ' + suffix
64
+ // 20-99
65
+ const ones = n % 10
66
+ const tens = Math.floor(n / 10)
67
+ const tensWord = ONES[tens] + ' mươi'
68
+
69
+ if (ones === 0) return tensWord
70
+ if (ones === 1) return tensWord + ' ' + MOT_FINAL
71
+ if (ones === 5) return tensWord + ' ' + LAM
72
+ return tensWord + ' ' + ONES[ones]
73
+ }
74
+
75
+ /**
76
+ * Builds segment word for 0-999.
77
+ * Only used during table construction.
78
+ */
79
+ function buildSegment (n) {
80
+ if (n === 0) return ''
81
+
82
+ const hundreds = Math.floor(n / 100)
83
+ const remainder = n % 100
84
+
85
+ let result = ''
86
+
87
+ if (hundreds > 0) {
88
+ result = ONES[hundreds] + ' ' + HUNDRED
113
89
  }
114
90
 
115
- /**
116
- * Convert numbers less than 1000 to Vietnamese words.
117
- *
118
- * @param {number} number The number to convert (0-999).
119
- * @returns {string} The Vietnamese representation.
120
- */
121
- convertLess1000 (number) {
122
- const words = []
123
- const tensUnitsPart = number % 100
124
- const hundredsPart = number - tensUnitsPart
125
- if (hundredsPart > 0) {
126
- words.push(this.base[hundredsPart / 100], 'trăm')
127
- }
128
- if (tensUnitsPart > 0 && tensUnitsPart < 10) {
129
- if (words.length > 0) {
130
- words.push('lẻ')
131
- }
132
- if (tensUnitsPart === 5) {
133
- words.push('năm')
91
+ if (remainder > 0) {
92
+ if (remainder < 10) {
93
+ // Single digit after hundreds needs "lẻ"
94
+ if (result) {
95
+ result += ' ' + LE + ' '
96
+ // Use "năm" not "lăm" after lẻ
97
+ result += remainder === 5 ? 'năm' : ONES[remainder]
134
98
  } else {
135
- words.push(this.base[tensUnitsPart])
99
+ result = ONES[remainder]
136
100
  }
101
+ } else {
102
+ // 10-99 after hundreds
103
+ if (result) result += ' '
104
+ result += BELOW_100[remainder]
137
105
  }
138
- if (tensUnitsPart >= 10) {
139
- words.push(this.convertWholePart(tensUnitsPart))
140
- }
141
- return words.join(' ')
142
106
  }
143
107
 
144
- /**
145
- * Convert numbers greater than 1000 to Vietnamese words.
146
- *
147
- * @param {bigint} number The number to convert (>= 1000).
148
- * @returns {string} The Vietnamese representation.
149
- */
150
- convertMore1000 (number) {
151
- const words = []
152
- let division = number / 1000n
153
- let power = 1
154
- while (division >= 1000n) {
155
- division = division / 1000n
156
- power = power + 1
108
+ return result
109
+ }
110
+
111
+ // Precompute all 100 below-hundred words (0-99)
112
+ // BELOW_100[n] gives the Vietnamese word for n
113
+ const BELOW_100 = new Array(100)
114
+ for (let i = 0; i < 100; i++) {
115
+ BELOW_100[i] = buildBelowHundred(i)
116
+ }
117
+
118
+ // Precompute all 1000 segment words (0-999)
119
+ // SEGMENTS[n] gives the Vietnamese word for n within a segment
120
+ const SEGMENTS = new Array(1000)
121
+ for (let i = 0; i < 1000; i++) {
122
+ SEGMENTS[i] = buildSegment(i)
123
+ }
124
+
125
+ // Precompute "lẻ" prefixed versions for small remainders after scale words
126
+ // LE_SEGMENTS[n] gives "lẻ X" for n in range 1-99
127
+ const LE_SEGMENTS = new Array(100)
128
+ LE_SEGMENTS[0] = ''
129
+ for (let i = 1; i < 10; i++) {
130
+ // Use "năm" not "lăm" after lẻ
131
+ LE_SEGMENTS[i] = LE + ' ' + (i === 5 ? 'năm' : ONES[i])
132
+ }
133
+ for (let i = 10; i < 100; i++) {
134
+ LE_SEGMENTS[i] = LE + ' ' + BELOW_100[i]
135
+ }
136
+
137
+ // ============================================================================
138
+ // Conversion Functions
139
+ // ============================================================================
140
+
141
+ /**
142
+ * Converts a non-negative integer to Vietnamese words.
143
+ *
144
+ * @param {bigint} n - Non-negative integer to convert
145
+ * @returns {string} Vietnamese words
146
+ */
147
+ function integerToWords (n) {
148
+ if (n === 0n) return ZERO
149
+
150
+ // Fast path: numbers < 100 (direct lookup)
151
+ if (n < 100n) {
152
+ return BELOW_100[Number(n)]
153
+ }
154
+
155
+ // Fast path: numbers < 1000 (direct lookup)
156
+ if (n < 1000n) {
157
+ return SEGMENTS[Number(n)]
158
+ }
159
+
160
+ // Fast path: numbers < 1,000,000 (thousands)
161
+ if (n < 1_000_000n) {
162
+ const thousands = Number(n / 1000n)
163
+ const remainder = Number(n % 1000n)
164
+
165
+ const thousandsWords = SEGMENTS[thousands] + ' ' + SCALES[1]
166
+
167
+ if (remainder === 0) {
168
+ return thousandsWords
157
169
  }
158
- const r = number - (division * BigInt(Math.pow(1000, power)))
159
- words.push(this.convertWholePart(division), this.thousands[power])
160
- if (r > 0n) {
161
- if (r <= 99n) {
162
- words.push('lẻ')
163
- }
164
- words.push(this.convertWholePart(r))
170
+
171
+ // Check if remainder needs "lẻ" marker (< 100)
172
+ if (remainder < 100) {
173
+ return thousandsWords + ' ' + LE_SEGMENTS[remainder]
165
174
  }
166
- return words.join(' ')
175
+
176
+ return thousandsWords + ' ' + SEGMENTS[remainder]
167
177
  }
168
178
 
169
- convertWholePart (number) {
170
- if (number < 20n) {
171
- return this.base[Number(number)]
172
- } else {
173
- if (number < 100n) {
174
- return this.convertLess100(Number(number))
175
- } else {
176
- return (number < 1000n ? this.convertLess1000(Number(number)) : this.convertMore1000(number))
179
+ // For numbers >= 1,000,000, use scale decomposition
180
+ return buildLargeNumberWords(n)
181
+ }
182
+
183
+ /**
184
+ * Builds words for numbers >= 1,000,000.
185
+ *
186
+ * @param {bigint} n - Number >= 1,000,000
187
+ * @returns {string} Vietnamese words
188
+ */
189
+ function buildLargeNumberWords (n) {
190
+ const numStr = n.toString()
191
+ const len = numStr.length
192
+
193
+ // Build segments of 3 digits from right to left
194
+ const segments = []
195
+ const segmentSize = 3
196
+
197
+ const remainderLen = len % segmentSize
198
+ let pos = 0
199
+ if (remainderLen > 0) {
200
+ segments.push(Number(numStr.slice(0, remainderLen)))
201
+ pos = remainderLen
202
+ }
203
+ while (pos < len) {
204
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
205
+ pos += segmentSize
206
+ }
207
+
208
+ // Convert segments to words
209
+ const parts = []
210
+ let scaleIndex = segments.length - 1
211
+
212
+ for (let i = 0; i < segments.length; i++) {
213
+ const segment = segments[i]
214
+ if (segment !== 0) {
215
+ const words = SEGMENTS[segment]
216
+ if (words) {
217
+ if (scaleIndex > 0) {
218
+ parts.push(words + ' ' + SCALES[scaleIndex])
219
+ } else {
220
+ parts.push(words)
221
+ }
177
222
  }
178
223
  }
224
+ scaleIndex--
179
225
  }
226
+
227
+ // Join with "lẻ" logic for small remainders
228
+ const partsLen = parts.length
229
+ if (partsLen === 0) return ZERO
230
+ if (partsLen === 1) return parts[0]
231
+
232
+ // Check if final segment needs "lẻ" marker (remainder <= 99 after scale word)
233
+ const lastSegment = segments[segments.length - 1]
234
+ if (lastSegment > 0 && lastSegment <= 99) {
235
+ // Last segment is small (no hundreds), needs "lẻ" after scale word
236
+ let result = parts[0]
237
+ for (let i = 1; i < partsLen - 1; i++) {
238
+ result += ' ' + parts[i]
239
+ }
240
+ return result + ' ' + LE_SEGMENTS[lastSegment]
241
+ }
242
+
243
+ // Join with spaces
244
+ let result = parts[0]
245
+ for (let i = 1; i < partsLen; i++) {
246
+ result += ' ' + parts[i]
247
+ }
248
+ return result
249
+ }
250
+
251
+ /**
252
+ * Converts decimal digits to Vietnamese words.
253
+ *
254
+ * @param {string} decimalPart - Decimal digits (without the point)
255
+ * @returns {string} Vietnamese words for decimal part
256
+ */
257
+ function decimalPartToWords (decimalPart) {
258
+ let result = ''
259
+
260
+ // Handle leading zeros
261
+ let i = 0
262
+ while (i < decimalPart.length && decimalPart[i] === '0') {
263
+ if (result) result += ' '
264
+ result += ZERO
265
+ i++
266
+ }
267
+
268
+ // Convert remainder as a single number
269
+ const remainder = decimalPart.slice(i)
270
+ if (remainder) {
271
+ if (result) result += ' '
272
+ result += integerToWords(BigInt(remainder))
273
+ }
274
+
275
+ return result
180
276
  }
181
277
 
182
278
  /**
183
- * Converts a number to Vietnamese cardinal (written) form.
279
+ * Converts a numeric value to Vietnamese words.
184
280
  *
185
- * @param {number|string|bigint} value The number to convert.
186
- * @param {Object} [options={}] Configuration options.
187
- * @returns {string} The number expressed in Vietnamese words.
188
- * @throws {TypeError} If value is NaN or invalid type.
189
- * @throws {Error} If value is an invalid number string.
281
+ * This is the main public API. It accepts any valid numeric input
282
+ * (number, string, or bigint) and handles parsing internally.
283
+ *
284
+ * @param {number | string | bigint} value - The numeric value to convert
285
+ * @returns {string} The number in Vietnamese words
286
+ * @throws {TypeError} If value is not a valid numeric type
287
+ * @throws {Error} If value is not a valid number format
288
+ *
289
+ * @example
290
+ * toWords(42) // 'bốn mươi hai'
291
+ * toWords(101) // 'một trăm lẻ một'
292
+ * toWords(1000000) // 'một triệu'
190
293
  */
191
- export default function convertToWords (value, options = {}) {
192
- return new Vietnamese(options).convertToWords(value)
294
+ function toWords (value) {
295
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
296
+
297
+ let result = ''
298
+
299
+ if (isNegative) {
300
+ result = NEGATIVE + ' '
301
+ }
302
+
303
+ result += integerToWords(integerPart)
304
+
305
+ if (decimalPart) {
306
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
307
+ }
308
+
309
+ return result
193
310
  }
311
+
312
+ // ============================================================================
313
+ // Public API
314
+ // ============================================================================
315
+
316
+ export { toWords }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Converts a numeric value to Simplified Chinese words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @param {Object} [options] - Optional configuration
6
+ * @param {boolean} [options.formal=true] - Use formal/financial numerals
7
+ * @returns {string} The number in Simplified Chinese words
8
+ */
9
+ export function toWords(value: number | string | bigint, options?: {
10
+ formal?: boolean | undefined;
11
+ }): string;