n2words 1.24.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +183 -156
  3. package/dist/languages/am-Latn.js +3 -0
  4. package/dist/languages/am-Latn.js.map +1 -0
  5. package/dist/languages/am.js +3 -0
  6. package/dist/languages/am.js.map +1 -0
  7. package/dist/languages/ar.js +3 -2
  8. package/dist/languages/ar.js.map +1 -1
  9. package/dist/languages/az.js +3 -2
  10. package/dist/languages/az.js.map +1 -1
  11. package/dist/languages/bn.js +3 -2
  12. package/dist/languages/bn.js.map +1 -1
  13. package/dist/languages/cs.js +3 -2
  14. package/dist/languages/cs.js.map +1 -1
  15. package/dist/languages/da.js +3 -2
  16. package/dist/languages/da.js.map +1 -1
  17. package/dist/languages/de.js +3 -2
  18. package/dist/languages/de.js.map +1 -1
  19. package/dist/languages/el.js +3 -2
  20. package/dist/languages/el.js.map +1 -1
  21. package/dist/languages/en.js +3 -2
  22. package/dist/languages/en.js.map +1 -1
  23. package/dist/languages/es.js +3 -2
  24. package/dist/languages/es.js.map +1 -1
  25. package/dist/languages/fa.js +3 -2
  26. package/dist/languages/fa.js.map +1 -1
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -2
  30. package/dist/languages/fil.js.map +1 -1
  31. package/dist/languages/fr-BE.js +3 -2
  32. package/dist/languages/fr-BE.js.map +1 -1
  33. package/dist/languages/fr.js +3 -2
  34. package/dist/languages/fr.js.map +1 -1
  35. package/dist/languages/gu.js +3 -2
  36. package/dist/languages/gu.js.map +1 -1
  37. package/dist/languages/ha.js +3 -0
  38. package/dist/languages/ha.js.map +1 -0
  39. package/dist/languages/hbo.js +3 -0
  40. package/dist/languages/hbo.js.map +1 -0
  41. package/dist/languages/he.js +3 -2
  42. package/dist/languages/he.js.map +1 -1
  43. package/dist/languages/hi.js +3 -2
  44. package/dist/languages/hi.js.map +1 -1
  45. package/dist/languages/hr.js +3 -2
  46. package/dist/languages/hr.js.map +1 -1
  47. package/dist/languages/hu.js +3 -2
  48. package/dist/languages/hu.js.map +1 -1
  49. package/dist/languages/id.js +3 -2
  50. package/dist/languages/id.js.map +1 -1
  51. package/dist/languages/it.js +3 -2
  52. package/dist/languages/it.js.map +1 -1
  53. package/dist/languages/ja.js +3 -2
  54. package/dist/languages/ja.js.map +1 -1
  55. package/dist/languages/kn.js +3 -2
  56. package/dist/languages/kn.js.map +1 -1
  57. package/dist/languages/ko.js +3 -2
  58. package/dist/languages/ko.js.map +1 -1
  59. package/dist/languages/lt.js +3 -2
  60. package/dist/languages/lt.js.map +1 -1
  61. package/dist/languages/lv.js +3 -2
  62. package/dist/languages/lv.js.map +1 -1
  63. package/dist/languages/mr.js +3 -2
  64. package/dist/languages/mr.js.map +1 -1
  65. package/dist/languages/ms.js +3 -2
  66. package/dist/languages/ms.js.map +1 -1
  67. package/dist/languages/nb.js +3 -2
  68. package/dist/languages/nb.js.map +1 -1
  69. package/dist/languages/nl.js +3 -2
  70. package/dist/languages/nl.js.map +1 -1
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -2
  74. package/dist/languages/pl.js.map +1 -1
  75. package/dist/languages/pt.js +3 -2
  76. package/dist/languages/pt.js.map +1 -1
  77. package/dist/languages/ro.js +3 -2
  78. package/dist/languages/ro.js.map +1 -1
  79. package/dist/languages/ru.js +3 -2
  80. package/dist/languages/ru.js.map +1 -1
  81. package/dist/languages/sr-Cyrl.js +3 -0
  82. package/dist/languages/sr-Cyrl.js.map +1 -0
  83. package/dist/languages/sr-Latn.js +3 -2
  84. package/dist/languages/sr-Latn.js.map +1 -1
  85. package/dist/languages/sv.js +3 -2
  86. package/dist/languages/sv.js.map +1 -1
  87. package/dist/languages/sw.js +3 -2
  88. package/dist/languages/sw.js.map +1 -1
  89. package/dist/languages/ta.js +3 -2
  90. package/dist/languages/ta.js.map +1 -1
  91. package/dist/languages/te.js +3 -2
  92. package/dist/languages/te.js.map +1 -1
  93. package/dist/languages/th.js +3 -2
  94. package/dist/languages/th.js.map +1 -1
  95. package/dist/languages/tr.js +3 -2
  96. package/dist/languages/tr.js.map +1 -1
  97. package/dist/languages/uk.js +3 -2
  98. package/dist/languages/uk.js.map +1 -1
  99. package/dist/languages/ur.js +3 -2
  100. package/dist/languages/ur.js.map +1 -1
  101. package/dist/languages/vi.js +3 -2
  102. package/dist/languages/vi.js.map +1 -1
  103. package/dist/languages/zh-Hans.js +3 -2
  104. package/dist/languages/zh-Hans.js.map +1 -1
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +3 -2
  108. package/dist/n2words.js.map +1 -1
  109. package/lib/languages/am-Latn.d.ts +7 -0
  110. package/lib/languages/am-Latn.js +164 -0
  111. package/lib/languages/am.d.ts +7 -0
  112. package/lib/languages/am.js +164 -0
  113. package/lib/languages/ar.d.ts +17 -0
  114. package/lib/languages/ar.js +171 -209
  115. package/lib/languages/az.d.ts +7 -0
  116. package/lib/languages/az.js +167 -49
  117. package/lib/languages/bn.d.ts +7 -0
  118. package/lib/languages/bn.js +142 -123
  119. package/lib/languages/cs.d.ts +18 -0
  120. package/lib/languages/cs.js +303 -176
  121. package/lib/languages/da.d.ts +14 -0
  122. package/lib/languages/da.js +267 -139
  123. package/lib/languages/de.d.ts +17 -0
  124. package/lib/languages/de.js +310 -113
  125. package/lib/languages/el.d.ts +14 -0
  126. package/lib/languages/el.js +225 -98
  127. package/lib/languages/en.d.ts +17 -0
  128. package/lib/languages/en.js +235 -102
  129. package/lib/languages/es.d.ts +21 -0
  130. package/lib/languages/es.js +307 -125
  131. package/lib/languages/fa.d.ts +7 -0
  132. package/lib/languages/fa.js +115 -108
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +7 -0
  136. package/lib/languages/fil.js +199 -139
  137. package/lib/languages/fr-BE.d.ts +11 -0
  138. package/lib/languages/fr-BE.js +287 -48
  139. package/lib/languages/fr.d.ts +21 -0
  140. package/lib/languages/fr.js +343 -119
  141. package/lib/languages/gu.d.ts +7 -0
  142. package/lib/languages/gu.js +125 -144
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +13 -0
  146. package/lib/languages/hbo.js +300 -0
  147. package/lib/languages/he.d.ts +13 -0
  148. package/lib/languages/he.js +230 -283
  149. package/lib/languages/hi.d.ts +7 -0
  150. package/lib/languages/hi.js +142 -123
  151. package/lib/languages/hr.d.ts +11 -0
  152. package/lib/languages/hr.js +190 -129
  153. package/lib/languages/hu.d.ts +7 -0
  154. package/lib/languages/hu.js +194 -133
  155. package/lib/languages/id.d.ts +7 -0
  156. package/lib/languages/id.js +167 -140
  157. package/lib/languages/it.d.ts +19 -0
  158. package/lib/languages/it.js +337 -108
  159. package/lib/languages/ja.d.ts +17 -0
  160. package/lib/languages/ja.js +224 -155
  161. package/lib/languages/kn.d.ts +7 -0
  162. package/lib/languages/kn.js +128 -62
  163. package/lib/languages/ko.d.ts +14 -0
  164. package/lib/languages/ko.js +250 -70
  165. package/lib/languages/lt.d.ts +18 -0
  166. package/lib/languages/lt.js +287 -148
  167. package/lib/languages/lv.d.ts +18 -0
  168. package/lib/languages/lv.js +291 -123
  169. package/lib/languages/mr.d.ts +7 -0
  170. package/lib/languages/mr.js +125 -144
  171. package/lib/languages/ms.d.ts +7 -0
  172. package/lib/languages/ms.js +171 -112
  173. package/lib/languages/nb.d.ts +14 -0
  174. package/lib/languages/nb.js +275 -100
  175. package/lib/languages/nl.d.ts +26 -0
  176. package/lib/languages/nl.js +307 -174
  177. package/lib/languages/pa.d.ts +7 -0
  178. package/lib/languages/pa.js +163 -0
  179. package/lib/languages/pl.d.ts +22 -0
  180. package/lib/languages/pl.js +299 -158
  181. package/lib/languages/pt.d.ts +17 -0
  182. package/lib/languages/pt.js +279 -120
  183. package/lib/languages/ro.d.ts +18 -0
  184. package/lib/languages/ro.js +214 -337
  185. package/lib/languages/ru.d.ts +11 -0
  186. package/lib/languages/ru.js +219 -95
  187. package/lib/languages/sr-Cyrl.d.ts +11 -0
  188. package/lib/languages/sr-Cyrl.js +215 -0
  189. package/lib/languages/sr-Latn.d.ts +11 -0
  190. package/lib/languages/sr-Latn.js +190 -132
  191. package/lib/languages/sv.d.ts +14 -0
  192. package/lib/languages/sv.js +280 -103
  193. package/lib/languages/sw.d.ts +7 -0
  194. package/lib/languages/sw.js +135 -103
  195. package/lib/languages/ta.d.ts +7 -0
  196. package/lib/languages/ta.js +133 -205
  197. package/lib/languages/te.d.ts +7 -0
  198. package/lib/languages/te.js +148 -213
  199. package/lib/languages/th.d.ts +7 -0
  200. package/lib/languages/th.js +139 -101
  201. package/lib/languages/tr.d.ts +18 -0
  202. package/lib/languages/tr.js +246 -66
  203. package/lib/languages/uk.d.ts +11 -0
  204. package/lib/languages/uk.js +197 -101
  205. package/lib/languages/ur.d.ts +7 -0
  206. package/lib/languages/ur.js +160 -123
  207. package/lib/languages/vi.d.ts +17 -0
  208. package/lib/languages/vi.js +287 -164
  209. package/lib/languages/zh-Hans.d.ts +11 -0
  210. package/lib/languages/zh-Hans.js +159 -142
  211. package/lib/languages/zh-Hant.d.ts +11 -0
  212. package/lib/languages/zh-Hant.js +202 -0
  213. package/lib/n2words.d.ts +53 -0
  214. package/lib/n2words.js +91 -227
  215. package/lib/utils/is-plain-object.d.ts +13 -0
  216. package/lib/utils/is-plain-object.js +17 -0
  217. package/lib/utils/parse-numeric.d.ts +17 -0
  218. package/lib/utils/parse-numeric.js +108 -0
  219. package/lib/utils/validate-options.d.ts +8 -0
  220. package/lib/utils/validate-options.js +16 -0
  221. package/package.json +118 -67
  222. package/dist/languages/pa-Guru.js +0 -2
  223. package/dist/languages/pa-Guru.js.map +0 -1
  224. package/lib/classes/abstract-language.js +0 -261
  225. package/lib/classes/greedy-scale-language.js +0 -195
  226. package/lib/classes/slavic-language.js +0 -251
  227. package/lib/classes/south-asian-language.js +0 -161
  228. package/lib/classes/turkic-language.js +0 -63
  229. package/lib/languages/pa-Guru.js +0 -126
  230. package/typings/classes/abstract-language.d.ts +0 -144
  231. package/typings/classes/greedy-scale-language.d.ts +0 -148
  232. package/typings/classes/slavic-language.d.ts +0 -145
  233. package/typings/classes/south-asian-language.d.ts +0 -101
  234. package/typings/classes/turkic-language.d.ts +0 -42
  235. package/typings/languages/ar.d.ts +0 -93
  236. package/typings/languages/az.d.ts +0 -25
  237. package/typings/languages/bn.d.ts +0 -1
  238. package/typings/languages/cs.d.ts +0 -120
  239. package/typings/languages/da.d.ts +0 -53
  240. package/typings/languages/de.d.ts +0 -26
  241. package/typings/languages/el.d.ts +0 -11
  242. package/typings/languages/en.d.ts +0 -30
  243. package/typings/languages/es.d.ts +0 -43
  244. package/typings/languages/fa.d.ts +0 -81
  245. package/typings/languages/fil.d.ts +0 -12
  246. package/typings/languages/fr-BE.d.ts +0 -41
  247. package/typings/languages/fr.d.ts +0 -43
  248. package/typings/languages/gu.d.ts +0 -12
  249. package/typings/languages/he.d.ts +0 -197
  250. package/typings/languages/hi.d.ts +0 -1
  251. package/typings/languages/hr.d.ts +0 -110
  252. package/typings/languages/hu.d.ts +0 -37
  253. package/typings/languages/id.d.ts +0 -69
  254. package/typings/languages/it.d.ts +0 -51
  255. package/typings/languages/ja.d.ts +0 -58
  256. package/typings/languages/kn.d.ts +0 -11
  257. package/typings/languages/ko.d.ts +0 -25
  258. package/typings/languages/lt.d.ts +0 -110
  259. package/typings/languages/lv.d.ts +0 -99
  260. package/typings/languages/mr.d.ts +0 -12
  261. package/typings/languages/ms.d.ts +0 -37
  262. package/typings/languages/nb.d.ts +0 -27
  263. package/typings/languages/nl.d.ts +0 -65
  264. package/typings/languages/pa-Guru.d.ts +0 -1
  265. package/typings/languages/pl.d.ts +0 -116
  266. package/typings/languages/pt.d.ts +0 -39
  267. package/typings/languages/ro.d.ts +0 -229
  268. package/typings/languages/ru.d.ts +0 -108
  269. package/typings/languages/sr-Latn.d.ts +0 -98
  270. package/typings/languages/sv.d.ts +0 -30
  271. package/typings/languages/sw.d.ts +0 -1
  272. package/typings/languages/ta.d.ts +0 -1
  273. package/typings/languages/te.d.ts +0 -1
  274. package/typings/languages/th.d.ts +0 -1
  275. package/typings/languages/tr.d.ts +0 -46
  276. package/typings/languages/uk.d.ts +0 -117
  277. package/typings/languages/ur.d.ts +0 -1
  278. package/typings/languages/vi.d.ts +0 -116
  279. package/typings/languages/zh-Hans.d.ts +0 -57
  280. package/typings/n2words.d.ts +0 -177
@@ -1,190 +1,259 @@
1
- import AbstractLanguage from '../classes/abstract-language.js'
2
-
3
1
  /**
4
- * Japanese language converter.
5
- *
6
- * Converts numbers to Japanese kanji numerals using the Sino-Japanese system:
7
- * - Uses kanji characters (一, 二, 三, etc.)
8
- * - Grouping by 万 (man, 10,000) and 億 (oku, 100,000,000)
9
- * - Unique scale units: 兆 (chō, trillion), 京 (kei, 10^16)
10
- * - Special rules for 1: omitted before 十 (10), 百 (100), 千 (1000), but kept for 万 and above
2
+ * Japanese language converter - Functional Implementation
11
3
  *
12
- * Key Features:
13
- * - Sino-Japanese number system (standard for general counting)
14
- * - Grouping by powers of 10,000 (万-based system, not 1,000)
15
- * - Scale units: 万 (10^4), 億 (10^8), 兆 (10^12), 京 (10^16)
16
- * - Special handling of 一 (one) prefix
17
- * - Support for very large numbers up to 無量大数 (10^68)
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.
18
6
  *
19
- * Algorithm:
20
- * 1. Split number into groups of 4 digits (万-based, not 3-digit thousand-based)
21
- * 2. Convert each group to kanji using special rules
22
- * 3. Append appropriate scale word (万, 億, 兆, etc.)
23
- * 4. Join all groups
7
+ * Key optimization: Precompute all segment values (0-9999) at module load.
8
+ * This eliminates all per-call string manipulation for segment conversion.
24
9
  *
25
- * Special Rules:
26
- * - 10, 100, 1000: Don't use prefix (十, 百, 千 not 一十, 一百, 一千)
27
- * - 10,000+: Keepprefix (一万, 一億, 一兆)
28
- * - Zero: 零 or 〇 (both acceptable, 零 is more formal)
10
+ * Japanese-specific rules (handled in precomputation):
11
+ * - Myriad (万-based) grouping: 4 digits per segment instead of 3
12
+ * - omission: Omit "" before 十, 百, 千 but NOT before 万 and higher scales
13
+ * - Kanji numerals: 零一二三四五六七八九
14
+ * - No spaces between characters
29
15
  */
30
- export class Japanese extends AbstractLanguage {
31
- negativeWord = 'マイナス'
32
- decimalSeparatorWord = '点'
33
- zeroWord = '零'
34
- wordSeparator = '' // Japanese doesn't use spaces between characters
35
- convertDecimalsPerDigit = true // Enable digit-by-digit decimal conversion
36
-
37
- // Digits used for group conversion (1-9)
38
- digits = ['一', '二', '三', '四', '五', '六', '七', '八', '九']
39
-
40
- // Scale words for grouping by 10^4
41
- scales = [
42
- '', // 10^0 (ones)
43
- '万', // 10^4 (man)
44
- '億', // 10^8 (oku)
45
- '兆', // 10^12 (chō)
46
- '京', // 10^16 (kei)
47
- '垓', // 10^20 (gai)
48
- '秭', // 10^24 (jo/shi)
49
- '穣', // 10^28 (jō)
50
- '溝', // 10^32 (kō)
51
- '澗', // 10^36 (kan)
52
- '正', // 10^40 (sei)
53
- '載', // 10^44 (sai)
54
- '極', // 10^48 (goku)
55
- '恒河沙', // 10^52 (gōgasha)
56
- '阿僧祇', // 10^56 (asōgi)
57
- '那由他', // 10^60 (nayuta)
58
- '不可思議', // 10^64 (fukashigi)
59
- '無量大数' // 10^68 (muryōtaisū)
60
- ]
61
-
62
- /**
63
- * Converts a group of up to 4 digits to Japanese kanji.
64
- * Handles special rules for omitting 一 before 十, 百, 千.
65
- *
66
- * Rule: Within a group, omit 一 before 十/百/千 EXCEPT when:
67
- * - It's a lower group (not isTopGroup) AND
68
- * - It would be the only character in that position
69
- *
70
- * @param {bigint} num - Number from 0 to 9999
71
- * @param {boolean} isTopGroup - Whether this is the highest non-zero group
72
- * @returns {string} Japanese kanji representation
73
- */
74
- convertGroup (num, isTopGroup = false) {
75
- if (num === 0n) return ''
76
-
77
- const thousands = num / 1000n
78
- const hundreds = (num % 1000n) / 100n
79
- const tens = (num % 100n) / 10n
80
- const ones = num % 10n
81
-
82
- let result = ''
83
-
84
- // Thousands (千)
85
- if (thousands > 0n) {
86
- // Always omit 一 before 千 when thousands === 1
87
- if (thousands === 1n) {
88
- result += '千'
89
- } else {
90
- result += this.digits[Number(thousands) - 1] + '千'
91
- }
92
- }
93
16
 
94
- // Hundreds (百)
95
- if (hundreds > 0n) {
96
- // Always omit 一 before 百 when hundreds === 1
97
- if (hundreds === 1n) {
98
- result += '百'
99
- } else {
100
- result += this.digits[Number(hundreds) - 1] + '百'
101
- }
17
+ import { parseNumericValue } from '../utils/parse-numeric.js'
18
+
19
+ // ============================================================================
20
+ // Vocabulary (module-level constants)
21
+ // ============================================================================
22
+
23
+ // Ones words (1-9), index 0 unused
24
+ const ONES = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九']
25
+
26
+ // Scale words for powers of 10,000 (万-based system)
27
+ // Index 0 = 万 (10^4), 1 = 億 (10^8), 2 = 兆 (10^12), etc.
28
+ const SCALES = [
29
+ '万', // 10^4 (man)
30
+ '億', // 10^8 (oku)
31
+ '兆', // 10^12 (chō)
32
+ '京', // 10^16 (kei)
33
+ '垓', // 10^20 (gai)
34
+ '秭', // 10^24 (jo/shi)
35
+ '穣', // 10^28 (jō)
36
+ '溝', // 10^32 (kō)
37
+ '澗', // 10^36 (kan)
38
+ '正', // 10^40 (sei)
39
+ '載', // 10^44 (sai)
40
+ '極', // 10^48 (goku)
41
+ '恒河沙', // 10^52 (gōgasha)
42
+ '阿僧祇', // 10^56 (asōgi)
43
+ '那由他', // 10^60 (nayuta)
44
+ '不可思議', // 10^64 (fukashigi)
45
+ '無量大数' // 10^68 (muryōtaisū)
46
+ ]
47
+
48
+ const ZERO = '零'
49
+ const NEGATIVE = 'マイナス'
50
+ const DECIMAL_SEP = '点'
51
+
52
+ // Internal scale words (within 4-digit segments)
53
+ const TEN = '十'
54
+ const HUNDRED = '百'
55
+ const THOUSAND = '千'
56
+
57
+ // ============================================================================
58
+ // Precomputed Lookup Tables (built once at module load)
59
+ // ============================================================================
60
+
61
+ /**
62
+ * Builds segment word for 0-9999 with 一 omission rules.
63
+ * - Omit 一 before 十, 百, 千
64
+ * Only used during table construction.
65
+ */
66
+ function buildSegment (n) {
67
+ if (n === 0) return ''
68
+
69
+ const ones = n % 10
70
+ const tens = Math.floor(n / 10) % 10
71
+ const hundreds = Math.floor(n / 100) % 10
72
+ const thousands = Math.floor(n / 1000)
73
+
74
+ let result = ''
75
+
76
+ // Thousands (千) - omit 一 when 1
77
+ if (thousands > 0) {
78
+ if (thousands === 1) {
79
+ result += THOUSAND
80
+ } else {
81
+ result += ONES[thousands] + THOUSAND
102
82
  }
83
+ }
103
84
 
104
- // Tens ()
105
- if (tens > 0n) {
106
- // Always omit 一 before 十 when tens === 1
107
- if (tens === 1n) {
108
- result += '十'
109
- } else {
110
- result += this.digits[Number(tens) - 1] + '十'
111
- }
85
+ // Hundreds () - omit 一 when 1
86
+ if (hundreds > 0) {
87
+ if (hundreds === 1) {
88
+ result += HUNDRED
89
+ } else {
90
+ result += ONES[hundreds] + HUNDRED
112
91
  }
92
+ }
113
93
 
114
- // Ones
115
- if (ones > 0n) {
116
- result += this.digits[Number(ones) - 1]
94
+ // Tens (十) - omit 一 when 1
95
+ if (tens > 0) {
96
+ if (tens === 1) {
97
+ result += TEN
98
+ } else {
99
+ result += ONES[tens] + TEN
117
100
  }
101
+ }
118
102
 
119
- return result
103
+ // Ones
104
+ if (ones > 0) {
105
+ result += ONES[ones]
120
106
  }
121
107
 
122
- /**
123
- * Converts a BigInt number to Japanese cardinal form.
124
- *
125
- * @param {bigint} number - The number to convert
126
- * @returns {string} Japanese kanji representation
127
- */
128
- convertWholePart (number) {
129
- if (number === 0n) {
130
- return this.zeroWord
131
- }
108
+ return result
109
+ }
110
+
111
+ // Precompute all 10000 segment words (0-9999)
112
+ // SEGMENTS[n] gives the Japanese word for n within a segment
113
+ const SEGMENTS = new Array(10000)
114
+ for (let i = 0; i < 10000; i++) {
115
+ SEGMENTS[i] = buildSegment(i)
116
+ }
132
117
 
133
- let temp = number
134
- let scaleIndex = 0
135
- const groups = []
118
+ // ============================================================================
119
+ // Conversion Functions
120
+ // ============================================================================
136
121
 
137
- // Split into groups of 4 digits (万-based system)
138
- while (temp > 0n) {
139
- const group = temp % 10000n
140
- if (group > 0n) {
141
- groups.push({ value: group, scale: scaleIndex })
142
- }
143
- temp = temp / 10000n
144
- scaleIndex++
122
+ /**
123
+ * Converts a non-negative integer to Japanese words.
124
+ *
125
+ * @param {bigint} n - Non-negative integer to convert
126
+ * @returns {string} Japanese kanji words
127
+ */
128
+ function integerToWords (n) {
129
+ if (n === 0n) return ZERO
130
+
131
+ // Fast path: numbers < 10000 (direct lookup)
132
+ if (n < 10000n) {
133
+ return SEGMENTS[Number(n)]
134
+ }
135
+
136
+ // Fast path: numbers < 100,000,000 (万 range)
137
+ if (n < 100_000_000n) {
138
+ const man = Number(n / 10000n)
139
+ const remainder = Number(n % 10000n)
140
+
141
+ // For 万 and above, we need 一 before the scale word when segment is 1
142
+ let result
143
+ if (man === 1) {
144
+ result = '一' + SCALES[0] // 一万
145
+ } else {
146
+ result = SEGMENTS[man] + SCALES[0]
145
147
  }
146
148
 
147
- // Reverse to process from highest to lowest
148
- groups.reverse()
149
+ if (remainder > 0) {
150
+ result += SEGMENTS[remainder]
151
+ }
149
152
 
150
- let result = ''
153
+ return result
154
+ }
151
155
 
152
- for (let i = 0; i < groups.length; i++) {
153
- const { value, scale } = groups[i]
154
- const isTopGroup = (i === 0)
156
+ // For numbers >= 100,000,000, use scale decomposition
157
+ return buildLargeNumberWords(n)
158
+ }
155
159
 
156
- const groupStr = this.convertGroup(value, isTopGroup)
160
+ /**
161
+ * Builds words for numbers >= 100,000,000.
162
+ * Uses BigInt modulo for 4-digit (myriad) segment extraction.
163
+ *
164
+ * @param {bigint} n - Number >= 100,000,000
165
+ * @returns {string} Japanese kanji words
166
+ */
167
+ function buildLargeNumberWords (n) {
168
+ // Extract segments using BigInt modulo (faster than string slicing)
169
+ // Segments stored least-significant first (index 0 = units, 1 = 万, etc.)
170
+ const segments = []
171
+ let temp = n
172
+ while (temp > 0n) {
173
+ segments.push(Number(temp % 10000n))
174
+ temp = temp / 10000n
175
+ }
176
+
177
+ // Build result string directly (process from most-significant to least)
178
+ let result = ''
179
+
180
+ for (let i = segments.length - 1; i >= 0; i--) {
181
+ const segment = segments[i]
182
+ if (segment === 0) continue
157
183
 
158
- // For scales >= 1 ( and above), always add the scale word
159
- if (scale >= 1) {
160
- // Special case: if group is 1 and scale >= 1, we need 一 before the scale
161
- if (value === 1n) {
162
- result += '一' + this.scales[scale]
163
- } else {
164
- result += groupStr + this.scales[scale]
165
- }
184
+ if (i > 0) {
185
+ // For scales >= 万, we need 一 before scale word when segment is 1
186
+ if (segment === 1) {
187
+ result += '一' + SCALES[i - 1]
166
188
  } else {
167
- result += groupStr
189
+ result += SEGMENTS[segment] + SCALES[i - 1]
168
190
  }
191
+ } else {
192
+ // Units segment (no scale word)
193
+ result += SEGMENTS[segment]
169
194
  }
195
+ }
170
196
 
171
- return result
197
+ return result || ZERO
198
+ }
199
+
200
+ /**
201
+ * Converts decimal digits to Japanese words (digit by digit).
202
+ *
203
+ * @param {string} decimalPart - Decimal digits (without the point)
204
+ * @returns {string} Japanese kanji words for decimal part
205
+ */
206
+ function decimalPartToWords (decimalPart) {
207
+ let result = ''
208
+
209
+ for (let i = 0; i < decimalPart.length; i++) {
210
+ const digit = parseInt(decimalPart[i], 10)
211
+ if (digit === 0) {
212
+ result += ZERO
213
+ } else {
214
+ result += ONES[digit]
215
+ }
172
216
  }
217
+
218
+ return result
173
219
  }
174
220
 
175
221
  /**
176
- * Converts a value to cardinal (written) form in Japanese.
222
+ * Converts a numeric value to Japanese words.
177
223
  *
178
- * @param {number|string|bigint} value - Number to convert.
179
- * @param {Object} [options] - Options for the converter.
180
- * @returns {string} Value in Japanese kanji numerals.
181
- * @throws {Error} If value is invalid.
224
+ * This is the main public API. It accepts any valid numeric input
225
+ * (number, string, or bigint) and handles parsing internally.
226
+ *
227
+ * @param {number | string | bigint} value - The numeric value to convert
228
+ * @returns {string} The number in Japanese kanji words
229
+ * @throws {TypeError} If value is not a valid numeric type
230
+ * @throws {Error} If value is not a valid number format
182
231
  *
183
232
  * @example
184
- * convertToWords(42, { lang: 'ja' }); // '四十二'
185
- * convertToWords(1000, { lang: 'ja' }); // ''
186
- * convertToWords(10000, { lang: 'ja' }); // '一万'
233
+ * toWords(42) // '四十二'
234
+ * toWords(10000) // '一万'
235
+ * toWords(100000000) // '一億'
187
236
  */
188
- export default function convertToWords (value, options = {}) {
189
- return new Japanese(options).convertToWords(value)
237
+ function toWords (value) {
238
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
239
+
240
+ let result = ''
241
+
242
+ if (isNegative) {
243
+ result = NEGATIVE
244
+ }
245
+
246
+ result += integerToWords(integerPart)
247
+
248
+ if (decimalPart) {
249
+ result += DECIMAL_SEP + decimalPartToWords(decimalPart)
250
+ }
251
+
252
+ return result
190
253
  }
254
+
255
+ // ============================================================================
256
+ // Public API
257
+ // ============================================================================
258
+
259
+ export { toWords }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Converts a numeric value to Kannada words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Kannada words
6
+ */
7
+ export function toWords(value: number | string | bigint): string;
@@ -1,71 +1,137 @@
1
1
  /**
2
- * Converts numbers to their word representation in Kannada (ಕನ್ನಡ).
3
- * @module languages/kn
2
+ * Kannada language converter - Functional Implementation
3
+ *
4
+ * Self-contained converter for South Asian numbering.
5
+ *
6
+ * Key features:
7
+ * - Indian numbering system (ಸಾವಿರ, ಲಕ್ಷ, ಕೋಟಿ)
8
+ * - Kannada script
9
+ * - 3-2-2 grouping pattern (last 3 digits, then groups of 2)
10
+ * - Complete word forms for 0-99
11
+ * - Per-digit decimal reading
4
12
  */
5
13
 
6
- import SouthAsianLanguage from '../classes/south-asian-language.js'
14
+ import { parseNumericValue } from '../utils/parse-numeric.js'
7
15
 
8
- /**
9
- * Kannada language implementation using Indian-style number grouping.
10
- * @extends SouthAsianLanguage
11
- */
12
- class KannadaLanguage extends SouthAsianLanguage {
13
- negativeWord = 'ಋಣಾತ್ಮಕ'
14
- decimalSeparatorWord = 'ದಶಮಾಂಶ'
15
- zeroWord = 'ಸೊನ್ನೆ'
16
- hundredWord = 'ನೂರು'
17
- convertDecimalsPerDigit = true
18
-
19
- /**
20
- * Array of number words from 0 to 99.
21
- * Index matches the number value.
22
- * @type {string[]}
23
- */
24
- belowHundred = [
25
- 'ಸೊನ್ನೆ', 'ಒಂದು', 'ಎರಡು', 'ಮೂರು', 'ನಾಲ್ಕು', 'ಐದು', 'ಆರು', 'ಏಳು', 'ಎಂಟು', 'ಒಂಬತ್ತು',
26
- 'ಹತ್ತು', 'ಹನ್ನೊಂದು', 'ಹನ್ನೆರಡು', 'ಹದಿಮೂರು', 'ಹದಿನಾಲ್ಕು', 'ಹದಿನೈದು', 'ಹದಿನಾರು', 'ಹದಿನೇಳು', 'ಹದಿನೆಂಟು', 'ಹತ್ತೊಂಬತ್ತು',
27
- 'ಇಪ್ಪತ್ತು', 'ಇಪ್ಪತ್ತೊಂದು', 'ಇಪ್ಪತ್ತೆರಡು', 'ಇಪ್ಪತ್ತಮೂರು', 'ಇಪ್ಪತ್ತನಾಲ್ಕು', 'ಇಪ್ಪತ್ತೈದು', 'ಇಪ್ಪತ್ತಾರು', 'ಇಪ್ಪತ್ತೇಳು', 'ಇಪ್ಪತ್ತೆಂಟು', 'ಇಪ್ಪತ್ತೊಂಬತ್ತು',
28
- 'ಮೂವತ್ತು', 'ಮೂವತ್ತೊಂದು', 'ಮೂವತ್ತೆರಡು', 'ಮೂವತ್ತಮೂರು', 'ಮೂವತ್ತನಾಲ್ಕು', 'ಮೂವತ್ತೈದು', 'ಮೂವತ್ತಾರು', 'ಮೂವತ್ತೇಳು', 'ಮೂವತ್ತೆಂಟು', 'ಮೂವತ್ತೊಂಬತ್ತು',
29
- 'ನಲವತ್ತು', 'ನಲವತ್ತೊಂದು', 'ನಲವತ್ತೆರಡು', 'ನಲವತ್ತಮೂರು', 'ನಲವತ್ತನಾಲ್ಕು', 'ನಲವತ್ತೈದು', 'ನಲವತ್ತಾರು', 'ನಲವತ್ತೇಳು', 'ನಲವತ್ತೆಂಟು', 'ನಲವತ್ತೊಂಬತ್ತು',
30
- 'ಐವತ್ತು', 'ಐವತ್ತೊಂದು', 'ಐವತ್ತೆರಡು', 'ಐವತ್ತಮೂರು', 'ಐವತ್ತನಾಲ್ಕು', 'ಐವತ್ತೈದು', 'ಐವತ್ತಾರು', 'ಐವತ್ತೇಳು', 'ಐವತ್ತೆಂಟು', 'ಐವತ್ತೊಂಬತ್ತು',
31
- 'ಅರವತ್ತು', 'ಅರವತ್ತೊಂದು', 'ಅರವತ್ತೆರಡು', 'ಅರವತ್ತಮೂರು', 'ಅರವತ್ತನಾಲ್ಕು', 'ಅರವತ್ತೈದು', 'ಅರವತ್ತಾರು', 'ಅರವತ್ತೇಳು', 'ಅರವತ್ತೆಂಟು', 'ಅರವತ್ತೊಂಬತ್ತು',
32
- 'ಎಪ್ಪತ್ತು', 'ಎಪ್ಪತ್ತೊಂದು', 'ಎಪ್ಪತ್ತೆರಡು', 'ಎಪ್ಪತ್ತಮೂರು', 'ಎಪ್ಪತ್ತನಾಲ್ಕು', 'ಎಪ್ಪತ್ತೈದು', 'ಎಪ್ಪತ್ತಾರು', 'ಎಪ್ಪತ್ತೇಳು', 'ಎಪ್ಪತ್ತೆಂಟು', 'ಎಪ್ಪತ್ತೊಂಬತ್ತು',
33
- 'ಎಂಬತ್ತು', 'ಎಂಬತ್ತೊಂದು', 'ಎಂಬತ್ತೆರಡು', 'ಎಂಬತ್ತಮೂರು', 'ಎಂಬತ್ತನಾಲ್ಕು', 'ಎಂಬತ್ತೈದು', 'ಎಂಬತ್ತಾರು', 'ಎಂಬತ್ತೇಳು', 'ಎಂಬತ್ತೆಂಟು', 'ಎಂಬತ್ತೊಂಬತ್ತು',
34
- 'ತೊಂಬತ್ತು', 'ತೊಂಬತ್ತೊಂದು', 'ತೊಂಬತ್ತೆರಡು', 'ತೊಂಬತ್ತಮೂರು', 'ತೊಂಬತ್ತನಾಲ್ಕು', 'ತೊಂಬತ್ತೈದು', 'ತೊಂಬತ್ತಾರು', 'ತೊಂಬತ್ತೇಳು', 'ತೊಂಬತ್ತೆಂಟು', 'ತೊಂಬತ್ತೊಂಬತ್ತು'
35
- ]
36
-
37
- /**
38
- * Scale words for powers of ten in Indian numbering system.
39
- * Index 0 = units, 1 = thousand, 2 = lakh, 3 = crore, etc.
40
- * @type {string[]}
41
- */
42
- scaleWords = [
43
- '', // units
44
- 'ಸಾವಿರ', // thousand (1,000)
45
- 'ಲಕ್ಷ', // lakh (100,000)
46
- 'ಕೋಟಿ', // crore (10,000,000)
47
- 'ಅಬ್ಜ', // arab (1,000,000,000)
48
- 'ಖರ್ವ', // kharab (100,000,000,000)
49
- 'ನೀಲ', // neel (10,000,000,000,000)
50
- 'ಪದ್ಮ', // padma (1,000,000,000,000,000)
51
- 'ಶಂಖ' // shankh (100,000,000,000,000,000)
52
- ]
53
-
54
- /**
55
- * Converts numbers using the belowHundred array directly.
56
- */
16
+ // ============================================================================
17
+ // Vocabulary
18
+ // ============================================================================
19
+
20
+ const ZERO = 'ಸೊನ್ನೆ'
21
+ const NEGATIVE = 'ಋಣಾತ್ಮಕ'
22
+ const DECIMAL_SEP = 'ದಶಮಾಂಶ'
23
+ const HUNDRED = 'ನೂರು'
24
+
25
+ const BELOW_HUNDRED = [
26
+ 'ಸೊನ್ನೆ', 'ಒಂದು', 'ಎರಡು', 'ಮೂರು', 'ನಾಲ್ಕು', 'ಐದು', 'ಆರು', 'ಏಳು', 'ಎಂಟು', 'ಒಂಬತ್ತು',
27
+ 'ಹತ್ತು', 'ಹನ್ನೊಂದು', 'ಹನ್ನೆರಡು', 'ಹದಿಮೂರು', 'ಹದಿನಾಲ್ಕು', 'ಹದಿನೈದು', 'ಹದಿನಾರು', 'ಹದಿನೇಳು', 'ಹದಿನೆಂಟು', 'ಹತ್ತೊಂಬತ್ತು',
28
+ 'ಇಪ್ಪತ್ತು', 'ಇಪ್ಪತ್ತೊಂದು', 'ಇಪ್ಪತ್ತೆರಡು', 'ಇಪ್ಪತ್ತಮೂರು', 'ಇಪ್ಪತ್ತನಾಲ್ಕು', 'ಇಪ್ಪತ್ತೈದು', 'ಇಪ್ಪತ್ತಾರು', 'ಇಪ್ಪತ್ತೇಳು', 'ಇಪ್ಪತ್ತೆಂಟು', 'ಇಪ್ಪತ್ತೊಂಬತ್ತು',
29
+ 'ಮೂವತ್ತು', 'ಮೂವತ್ತೊಂದು', 'ಮೂವತ್ತೆರಡು', 'ಮೂವತ್ತಮೂರು', 'ಮೂವತ್ತನಾಲ್ಕು', 'ಮೂವತ್ತೈದು', 'ಮೂವತ್ತಾರು', 'ಮೂವತ್ತೇಳು', 'ಮೂವತ್ತೆಂಟು', 'ಮೂವತ್ತೊಂಬತ್ತು',
30
+ 'ನಲವತ್ತು', 'ನಲವತ್ತೊಂದು', 'ನಲವತ್ತೆರಡು', 'ನಲವತ್ತಮೂರು', 'ನಲವತ್ತನಾಲ್ಕು', 'ನಲವತ್ತೈದು', 'ನಲವತ್ತಾರು', 'ನಲವತ್ತೇಳು', 'ನಲವತ್ತೆಂಟು', 'ನಲವತ್ತೊಂಬತ್ತು',
31
+ 'ಐವತ್ತು', 'ಐವತ್ತೊಂದು', 'ಐವತ್ತೆರಡು', 'ಐವತ್ತಮೂರು', 'ಐವತ್ತನಾಲ್ಕು', 'ಐವತ್ತೈದು', 'ಐವತ್ತಾರು', 'ಐವತ್ತೇಳು', 'ಐವತ್ತೆಂಟು', 'ಐವತ್ತೊಂಬತ್ತು',
32
+ 'ಅರವತ್ತು', 'ಅರವತ್ತೊಂದು', 'ಅರವತ್ತೆರಡು', 'ಅರವತ್ತಮೂರು', 'ಅರವತ್ತನಾಲ್ಕು', 'ಅರವತ್ತೈದು', 'ಅರವತ್ತಾರು', 'ಅರವತ್ತೇಳು', 'ಅರವತ್ತೆಂಟು', 'ಅರವತ್ತೊಂಬತ್ತು',
33
+ 'ಎಪ್ಪತ್ತು', 'ಎಪ್ಪತ್ತೊಂದು', 'ಎಪ್ಪತ್ತೆರಡು', 'ಎಪ್ಪತ್ತಮೂರು', 'ಎಪ್ಪತ್ತನಾಲ್ಕು', 'ಎಪ್ಪತ್ತೈದು', 'ಎಪ್ಪತ್ತಾರು', 'ಎಪ್ಪತ್ತೇಳು', 'ಎಪ್ಪತ್ತೆಂಟು', 'ಎಪ್ಪತ್ತೊಂಬತ್ತು',
34
+ 'ಎಂಬತ್ತು', 'ಎಂಬತ್ತೊಂದು', 'ಎಂಬತ್ತೆರಡು', 'ಎಂಬತ್ತಮೂರು', 'ಎಂಬತ್ತನಾಲ್ಕು', 'ಎಂಬತ್ತೈದು', 'ಎಂಬತ್ತಾರು', 'ಎಂಬತ್ತೇಳು', 'ಎಂಬತ್ತೆಂಟು', 'ಎಂಬತ್ತೊಂಬತ್ತು',
35
+ 'ತೊಂಬತ್ತು', 'ತೊಂಬತ್ತೊಂದು', 'ತೊಂಬತ್ತೆರಡು', 'ತೊಂಬತ್ತಮೂರು', 'ತೊಂಬತ್ತನಾಲ್ಕು', 'ತೊಂಬತ್ತೈದು', 'ತೊಂಬತ್ತಾರು', 'ತೊಂಬತ್ತೇಳು', 'ತೊಂಬತ್ತೆಂಟು', 'ತೊಂಬತ್ತೊಂಬತ್ತು'
36
+ ]
37
+
38
+ // Scale words: index 0 = units (empty), 1 = thousand, 2 = lakh, 3 = crore, etc.
39
+ const SCALE_WORDS = ['', 'ಸಾವಿರ', 'ಲಕ್ಷ', 'ಕೋಟಿ', 'ಅಬ್ಜ', 'ಖರ್ವ', 'ನೀಲ', 'ಪದ್ಮ', 'ಶಂಖ']
40
+
41
+ // ============================================================================
42
+ // Segment Splitting (inlined for performance)
43
+ // ============================================================================
44
+
45
+ function groupByThreeThenTwos (n) {
46
+ const numStr = n.toString()
47
+ if (numStr.length <= 3) return [Number(numStr)]
48
+
49
+ const segments = []
50
+ segments.unshift(Number(numStr.slice(-3)))
51
+
52
+ let remaining = numStr.slice(0, -3)
53
+ while (remaining.length > 0) {
54
+ segments.unshift(Number(remaining.slice(-2)))
55
+ remaining = remaining.slice(0, -2)
56
+ }
57
+
58
+ return segments
59
+ }
60
+
61
+ function segmentToWords (n) {
62
+ if (n === 0) return ''
63
+ if (n < 100) return BELOW_HUNDRED[n]
64
+
65
+ const hundreds = Math.trunc(n / 100)
66
+ const remainder = n % 100
67
+
68
+ if (remainder === 0) {
69
+ return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED
70
+ }
71
+ return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED + ' ' + BELOW_HUNDRED[remainder]
72
+ }
73
+
74
+ // ============================================================================
75
+ // Conversion Functions
76
+ // ============================================================================
77
+
78
+ function integerToWords (n) {
79
+ if (n === 0n) return ZERO
80
+
81
+ const segments = groupByThreeThenTwos(n)
82
+ const segmentCount = segments.length
83
+ const words = []
84
+
85
+ for (let i = 0; i < segmentCount; i++) {
86
+ const segmentValue = segments[i]
87
+ if (segmentValue === 0) continue
88
+
89
+ const scaleIndex = segmentCount - i - 1
90
+ words.push(segmentToWords(segmentValue))
91
+ if (scaleIndex > 0 && SCALE_WORDS[scaleIndex]) {
92
+ words.push(SCALE_WORDS[scaleIndex])
93
+ }
94
+ }
95
+
96
+ return words.join(' ').trim()
97
+ }
98
+
99
+ function decimalPartToWords (decimalPart) {
100
+ // Per-digit decimal reading
101
+ const digits = []
102
+ for (const char of decimalPart) {
103
+ const d = parseInt(char, 10)
104
+ digits.push(d === 0 ? ZERO : BELOW_HUNDRED[d])
105
+ }
106
+ return digits.join(' ')
57
107
  }
58
108
 
59
109
  /**
60
- * Converts a number to its word representation in Kannada.
61
- * @param {number|string|bigint} value - The number to convert
62
- * @param {Object} [options={}] - Conversion options
63
- * @returns {string} The word representation of the number
64
- * @example
65
- * convertToWords(42) // 'ನಲವತ್ತೆರಡು'
66
- * convertToWords(1000) // 'ಒಂದು ಸಾವಿರ'
67
- * convertToWords(100000) // 'ಒಂದು ಲಕ್ಷ'
110
+ * Converts a numeric value to Kannada words.
111
+ *
112
+ * @param {number | string | bigint} value - The numeric value to convert
113
+ * @returns {string} The number in Kannada words
68
114
  */
69
- export default function convertToWords (value, options = {}) {
70
- return new KannadaLanguage(options).convertToWords(value)
115
+ function toWords (value) {
116
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
117
+
118
+ let result = ''
119
+
120
+ if (isNegative) {
121
+ result = NEGATIVE + ' '
122
+ }
123
+
124
+ result += integerToWords(integerPart)
125
+
126
+ if (decimalPart) {
127
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
128
+ }
129
+
130
+ return result
71
131
  }
132
+
133
+ // ============================================================================
134
+ // Exports
135
+ // ============================================================================
136
+
137
+ export { toWords }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Converts a numeric value to Korean words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Korean words
6
+ * @throws {TypeError} If value is not a valid numeric type
7
+ * @throws {Error} If value is not a valid number format
8
+ *
9
+ * @example
10
+ * toWords(21) // '이십일'
11
+ * toWords(10000) // '만'
12
+ * toWords(1000000) // '백만'
13
+ */
14
+ export function toWords(value: number | string | bigint): string;