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,111 +1,182 @@
1
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js'
2
-
3
1
  /**
4
- * Simplified Chinese language converter.
2
+ * Simplified Chinese language converter - Functional Implementation
3
+ *
4
+ * Self-contained converter for Simplified Chinese.
5
5
  *
6
- * Supports:
7
- * - Simplified Chinese characters (简体中文)
6
+ * Key features:
7
+ * - Myriad-based (万, 亿) grouping - 4 digits
8
+ * - Formal (financial) vs common numerals
9
+ * - Zero insertion for skipped positions
8
10
  * - No word separators (concatenated format)
9
- * - Optional formal (financial) vs common numerals
10
11
  */
11
- export class SimplifiedChinese extends GreedyScaleLanguage {
12
- negativeWord = ''
13
- decimalSeparatorWord = ''
14
- zeroWord = '零'
15
- wordSeparator = ''
16
-
17
- constructor (options = {}) {
18
- super()
19
-
20
- this.setOptions({
21
- formal: true
22
- }, options)
23
-
24
- if (this.options.formal) {
25
- this.scaleWords = [
26
- [1_000_000_000_000n, ''],
27
- [100_000_000n, '亿'],
28
- [10_000n, ''],
29
- [1000n, '仟'],
30
- [100n, '佰'],
31
- [10n, '拾'],
32
- [9n, ''],
33
- [8n, ''],
34
- [7n, '柒'],
35
- [6n, ''],
36
- [5n, ''],
37
- [4n, ''],
38
- [3n, '叁'],
39
- [2n, '贰'],
40
- [1n, '壹'],
41
- [0n, '零']
42
- ]
43
- } else {
44
- this.scaleWords = [
45
- [1_000_000_000_000n, '万'],
46
- [100_000_000n, '亿'],
47
- [10_000n, ''],
48
- [1000n, '千'],
49
- [100n, ''],
50
- [10n, '十'],
51
- [9n, '九'],
52
- [8n, '八'],
53
- [7n, '七'],
54
- [6n, '六'],
55
- [5n, '五'],
56
- [4n, '四'],
57
- [3n, '三'],
58
- [2n, '二'],
59
- [1n, '一'],
60
- [0n, '零']
61
- ]
62
- }
12
+
13
+ import { parseNumericValue } from '../utils/parse-numeric.js'
14
+ import { validateOptions } from '../utils/validate-options.js'
15
+
16
+ // ============================================================================
17
+ // Vocabulary
18
+ // ============================================================================
19
+
20
+ // Common (everyday) numerals
21
+ const ONES_COMMON = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
22
+ const TEN_COMMON = '十'
23
+ const HUNDRED_COMMON = '百'
24
+ const THOUSAND_COMMON = '千'
25
+
26
+ // Formal (financial) numerals - harder to alter/forge
27
+ const ONES_FORMAL = ['零', '', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
28
+ const TEN_FORMAL = ''
29
+ const HUNDRED_FORMAL = ''
30
+ const THOUSAND_FORMAL = '仟'
31
+
32
+ // Scale words
33
+ const WAN_WORD = '' // 10,000
34
+ const YI_WORD = '亿' // 100,000,000
35
+
36
+ const ZERO = ''
37
+ const NEGATIVE = ''
38
+ const DECIMAL_SEP = ''
39
+
40
+ // ============================================================================
41
+ // Conversion Functions
42
+ // ============================================================================
43
+
44
+ /**
45
+ * Convert number below 万 (10,000) to words using direct string concatenation.
46
+ */
47
+ function convertBelowWan (value, ones, ten, hundred, thousand) {
48
+ if (value === 0n) return ''
49
+
50
+ let result = ''
51
+ let needsZero = false
52
+
53
+ // Thousands (千)
54
+ const thousandsVal = value / 1000n
55
+ const thousandsRemainder = value % 1000n
56
+ if (thousandsVal > 0n) {
57
+ result = ones[Number(thousandsVal)] + thousand
58
+ needsZero = thousandsRemainder > 0n && thousandsRemainder < 100n
63
59
  }
64
60
 
65
- /** Combines two word-sets according to Chinese grammar rules. */
66
- combineWordSets (preceding, following) {
67
- const precedingWord = Object.keys(preceding)[0]
68
- const precedingValue = Object.values(preceding)[0]
69
- const followingWord = Object.keys(following)[0]
70
- const followingValue = Object.values(following)[0]
61
+ // Hundreds (百)
62
+ const hundredsVal = thousandsRemainder / 100n
63
+ const hundredsRemainder = thousandsRemainder % 100n
64
+ if (hundredsVal > 0n) {
65
+ if (needsZero) result += ZERO
66
+ result += ones[Number(hundredsVal)] + hundred
67
+ needsZero = hundredsRemainder > 0n && hundredsRemainder < 10n
68
+ } else if (thousandsVal > 0n && hundredsRemainder > 0n) {
69
+ needsZero = true
70
+ }
71
71
 
72
- // Implicit one: omit 1 before single digits (< 10)
73
- if (precedingValue === 1n && followingValue < 10n) {
74
- return following
75
- }
72
+ // Tens ()
73
+ const tensVal = hundredsRemainder / 10n
74
+ const onesVal = hundredsRemainder % 10n
75
+ if (tensVal > 0n) {
76
+ if (needsZero) result += ZERO
77
+ result += ones[Number(tensVal)] + ten
78
+ needsZero = false
79
+ } else if ((hundredsVal > 0n || thousandsVal > 0n) && onesVal > 0n) {
80
+ needsZero = true
81
+ }
76
82
 
77
- // Multiply when following > preceding (scale words like 仁, 万, 亿)
78
- if (followingValue > precedingValue) {
79
- return { [`${precedingWord}${followingWord}`]: precedingValue * followingValue }
80
- }
83
+ // Ones
84
+ if (onesVal > 0n) {
85
+ if (needsZero) result += ZERO
86
+ result += ones[Number(onesVal)]
87
+ }
88
+
89
+ return result
90
+ }
91
+
92
+ /**
93
+ * Convert number below 亿 (100 million) to words.
94
+ */
95
+ function convertBelowYi (value, ones, ten, hundred, thousand) {
96
+ if (value === 0n) return ''
81
97
 
82
- // Insert "零" (zero) when position skip levels (e.g., 1003 = 千零三)
83
- // zeroDigit() checks if gap exists between preceding and following magnitude
84
- if (this.zeroDigit(precedingValue) > this.digit(followingValue)) {
85
- return { [`${precedingWord}${this.zeroWord}${followingWord}`]: precedingValue + followingValue }
98
+ if (value >= 10_000n) {
99
+ const wanValue = value / 10_000n
100
+ const wanRemainder = value % 10_000n
101
+
102
+ let result = convertBelowWan(wanValue, ones, ten, hundred, thousand) + WAN_WORD
103
+
104
+ if (wanRemainder > 0n) {
105
+ const needsZero = (wanValue % 10n === 0n) || (wanRemainder < 1000n)
106
+ if (needsZero) result += ZERO
107
+ result += convertBelowWan(wanRemainder, ones, ten, hundred, thousand)
86
108
  }
87
109
 
88
- // Default: concatenate without zero insertion
89
- return { [`${precedingWord}${followingWord}`]: precedingValue + followingValue }
110
+ return result
90
111
  }
91
112
 
92
- /** Returns the number of digits in a number. */
93
- digit (number_) {
94
- return number_.toString().length
113
+ return convertBelowWan(value, ones, ten, hundred, thousand)
114
+ }
115
+
116
+ function integerToWords (n, formal = true) {
117
+ if (n === 0n) return ZERO
118
+
119
+ const ones = formal ? ONES_FORMAL : ONES_COMMON
120
+ const ten = formal ? TEN_FORMAL : TEN_COMMON
121
+ const hundred = formal ? HUNDRED_FORMAL : HUNDRED_COMMON
122
+ const thousand = formal ? THOUSAND_FORMAL : THOUSAND_COMMON
123
+
124
+ // Handle numbers >= 亿 (100 million)
125
+ if (n >= 100_000_000n) {
126
+ const yiValue = n / 100_000_000n
127
+ const yiRemainder = n % 100_000_000n
128
+
129
+ let result = convertBelowYi(yiValue, ones, ten, hundred, thousand) + YI_WORD
130
+
131
+ if (yiRemainder > 0n) {
132
+ if (yiRemainder < 10_000_000n) result += ZERO
133
+ result += convertBelowYi(yiRemainder, ones, ten, hundred, thousand)
134
+ }
135
+
136
+ return result
95
137
  }
96
138
 
97
- /** Counts the number of zero digits in a number. */
98
- zeroDigit (number_) {
99
- return [...number_.toString()].filter(c => c === '0').length
139
+ return convertBelowYi(n, ones, ten, hundred, thousand)
140
+ }
141
+
142
+ /**
143
+ * Convert decimal digits to words using direct concatenation.
144
+ */
145
+ function decimalDigitsToWords (decimalString, ones) {
146
+ let result = ''
147
+ for (let i = 0; i < decimalString.length; i++) {
148
+ result += ones[Number(decimalString[i])]
100
149
  }
150
+ return result
151
+ }
101
152
 
102
- /** Converts decimal digits to words by reading each digit individually. */
103
- decimalDigitsToWords (decimalString) {
104
- const words = []
105
- for (let i = 0; i < decimalString.length; i++) {
106
- const digitValue = BigInt(decimalString[i])
107
- words.push(this.integerToWords(digitValue))
108
- }
109
- return words
153
+ /**
154
+ * Converts a numeric value to Simplified Chinese words.
155
+ *
156
+ * @param {number | string | bigint} value - The numeric value to convert
157
+ * @param {Object} [options] - Optional configuration
158
+ * @param {boolean} [options.formal=true] - Use formal/financial numerals
159
+ * @returns {string} The number in Simplified Chinese words
160
+ */
161
+ function toWords (value, options) {
162
+ options = validateOptions(options)
163
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
164
+ const formal = options.formal !== false // Default to true
165
+
166
+ let result = isNegative ? NEGATIVE : ''
167
+
168
+ result += integerToWords(integerPart, formal)
169
+
170
+ if (decimalPart) {
171
+ const ones = formal ? ONES_FORMAL : ONES_COMMON
172
+ result += DECIMAL_SEP + decimalDigitsToWords(decimalPart, ones)
110
173
  }
174
+
175
+ return result
111
176
  }
177
+
178
+ // ============================================================================
179
+ // Exports
180
+ // ============================================================================
181
+
182
+ export { toWords }
@@ -1,21 +1,11 @@
1
1
  /**
2
- * Traditional Chinese language converter.
2
+ * Converts a numeric value to Traditional Chinese words.
3
3
  *
4
- * Supports:
5
- * - Traditional 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 Traditional Chinese words
8
8
  */
9
- export class TraditionalChinese extends GreedyScaleLanguage {
10
- constructor(options?: {});
11
- scaleWords: (string | bigint)[][];
12
- /** Combines two word-sets with Traditional Chinese grammar rules and zero insertion. */
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;
@@ -1,111 +1,202 @@
1
- import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js'
2
-
3
1
  /**
4
- * Traditional Chinese language converter.
2
+ * Traditional Chinese language converter - Functional Implementation
3
+ *
4
+ * Self-contained converter for Traditional Chinese.
5
5
  *
6
- * Supports:
7
- * - Traditional Chinese characters (繁體中文)
6
+ * Key features:
7
+ * - Myriad-based (萬, 億) grouping - 4 digits
8
+ * - Formal (financial) vs common numerals
9
+ * - Zero insertion for skipped positions
8
10
  * - No word separators (concatenated format)
9
- * - Optional formal (financial) vs common numerals
11
+ *
12
+ * Differences from Simplified:
13
+ * - Different character forms (e.g., 負/负, 點/点, 億/亿, 萬/万)
14
+ * - Some formal numerals differ (參/叁, 貳/贰, 陸/陆)
10
15
  */
11
- export class TraditionalChinese extends GreedyScaleLanguage {
12
- negativeWord = ''
13
- decimalSeparatorWord = ''
14
- zeroWord = '零'
15
- wordSeparator = ''
16
-
17
- constructor (options = {}) {
18
- super()
19
-
20
- this.setOptions({
21
- formal: true
22
- }, options)
23
-
24
- if (this.options.formal) {
25
- this.scaleWords = [
26
- [1_000_000_000_000n, ''],
27
- [100_000_000n, ''],
28
- [10_000n, ''],
29
- [1000n, '仟'],
30
- [100n, '佰'],
31
- [10n, '拾'],
32
- [9n, ''],
33
- [8n, ''],
34
- [7n, '柒'],
35
- [6n, ''],
36
- [5n, ''],
37
- [4n, ''],
38
- [3n, '參'],
39
- [2n, '貳'],
40
- [1n, '壹'],
41
- [0n, '零']
42
- ]
43
- } else {
44
- this.scaleWords = [
45
- [1_000_000_000_000n, '萬'],
46
- [100_000_000n, '億'],
47
- [10_000n, '萬'],
48
- [1000n, '千'],
49
- [100n, '百'],
50
- [10n, '十'],
51
- [9n, '九'],
52
- [8n, '八'],
53
- [7n, ''],
54
- [6n, '六'],
55
- [5n, '五'],
56
- [4n, '四'],
57
- [3n, '三'],
58
- [2n, '二'],
59
- [1n, '一'],
60
- [0n, '零']
61
- ]
16
+
17
+ import { parseNumericValue } from '../utils/parse-numeric.js'
18
+ import { validateOptions } from '../utils/validate-options.js'
19
+
20
+ // ============================================================================
21
+ // Vocabulary
22
+ // ============================================================================
23
+
24
+ // Common (everyday) numerals - Traditional forms
25
+ const ONES_COMMON = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']
26
+ const TEN_COMMON = '十'
27
+ const HUNDRED_COMMON = '百'
28
+ const THOUSAND_COMMON = '千'
29
+
30
+ // Formal (financial) numerals - Traditional forms
31
+ const ONES_FORMAL = ['零', '', '貳', '參', '肆', '伍', '陸', '柒', '捌', '玖']
32
+ const TEN_FORMAL = ''
33
+ const HUNDRED_FORMAL = ''
34
+ const THOUSAND_FORMAL = '仟'
35
+
36
+ // Scale words - Traditional forms
37
+ const WAN_WORD = '' // 10,000
38
+ const YI_WORD = '' // 100,000,000
39
+
40
+ const ZERO = ''
41
+ const NEGATIVE = ''
42
+ const DECIMAL_SEP = ''
43
+
44
+ // ============================================================================
45
+ // Conversion Functions
46
+ // ============================================================================
47
+
48
+ function integerToWords (n, formal = true) {
49
+ if (n === 0n) return ZERO
50
+
51
+ const ones = formal ? ONES_FORMAL : ONES_COMMON
52
+ const ten = formal ? TEN_FORMAL : TEN_COMMON
53
+ const hundred = formal ? HUNDRED_FORMAL : HUNDRED_COMMON
54
+ const thousand = formal ? THOUSAND_FORMAL : THOUSAND_COMMON
55
+
56
+ // Convert number below 萬 (10,000)
57
+ function convertBelowWan (value) {
58
+ if (value === 0n) return ''
59
+
60
+ const parts = []
61
+ let needsZero = false
62
+
63
+ // Thousands (千)
64
+ const thousandsVal = value / 1000n
65
+ const thousandsRemainder = value % 1000n
66
+ if (thousandsVal > 0n) {
67
+ parts.push(ones[Number(thousandsVal)] + thousand)
68
+ needsZero = thousandsRemainder > 0n && thousandsRemainder < 100n
62
69
  }
63
- }
64
70
 
65
- /** Combines two word-sets with Traditional Chinese grammar rules and zero insertion. */
66
- combineWordSets (preceding, following) {
67
- const precedingWord = Object.keys(preceding)[0]
68
- const precedingValue = Object.values(preceding)[0]
69
- const followingWord = Object.keys(following)[0]
70
- const followingValue = Object.values(following)[0]
71
+ // Hundreds (百)
72
+ const hundredsVal = thousandsRemainder / 100n
73
+ const hundredsRemainder = thousandsRemainder % 100n
74
+ if (hundredsVal > 0n) {
75
+ if (needsZero) {
76
+ parts.push(ZERO)
77
+ needsZero = false
78
+ }
79
+ parts.push(ones[Number(hundredsVal)] + hundred)
80
+ needsZero = hundredsRemainder > 0n && hundredsRemainder < 10n
81
+ } else if (thousandsVal > 0n && hundredsRemainder > 0n) {
82
+ needsZero = true
83
+ }
71
84
 
72
- // Implicit one: omit 1 before single digits (< 10)
73
- if (precedingValue === 1n && followingValue < 10n) {
74
- return following
85
+ // Tens ()
86
+ const tensVal = hundredsRemainder / 10n
87
+ const onesVal = hundredsRemainder % 10n
88
+ if (tensVal > 0n) {
89
+ if (needsZero) {
90
+ parts.push(ZERO)
91
+ needsZero = false
92
+ }
93
+ parts.push(ones[Number(tensVal)] + ten)
94
+ } else if ((hundredsVal > 0n || thousandsVal > 0n) && onesVal > 0n) {
95
+ needsZero = true
75
96
  }
76
97
 
77
- // Multiply when following > preceding (scale words like 千, 萬, 億)
78
- if (followingValue > precedingValue) {
79
- return { [`${precedingWord}${followingWord}`]: precedingValue * followingValue }
98
+ // Ones
99
+ if (onesVal > 0n) {
100
+ if (needsZero) {
101
+ parts.push(ZERO)
102
+ }
103
+ parts.push(ones[Number(onesVal)])
80
104
  }
81
105
 
82
- // Insert "零" (zero) when position skip levels (e.g., 1003 = 千零三)
83
- // zeroDigit() checks if gap exists between preceding and following magnitude
84
- if (this.zeroDigit(precedingValue) > this.digit(followingValue)) {
85
- return { [`${precedingWord}${this.zeroWord}${followingWord}`]: precedingValue + followingValue }
106
+ return parts.join('')
107
+ }
108
+
109
+ // Convert number below (100 million)
110
+ function convertBelowYi (value) {
111
+ if (value === 0n) return ''
112
+
113
+ const parts = []
114
+
115
+ if (value >= 10_000n) {
116
+ const wanValue = value / 10_000n
117
+ const wanRemainder = value % 10_000n
118
+
119
+ parts.push(convertBelowWan(wanValue) + WAN_WORD)
120
+
121
+ if (wanRemainder > 0n) {
122
+ const wanEndsWithZero = wanValue % 10n === 0n
123
+ const remainderMissesThousands = wanRemainder < 1000n
124
+ const needsZero = wanEndsWithZero || remainderMissesThousands
125
+ if (needsZero) {
126
+ parts.push(ZERO)
127
+ }
128
+ parts.push(convertBelowWan(wanRemainder))
129
+ }
130
+ } else {
131
+ parts.push(convertBelowWan(value))
86
132
  }
87
133
 
88
- // Default: concatenate without zero insertion
89
- return { [`${precedingWord}${followingWord}`]: precedingValue + followingValue }
134
+ return parts.join('')
90
135
  }
91
136
 
92
- /** Returns the number of digits in a number. */
93
- digit (number_) {
94
- return number_.toString().length
137
+ // Main conversion
138
+ const parts = []
139
+
140
+ if (n >= 100_000_000n) {
141
+ const yiValue = n / 100_000_000n
142
+ const yiRemainder = n % 100_000_000n
143
+
144
+ const yiWords = convertBelowYi(yiValue)
145
+ parts.push(yiWords + YI_WORD)
146
+
147
+ if (yiRemainder > 0n) {
148
+ const needsZero = yiRemainder < 10_000_000n
149
+ if (needsZero) {
150
+ parts.push(ZERO)
151
+ }
152
+ parts.push(convertBelowYi(yiRemainder))
153
+ }
154
+ } else {
155
+ parts.push(convertBelowYi(n))
95
156
  }
96
157
 
97
- /** Counts the number of zero digits in a number. */
98
- zeroDigit (number_) {
99
- return [...number_.toString()].filter(c => c === '0').length
158
+ return parts.join('')
159
+ }
160
+
161
+ function decimalDigitsToWords (decimalString, formal = true) {
162
+ const ones = formal ? ONES_FORMAL : ONES_COMMON
163
+ const words = []
164
+ for (const char of decimalString) {
165
+ words.push(ones[Number(char)])
100
166
  }
167
+ return words
168
+ }
101
169
 
102
- /** Converts decimal digits to words by reading each digit individually. */
103
- decimalDigitsToWords (decimalString) {
104
- const words = []
105
- for (let i = 0; i < decimalString.length; i++) {
106
- const digitValue = BigInt(decimalString[i])
107
- words.push(this.integerToWords(digitValue))
108
- }
109
- return words
170
+ /**
171
+ * Converts a numeric value to Traditional Chinese words.
172
+ *
173
+ * @param {number | string | bigint} value - The numeric value to convert
174
+ * @param {Object} [options] - Optional configuration
175
+ * @param {boolean} [options.formal=true] - Use formal/financial numerals
176
+ * @returns {string} The number in Traditional Chinese words
177
+ */
178
+ function toWords (value, options) {
179
+ options = validateOptions(options)
180
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
181
+ const formal = options.formal !== false // Default to true
182
+
183
+ let result = ''
184
+
185
+ if (isNegative) {
186
+ result = NEGATIVE
110
187
  }
188
+
189
+ result += integerToWords(integerPart, formal)
190
+
191
+ if (decimalPart) {
192
+ result += DECIMAL_SEP + decimalDigitsToWords(decimalPart, formal).join('')
193
+ }
194
+
195
+ return result
111
196
  }
197
+
198
+ // ============================================================================
199
+ // Exports
200
+ // ============================================================================
201
+
202
+ export { toWords }