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,167 +1,295 @@
1
- import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
1
+ /**
2
+ * Danish language converter - Functional Implementation
3
+ *
4
+ * A performance-optimized number-to-words converter using precomputed lookup tables.
5
+ *
6
+ * Key features:
7
+ * - Vigesimal (base-20) tens naming: halvtreds (50), treds (60), etc.
8
+ * - Units-before-tens: "enogtyve" (21) = one-and-twenty
9
+ * - Compound thousands: "ettusind", "firetusinde"
10
+ * - "og" conjunction after hundreds and thousands
11
+ * - Long scale for millions+
12
+ */
13
+
14
+ import { parseNumericValue } from '../utils/parse-numeric.js'
15
+
16
+ // ============================================================================
17
+ // Vocabulary (module-level constants)
18
+ // ============================================================================
19
+
20
+ const ONES = ['', 'et', 'to', 'tre', 'fire', 'fem', 'seks', 'syv', 'otte', 'ni']
21
+ // "en" form used in vigesimal pattern (X og Y) and before millions
22
+ const ONES_VIGESIMAL = ['', 'en', 'to', 'tre', 'fire', 'fem', 'seks', 'syv', 'otte', 'ni']
23
+
24
+ const TEENS = ['ti', 'elleve', 'tolv', 'tretten', 'fjorten', 'femten', 'seksten', 'sytten', 'atten', 'nitten']
25
+
26
+ // Danish vigesimal tens (base-20 derived names)
27
+ const TENS = ['', '', 'tyve', 'tredive', 'fyrre', 'halvtreds', 'treds', 'halvfjerds', 'firs', 'halvfems']
28
+
29
+ const HUNDRED = 'hundrede'
30
+ const THOUSAND = 'tusind'
31
+
32
+ const ZERO = 'nul'
33
+ const NEGATIVE = 'minus'
34
+ const DECIMAL_SEP = 'komma'
35
+
36
+ // Long scale: millioner, millarder, billioner, etc.
37
+ const SCALES = ['millioner', 'millarder', 'billioner', 'billarder', 'trillioner', 'trillarder', 'quadrillioner', 'quadrillarder']
38
+
39
+ // ============================================================================
40
+ // Precomputed Lookup Tables (built once at module load)
41
+ // ============================================================================
2
42
 
3
43
  /**
4
- * @typedef {Object} DanishOptions
5
- * @property {boolean} [ordFlag=false] Enable ordinal number conversion.
44
+ * Builds segment word for 0-999.
6
45
  */
46
+ function buildSegment (n) {
47
+ if (n === 0) return ''
48
+
49
+ const ones = n % 10
50
+ const tens = Math.floor(n / 10) % 10
51
+ const hundreds = Math.floor(n / 100)
52
+
53
+ const parts = []
54
+
55
+ // Hundreds: "ethundrede", "tohundrede" (compound, no space)
56
+ if (hundreds > 0) {
57
+ parts.push(ONES[hundreds] + HUNDRED)
58
+ }
59
+
60
+ // Tens and ones
61
+ const tensOnes = n % 100
62
+
63
+ if (tensOnes === 0) {
64
+ // Just hundreds
65
+ } else if (tensOnes < 10) {
66
+ // Single digit
67
+ parts.push(ONES[ones])
68
+ } else if (tensOnes < 20) {
69
+ // Teens
70
+ parts.push(TEENS[ones])
71
+ } else if (ones === 0) {
72
+ // Even tens
73
+ parts.push(TENS[tens])
74
+ } else {
75
+ // Units-before-tens: "enogtyve", "treogfyrre"
76
+ parts.push(ONES_VIGESIMAL[ones] + 'og' + TENS[tens])
77
+ }
78
+
79
+ // Combine with " og " between hundreds and remainder
80
+ if (parts.length === 2) {
81
+ return parts[0] + ' og ' + parts[1]
82
+ }
83
+ return parts[0] || ''
84
+ }
85
+
86
+ // Precompute all 1000 segment words (0-999)
87
+ const SEGMENTS = new Array(1000)
88
+
89
+ for (let i = 0; i < 1000; i++) {
90
+ SEGMENTS[i] = buildSegment(i)
91
+ }
92
+
93
+ // ============================================================================
94
+ // Conversion Functions
95
+ // ============================================================================
7
96
 
8
97
  /**
9
- * Danish language converter.
10
- *
11
- * GreedyScaleLanguage with Danish-specific extensions:
12
- * - Unique vigesimal (base-20) number system for 50-90
13
- * - Special composition rules ("og" for "and" between units and tens)
14
- * - Reverse digit order (e.g., "fem-og-tyve" = five-and-twenty = 25)
15
- * - Support for ordinal numbers via ordFlag option
98
+ * Converts a non-negative integer to Danish words.
16
99
  *
17
- * Key Features:
18
- * - Vigesimal tens: halvtreds (50), treds (60), halvfjerds (70), firs (80), halvfems (90)
19
- * - Units-before-tens pattern (e.g., "tre-og-tyve" = 23)
20
- * - "et" prefix for hundreds/thousands (not "en")
21
- * - Optional ordinal number conversion via ordFlag option
22
- * - Inline merge logic tailored for Danish ordering
100
+ * @param {bigint} n - Non-negative integer to convert
101
+ * @returns {string} Danish words
23
102
  */
24
- export class Danish extends GreedyScaleLanguage {
25
- negativeWord = 'minus'
26
- decimalSeparatorWord = 'komma'
27
- zeroWord = 'nul'
28
- scaleWordPairs = [
29
- [1_000_000_000_000_000_000_000_000_000n, 'quadrillarder'],
30
- [1_000_000_000_000_000_000_000_000n, 'quadrillioner'],
31
- [1_000_000_000_000_000_000_000n, 'trillarder'],
32
- [1_000_000_000_000_000_000n, 'trillioner'],
33
- [1_000_000_000_000_000n, 'billarder'],
34
- [1_000_000_000_000n, 'billioner'],
35
- [1_000_000_000n, 'millarder'],
36
- [1_000_000n, 'millioner'],
37
- [1000n, 'tusind'],
38
- [100n, 'hundrede'],
39
- [90n, 'halvfems'],
40
- [80n, 'firs'],
41
- [70n, 'halvfjerds'],
42
- [60n, 'treds'],
43
- [50n, 'halvtreds'],
44
- [40n, 'fyrre'],
45
- [30n, 'tredive'],
46
- [20n, 'tyve'],
47
- [19n, 'nitten'],
48
- [18n, 'atten'],
49
- [17n, 'sytten'],
50
- [16n, 'seksten'],
51
- [15n, 'femten'],
52
- [14n, 'fjorten'],
53
- [13n, 'tretten'],
54
- [12n, 'tolv'],
55
- [11n, 'elleve'],
56
- [10n, 'ti'],
57
- [9n, 'ni'],
58
- [8n, 'otte'],
59
- [7n, 'syv'],
60
- [6n, 'seks'],
61
- [5n, 'fem'],
62
- [4n, 'fire'],
63
- [3n, 'tre'],
64
- [2n, 'to'],
65
- [1n, 'et'],
66
- [0n, 'nul']
67
- ]
68
-
69
- /**
70
- * Initializes the Danish converter with language-specific options.
71
- *
72
- * @param {DanishOptions} [options={}] Configuration options.
73
- */
74
- constructor ({ ordFlag = false } = {}) {
75
- super()
76
-
77
- this.ordFlag = ordFlag
103
+ function integerToWords (n) {
104
+ if (n === 0n) return ZERO
105
+
106
+ // Fast path: numbers < 1000 (direct lookup)
107
+ if (n < 1000n) {
108
+ return SEGMENTS[Number(n)]
78
109
  }
79
110
 
80
- /**
81
- * Merges two adjacent word-number pairs according to Danish grammar rules.
82
- * Danish uses complex vigesimal (base-20) patterns and reverse digit ordering.
83
- *
84
- * Key Danish patterns:
85
- * - Vigesimal tens: halvtreds(50), treds(60), halvfjerds(70), firs(80), halvfems(90)
86
- * - Units-before-tens order with "og" (and): "tre-og-tyve" (3 and 20 = 25)
87
- * - "et" prefix for hundreds/thousands (not "en")
88
- * - Space separators for large magnitudes (≥ millions)
89
- * - Ordinal support via this.ordFlag
90
- *
91
- * @param {Object} current The left operand as `{ word: bigint }`.
92
- * @param {Object} next The right operand as `{ word: bigint }`.
93
- * @returns {Object} Merged pair with combined word and resulting number (bigint).
94
- */
95
- mergeScales (current, next) {
96
- let cText = Object.keys(current)[0]
97
- let nText = Object.keys(next)[0]
98
- const cNumber = Object.values(current)[0] // BigInt (e.g., 1n, 100n, 1000n)
99
- const nNumber = Object.values(next)[0] // BigInt (e.g., magnitude level like 100n, 1000n)
100
-
101
- // Prepend "et" to hundreds and thousands (not "en") for proper Danish form
102
- if (nNumber === 100n || nNumber === 1000n) {
103
- next = { [`et${nText}`]: nNumber }
104
- }
111
+ // Fast path: numbers < 1,000,000 (thousands)
112
+ if (n < 1_000_000n) {
113
+ const thousands = Number(n / 1000n)
114
+ const remainder = Number(n % 1000n)
105
115
 
106
- // Implicit '1' handling: omit '1' before most magnitudes (except millions/ordinals)
107
- if (cNumber === 1n) {
108
- if (nNumber < 1_000_000n || this.ordFlag) {
109
- return next // Just the magnitude word (e.g., "hundrede" not "en hundrede")
110
- }
111
- cText = 'en' // Explicit "en" (one) for millions and above
116
+ // Compound thousands: "ettusind", "firetusind"
117
+ let result = SEGMENTS[thousands] + THOUSAND
118
+
119
+ if (remainder > 0) {
120
+ // Add 'e' suffix and " og " for remainder: "firetusinde og ..."
121
+ result += 'e og ' + SEGMENTS[remainder]
112
122
  }
113
123
 
114
- // Multiplication across magnitude boundaries
115
- if (nNumber > cNumber) {
116
- // Space for million+ (e.g., "en million", "to millioner")
117
- if (nNumber >= 1_000_000n) {
118
- cText += ' '
124
+ return result
125
+ }
126
+
127
+ // For numbers >= 1,000,000, use scale decomposition
128
+ return buildLargeNumberWords(n)
129
+ }
130
+
131
+ /**
132
+ * Builds words for numbers >= 1,000,000.
133
+ *
134
+ * @param {bigint} n - Number >= 1,000,000
135
+ * @returns {string} Danish words
136
+ */
137
+ function buildLargeNumberWords (n) {
138
+ const numStr = n.toString()
139
+ const len = numStr.length
140
+
141
+ // Build segments of 3 digits from right to left
142
+ const segments = []
143
+ const segmentSize = 3
144
+
145
+ const remainderLen = len % segmentSize
146
+ let pos = 0
147
+ if (remainderLen > 0) {
148
+ segments.push(Number(numStr.slice(0, remainderLen)))
149
+ pos = remainderLen
150
+ }
151
+ while (pos < len) {
152
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
153
+ pos += segmentSize
154
+ }
155
+
156
+ // Convert segments to words with scale tracking
157
+ // scaleIndex: 0 = units, 1 = thousands, 2 = millions, etc.
158
+ const parts = []
159
+ let scaleIndex = segments.length - 1
160
+
161
+ for (let i = 0; i < segments.length; i++) {
162
+ const segment = segments[i]
163
+
164
+ if (segment !== 0) {
165
+ const segmentWord = SEGMENTS[segment]
166
+
167
+ if (scaleIndex === 0) {
168
+ // Units segment
169
+ parts.push({ word: segmentWord, type: 'units' })
170
+ } else if (scaleIndex === 1) {
171
+ // Thousands - compound form
172
+ parts.push({ word: segmentWord + THOUSAND, type: 'thousand' })
173
+ } else {
174
+ // Millions+ - space-separated, use "en" for 1
175
+ const scaleWord = SCALES[scaleIndex - 2]
176
+ let numWord = segmentWord
177
+ // "et" → "en" before millions+
178
+ if (segment === 1) {
179
+ numWord = 'en'
180
+ }
181
+ parts.push({ word: numWord + ' ' + scaleWord, type: 'million' })
119
182
  }
120
- return { [`${cText}${nText}`]: cNumber * nNumber }
121
183
  }
122
184
 
123
- // Addition with separator rules:
124
- // "og" (and) for hundreds + smaller numbers
125
- if (cNumber >= 100n && cNumber < 1000n) {
126
- cText += ' og '
127
- } else if (cNumber >= 1000n && cNumber <= 100_000n) {
128
- // Special "e og" for thousands (e.g., "tusinde og tyve")
129
- cText += 'e og '
130
- }
185
+ scaleIndex--
186
+ }
131
187
 
132
- // Units-before-tens reversal (Danish vigesimal pattern):
133
- // For small units (< 10) with tens (10-99), swap order: "tre og tyve" (25)
134
- if (nNumber < 10n && cNumber > 10n && cNumber < 100n) {
135
- if (nNumber === 1n) {
136
- nText = 'en' // Convert 1 to "en" for vigesimal context
188
+ // Join parts with Danish rules
189
+ return joinDanishParts(parts)
190
+ }
191
+
192
+ /**
193
+ * Joins parts with Danish spacing rules.
194
+ * - After thousands with remainder: "tusinde og"
195
+ * - Millions are space-separated
196
+ *
197
+ * @param {Array} parts - Parts with type metadata
198
+ * @returns {string} Joined string
199
+ */
200
+ function joinDanishParts (parts) {
201
+ if (parts.length === 0) return ZERO
202
+ if (parts.length === 1) return parts[0].word
203
+
204
+ const result = []
205
+
206
+ for (let i = 0; i < parts.length; i++) {
207
+ const part = parts[i]
208
+ const nextPart = parts[i + 1]
209
+
210
+ if (part.type === 'thousand' && nextPart && nextPart.type === 'units') {
211
+ // Thousands followed by units: add "e og"
212
+ result.push(part.word + 'e og ' + nextPart.word)
213
+ i++ // Skip the units part
214
+ } else if (part.type === 'million') {
215
+ if (result.length > 0) {
216
+ result.push(' ')
217
+ }
218
+ result.push(part.word)
219
+ if (nextPart) {
220
+ result.push(' ')
221
+ }
222
+ } else {
223
+ if (result.length > 0 && !result[result.length - 1].endsWith(' ')) {
224
+ result.push(' ')
137
225
  }
138
- // Swap positions: units go after "og", tens go before
139
- const temporary = nText
140
- nText = cText
141
- cText = temporary + 'og'
142
- } else if (cNumber >= 1_000_000n) {
143
- // Space for large magnitudes (millions+)
144
- cText += ' '
226
+ result.push(part.word)
145
227
  }
228
+ }
229
+
230
+ return result.join('')
231
+ }
232
+
233
+ /**
234
+ * Converts decimal digits to Danish words.
235
+ *
236
+ * @param {string} decimalPart - Decimal digits (without the point)
237
+ * @returns {string} Danish words for decimal part
238
+ */
239
+ function decimalPartToWords (decimalPart) {
240
+ let result = ''
146
241
 
147
- return { [`${cText}${nText}`]: cNumber + nNumber }
242
+ // Handle leading zeros
243
+ let i = 0
244
+ while (i < decimalPart.length && decimalPart[i] === '0') {
245
+ if (result) result += ' '
246
+ result += ZERO
247
+ i++
148
248
  }
249
+
250
+ // Convert remainder as a single number
251
+ const remainder = decimalPart.slice(i)
252
+ if (remainder) {
253
+ if (result) result += ' '
254
+ result += integerToWords(BigInt(remainder))
255
+ }
256
+
257
+ return result
149
258
  }
150
259
 
151
260
  /**
152
- * Converts a number to Danish cardinal (written) form.
261
+ * Converts a numeric value to Danish words.
153
262
  *
154
- * @param {number|string|bigint} value The number to convert.
155
- * @param {Object} [options] Conversion options (see Danish class options).
156
- * @param {boolean} [options.ordFlag=false] Enable ordinal number conversion.
157
- * @returns {string} The number expressed in Danish words.
158
- * @throws {TypeError} If value is NaN or invalid type.
159
- * @throws {Error} If value is an invalid number string.
263
+ * @param {number | string | bigint} value - The numeric value to convert
264
+ * @returns {string} The number in Danish words
265
+ * @throws {TypeError} If value is not a valid numeric type
266
+ * @throws {Error} If value is not a valid number format
160
267
  *
161
268
  * @example
162
- * convertToWords(25); // 'femogtyve' (five-and-twenty)
163
- * convertToWords(50); // 'halvtreds' (half-third-times-twenty)
269
+ * toWords(21) // 'enogtyve'
270
+ * toWords(1000) // 'ettusind'
271
+ * toWords(1000000) // 'en millioner'
164
272
  */
165
- export default function convertToWords (value, options = {}) {
166
- return new Danish(options).convertToWords(value)
273
+ function toWords (value) {
274
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
275
+
276
+ let result = ''
277
+
278
+ if (isNegative) {
279
+ result = NEGATIVE + ' '
280
+ }
281
+
282
+ result += integerToWords(integerPart)
283
+
284
+ if (decimalPart) {
285
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
286
+ }
287
+
288
+ return result
167
289
  }
290
+
291
+ // ============================================================================
292
+ // Public API
293
+ // ============================================================================
294
+
295
+ export { toWords }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Converts a numeric value to German words.
3
+ *
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 German 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(21) // 'einundzwanzig'
14
+ * toWords(1000) // 'eintausend'
15
+ * toWords(1000000) // 'eine Million'
16
+ */
17
+ export function toWords(value: number | string | bigint): string;