n2words 2.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +86 -188
  3. package/dist/languages/am-Latn.js +3 -0
  4. package/dist/languages/am-Latn.js.map +1 -0
  5. package/dist/languages/am.js +3 -0
  6. package/dist/languages/am.js.map +1 -0
  7. package/dist/languages/ar.js +3 -0
  8. package/dist/languages/ar.js.map +1 -0
  9. package/dist/languages/az.js +3 -0
  10. package/dist/languages/az.js.map +1 -0
  11. package/dist/languages/bn.js +3 -0
  12. package/dist/languages/bn.js.map +1 -0
  13. package/dist/languages/cs.js +3 -0
  14. package/dist/languages/cs.js.map +1 -0
  15. package/dist/languages/da.js +3 -0
  16. package/dist/languages/da.js.map +1 -0
  17. package/dist/languages/de.js +3 -0
  18. package/dist/languages/de.js.map +1 -0
  19. package/dist/languages/el.js +3 -0
  20. package/dist/languages/el.js.map +1 -0
  21. package/dist/languages/en.js +3 -0
  22. package/dist/languages/en.js.map +1 -0
  23. package/dist/languages/es.js +3 -0
  24. package/dist/languages/es.js.map +1 -0
  25. package/dist/languages/fa.js +3 -0
  26. package/dist/languages/fa.js.map +1 -0
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -0
  30. package/dist/languages/fil.js.map +1 -0
  31. package/dist/languages/fr-BE.js +3 -0
  32. package/dist/languages/fr-BE.js.map +1 -0
  33. package/dist/languages/fr.js +3 -0
  34. package/dist/languages/fr.js.map +1 -0
  35. package/dist/languages/gu.js +3 -0
  36. package/dist/languages/gu.js.map +1 -0
  37. package/dist/languages/ha.js +3 -0
  38. package/dist/languages/ha.js.map +1 -0
  39. package/dist/languages/hbo.js +3 -0
  40. package/dist/languages/hbo.js.map +1 -0
  41. package/dist/languages/he.js +3 -0
  42. package/dist/languages/he.js.map +1 -0
  43. package/dist/languages/hi.js +3 -0
  44. package/dist/languages/hi.js.map +1 -0
  45. package/dist/languages/hr.js +3 -0
  46. package/dist/languages/hr.js.map +1 -0
  47. package/dist/languages/hu.js +3 -0
  48. package/dist/languages/hu.js.map +1 -0
  49. package/dist/languages/id.js +3 -0
  50. package/dist/languages/id.js.map +1 -0
  51. package/dist/languages/it.js +3 -0
  52. package/dist/languages/it.js.map +1 -0
  53. package/dist/languages/ja.js +3 -0
  54. package/dist/languages/ja.js.map +1 -0
  55. package/dist/languages/ka.js +3 -0
  56. package/dist/languages/ka.js.map +1 -0
  57. package/dist/languages/kn.js +3 -0
  58. package/dist/languages/kn.js.map +1 -0
  59. package/dist/languages/ko.js +3 -0
  60. package/dist/languages/ko.js.map +1 -0
  61. package/dist/languages/lt.js +3 -0
  62. package/dist/languages/lt.js.map +1 -0
  63. package/dist/languages/lv.js +3 -0
  64. package/dist/languages/lv.js.map +1 -0
  65. package/dist/languages/mr.js +3 -0
  66. package/dist/languages/mr.js.map +1 -0
  67. package/dist/languages/ms.js +3 -0
  68. package/dist/languages/ms.js.map +1 -0
  69. package/dist/languages/nb.js +3 -0
  70. package/dist/languages/nb.js.map +1 -0
  71. package/dist/languages/nl.js +3 -0
  72. package/dist/languages/nl.js.map +1 -0
  73. package/dist/languages/pa.js +3 -0
  74. package/dist/languages/pa.js.map +1 -0
  75. package/dist/languages/pl.js +3 -0
  76. package/dist/languages/pl.js.map +1 -0
  77. package/dist/languages/pt.js +3 -0
  78. package/dist/languages/pt.js.map +1 -0
  79. package/dist/languages/ro.js +3 -0
  80. package/dist/languages/ro.js.map +1 -0
  81. package/dist/languages/ru.js +3 -0
  82. package/dist/languages/ru.js.map +1 -0
  83. package/dist/languages/sr-Cyrl.js +3 -0
  84. package/dist/languages/sr-Cyrl.js.map +1 -0
  85. package/dist/languages/sr-Latn.js +3 -0
  86. package/dist/languages/sr-Latn.js.map +1 -0
  87. package/dist/languages/sv.js +3 -0
  88. package/dist/languages/sv.js.map +1 -0
  89. package/dist/languages/sw.js +3 -0
  90. package/dist/languages/sw.js.map +1 -0
  91. package/dist/languages/ta.js +3 -0
  92. package/dist/languages/ta.js.map +1 -0
  93. package/dist/languages/te.js +3 -0
  94. package/dist/languages/te.js.map +1 -0
  95. package/dist/languages/th.js +3 -0
  96. package/dist/languages/th.js.map +1 -0
  97. package/dist/languages/tr.js +3 -0
  98. package/dist/languages/tr.js.map +1 -0
  99. package/dist/languages/uk.js +3 -0
  100. package/dist/languages/uk.js.map +1 -0
  101. package/dist/languages/ur.js +3 -0
  102. package/dist/languages/ur.js.map +1 -0
  103. package/dist/languages/vi.js +3 -0
  104. package/dist/languages/vi.js.map +1 -0
  105. package/dist/languages/yo.js +3 -0
  106. package/dist/languages/yo.js.map +1 -0
  107. package/dist/languages/zh-Hans.js +3 -0
  108. package/dist/languages/zh-Hans.js.map +1 -0
  109. package/dist/languages/zh-Hant.js +3 -0
  110. package/dist/languages/zh-Hant.js.map +1 -0
  111. package/dist/n2words.js +2 -2
  112. package/dist/n2words.js.map +1 -1
  113. package/lib/languages/am-Latn.d.ts +7 -0
  114. package/lib/languages/am-Latn.js +159 -0
  115. package/lib/languages/am.d.ts +7 -0
  116. package/lib/languages/am.js +159 -0
  117. package/lib/languages/ar.d.ts +14 -27
  118. package/lib/languages/ar.js +175 -129
  119. package/lib/languages/az.d.ts +4 -9
  120. package/lib/languages/az.js +166 -37
  121. package/lib/languages/bn.d.ts +4 -8
  122. package/lib/languages/bn.js +159 -124
  123. package/lib/languages/cs.d.ts +15 -85
  124. package/lib/languages/cs.js +293 -114
  125. package/lib/languages/da.d.ts +11 -12
  126. package/lib/languages/da.js +269 -101
  127. package/lib/languages/de.d.ts +14 -11
  128. package/lib/languages/de.js +305 -86
  129. package/lib/languages/el.d.ts +11 -11
  130. package/lib/languages/el.js +224 -78
  131. package/lib/languages/en.d.ts +14 -13
  132. package/lib/languages/en.js +228 -72
  133. package/lib/languages/es.d.ts +18 -12
  134. package/lib/languages/es.js +297 -103
  135. package/lib/languages/fa.d.ts +4 -44
  136. package/lib/languages/fa.js +112 -122
  137. package/lib/languages/fi.d.ts +14 -0
  138. package/lib/languages/fi.js +238 -0
  139. package/lib/languages/fil.d.ts +4 -13
  140. package/lib/languages/fil.js +196 -106
  141. package/lib/languages/fr-BE.d.ts +8 -8
  142. package/lib/languages/fr-BE.js +285 -19
  143. package/lib/languages/fr.d.ts +18 -12
  144. package/lib/languages/fr.js +339 -89
  145. package/lib/languages/gu.d.ts +4 -8
  146. package/lib/languages/gu.js +143 -125
  147. package/lib/languages/ha.d.ts +7 -0
  148. package/lib/languages/ha.js +225 -0
  149. package/lib/languages/hbo.d.ts +10 -110
  150. package/lib/languages/hbo.js +245 -214
  151. package/lib/languages/he.d.ts +10 -77
  152. package/lib/languages/he.js +231 -172
  153. package/lib/languages/hi.d.ts +4 -8
  154. package/lib/languages/hi.js +163 -124
  155. package/lib/languages/hr.d.ts +8 -77
  156. package/lib/languages/hr.js +200 -89
  157. package/lib/languages/hu.d.ts +4 -19
  158. package/lib/languages/hu.js +198 -119
  159. package/lib/languages/id.d.ts +4 -34
  160. package/lib/languages/id.js +166 -129
  161. package/lib/languages/it.d.ts +16 -34
  162. package/lib/languages/it.js +307 -97
  163. package/lib/languages/ja.d.ts +14 -14
  164. package/lib/languages/ja.js +221 -111
  165. package/lib/languages/ka.d.ts +17 -0
  166. package/lib/languages/ka.js +291 -0
  167. package/lib/languages/kn.d.ts +4 -8
  168. package/lib/languages/kn.js +143 -35
  169. package/lib/languages/ko.d.ts +11 -11
  170. package/lib/languages/ko.js +250 -49
  171. package/lib/languages/lt.d.ts +15 -67
  172. package/lib/languages/lt.js +287 -122
  173. package/lib/languages/lv.d.ts +15 -67
  174. package/lib/languages/lv.js +288 -106
  175. package/lib/languages/mr.d.ts +4 -8
  176. package/lib/languages/mr.js +143 -125
  177. package/lib/languages/ms.d.ts +4 -28
  178. package/lib/languages/ms.js +166 -116
  179. package/lib/languages/nb.d.ts +11 -9
  180. package/lib/languages/nb.js +272 -87
  181. package/lib/languages/nl.d.ts +23 -13
  182. package/lib/languages/nl.js +299 -133
  183. package/lib/languages/pa.d.ts +4 -8
  184. package/lib/languages/pa.js +151 -124
  185. package/lib/languages/pl.d.ts +19 -77
  186. package/lib/languages/pl.js +294 -87
  187. package/lib/languages/pt.d.ts +14 -26
  188. package/lib/languages/pt.js +272 -92
  189. package/lib/languages/ro.d.ts +15 -155
  190. package/lib/languages/ro.js +219 -235
  191. package/lib/languages/ru.d.ts +8 -82
  192. package/lib/languages/ru.js +239 -90
  193. package/lib/languages/sr-Cyrl.d.ts +8 -77
  194. package/lib/languages/sr-Cyrl.js +197 -89
  195. package/lib/languages/sr-Latn.d.ts +8 -77
  196. package/lib/languages/sr-Latn.js +197 -89
  197. package/lib/languages/sv.d.ts +11 -11
  198. package/lib/languages/sv.js +278 -74
  199. package/lib/languages/sw.d.ts +4 -36
  200. package/lib/languages/sw.js +133 -106
  201. package/lib/languages/ta.d.ts +4 -17
  202. package/lib/languages/ta.js +143 -202
  203. package/lib/languages/te.d.ts +4 -19
  204. package/lib/languages/te.js +133 -196
  205. package/lib/languages/th.d.ts +4 -14
  206. package/lib/languages/th.js +135 -91
  207. package/lib/languages/tr.d.ts +15 -9
  208. package/lib/languages/tr.js +245 -49
  209. package/lib/languages/uk.d.ts +8 -82
  210. package/lib/languages/uk.js +206 -78
  211. package/lib/languages/ur.d.ts +4 -8
  212. package/lib/languages/ur.js +151 -124
  213. package/lib/languages/vi.d.ts +14 -69
  214. package/lib/languages/vi.js +278 -129
  215. package/lib/languages/yo.d.ts +7 -0
  216. package/lib/languages/yo.js +303 -0
  217. package/lib/languages/zh-Hans.d.ts +8 -18
  218. package/lib/languages/zh-Hans.js +163 -92
  219. package/lib/languages/zh-Hant.d.ts +8 -18
  220. package/lib/languages/zh-Hant.js +181 -90
  221. package/lib/n2words.d.ts +55 -209
  222. package/lib/n2words.js +115 -530
  223. package/lib/utils/is-plain-object.d.ts +13 -0
  224. package/lib/utils/is-plain-object.js +17 -0
  225. package/lib/utils/parse-numeric.d.ts +17 -0
  226. package/lib/utils/parse-numeric.js +108 -0
  227. package/lib/utils/validate-options.d.ts +8 -0
  228. package/lib/utils/validate-options.js +16 -0
  229. package/package.json +26 -14
  230. package/dist/ArabicConverter.js +0 -3
  231. package/dist/ArabicConverter.js.map +0 -1
  232. package/dist/AzerbaijaniConverter.js +0 -3
  233. package/dist/AzerbaijaniConverter.js.map +0 -1
  234. package/dist/BanglaConverter.js +0 -3
  235. package/dist/BanglaConverter.js.map +0 -1
  236. package/dist/BiblicalHebrewConverter.js +0 -3
  237. package/dist/BiblicalHebrewConverter.js.map +0 -1
  238. package/dist/CroatianConverter.js +0 -3
  239. package/dist/CroatianConverter.js.map +0 -1
  240. package/dist/CzechConverter.js +0 -3
  241. package/dist/CzechConverter.js.map +0 -1
  242. package/dist/DanishConverter.js +0 -3
  243. package/dist/DanishConverter.js.map +0 -1
  244. package/dist/DutchConverter.js +0 -3
  245. package/dist/DutchConverter.js.map +0 -1
  246. package/dist/EnglishConverter.js +0 -3
  247. package/dist/EnglishConverter.js.map +0 -1
  248. package/dist/FilipinoConverter.js +0 -3
  249. package/dist/FilipinoConverter.js.map +0 -1
  250. package/dist/FrenchBelgiumConverter.js +0 -3
  251. package/dist/FrenchBelgiumConverter.js.map +0 -1
  252. package/dist/FrenchConverter.js +0 -3
  253. package/dist/FrenchConverter.js.map +0 -1
  254. package/dist/GermanConverter.js +0 -3
  255. package/dist/GermanConverter.js.map +0 -1
  256. package/dist/GreekConverter.js +0 -3
  257. package/dist/GreekConverter.js.map +0 -1
  258. package/dist/GujaratiConverter.js +0 -3
  259. package/dist/GujaratiConverter.js.map +0 -1
  260. package/dist/HebrewConverter.js +0 -3
  261. package/dist/HebrewConverter.js.map +0 -1
  262. package/dist/HindiConverter.js +0 -3
  263. package/dist/HindiConverter.js.map +0 -1
  264. package/dist/HungarianConverter.js +0 -3
  265. package/dist/HungarianConverter.js.map +0 -1
  266. package/dist/IndonesianConverter.js +0 -3
  267. package/dist/IndonesianConverter.js.map +0 -1
  268. package/dist/ItalianConverter.js +0 -3
  269. package/dist/ItalianConverter.js.map +0 -1
  270. package/dist/JapaneseConverter.js +0 -3
  271. package/dist/JapaneseConverter.js.map +0 -1
  272. package/dist/KannadaConverter.js +0 -3
  273. package/dist/KannadaConverter.js.map +0 -1
  274. package/dist/KoreanConverter.js +0 -3
  275. package/dist/KoreanConverter.js.map +0 -1
  276. package/dist/LatvianConverter.js +0 -3
  277. package/dist/LatvianConverter.js.map +0 -1
  278. package/dist/LithuanianConverter.js +0 -3
  279. package/dist/LithuanianConverter.js.map +0 -1
  280. package/dist/MalayConverter.js +0 -3
  281. package/dist/MalayConverter.js.map +0 -1
  282. package/dist/MarathiConverter.js +0 -3
  283. package/dist/MarathiConverter.js.map +0 -1
  284. package/dist/NorwegianBokmalConverter.js +0 -3
  285. package/dist/NorwegianBokmalConverter.js.map +0 -1
  286. package/dist/PersianConverter.js +0 -3
  287. package/dist/PersianConverter.js.map +0 -1
  288. package/dist/PolishConverter.js +0 -3
  289. package/dist/PolishConverter.js.map +0 -1
  290. package/dist/PortugueseConverter.js +0 -3
  291. package/dist/PortugueseConverter.js.map +0 -1
  292. package/dist/PunjabiConverter.js +0 -3
  293. package/dist/PunjabiConverter.js.map +0 -1
  294. package/dist/RomanianConverter.js +0 -3
  295. package/dist/RomanianConverter.js.map +0 -1
  296. package/dist/RussianConverter.js +0 -3
  297. package/dist/RussianConverter.js.map +0 -1
  298. package/dist/SerbianCyrillicConverter.js +0 -3
  299. package/dist/SerbianCyrillicConverter.js.map +0 -1
  300. package/dist/SerbianLatinConverter.js +0 -3
  301. package/dist/SerbianLatinConverter.js.map +0 -1
  302. package/dist/SimplifiedChineseConverter.js +0 -3
  303. package/dist/SimplifiedChineseConverter.js.map +0 -1
  304. package/dist/SpanishConverter.js +0 -3
  305. package/dist/SpanishConverter.js.map +0 -1
  306. package/dist/SwahiliConverter.js +0 -3
  307. package/dist/SwahiliConverter.js.map +0 -1
  308. package/dist/SwedishConverter.js +0 -3
  309. package/dist/SwedishConverter.js.map +0 -1
  310. package/dist/TamilConverter.js +0 -3
  311. package/dist/TamilConverter.js.map +0 -1
  312. package/dist/TeluguConverter.js +0 -3
  313. package/dist/TeluguConverter.js.map +0 -1
  314. package/dist/ThaiConverter.js +0 -3
  315. package/dist/ThaiConverter.js.map +0 -1
  316. package/dist/TraditionalChineseConverter.js +0 -3
  317. package/dist/TraditionalChineseConverter.js.map +0 -1
  318. package/dist/TurkishConverter.js +0 -3
  319. package/dist/TurkishConverter.js.map +0 -1
  320. package/dist/UkrainianConverter.js +0 -3
  321. package/dist/UkrainianConverter.js.map +0 -1
  322. package/dist/UrduConverter.js +0 -3
  323. package/dist/UrduConverter.js.map +0 -1
  324. package/dist/VietnameseConverter.js +0 -3
  325. package/dist/VietnameseConverter.js.map +0 -1
  326. package/lib/classes/abstract-language.d.ts +0 -178
  327. package/lib/classes/abstract-language.js +0 -268
  328. package/lib/classes/greedy-scale-language.d.ts +0 -109
  329. package/lib/classes/greedy-scale-language.js +0 -201
  330. package/lib/classes/slavic-language.d.ts +0 -148
  331. package/lib/classes/slavic-language.js +0 -281
  332. package/lib/classes/south-asian-language.d.ts +0 -70
  333. package/lib/classes/south-asian-language.js +0 -154
  334. package/lib/classes/turkic-language.d.ts +0 -26
  335. package/lib/classes/turkic-language.js +0 -59
@@ -1,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,296 @@
1
- import { AbstractLanguage } from '../classes/abstract-language.js'
2
-
3
1
  /**
4
- * Vietnamese language converter.
2
+ * Vietnamese language converter - Functional Implementation
3
+ *
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
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
6
+ * Vietnamese-specific rules:
7
+ * - Special pronunciation: "lăm" for 5 in tens position, "mốt" for final 1
8
+ * - "Lẻ" (odd/extra) marker when tens place is zero after hundreds/scales
9
+ * - Short scale system with Vietnamese words (nghìn, triệu, tỷ)
10
10
  */
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
-
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
- }
49
-
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'
71
- }
72
-
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
11
+
12
+ import { parseNumericValue } from '../utils/parse-numeric.js'
13
+
14
+ // ============================================================================
15
+ // Vocabulary (module-level constants)
16
+ // ============================================================================
17
+
18
+ // Base vocabulary for building lookup tables
19
+ const ONES = ['không', 'một', 'hai', 'ba', 'bốn', 'năm', 'sáu', 'bảy', 'tám', 'chín']
20
+
21
+ // Scale words indexed by scale level (0 = units, 1 = thousands, etc.)
22
+ const SCALES = [
23
+ '', 'nghìn', 'triệu', 'tỷ', 'nghìn tỷ', 'trăm nghìn tỷ',
24
+ 'Quintillion', 'Sextillion', 'Septillion', 'Octillion',
25
+ 'Nonillion', 'Decillion', 'Undecillion', 'Duodecillion',
26
+ 'Tredecillion', 'Quattuordecillion', 'Sexdecillion',
27
+ 'Septendecillion', 'Octodecillion', 'Novemdecillion', 'Vigintillion'
28
+ ]
29
+
30
+ const HUNDRED = 'trăm'
31
+ const ZERO = 'không'
32
+ const NEGATIVE = 'âm'
33
+ const DECIMAL_SEP = 'phẩy'
34
+ const LE = 'lẻ' // "odd/extra" marker for gaps
35
+
36
+ // Special forms
37
+ const MOT_FINAL = 'mốt' // 1 in tens position (21, 31, etc.)
38
+ const LAM = 'lăm' // 5 in tens position (25, 35, etc.)
39
+
40
+ // ============================================================================
41
+ // Segment Building
42
+ // ============================================================================
43
+
44
+ /**
45
+ * Builds word for 0-99 with special forms (mốt, lăm).
46
+ */
47
+ function buildBelowHundred (n) {
48
+ if (n === 0) return ONES[0]
49
+ if (n < 10) return ONES[n]
50
+
51
+ // Teens: 10-19
52
+ if (n < 20) {
53
+ const ones = n - 10
54
+ if (ones === 0) return 'mười'
55
+ if (ones === 5) return 'mười lăm'
56
+ return 'mười ' + ONES[ones]
90
57
  }
91
58
 
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')
59
+ // 20-99
60
+ const ones = n % 10
61
+ const tens = Math.floor(n / 10)
62
+ const tensWord = ONES[tens] + ' mươi'
63
+
64
+ if (ones === 0) return tensWord
65
+ if (ones === 1) return tensWord + ' ' + MOT_FINAL
66
+ if (ones === 5) return tensWord + ' ' + LAM
67
+ return tensWord + ' ' + ONES[ones]
68
+ }
69
+
70
+ /**
71
+ * Builds segment word for 0-999.
72
+ */
73
+ function buildSegment (n) {
74
+ if (n === 0) return ''
75
+
76
+ const hundreds = Math.floor(n / 100)
77
+ const remainder = n % 100
78
+
79
+ let result = ''
80
+
81
+ if (hundreds > 0) {
82
+ result = ONES[hundreds] + ' ' + HUNDRED
83
+ }
84
+
85
+ if (remainder > 0) {
86
+ if (remainder < 10) {
87
+ // Single digit after hundreds needs "lẻ"
88
+ if (result) {
89
+ result += ' ' + LE + ' '
90
+ // Use "năm" not "lăm" after lẻ
91
+ result += remainder === 5 ? 'năm' : ONES[remainder]
106
92
  } else {
107
- words.push(this.belowTwentyWords[tensUnitsPart])
93
+ result = ONES[remainder]
108
94
  }
95
+ } else {
96
+ // 10-99 after hundreds
97
+ if (result) result += ' '
98
+ result += buildBelowHundred(remainder)
109
99
  }
110
- if (tensUnitsPart >= 10) {
111
- words.push(this.integerToWords(tensUnitsPart))
112
- }
113
- return words.join(' ')
114
100
  }
115
101
 
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
102
+ return result
103
+ }
104
+
105
+ /**
106
+ * Builds "lẻ" prefixed word for small remainders (1-99) after scale words.
107
+ */
108
+ function buildLeSegment (n) {
109
+ if (n === 0) return ''
110
+ if (n < 10) {
111
+ // Use "năm" not "lăm" after lẻ
112
+ return LE + ' ' + (n === 5 ? 'năm' : ONES[n])
113
+ }
114
+ return LE + ' ' + buildBelowHundred(n)
115
+ }
116
+
117
+ // ============================================================================
118
+ // Conversion Functions
119
+ // ============================================================================
120
+
121
+ /**
122
+ * Converts a non-negative integer to Vietnamese words.
123
+ *
124
+ * @param {bigint} n - Non-negative integer to convert
125
+ * @returns {string} Vietnamese words
126
+ */
127
+ function integerToWords (n) {
128
+ if (n === 0n) return ZERO
129
+
130
+ // Fast path: numbers < 100
131
+ if (n < 100n) {
132
+ return buildBelowHundred(Number(n))
133
+ }
134
+
135
+ // Fast path: numbers < 1000
136
+ if (n < 1000n) {
137
+ return buildSegment(Number(n))
138
+ }
139
+
140
+ // Fast path: numbers < 1,000,000 (thousands)
141
+ if (n < 1_000_000n) {
142
+ const thousands = Number(n / 1000n)
143
+ const remainder = Number(n % 1000n)
144
+
145
+ const thousandsWords = buildSegment(thousands) + ' ' + SCALES[1]
146
+
147
+ if (remainder === 0) {
148
+ return thousandsWords
124
149
  }
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))
150
+
151
+ // Check if remainder needs "lẻ" marker (< 100)
152
+ if (remainder < 100) {
153
+ return thousandsWords + ' ' + buildLeSegment(remainder)
132
154
  }
133
- return words.join(' ')
155
+
156
+ return thousandsWords + ' ' + buildSegment(remainder)
134
157
  }
135
158
 
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))
159
+ // For numbers >= 1,000,000, use scale decomposition
160
+ return buildLargeNumberWords(n)
161
+ }
162
+
163
+ /**
164
+ * Builds words for numbers >= 1,000,000.
165
+ *
166
+ * @param {bigint} n - Number >= 1,000,000
167
+ * @returns {string} Vietnamese words
168
+ */
169
+ function buildLargeNumberWords (n) {
170
+ const numStr = n.toString()
171
+ const len = numStr.length
172
+
173
+ // Build segments of 3 digits from right to left
174
+ const segments = []
175
+ const segmentSize = 3
176
+
177
+ const remainderLen = len % segmentSize
178
+ let pos = 0
179
+ if (remainderLen > 0) {
180
+ segments.push(Number(numStr.slice(0, remainderLen)))
181
+ pos = remainderLen
182
+ }
183
+ while (pos < len) {
184
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
185
+ pos += segmentSize
186
+ }
187
+
188
+ // Convert segments to words
189
+ const parts = []
190
+ let scaleIndex = segments.length - 1
191
+
192
+ for (let i = 0; i < segments.length; i++) {
193
+ const segment = segments[i]
194
+ if (segment !== 0) {
195
+ const words = buildSegment(segment)
196
+ if (words) {
197
+ if (scaleIndex > 0) {
198
+ parts.push(words + ' ' + SCALES[scaleIndex])
199
+ } else {
200
+ parts.push(words)
201
+ }
144
202
  }
145
203
  }
204
+ scaleIndex--
205
+ }
206
+
207
+ // Join with "lẻ" logic for small remainders
208
+ const partsLen = parts.length
209
+ if (partsLen === 0) return ZERO
210
+ if (partsLen === 1) return parts[0]
211
+
212
+ // Check if final segment needs "lẻ" marker (remainder <= 99 after scale word)
213
+ const lastSegment = segments[segments.length - 1]
214
+ if (lastSegment > 0 && lastSegment <= 99) {
215
+ // Last segment is small (no hundreds), needs "lẻ" after scale word
216
+ let result = parts[0]
217
+ for (let i = 1; i < partsLen - 1; i++) {
218
+ result += ' ' + parts[i]
219
+ }
220
+ return result + ' ' + buildLeSegment(lastSegment)
221
+ }
222
+
223
+ // Join with spaces
224
+ let result = parts[0]
225
+ for (let i = 1; i < partsLen; i++) {
226
+ result += ' ' + parts[i]
227
+ }
228
+ return result
229
+ }
230
+
231
+ /**
232
+ * Converts decimal digits to Vietnamese words.
233
+ *
234
+ * @param {string} decimalPart - Decimal digits (without the point)
235
+ * @returns {string} Vietnamese words for decimal part
236
+ */
237
+ function decimalPartToWords (decimalPart) {
238
+ let result = ''
239
+
240
+ // Handle leading zeros
241
+ let i = 0
242
+ while (i < decimalPart.length && decimalPart[i] === '0') {
243
+ if (result) result += ' '
244
+ result += ZERO
245
+ i++
246
+ }
247
+
248
+ // Convert remainder as a single number
249
+ const remainder = decimalPart.slice(i)
250
+ if (remainder) {
251
+ if (result) result += ' '
252
+ result += integerToWords(BigInt(remainder))
253
+ }
254
+
255
+ return result
256
+ }
257
+
258
+ /**
259
+ * Converts a numeric value to Vietnamese words.
260
+ *
261
+ * This is the main public API. It accepts any valid numeric input
262
+ * (number, string, or bigint) and handles parsing internally.
263
+ *
264
+ * @param {number | string | bigint} value - The numeric value to convert
265
+ * @returns {string} The number in Vietnamese words
266
+ * @throws {TypeError} If value is not a valid numeric type
267
+ * @throws {Error} If value is not a valid number format
268
+ *
269
+ * @example
270
+ * toWords(42) // 'bốn mươi hai'
271
+ * toWords(101) // 'một trăm lẻ một'
272
+ * toWords(1000000) // 'một triệu'
273
+ */
274
+ function toWords (value) {
275
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
276
+
277
+ let result = ''
278
+
279
+ if (isNegative) {
280
+ result = NEGATIVE + ' '
146
281
  }
282
+
283
+ result += integerToWords(integerPart)
284
+
285
+ if (decimalPart) {
286
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
287
+ }
288
+
289
+ return result
147
290
  }
291
+
292
+ // ============================================================================
293
+ // Public API
294
+ // ============================================================================
295
+
296
+ export { toWords }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts a numeric value to Yoruba words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Yoruba words
6
+ */
7
+ export function toWords(value: number | string | bigint): string;