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,120 +1,295 @@
1
- import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
1
+ /**
2
+ * Norwegian Bokmål language converter - Functional Implementation
3
+ *
4
+ * A performance-optimized number-to-words converter using precomputed lookup tables.
5
+ *
6
+ * Key features:
7
+ * - Hyphenated tens+ones: "tjue-en" (21)
8
+ * - "og" conjunction after hundreds
9
+ * - Comma separator after thousands before hundreds
10
+ * - Short scale: million, milliard, billion, etc.
11
+ */
12
+
13
+ import { parseNumericValue } from '../utils/parse-numeric.js'
14
+
15
+ // ============================================================================
16
+ // Vocabulary (module-level constants)
17
+ // ============================================================================
18
+
19
+ const ONES = ['', 'en', 'to', 'tre', 'fire', 'fem', 'seks', 'syv', 'åtte', 'ni']
20
+
21
+ const TEENS = ['ti', 'elleve', 'tolv', 'tretten', 'fjorten', 'femten', 'seksten', 'sytten', 'atten', 'nitten']
22
+ const TENS = ['', '', 'tjue', 'tretti', 'førti', 'femti', 'seksti', 'sytti', 'åtti', 'nitti']
23
+
24
+ const HUNDRED = 'hundre'
25
+ const THOUSAND = 'tusen'
26
+
27
+ const ZERO = 'null'
28
+ const NEGATIVE = 'minus'
29
+ const DECIMAL_SEP = 'komma'
30
+
31
+ // Short scale: million, milliard, billion, etc.
32
+ const SCALES = ['million', 'milliard', 'billion', 'billiard', 'kvintillion', 'sekstillion', 'septillion', 'oktillion']
33
+
34
+ // ============================================================================
35
+ // Precomputed Lookup Tables (built once at module load)
36
+ // ============================================================================
37
+
38
+ /**
39
+ * Builds segment word for 0-999.
40
+ * Returns object with word and hasHundred flag.
41
+ */
42
+ function buildSegment (n) {
43
+ if (n === 0) return { word: '', hasHundred: false }
44
+
45
+ const ones = n % 10
46
+ const tens = Math.floor(n / 10) % 10
47
+ const hundreds = Math.floor(n / 100)
48
+
49
+ const parts = []
50
+ let hasHundred = false
51
+
52
+ // Hundreds: "en hundre", "to hundre"
53
+ if (hundreds > 0) {
54
+ hasHundred = true
55
+ parts.push(ONES[hundreds] + ' ' + HUNDRED)
56
+ }
57
+
58
+ // Tens and ones
59
+ const tensOnes = n % 100
60
+
61
+ if (tensOnes === 0) {
62
+ // Just hundreds
63
+ } else if (tensOnes < 10) {
64
+ // Single digit
65
+ parts.push(ONES[ones])
66
+ } else if (tensOnes < 20) {
67
+ // Teens
68
+ parts.push(TEENS[ones])
69
+ } else if (ones === 0) {
70
+ // Even tens
71
+ parts.push(TENS[tens])
72
+ } else {
73
+ // Hyphenated: "tjue-en"
74
+ parts.push(TENS[tens] + '-' + ONES[ones])
75
+ }
76
+
77
+ // Combine with " og " between hundreds and remainder
78
+ if (parts.length === 2) {
79
+ return { word: parts[0] + ' og ' + parts[1], hasHundred: true }
80
+ }
81
+ return { word: parts[0] || '', hasHundred }
82
+ }
83
+
84
+ // Precompute all 1000 segment words (0-999)
85
+ const SEGMENTS = new Array(1000)
86
+ const SEGMENTS_HAS_HUNDRED = new Array(1000)
87
+
88
+ for (let i = 0; i < 1000; i++) {
89
+ const result = buildSegment(i)
90
+ SEGMENTS[i] = result.word
91
+ SEGMENTS_HAS_HUNDRED[i] = result.hasHundred
92
+ }
93
+
94
+ // ============================================================================
95
+ // Conversion Functions
96
+ // ============================================================================
2
97
 
3
98
  /**
4
- * Norwegian language converter.
99
+ * Converts a non-negative integer to Norwegian Bokmål words.
5
100
  *
6
- * GreedyScaleLanguage with inline Norwegian merge rules:
7
- * - Hyphenation for compound numbers (e.g., "tjueen")
8
- * - "og" (and) for hundreds combinations
9
- * - Comma separation for non-magnitude additions
10
- * - Implicit '1' before tens and magnitudes
11
- * - Space separators for large numbers
101
+ * @param {bigint} n - Non-negative integer to convert
102
+ * @returns {string} Norwegian words
12
103
  */
13
- export class Norwegian extends GreedyScaleLanguage {
14
- negativeWord = 'minus'
15
- decimalSeparatorWord = 'komma'
16
- zeroWord = 'null'
17
- scaleWordPairs = [
18
- [1_000_000_000_000_000_000_000_000_000_000_000n, 'quintillard'],
19
- [1_000_000_000_000_000_000_000_000_000_000n, 'quintillion'],
20
- [1_000_000_000_000_000_000_000_000_000n, 'quadrillard'],
21
- [1_000_000_000_000_000_000_000_000n, 'quadrillion'],
22
- [1_000_000_000_000_000_000_000n, 'trillard'],
23
- [1_000_000_000_000_000_000n, 'trillion'],
24
- [1_000_000_000_000_000n, 'billard'],
25
- [1_000_000_000_000n, 'billion'],
26
- [1_000_000_000n, 'millard'],
27
- [1_000_000n, 'million'],
28
- [1000n, 'tusen'],
29
- [100n, 'hundre'],
30
- [90n, 'nitti'],
31
- [80n, 'åtti'],
32
- [70n, 'sytti'],
33
- [60n, 'seksti'],
34
- [50n, 'femti'],
35
- [40n, 'førti'],
36
- [30n, 'tretti'],
37
- [20n, 'tjue'],
38
- [19n, 'nitten'],
39
- [18n, 'atten'],
40
- [17n, 'sytten'],
41
- [16n, 'seksten'],
42
- [15n, 'femten'],
43
- [14n, 'fjorten'],
44
- [13n, 'tretten'],
45
- [12n, 'tolv'],
46
- [11n, 'elleve'],
47
- [10n, 'ti'],
48
- [9n, 'ni'],
49
- [8n, 'åtte'],
50
- [7n, 'syv'],
51
- [6n, 'seks'],
52
- [5n, 'fem'],
53
- [4n, 'fire'],
54
- [3n, 'tre'],
55
- [2n, 'to'],
56
- [1n, 'en'],
57
- [0n, 'null']
58
- ]
59
-
60
- /**
61
- * Merges two adjacent word-number pairs according to Norwegian grammar rules.
62
- *
63
- * Norwegian-specific rules:
64
- * - Implicit \"en\": `mergeScales({ 'en': 1n }, { 'hundre': 100n })` → `{ 'hundre': 100n }`
65
- * - Hyphenation for compound tens: `mergeScales({ 'tjue': 20n }, { 'en': 1n })` → `{ 'tjue-en': 21n }`
66
- * - \"og\" (and) after hundreds: `mergeScales({ 'hundre': 100n }, { 'en': 1n })` → `{ 'hundre og en': 101n }`
67
- * - Space-separated for large numbers (thousands+)
68
- * - Comma separator for non-magnitude additions (e.g., \"tusen, en\")
69
- *
70
- * @param {Object} leftPair The left operand as `{ word: BigInt }`.
71
- * @param {Object} rightPair The right operand as `{ word: BigInt }`.
72
- * @returns {Object} Merged pair with combined word and resulting numeric value.
73
- *
74
- * @example
75
- * mergeScales({ 'en': 1n }, { 'hundre': 100n }); // { 'hundre': 100n }
76
- * mergeScales({ 'tjue': 20n }, { 'tre': 3n }); // { 'tjue-tre': 23n }
77
- */
78
- // Norwegian merge rules mirror the former Scandinavian base logic
79
- mergeScales (leftPair, rightPair) {
80
- const leftWord = Object.keys(leftPair)[0]
81
- const rightWord = Object.keys(rightPair)[0]
82
- const leftNumber = Object.values(leftPair)[0]
83
- const rightNumber = Object.values(rightPair)[0]
84
-
85
- if (leftNumber === 1n && rightNumber < 100n) {
86
- return rightPair
87
- }
104
+ function integerToWords (n) {
105
+ if (n === 0n) return ZERO
88
106
 
89
- if (leftNumber < 100n && leftNumber > rightNumber) {
90
- return { [`${leftWord}-${rightWord}`]: leftNumber + rightNumber }
107
+ // Fast path: numbers < 1000 (direct lookup)
108
+ if (n < 1000n) {
109
+ return SEGMENTS[Number(n)]
110
+ }
111
+
112
+ // Fast path: numbers < 1,000,000 (thousands)
113
+ if (n < 1_000_000n) {
114
+ const thousands = Number(n / 1000n)
115
+ const remainder = Number(n % 1000n)
116
+
117
+ let result = SEGMENTS[thousands] + ' ' + THOUSAND
118
+
119
+ if (remainder > 0) {
120
+ // Comma before hundreds, " og " before small numbers
121
+ if (SEGMENTS_HAS_HUNDRED[remainder]) {
122
+ result += ', ' + SEGMENTS[remainder]
123
+ } else {
124
+ result += ' og ' + SEGMENTS[remainder]
125
+ }
91
126
  }
92
127
 
93
- if (leftNumber >= 100n && rightNumber < 100n) {
94
- return { [`${leftWord} og ${rightWord}`]: leftNumber + rightNumber }
128
+ return result
129
+ }
130
+
131
+ // For numbers >= 1,000,000, use scale decomposition
132
+ return buildLargeNumberWords(n)
133
+ }
134
+
135
+ /**
136
+ * Builds words for numbers >= 1,000,000.
137
+ *
138
+ * @param {bigint} n - Number >= 1,000,000
139
+ * @returns {string} Norwegian words
140
+ */
141
+ function buildLargeNumberWords (n) {
142
+ const numStr = n.toString()
143
+ const len = numStr.length
144
+
145
+ // Build segments of 3 digits from right to left
146
+ const segments = []
147
+ const segmentSize = 3
148
+
149
+ const remainderLen = len % segmentSize
150
+ let pos = 0
151
+ if (remainderLen > 0) {
152
+ segments.push(Number(numStr.slice(0, remainderLen)))
153
+ pos = remainderLen
154
+ }
155
+ while (pos < len) {
156
+ segments.push(Number(numStr.slice(pos, pos + segmentSize)))
157
+ pos += segmentSize
158
+ }
159
+
160
+ // Convert segments to words
161
+ const parts = []
162
+ let scaleIndex = segments.length - 1
163
+
164
+ for (let i = 0; i < segments.length; i++) {
165
+ const segment = segments[i]
166
+
167
+ if (segment !== 0) {
168
+ const segmentWord = SEGMENTS[segment]
169
+ const hasHundred = SEGMENTS_HAS_HUNDRED[segment]
170
+
171
+ if (scaleIndex === 0) {
172
+ // Units segment
173
+ parts.push({ word: segmentWord, hasHundred, type: 'units' })
174
+ } else if (scaleIndex === 1) {
175
+ // Thousands
176
+ parts.push({ word: segmentWord + ' ' + THOUSAND, hasHundred: false, type: 'thousand' })
177
+ } else {
178
+ // Millions+
179
+ const scaleWord = SCALES[scaleIndex - 2]
180
+ parts.push({ word: segmentWord + ' ' + scaleWord, hasHundred: false, type: 'million' })
181
+ }
95
182
  }
96
183
 
97
- if (rightNumber > leftNumber) {
98
- return { [`${leftWord} ${rightWord}`]: leftNumber * rightNumber }
184
+ scaleIndex--
185
+ }
186
+
187
+ // Join parts with Norwegian rules
188
+ return joinNorwegianParts(parts)
189
+ }
190
+
191
+ /**
192
+ * Joins parts with Norwegian spacing and comma rules.
193
+ *
194
+ * @param {Array} parts - Parts with type metadata
195
+ * @returns {string} Joined string
196
+ */
197
+ function joinNorwegianParts (parts) {
198
+ if (parts.length === 0) return ZERO
199
+ if (parts.length === 1) return parts[0].word
200
+
201
+ const result = []
202
+
203
+ for (let i = 0; i < parts.length; i++) {
204
+ const part = parts[i]
205
+ const nextPart = parts[i + 1]
206
+
207
+ result.push(part.word)
208
+
209
+ if (nextPart) {
210
+ if (part.type === 'thousand') {
211
+ // After thousands: comma if next has hundred, else " og "
212
+ if (nextPart.hasHundred) {
213
+ result.push(', ')
214
+ } else {
215
+ result.push(' og ')
216
+ }
217
+ } else if (part.type === 'million') {
218
+ // After millions: " og " for units without hundred, space otherwise
219
+ if (nextPart.type === 'units' && !nextPart.hasHundred) {
220
+ result.push(' og ')
221
+ } else {
222
+ result.push(' ')
223
+ }
224
+ } else {
225
+ result.push(' ')
226
+ }
99
227
  }
228
+ }
229
+
230
+ return result.join('')
231
+ }
232
+
233
+ /**
234
+ * Converts decimal digits to Norwegian words.
235
+ *
236
+ * @param {string} decimalPart - Decimal digits (without the point)
237
+ * @returns {string} Norwegian words for decimal part
238
+ */
239
+ function decimalPartToWords (decimalPart) {
240
+ let result = ''
100
241
 
101
- return { [`${leftWord}, ${rightWord}`]: leftNumber + rightNumber }
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++
102
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
103
258
  }
104
259
 
105
260
  /**
106
- * Converts a number to Norwegian cardinal (written) form.
261
+ * Converts a numeric value to Norwegian Bokmål words.
107
262
  *
108
- * @param {number|string|bigint} value The number to convert.
109
- * @param {Object} [options] Conversion options (see Norwegian class options).
110
- * @returns {string} The number expressed in Norwegian words.
111
- * @throws {TypeError} If value is NaN or invalid type.
112
- * @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 Norwegian words
265
+ * @throws {TypeError} If value is not a valid numeric type
266
+ * @throws {Error} If value is not a valid number format
113
267
  *
114
268
  * @example
115
- * convertToWords(42); // 'førti-to'
116
- * convertToWords('1.5'); // 'en komma fem'
269
+ * toWords(21) // 'tjue-en'
270
+ * toWords(101) // 'en hundre og en'
271
+ * toWords(1000000) // 'en million'
117
272
  */
118
- export default function convertToWords (value, options = {}) {
119
- return new Norwegian(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
120
289
  }
290
+
291
+ // ============================================================================
292
+ // Public API
293
+ // ============================================================================
294
+
295
+ export { toWords }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Converts a numeric value to Dutch 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
+ * @param {Object} [options] - Optional configuration
9
+ * @param {boolean} [options.accentOne=true] - Use "één" instead of "een"
10
+ * @param {boolean} [options.includeOptionalAnd=false] - Include "en" before small numbers
11
+ * @param {boolean} [options.noHundredPairing=false] - Disable hundred pairing (1104→duizend honderdvier)
12
+ * @returns {string} The number in Dutch words
13
+ * @throws {TypeError} If value is not a valid numeric type
14
+ * @throws {Error} If value is not a valid number format
15
+ *
16
+ * @example
17
+ * toWords(21) // 'eenentwintig'
18
+ * toWords(1) // 'één'
19
+ * toWords(1, {accentOne: false}) // 'een'
20
+ * toWords(1104) // 'elfhonderd vier'
21
+ */
22
+ export function toWords(value: number | string | bigint, options?: {
23
+ accentOne?: boolean | undefined;
24
+ includeOptionalAnd?: boolean | undefined;
25
+ noHundredPairing?: boolean | undefined;
26
+ }): string;