n2words 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +86 -188
  3. package/dist/languages/am-Latn.js +3 -0
  4. package/dist/languages/am-Latn.js.map +1 -0
  5. package/dist/languages/am.js +3 -0
  6. package/dist/languages/am.js.map +1 -0
  7. package/dist/languages/ar.js +3 -0
  8. package/dist/languages/ar.js.map +1 -0
  9. package/dist/languages/az.js +3 -0
  10. package/dist/languages/az.js.map +1 -0
  11. package/dist/languages/bn.js +3 -0
  12. package/dist/languages/bn.js.map +1 -0
  13. package/dist/languages/cs.js +3 -0
  14. package/dist/languages/cs.js.map +1 -0
  15. package/dist/languages/da.js +3 -0
  16. package/dist/languages/da.js.map +1 -0
  17. package/dist/languages/de.js +3 -0
  18. package/dist/languages/de.js.map +1 -0
  19. package/dist/languages/el.js +3 -0
  20. package/dist/languages/el.js.map +1 -0
  21. package/dist/languages/en.js +3 -0
  22. package/dist/languages/en.js.map +1 -0
  23. package/dist/languages/es.js +3 -0
  24. package/dist/languages/es.js.map +1 -0
  25. package/dist/languages/fa.js +3 -0
  26. package/dist/languages/fa.js.map +1 -0
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -0
  30. package/dist/languages/fil.js.map +1 -0
  31. package/dist/languages/fr-BE.js +3 -0
  32. package/dist/languages/fr-BE.js.map +1 -0
  33. package/dist/languages/fr.js +3 -0
  34. package/dist/languages/fr.js.map +1 -0
  35. package/dist/languages/gu.js +3 -0
  36. package/dist/languages/gu.js.map +1 -0
  37. package/dist/languages/ha.js +3 -0
  38. package/dist/languages/ha.js.map +1 -0
  39. package/dist/languages/hbo.js +3 -0
  40. package/dist/languages/hbo.js.map +1 -0
  41. package/dist/languages/he.js +3 -0
  42. package/dist/languages/he.js.map +1 -0
  43. package/dist/languages/hi.js +3 -0
  44. package/dist/languages/hi.js.map +1 -0
  45. package/dist/languages/hr.js +3 -0
  46. package/dist/languages/hr.js.map +1 -0
  47. package/dist/languages/hu.js +3 -0
  48. package/dist/languages/hu.js.map +1 -0
  49. package/dist/languages/id.js +3 -0
  50. package/dist/languages/id.js.map +1 -0
  51. package/dist/languages/it.js +3 -0
  52. package/dist/languages/it.js.map +1 -0
  53. package/dist/languages/ja.js +3 -0
  54. package/dist/languages/ja.js.map +1 -0
  55. package/dist/languages/kn.js +3 -0
  56. package/dist/languages/kn.js.map +1 -0
  57. package/dist/languages/ko.js +3 -0
  58. package/dist/languages/ko.js.map +1 -0
  59. package/dist/languages/lt.js +3 -0
  60. package/dist/languages/lt.js.map +1 -0
  61. package/dist/languages/lv.js +3 -0
  62. package/dist/languages/lv.js.map +1 -0
  63. package/dist/languages/mr.js +3 -0
  64. package/dist/languages/mr.js.map +1 -0
  65. package/dist/languages/ms.js +3 -0
  66. package/dist/languages/ms.js.map +1 -0
  67. package/dist/languages/nb.js +3 -0
  68. package/dist/languages/nb.js.map +1 -0
  69. package/dist/languages/nl.js +3 -0
  70. package/dist/languages/nl.js.map +1 -0
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -0
  74. package/dist/languages/pl.js.map +1 -0
  75. package/dist/languages/pt.js +3 -0
  76. package/dist/languages/pt.js.map +1 -0
  77. package/dist/languages/ro.js +3 -0
  78. package/dist/languages/ro.js.map +1 -0
  79. package/dist/languages/ru.js +3 -0
  80. package/dist/languages/ru.js.map +1 -0
  81. package/dist/languages/sr-Cyrl.js +3 -0
  82. package/dist/languages/sr-Cyrl.js.map +1 -0
  83. package/dist/languages/sr-Latn.js +3 -0
  84. package/dist/languages/sr-Latn.js.map +1 -0
  85. package/dist/languages/sv.js +3 -0
  86. package/dist/languages/sv.js.map +1 -0
  87. package/dist/languages/sw.js +3 -0
  88. package/dist/languages/sw.js.map +1 -0
  89. package/dist/languages/ta.js +3 -0
  90. package/dist/languages/ta.js.map +1 -0
  91. package/dist/languages/te.js +3 -0
  92. package/dist/languages/te.js.map +1 -0
  93. package/dist/languages/th.js +3 -0
  94. package/dist/languages/th.js.map +1 -0
  95. package/dist/languages/tr.js +3 -0
  96. package/dist/languages/tr.js.map +1 -0
  97. package/dist/languages/uk.js +3 -0
  98. package/dist/languages/uk.js.map +1 -0
  99. package/dist/languages/ur.js +3 -0
  100. package/dist/languages/ur.js.map +1 -0
  101. package/dist/languages/vi.js +3 -0
  102. package/dist/languages/vi.js.map +1 -0
  103. package/dist/languages/zh-Hans.js +3 -0
  104. package/dist/languages/zh-Hans.js.map +1 -0
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +2 -2
  108. package/dist/n2words.js.map +1 -1
  109. package/lib/languages/am-Latn.d.ts +7 -0
  110. package/lib/languages/am-Latn.js +164 -0
  111. package/lib/languages/am.d.ts +7 -0
  112. package/lib/languages/am.js +164 -0
  113. package/lib/languages/ar.d.ts +14 -27
  114. package/lib/languages/ar.js +175 -129
  115. package/lib/languages/az.d.ts +4 -9
  116. package/lib/languages/az.js +171 -37
  117. package/lib/languages/bn.d.ts +4 -8
  118. package/lib/languages/bn.js +138 -124
  119. package/lib/languages/cs.d.ts +15 -85
  120. package/lib/languages/cs.js +310 -114
  121. package/lib/languages/da.d.ts +11 -12
  122. package/lib/languages/da.js +276 -101
  123. package/lib/languages/de.d.ts +14 -11
  124. package/lib/languages/de.js +317 -86
  125. package/lib/languages/el.d.ts +11 -11
  126. package/lib/languages/el.js +231 -78
  127. package/lib/languages/en.d.ts +14 -13
  128. package/lib/languages/en.js +242 -72
  129. package/lib/languages/es.d.ts +18 -12
  130. package/lib/languages/es.js +317 -103
  131. package/lib/languages/fa.d.ts +4 -44
  132. package/lib/languages/fa.js +112 -122
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +4 -13
  136. package/lib/languages/fil.js +207 -106
  137. package/lib/languages/fr-BE.d.ts +8 -8
  138. package/lib/languages/fr-BE.js +294 -19
  139. package/lib/languages/fr.d.ts +18 -12
  140. package/lib/languages/fr.js +352 -89
  141. package/lib/languages/gu.d.ts +4 -8
  142. package/lib/languages/gu.js +130 -125
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +10 -110
  146. package/lib/languages/hbo.js +263 -214
  147. package/lib/languages/he.d.ts +10 -77
  148. package/lib/languages/he.js +242 -172
  149. package/lib/languages/hi.d.ts +4 -8
  150. package/lib/languages/hi.js +138 -124
  151. package/lib/languages/hr.d.ts +8 -77
  152. package/lib/languages/hr.js +194 -89
  153. package/lib/languages/hu.d.ts +4 -19
  154. package/lib/languages/hu.js +198 -119
  155. package/lib/languages/id.d.ts +4 -34
  156. package/lib/languages/id.js +171 -129
  157. package/lib/languages/it.d.ts +16 -34
  158. package/lib/languages/it.js +339 -94
  159. package/lib/languages/ja.d.ts +14 -14
  160. package/lib/languages/ja.js +233 -111
  161. package/lib/languages/kn.d.ts +4 -8
  162. package/lib/languages/kn.js +130 -35
  163. package/lib/languages/ko.d.ts +11 -11
  164. package/lib/languages/ko.js +257 -49
  165. package/lib/languages/lt.d.ts +15 -67
  166. package/lib/languages/lt.js +296 -122
  167. package/lib/languages/lv.d.ts +15 -67
  168. package/lib/languages/lv.js +297 -106
  169. package/lib/languages/mr.d.ts +4 -8
  170. package/lib/languages/mr.js +130 -125
  171. package/lib/languages/ms.d.ts +4 -28
  172. package/lib/languages/ms.js +171 -116
  173. package/lib/languages/nb.d.ts +11 -9
  174. package/lib/languages/nb.js +282 -87
  175. package/lib/languages/nl.d.ts +23 -13
  176. package/lib/languages/nl.js +317 -133
  177. package/lib/languages/pa.d.ts +4 -8
  178. package/lib/languages/pa.js +156 -124
  179. package/lib/languages/pl.d.ts +19 -77
  180. package/lib/languages/pl.js +307 -87
  181. package/lib/languages/pt.d.ts +14 -26
  182. package/lib/languages/pt.js +286 -92
  183. package/lib/languages/ro.d.ts +15 -155
  184. package/lib/languages/ro.js +219 -235
  185. package/lib/languages/ru.d.ts +8 -82
  186. package/lib/languages/ru.js +222 -78
  187. package/lib/languages/sr-Cyrl.d.ts +8 -77
  188. package/lib/languages/sr-Cyrl.js +191 -89
  189. package/lib/languages/sr-Latn.d.ts +8 -77
  190. package/lib/languages/sr-Latn.js +191 -89
  191. package/lib/languages/sv.d.ts +11 -11
  192. package/lib/languages/sv.js +288 -74
  193. package/lib/languages/sw.d.ts +4 -36
  194. package/lib/languages/sw.js +133 -106
  195. package/lib/languages/ta.d.ts +4 -17
  196. package/lib/languages/ta.js +129 -201
  197. package/lib/languages/te.d.ts +4 -19
  198. package/lib/languages/te.js +141 -196
  199. package/lib/languages/th.d.ts +4 -14
  200. package/lib/languages/th.js +135 -91
  201. package/lib/languages/tr.d.ts +15 -9
  202. package/lib/languages/tr.js +256 -49
  203. package/lib/languages/uk.d.ts +8 -82
  204. package/lib/languages/uk.js +200 -78
  205. package/lib/languages/ur.d.ts +4 -8
  206. package/lib/languages/ur.js +156 -124
  207. package/lib/languages/vi.d.ts +14 -69
  208. package/lib/languages/vi.js +294 -125
  209. package/lib/languages/zh-Hans.d.ts +8 -18
  210. package/lib/languages/zh-Hans.js +163 -92
  211. package/lib/languages/zh-Hant.d.ts +8 -18
  212. package/lib/languages/zh-Hant.js +181 -90
  213. package/lib/n2words.d.ts +53 -209
  214. package/lib/n2words.js +111 -530
  215. package/lib/utils/is-plain-object.d.ts +13 -0
  216. package/lib/utils/is-plain-object.js +17 -0
  217. package/lib/utils/parse-numeric.d.ts +17 -0
  218. package/lib/utils/parse-numeric.js +108 -0
  219. package/lib/utils/validate-options.d.ts +8 -0
  220. package/lib/utils/validate-options.js +16 -0
  221. package/package.json +26 -14
  222. package/dist/ArabicConverter.js +0 -3
  223. package/dist/ArabicConverter.js.map +0 -1
  224. package/dist/AzerbaijaniConverter.js +0 -3
  225. package/dist/AzerbaijaniConverter.js.map +0 -1
  226. package/dist/BanglaConverter.js +0 -3
  227. package/dist/BanglaConverter.js.map +0 -1
  228. package/dist/BiblicalHebrewConverter.js +0 -3
  229. package/dist/BiblicalHebrewConverter.js.map +0 -1
  230. package/dist/CroatianConverter.js +0 -3
  231. package/dist/CroatianConverter.js.map +0 -1
  232. package/dist/CzechConverter.js +0 -3
  233. package/dist/CzechConverter.js.map +0 -1
  234. package/dist/DanishConverter.js +0 -3
  235. package/dist/DanishConverter.js.map +0 -1
  236. package/dist/DutchConverter.js +0 -3
  237. package/dist/DutchConverter.js.map +0 -1
  238. package/dist/EnglishConverter.js +0 -3
  239. package/dist/EnglishConverter.js.map +0 -1
  240. package/dist/FilipinoConverter.js +0 -3
  241. package/dist/FilipinoConverter.js.map +0 -1
  242. package/dist/FrenchBelgiumConverter.js +0 -3
  243. package/dist/FrenchBelgiumConverter.js.map +0 -1
  244. package/dist/FrenchConverter.js +0 -3
  245. package/dist/FrenchConverter.js.map +0 -1
  246. package/dist/GermanConverter.js +0 -3
  247. package/dist/GermanConverter.js.map +0 -1
  248. package/dist/GreekConverter.js +0 -3
  249. package/dist/GreekConverter.js.map +0 -1
  250. package/dist/GujaratiConverter.js +0 -3
  251. package/dist/GujaratiConverter.js.map +0 -1
  252. package/dist/HebrewConverter.js +0 -3
  253. package/dist/HebrewConverter.js.map +0 -1
  254. package/dist/HindiConverter.js +0 -3
  255. package/dist/HindiConverter.js.map +0 -1
  256. package/dist/HungarianConverter.js +0 -3
  257. package/dist/HungarianConverter.js.map +0 -1
  258. package/dist/IndonesianConverter.js +0 -3
  259. package/dist/IndonesianConverter.js.map +0 -1
  260. package/dist/ItalianConverter.js +0 -3
  261. package/dist/ItalianConverter.js.map +0 -1
  262. package/dist/JapaneseConverter.js +0 -3
  263. package/dist/JapaneseConverter.js.map +0 -1
  264. package/dist/KannadaConverter.js +0 -3
  265. package/dist/KannadaConverter.js.map +0 -1
  266. package/dist/KoreanConverter.js +0 -3
  267. package/dist/KoreanConverter.js.map +0 -1
  268. package/dist/LatvianConverter.js +0 -3
  269. package/dist/LatvianConverter.js.map +0 -1
  270. package/dist/LithuanianConverter.js +0 -3
  271. package/dist/LithuanianConverter.js.map +0 -1
  272. package/dist/MalayConverter.js +0 -3
  273. package/dist/MalayConverter.js.map +0 -1
  274. package/dist/MarathiConverter.js +0 -3
  275. package/dist/MarathiConverter.js.map +0 -1
  276. package/dist/NorwegianBokmalConverter.js +0 -3
  277. package/dist/NorwegianBokmalConverter.js.map +0 -1
  278. package/dist/PersianConverter.js +0 -3
  279. package/dist/PersianConverter.js.map +0 -1
  280. package/dist/PolishConverter.js +0 -3
  281. package/dist/PolishConverter.js.map +0 -1
  282. package/dist/PortugueseConverter.js +0 -3
  283. package/dist/PortugueseConverter.js.map +0 -1
  284. package/dist/PunjabiConverter.js +0 -3
  285. package/dist/PunjabiConverter.js.map +0 -1
  286. package/dist/RomanianConverter.js +0 -3
  287. package/dist/RomanianConverter.js.map +0 -1
  288. package/dist/RussianConverter.js +0 -3
  289. package/dist/RussianConverter.js.map +0 -1
  290. package/dist/SerbianCyrillicConverter.js +0 -3
  291. package/dist/SerbianCyrillicConverter.js.map +0 -1
  292. package/dist/SerbianLatinConverter.js +0 -3
  293. package/dist/SerbianLatinConverter.js.map +0 -1
  294. package/dist/SimplifiedChineseConverter.js +0 -3
  295. package/dist/SimplifiedChineseConverter.js.map +0 -1
  296. package/dist/SpanishConverter.js +0 -3
  297. package/dist/SpanishConverter.js.map +0 -1
  298. package/dist/SwahiliConverter.js +0 -3
  299. package/dist/SwahiliConverter.js.map +0 -1
  300. package/dist/SwedishConverter.js +0 -3
  301. package/dist/SwedishConverter.js.map +0 -1
  302. package/dist/TamilConverter.js +0 -3
  303. package/dist/TamilConverter.js.map +0 -1
  304. package/dist/TeluguConverter.js +0 -3
  305. package/dist/TeluguConverter.js.map +0 -1
  306. package/dist/ThaiConverter.js +0 -3
  307. package/dist/ThaiConverter.js.map +0 -1
  308. package/dist/TraditionalChineseConverter.js +0 -3
  309. package/dist/TraditionalChineseConverter.js.map +0 -1
  310. package/dist/TurkishConverter.js +0 -3
  311. package/dist/TurkishConverter.js.map +0 -1
  312. package/dist/UkrainianConverter.js +0 -3
  313. package/dist/UkrainianConverter.js.map +0 -1
  314. package/dist/UrduConverter.js +0 -3
  315. package/dist/UrduConverter.js.map +0 -1
  316. package/dist/VietnameseConverter.js +0 -3
  317. package/dist/VietnameseConverter.js.map +0 -1
  318. package/lib/classes/abstract-language.d.ts +0 -178
  319. package/lib/classes/abstract-language.js +0 -268
  320. package/lib/classes/greedy-scale-language.d.ts +0 -109
  321. package/lib/classes/greedy-scale-language.js +0 -201
  322. package/lib/classes/slavic-language.d.ts +0 -148
  323. package/lib/classes/slavic-language.js +0 -281
  324. package/lib/classes/south-asian-language.d.ts +0 -70
  325. package/lib/classes/south-asian-language.js +0 -154
  326. package/lib/classes/turkic-language.d.ts +0 -26
  327. package/lib/classes/turkic-language.js +0 -59
@@ -1,72 +1,17 @@
1
1
  /**
2
- * Vietnamese language converter.
2
+ * Converts a numeric value to Vietnamese words.
3
3
  *
4
- * Supports:
5
- * - Special pronunciation rules (lăm for 5, mốt for final 1)
6
- * - "Lẻ" (odd/extra) when tens place is zero
7
- * - Vietnamese diacritical marks
4
+ * This is the main public API. It accepts any valid numeric input
5
+ * (number, string, or bigint) and handles parsing internally.
6
+ *
7
+ * @param {number | string | bigint} value - The numeric value to convert
8
+ * @returns {string} The number in Vietnamese words
9
+ * @throws {TypeError} If value is not a valid numeric type
10
+ * @throws {Error} If value is not a valid number format
11
+ *
12
+ * @example
13
+ * toWords(42) // 'bốn mươi hai'
14
+ * toWords(101) // 'một trăm lẻ một'
15
+ * toWords(1000000) // 'một triệu'
8
16
  */
9
- export class Vietnamese extends AbstractLanguage {
10
- belowTwentyWords: {
11
- 0: string;
12
- 1: string;
13
- 2: string;
14
- 3: string;
15
- 4: string;
16
- 5: string;
17
- 6: string;
18
- 7: string;
19
- 8: string;
20
- 9: string;
21
- 10: string;
22
- 11: string;
23
- 12: string;
24
- 13: string;
25
- 14: string;
26
- 15: string;
27
- 16: string;
28
- 17: string;
29
- 18: string;
30
- 19: string;
31
- };
32
- twentiesWords: {
33
- 20: string;
34
- 30: string;
35
- 40: string;
36
- 50: string;
37
- 60: string;
38
- 70: string;
39
- 80: string;
40
- 90: string;
41
- };
42
- scaleWords: {
43
- 1: string;
44
- 2: string;
45
- 3: string;
46
- 4: string;
47
- 5: string;
48
- 6: string;
49
- 7: string;
50
- 8: string;
51
- 9: string;
52
- 10: string;
53
- 11: string;
54
- 12: string;
55
- 13: string;
56
- 14: string;
57
- 15: string;
58
- 16: string;
59
- 17: string;
60
- 18: string;
61
- 19: string;
62
- 20: string;
63
- };
64
- /** Convert numbers less than 100 to Vietnamese words. */
65
- convertLess100(number: any): any;
66
- /** Convert numbers less than 1000 to Vietnamese words. */
67
- convertLess1000(number: any): any;
68
- /** Convert numbers greater than 1000 to Vietnamese words. */
69
- convertMore1000(number: any): any;
70
- integerToWords(integerPart: any): any;
71
- }
72
- import { AbstractLanguage } from '../classes/abstract-language.js';
17
+ export function toWords(value: number | string | bigint): string;
@@ -1,147 +1,316 @@
1
- import { AbstractLanguage } from '../classes/abstract-language.js'
2
-
3
1
  /**
4
- * Vietnamese language converter.
2
+ * Vietnamese language converter - Functional Implementation v2
3
+ *
4
+ * A performance-optimized number-to-words converter using precomputed lookup tables.
5
+ * Self-contained module with its own input validation, ready for subpath exports.
5
6
  *
6
- * Supports:
7
- * - Special pronunciation rules (lăm for 5, mốt for final 1)
8
- * - "Lẻ" (odd/extra) when tens place is zero
9
- * - Vietnamese diacritical marks
7
+ * Key optimization: Precompute all segment values (0-999) at module load.
8
+ * This eliminates all per-call string manipulation for segment conversion.
9
+ *
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ỷ)
10
14
  */
11
- export class Vietnamese extends AbstractLanguage {
12
- negativeWord = 'âm'
13
- decimalSeparatorWord = 'phẩy'
14
- zeroWord = 'không'
15
-
16
- belowTwentyWords = {
17
- 0: 'không',
18
- 1: 'một',
19
- 2: 'hai',
20
- 3: 'ba',
21
- 4: 'bốn',
22
- 5: 'năm',
23
- 6: 'sáu',
24
- 7: 'bảy',
25
- 8: 'tám',
26
- 9: 'chín',
27
- 10: 'mười',
28
- 11: 'mười một',
29
- 12: 'mười hai',
30
- 13: 'mười ba',
31
- 14: 'mười bốn',
32
- 15: 'mười lăm',
33
- 16: 'mười sáu',
34
- 17: 'mười bảy',
35
- 18: 'mười tám',
36
- 19: 'mười chín'
37
- }
38
15
 
39
- twentiesWords = {
40
- 20: 'hai mươi',
41
- 30: 'ba mươi',
42
- 40: 'bốn mươi',
43
- 50: 'năm mươi',
44
- 60: 'sáu mươi',
45
- 70: 'bảy mươi',
46
- 80: 'tám mươi',
47
- 90: 'chín mươi'
48
- }
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']
49
24
 
50
- scaleWords = {
51
- 1: 'nghìn', // 10^1
52
- 2: 'triệu', // 10^2
53
- 3: 'tỷ', // 10^3
54
- 4: 'nghìn tỷ',
55
- 5: 'trăm nghìn tỷ',
56
- 6: 'Quintillion',
57
- 7: 'Sextillion',
58
- 8: 'Septillion',
59
- 9: 'Octillion',
60
- 10: 'Nonillion',
61
- 11: 'Decillion',
62
- 12: 'Undecillion',
63
- 13: 'Duodecillion',
64
- 14: 'Tredecillion',
65
- 15: 'Quattuordecillion',
66
- 16: 'Sexdecillion',
67
- 17: 'Septendecillion',
68
- 18: 'Octodecillion',
69
- 19: 'Novemdecillion',
70
- 20: 'Vigintillion'
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]
55
+
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]
71
62
  }
72
63
 
73
- /** Convert numbers less than 100 to Vietnamese words. */
74
- convertLess100 (number) {
75
- const unitsPart = number % 10
76
- const tensPart = number - unitsPart
77
- const tensPartText = this.twentiesWords[tensPart]
78
- if (unitsPart === 0) {
79
- return tensPartText
80
- }
81
- const unitsPartText = this.belowTwentyWords[unitsPart]
82
- let suffix = unitsPartText
83
- if (unitsPart === 1) {
84
- suffix = 'mốt'
85
- }
86
- if (unitsPart === 5) {
87
- suffix = 'lăm'
88
- }
89
- 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
90
89
  }
91
90
 
92
- /** Convert numbers less than 1000 to Vietnamese words. */
93
- convertLess1000 (number) {
94
- const words = []
95
- const tensUnitsPart = number % 100
96
- const hundredsPart = number - tensUnitsPart
97
- if (hundredsPart > 0) {
98
- words.push(this.belowTwentyWords[hundredsPart / 100], 'trăm')
99
- }
100
- if (tensUnitsPart > 0 && tensUnitsPart < 10) {
101
- if (words.length > 0) {
102
- words.push('lẻ')
103
- }
104
- if (tensUnitsPart === 5) {
105
- 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]
106
98
  } else {
107
- words.push(this.belowTwentyWords[tensUnitsPart])
99
+ result = ONES[remainder]
108
100
  }
101
+ } else {
102
+ // 10-99 after hundreds
103
+ if (result) result += ' '
104
+ result += BELOW_100[remainder]
109
105
  }
110
- if (tensUnitsPart >= 10) {
111
- words.push(this.integerToWords(tensUnitsPart))
112
- }
113
- return words.join(' ')
114
106
  }
115
107
 
116
- /** Convert numbers greater than 1000 to Vietnamese words. */
117
- convertMore1000 (number) {
118
- const words = []
119
- let division = number / 1000n
120
- let power = 1
121
- while (division >= 1000n) {
122
- division = division / 1000n
123
- 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
124
169
  }
125
- const r = number - (division * BigInt(Math.pow(1000, power)))
126
- words.push(this.integerToWords(division), this.scaleWords[power])
127
- if (r > 0n) {
128
- if (r <= 99n) {
129
- words.push('lẻ')
130
- }
131
- words.push(this.integerToWords(r))
170
+
171
+ // Check if remainder needs "lẻ" marker (< 100)
172
+ if (remainder < 100) {
173
+ return thousandsWords + ' ' + LE_SEGMENTS[remainder]
132
174
  }
133
- return words.join(' ')
175
+
176
+ return thousandsWords + ' ' + SEGMENTS[remainder]
134
177
  }
135
178
 
136
- integerToWords (integerPart) {
137
- if (integerPart < 20n) {
138
- return this.belowTwentyWords[Number(integerPart)]
139
- } else {
140
- if (integerPart < 100n) {
141
- return this.convertLess100(Number(integerPart))
142
- } else {
143
- return (integerPart < 1000n ? this.convertLess1000(Number(integerPart)) : this.convertMore1000(integerPart))
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
+ }
144
222
  }
145
223
  }
224
+ scaleIndex--
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
276
+ }
277
+
278
+ /**
279
+ * Converts a numeric value to Vietnamese words.
280
+ *
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'
293
+ */
294
+ function toWords (value) {
295
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
296
+
297
+ let result = ''
298
+
299
+ if (isNegative) {
300
+ result = NEGATIVE + ' '
146
301
  }
302
+
303
+ result += integerToWords(integerPart)
304
+
305
+ if (decimalPart) {
306
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
307
+ }
308
+
309
+ return result
147
310
  }
311
+
312
+ // ============================================================================
313
+ // Public API
314
+ // ============================================================================
315
+
316
+ export { toWords }
@@ -1,21 +1,11 @@
1
1
  /**
2
- * Simplified Chinese language converter.
2
+ * Converts a numeric value to Simplified Chinese words.
3
3
  *
4
- * Supports:
5
- * - Simplified Chinese characters (简体中文)
6
- * - No word separators (concatenated format)
7
- * - Optional formal (financial) vs common numerals
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
8
  */
9
- export class SimplifiedChinese extends GreedyScaleLanguage {
10
- constructor(options?: {});
11
- scaleWords: (string | bigint)[][];
12
- /** Combines two word-sets according to Chinese grammar rules. */
13
- combineWordSets(preceding: any, following: any): any;
14
- /** Returns the number of digits in a number. */
15
- digit(number_: any): any;
16
- /** Counts the number of zero digits in a number. */
17
- zeroDigit(number_: any): number;
18
- /** Converts decimal digits to words by reading each digit individually. */
19
- decimalDigitsToWords(decimalString: any): string[];
20
- }
21
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js';
9
+ export function toWords(value: number | string | bigint, options?: {
10
+ formal?: boolean | undefined;
11
+ }): string;