n2words 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/CHANGELOG.md +128 -42
  2. package/README.md +6 -4
  3. package/dist/am-ET.js +2 -2
  4. package/dist/am-ET.umd.js +2 -2
  5. package/dist/am-Latn-ET.js +2 -2
  6. package/dist/am-Latn-ET.umd.js +2 -2
  7. package/dist/ar-SA.js +2 -2
  8. package/dist/ar-SA.umd.js +2 -2
  9. package/dist/az-AZ.js +2 -2
  10. package/dist/az-AZ.umd.js +2 -2
  11. package/dist/bn-BD.js +2 -2
  12. package/dist/bn-BD.umd.js +2 -2
  13. package/dist/cs-CZ.js +2 -2
  14. package/dist/cs-CZ.umd.js +2 -2
  15. package/dist/da-DK.js +2 -2
  16. package/dist/da-DK.umd.js +2 -2
  17. package/dist/de-DE.js +2 -2
  18. package/dist/de-DE.umd.js +2 -2
  19. package/dist/el-GR.js +2 -2
  20. package/dist/el-GR.umd.js +2 -2
  21. package/dist/en-AU.js +2 -2
  22. package/dist/en-AU.umd.js +2 -2
  23. package/dist/en-BD.js +2 -2
  24. package/dist/en-BD.umd.js +2 -2
  25. package/dist/en-CA.js +2 -2
  26. package/dist/en-CA.umd.js +2 -2
  27. package/dist/en-GB.js +2 -2
  28. package/dist/en-GB.umd.js +2 -2
  29. package/dist/en-GH.js +2 -2
  30. package/dist/en-GH.umd.js +2 -2
  31. package/dist/en-IE.js +2 -2
  32. package/dist/en-IE.umd.js +2 -2
  33. package/dist/en-IN.js +2 -2
  34. package/dist/en-IN.umd.js +2 -2
  35. package/dist/en-KE.js +2 -2
  36. package/dist/en-KE.umd.js +2 -2
  37. package/dist/en-MY.js +2 -2
  38. package/dist/en-MY.umd.js +2 -2
  39. package/dist/en-NG.js +2 -2
  40. package/dist/en-NG.umd.js +2 -2
  41. package/dist/en-NZ.js +2 -2
  42. package/dist/en-NZ.umd.js +2 -2
  43. package/dist/en-PH.js +2 -2
  44. package/dist/en-PH.umd.js +2 -2
  45. package/dist/en-PK.js +2 -2
  46. package/dist/en-PK.umd.js +2 -2
  47. package/dist/en-SG.js +2 -2
  48. package/dist/en-SG.umd.js +2 -2
  49. package/dist/en-US.js +2 -2
  50. package/dist/en-US.umd.js +2 -2
  51. package/dist/en-ZA.js +2 -2
  52. package/dist/en-ZA.umd.js +2 -2
  53. package/dist/es-ES.js +2 -2
  54. package/dist/es-ES.umd.js +2 -2
  55. package/dist/es-MX.js +2 -2
  56. package/dist/es-MX.umd.js +2 -2
  57. package/dist/es-US.js +2 -2
  58. package/dist/es-US.umd.js +2 -2
  59. package/dist/fa-IR.js +2 -2
  60. package/dist/fa-IR.umd.js +2 -2
  61. package/dist/fi-FI.js +2 -2
  62. package/dist/fi-FI.umd.js +2 -2
  63. package/dist/fil-PH.js +2 -2
  64. package/dist/fil-PH.umd.js +2 -2
  65. package/dist/fr-BE.js +2 -2
  66. package/dist/fr-BE.umd.js +2 -2
  67. package/dist/fr-FR.js +2 -2
  68. package/dist/fr-FR.umd.js +2 -2
  69. package/dist/gu-IN.js +2 -2
  70. package/dist/gu-IN.umd.js +2 -2
  71. package/dist/ha-NG.js +2 -2
  72. package/dist/ha-NG.umd.js +2 -2
  73. package/dist/hbo-IL.js +2 -2
  74. package/dist/hbo-IL.umd.js +2 -2
  75. package/dist/he-IL.js +2 -2
  76. package/dist/he-IL.umd.js +2 -2
  77. package/dist/hi-IN.js +2 -2
  78. package/dist/hi-IN.umd.js +2 -2
  79. package/dist/hr-HR.js +2 -2
  80. package/dist/hr-HR.umd.js +2 -2
  81. package/dist/hu-HU.js +2 -2
  82. package/dist/hu-HU.umd.js +2 -2
  83. package/dist/id-ID.js +2 -2
  84. package/dist/id-ID.umd.js +2 -2
  85. package/dist/it-IT.js +2 -2
  86. package/dist/it-IT.umd.js +2 -2
  87. package/dist/ja-JP.js +2 -2
  88. package/dist/ja-JP.umd.js +2 -2
  89. package/dist/ka-GE.js +2 -2
  90. package/dist/ka-GE.umd.js +2 -2
  91. package/dist/kn-IN.js +2 -2
  92. package/dist/kn-IN.umd.js +2 -2
  93. package/dist/ko-KR.js +2 -2
  94. package/dist/ko-KR.umd.js +2 -2
  95. package/dist/lt-LT.js +2 -2
  96. package/dist/lt-LT.umd.js +2 -2
  97. package/dist/lv-LV.js +2 -2
  98. package/dist/lv-LV.umd.js +2 -2
  99. package/dist/mr-IN.js +2 -2
  100. package/dist/mr-IN.umd.js +2 -2
  101. package/dist/ms-MY.js +2 -2
  102. package/dist/ms-MY.umd.js +2 -2
  103. package/dist/nb-NO.js +2 -2
  104. package/dist/nb-NO.umd.js +2 -2
  105. package/dist/nl-NL.js +2 -2
  106. package/dist/nl-NL.umd.js +2 -2
  107. package/dist/pa-IN.js +2 -2
  108. package/dist/pa-IN.umd.js +2 -2
  109. package/dist/pl-PL.js +2 -2
  110. package/dist/pl-PL.umd.js +2 -2
  111. package/dist/pt-BR.js +2 -2
  112. package/dist/pt-BR.umd.js +2 -2
  113. package/dist/pt-PT.js +2 -2
  114. package/dist/pt-PT.umd.js +2 -2
  115. package/dist/ro-RO.js +2 -2
  116. package/dist/ro-RO.umd.js +2 -2
  117. package/dist/ru-RU.js +2 -2
  118. package/dist/ru-RU.umd.js +2 -2
  119. package/dist/sr-Cyrl-RS.js +2 -2
  120. package/dist/sr-Cyrl-RS.umd.js +2 -2
  121. package/dist/sr-Latn-RS.js +2 -2
  122. package/dist/sr-Latn-RS.umd.js +2 -2
  123. package/dist/sv-SE.js +2 -2
  124. package/dist/sv-SE.umd.js +2 -2
  125. package/dist/sw-KE.js +2 -2
  126. package/dist/sw-KE.umd.js +2 -2
  127. package/dist/ta-IN.js +2 -2
  128. package/dist/ta-IN.umd.js +2 -2
  129. package/dist/te-IN.js +2 -2
  130. package/dist/te-IN.umd.js +2 -2
  131. package/dist/th-TH.js +2 -2
  132. package/dist/th-TH.umd.js +2 -2
  133. package/dist/tr-TR.js +2 -2
  134. package/dist/tr-TR.umd.js +2 -2
  135. package/dist/uk-UA.js +2 -2
  136. package/dist/uk-UA.umd.js +2 -2
  137. package/dist/ur-PK.js +2 -2
  138. package/dist/ur-PK.umd.js +2 -2
  139. package/dist/vi-VN.js +2 -2
  140. package/dist/vi-VN.umd.js +2 -2
  141. package/dist/yo-NG.js +2 -2
  142. package/dist/yo-NG.umd.js +2 -2
  143. package/dist/zh-Hans-CN.js +2 -2
  144. package/dist/zh-Hans-CN.umd.js +2 -2
  145. package/dist/zh-Hant-TW.js +2 -2
  146. package/dist/zh-Hant-TW.umd.js +2 -2
  147. package/package.json +31 -22
  148. package/src/am-ET.d.ts +3 -5
  149. package/src/am-ET.js +41 -16
  150. package/src/am-Latn-ET.d.ts +3 -5
  151. package/src/am-Latn-ET.js +45 -16
  152. package/src/ar-SA.d.ts +44 -18
  153. package/src/ar-SA.js +93 -40
  154. package/src/az-AZ.d.ts +3 -5
  155. package/src/az-AZ.js +58 -20
  156. package/src/bn-BD.d.ts +3 -5
  157. package/src/bn-BD.js +32 -16
  158. package/src/cs-CZ.d.ts +3 -6
  159. package/src/cs-CZ.js +66 -42
  160. package/src/da-DK.d.ts +3 -6
  161. package/src/da-DK.js +53 -48
  162. package/src/de-DE.d.ts +17 -11
  163. package/src/de-DE.js +88 -57
  164. package/src/el-GR.d.ts +3 -6
  165. package/src/el-GR.js +45 -32
  166. package/src/en-AU.d.ts +17 -11
  167. package/src/en-AU.js +56 -41
  168. package/src/en-BD.d.ts +17 -11
  169. package/src/en-BD.js +60 -41
  170. package/src/en-CA.d.ts +36 -18
  171. package/src/en-CA.js +67 -46
  172. package/src/en-GB.d.ts +17 -11
  173. package/src/en-GB.js +56 -41
  174. package/src/en-GH.d.ts +32 -3
  175. package/src/en-GH.js +104 -26
  176. package/src/en-IE.d.ts +17 -11
  177. package/src/en-IE.js +56 -41
  178. package/src/en-IN.d.ts +17 -11
  179. package/src/en-IN.js +60 -41
  180. package/src/en-KE.d.ts +28 -3
  181. package/src/en-KE.js +93 -26
  182. package/src/en-MY.d.ts +26 -3
  183. package/src/en-MY.js +91 -26
  184. package/src/en-NG.d.ts +17 -11
  185. package/src/en-NG.js +56 -41
  186. package/src/en-NZ.d.ts +32 -3
  187. package/src/en-NZ.js +85 -31
  188. package/src/en-PH.d.ts +32 -3
  189. package/src/en-PH.js +97 -26
  190. package/src/en-PK.d.ts +17 -11
  191. package/src/en-PK.js +60 -41
  192. package/src/en-SG.d.ts +28 -3
  193. package/src/en-SG.js +93 -26
  194. package/src/en-US.d.ts +36 -18
  195. package/src/en-US.js +70 -47
  196. package/src/en-ZA.d.ts +17 -11
  197. package/src/en-ZA.js +56 -41
  198. package/src/es-ES.d.ts +53 -21
  199. package/src/es-ES.js +104 -56
  200. package/src/es-MX.d.ts +53 -21
  201. package/src/es-MX.js +104 -56
  202. package/src/es-US.d.ts +53 -21
  203. package/src/es-US.js +92 -51
  204. package/src/fa-IR.d.ts +3 -5
  205. package/src/fa-IR.js +28 -13
  206. package/src/fi-FI.d.ts +3 -6
  207. package/src/fi-FI.js +47 -29
  208. package/src/fil-PH.d.ts +3 -5
  209. package/src/fil-PH.js +61 -28
  210. package/src/fr-BE.d.ts +31 -15
  211. package/src/fr-BE.js +128 -57
  212. package/src/fr-FR.d.ts +31 -16
  213. package/src/fr-FR.js +97 -60
  214. package/src/gu-IN.d.ts +3 -5
  215. package/src/gu-IN.js +31 -16
  216. package/src/ha-NG.d.ts +3 -5
  217. package/src/ha-NG.js +55 -27
  218. package/src/hbo-IL.d.ts +26 -12
  219. package/src/hbo-IL.js +92 -51
  220. package/src/he-IL.d.ts +17 -10
  221. package/src/he-IL.js +92 -50
  222. package/src/hi-IN.d.ts +3 -5
  223. package/src/hi-IN.js +30 -17
  224. package/src/hr-HR.d.ts +21 -10
  225. package/src/hr-HR.js +89 -33
  226. package/src/hu-HU.d.ts +3 -5
  227. package/src/hu-HU.js +57 -23
  228. package/src/id-ID.d.ts +3 -5
  229. package/src/id-ID.js +56 -23
  230. package/src/it-IT.d.ts +17 -11
  231. package/src/it-IT.js +74 -43
  232. package/src/ja-JP.d.ts +3 -6
  233. package/src/ja-JP.js +39 -26
  234. package/src/ka-GE.d.ts +3 -6
  235. package/src/ka-GE.js +38 -26
  236. package/src/kn-IN.d.ts +3 -5
  237. package/src/kn-IN.js +31 -16
  238. package/src/ko-KR.d.ts +3 -6
  239. package/src/ko-KR.js +34 -26
  240. package/src/lt-LT.d.ts +21 -11
  241. package/src/lt-LT.js +64 -42
  242. package/src/lv-LV.d.ts +21 -11
  243. package/src/lv-LV.js +79 -51
  244. package/src/mr-IN.d.ts +3 -5
  245. package/src/mr-IN.js +31 -16
  246. package/src/ms-MY.d.ts +3 -5
  247. package/src/ms-MY.js +58 -24
  248. package/src/nb-NO.d.ts +3 -6
  249. package/src/nb-NO.js +54 -34
  250. package/src/nl-NL.d.ts +41 -20
  251. package/src/nl-NL.js +111 -69
  252. package/src/pa-IN.d.ts +3 -5
  253. package/src/pa-IN.js +32 -16
  254. package/src/pl-PL.d.ts +21 -11
  255. package/src/pl-PL.js +69 -45
  256. package/src/pt-BR.d.ts +22 -11
  257. package/src/pt-BR.js +93 -53
  258. package/src/pt-PT.d.ts +17 -11
  259. package/src/pt-PT.js +80 -48
  260. package/src/ro-RO.d.ts +21 -11
  261. package/src/ro-RO.js +77 -39
  262. package/src/ru-RU.d.ts +35 -15
  263. package/src/ru-RU.js +100 -38
  264. package/src/sr-Cyrl-RS.d.ts +35 -15
  265. package/src/sr-Cyrl-RS.js +100 -38
  266. package/src/sr-Latn-RS.d.ts +35 -15
  267. package/src/sr-Latn-RS.js +100 -38
  268. package/src/sv-SE.d.ts +3 -6
  269. package/src/sv-SE.js +53 -34
  270. package/src/sw-KE.d.ts +3 -5
  271. package/src/sw-KE.js +50 -20
  272. package/src/ta-IN.d.ts +3 -5
  273. package/src/ta-IN.js +29 -17
  274. package/src/te-IN.d.ts +3 -5
  275. package/src/te-IN.js +31 -16
  276. package/src/th-TH.d.ts +3 -5
  277. package/src/th-TH.js +42 -19
  278. package/src/tr-TR.d.ts +17 -11
  279. package/src/tr-TR.js +63 -37
  280. package/src/uk-UA.d.ts +21 -10
  281. package/src/uk-UA.js +89 -33
  282. package/src/ur-PK.d.ts +3 -5
  283. package/src/ur-PK.js +32 -16
  284. package/src/utils/check-max.d.ts +26 -0
  285. package/src/utils/check-max.js +33 -0
  286. package/src/utils/expand-scientific.d.ts +0 -4
  287. package/src/utils/expand-scientific.js +7 -9
  288. package/src/utils/is-plain-object.d.ts +3 -4
  289. package/src/utils/is-plain-object.js +3 -4
  290. package/src/utils/parse-cardinal.d.ts +1 -2
  291. package/src/utils/parse-cardinal.js +12 -9
  292. package/src/utils/parse-currency.d.ts +1 -2
  293. package/src/utils/parse-currency.js +9 -11
  294. package/src/utils/parse-ordinal.d.ts +0 -1
  295. package/src/utils/parse-ordinal.js +9 -10
  296. package/src/utils/resolve-options.d.ts +17 -0
  297. package/src/utils/resolve-options.js +56 -0
  298. package/src/utils/scale.d.ts +49 -0
  299. package/src/utils/scale.js +65 -0
  300. package/src/vi-VN.d.ts +3 -6
  301. package/src/vi-VN.js +41 -28
  302. package/src/yo-NG.d.ts +3 -5
  303. package/src/yo-NG.js +49 -33
  304. package/src/zh-Hans-CN.d.ts +45 -20
  305. package/src/zh-Hans-CN.js +84 -31
  306. package/src/zh-Hant-TW.d.ts +45 -20
  307. package/src/zh-Hant-TW.js +85 -34
  308. package/src/utils/validate-options.d.ts +0 -8
  309. package/src/utils/validate-options.js +0 -16
package/src/de-DE.js CHANGED
@@ -15,7 +15,9 @@
15
15
  import { parseCardinalValue } from './utils/parse-cardinal.js'
16
16
  import { parseCurrencyValue } from './utils/parse-currency.js'
17
17
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
18
- import { validateOptions } from './utils/validate-options.js'
18
+ import { checkMax } from './utils/check-max.js'
19
+ import { western } from './utils/scale.js'
20
+ import { resolveOptions } from './utils/resolve-options.js'
19
21
 
20
22
  // ============================================================================
21
23
  // Vocabulary (module-level constants)
@@ -39,6 +41,15 @@ const SCALES = ['tausend', 'Million', 'Milliarde', 'Billion', 'Billiarde', 'Tril
39
41
  // Pluralized scale words (million+)
40
42
  const SCALES_PLURAL = ['tausend', 'Millionen', 'Milliarden', 'Billionen', 'Billiarden', 'Trillionen', 'Trilliarden', 'Quadrillionen', 'Quadrilliarden']
41
43
 
44
+ // Supported magnitude ceiling (checked at the public entry points). SCALES is
45
+ // indexed [scaleIndex - 1] starting at 'tausend' (10^3), so segments are
46
+ // [units, then one per SCALES entry] -> ceiling 10^((SCALES.length + 1) * 3) =
47
+ // 10^30. Ordinal (cardinal + suffix) and currency build on the cardinal, and
48
+ // the decimal is spelled via integerToWords, so all share the ceiling.
49
+ export const cardinalMax = western(SCALES.length)
50
+ export const ordinalMax = western(SCALES.length)
51
+ export const currencyMax = western(SCALES.length)
52
+
42
53
  const HUNDRED = 'hundert'
43
54
  const ZERO = 'null'
44
55
  const NEGATIVE = 'minus'
@@ -75,11 +86,10 @@ const CENT = 'Cent'
75
86
  /**
76
87
  * Builds segment word for 0-999 (standalone form, uses "eins").
77
88
  * German inverts ones and tens: "einundzwanzig" = one-and-twenty
78
- *
79
89
  * @param {number} n - Number 0-999
80
90
  * @returns {string} German words for the segment
81
91
  */
82
- function buildSegment (n) {
92
+ function buildSegment(n) {
83
93
  if (n === 0) return ''
84
94
 
85
95
  const ones = n % 10
@@ -97,14 +107,17 @@ function buildSegment (n) {
97
107
  if (tens === 1) {
98
108
  // Teens
99
109
  result += TEENS[ones]
100
- } else if (tens >= 2 && ones > 0) {
110
+ }
111
+ else if (tens >= 2 && ones > 0) {
101
112
  // Inverted: "einundzwanzig" (one-and-twenty)
102
113
  // Use "ein" before "und", not "eins"
103
114
  result += (ones === 1 ? EIN : ONES[ones]) + 'und' + TENS[tens]
104
- } else if (tens >= 2) {
115
+ }
116
+ else if (tens >= 2) {
105
117
  // Just tens
106
118
  result += TENS[tens]
107
- } else if (ones > 0) {
119
+ }
120
+ else if (ones > 0) {
108
121
  // Just ones (no tens, possibly after hundreds)
109
122
  // Use "eins" for standalone/after hundreds
110
123
  result += ONES[ones]
@@ -116,11 +129,10 @@ function buildSegment (n) {
116
129
  /**
117
130
  * Builds segment word for use before "tausend".
118
131
  * Uses "ein" instead of "eins" for 1.
119
- *
120
132
  * @param {number} n - Number 0-999
121
133
  * @returns {string} German words for thousand context
122
134
  */
123
- function buildSegmentForThousand (n) {
135
+ function buildSegmentForThousand(n) {
124
136
  if (n === 0) return ''
125
137
  if (n === 1) return EIN // "eintausend"
126
138
 
@@ -136,13 +148,17 @@ function buildSegmentForThousand (n) {
136
148
 
137
149
  if (tens === 1) {
138
150
  result += TEENS[ones]
139
- } else if (tens >= 2 && ones > 0) {
151
+ }
152
+ else if (tens >= 2 && ones > 0) {
140
153
  result += (ones === 1 ? EIN : ONES[ones]) + 'und' + TENS[tens]
141
- } else if (tens >= 2) {
154
+ }
155
+ else if (tens >= 2) {
142
156
  result += TENS[tens]
143
- } else if (ones > 0 && hundreds > 0) {
157
+ }
158
+ else if (ones > 0 && hundreds > 0) {
144
159
  result += ONES[ones]
145
- } else if (ones > 0) {
160
+ }
161
+ else if (ones > 0) {
146
162
  result += ONES[ones]
147
163
  }
148
164
 
@@ -155,11 +171,10 @@ function buildSegmentForThousand (n) {
155
171
 
156
172
  /**
157
173
  * Converts a non-negative integer to German words.
158
- *
159
174
  * @param {bigint} n - Non-negative integer to convert
160
175
  * @returns {string} German words
161
176
  */
162
- function integerToWords (n) {
177
+ function integerToWords(n) {
163
178
  if (n === 0n) return ZERO
164
179
 
165
180
  // Fast path: numbers < 1000
@@ -188,11 +203,10 @@ function integerToWords (n) {
188
203
 
189
204
  /**
190
205
  * Builds words for numbers >= 1,000,000.
191
- *
192
206
  * @param {bigint} n - Number >= 1,000,000
193
207
  * @returns {string} German words
194
208
  */
195
- function buildLargeNumberWords (n) {
209
+ function buildLargeNumberWords(n) {
196
210
  const numStr = n.toString()
197
211
  const len = numStr.length
198
212
 
@@ -222,16 +236,19 @@ function buildLargeNumberWords (n) {
222
236
  if (scaleIndex === 0) {
223
237
  // Units segment (no scale word)
224
238
  parts.push({ words: buildSegment(segment), isScale: false, scaleLevel: 0 })
225
- } else if (scaleIndex === 1) {
239
+ }
240
+ else if (scaleIndex === 1) {
226
241
  // Thousands: compound without space
227
242
  const segWords = buildSegmentForThousand(segment)
228
243
  parts.push({ words: segWords + SCALES[0], isScale: false, scaleLevel: 1 })
229
- } else {
244
+ }
245
+ else {
230
246
  // Million+ : space around scale word
231
247
  let segWords
232
248
  if (segment === 1) {
233
249
  segWords = 'eine' // "eine Million"
234
- } else {
250
+ }
251
+ else {
235
252
  segWords = buildSegment(segment)
236
253
  }
237
254
  const scaleWord = segment === 1 ? SCALES[scaleIndex - 1] : SCALES_PLURAL[scaleIndex - 1]
@@ -250,11 +267,10 @@ function buildLargeNumberWords (n) {
250
267
  /**
251
268
  * Joins parts with German spacing rules.
252
269
  * Spaces only around million+ scale words.
253
- *
254
- * @param {Array} parts - Parts with metadata
270
+ * @param {Array<{words: string, isScale: boolean, scaleLevel: number}>} parts - Parts with metadata
255
271
  * @returns {string} Joined string
256
272
  */
257
- function joinGermanParts (parts) {
273
+ function joinGermanParts(parts) {
258
274
  if (parts.length === 0) return ZERO
259
275
 
260
276
  let result = ''
@@ -281,11 +297,10 @@ function joinGermanParts (parts) {
281
297
 
282
298
  /**
283
299
  * Converts decimal digits to German words.
284
- *
285
300
  * @param {string} decimalPart - Decimal digits (without the point)
286
301
  * @returns {string} German words for decimal part
287
302
  */
288
- function decimalPartToWords (decimalPart) {
303
+ function decimalPartToWords(decimalPart) {
289
304
  let result = ''
290
305
 
291
306
  // Handle leading zeros
@@ -311,19 +326,20 @@ function decimalPartToWords (decimalPart) {
311
326
  *
312
327
  * This is the main public API. It accepts any valid numeric input
313
328
  * (number, string, or bigint) and handles parsing internally.
314
- *
315
329
  * @param {number | string | bigint} value - The numeric value to convert
316
330
  * @returns {string} The number in German words
317
331
  * @throws {TypeError} If value is not a valid numeric type
318
332
  * @throws {Error} If value is not a valid number format
319
- *
320
333
  * @example
321
334
  * toCardinal(21) // 'einundzwanzig'
322
335
  * toCardinal(1000) // 'eintausend'
323
336
  * toCardinal(1000000) // 'eine Million'
324
337
  */
325
- function toCardinal (value) {
338
+ function toCardinal(value) {
326
339
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
340
+ // Both the integer part and the decimal's significant digits are spelled via
341
+ // the scale builder, so both must clear the ceiling.
342
+ checkMax(integerPart, cardinalMax, decimalPart)
327
343
 
328
344
  let result = ''
329
345
 
@@ -348,12 +364,11 @@ function toCardinal (value) {
348
364
  * Builds ordinal segment for 0-999.
349
365
  * Only the final component becomes ordinal.
350
366
  * Uses -te for 1-19, -ste for 20+.
351
- *
352
367
  * @param {number} n - Number 0-999
353
368
  * @param {boolean} isFinal - Whether this is the final segment (gets ordinal suffix)
354
369
  * @returns {string} German ordinal words for this segment
355
370
  */
356
- function buildOrdinalSegment (n, isFinal) {
371
+ function buildOrdinalSegment(n, isFinal) {
357
372
  if (n === 0) return ''
358
373
 
359
374
  const ones = n % 10
@@ -372,28 +387,35 @@ function buildOrdinalSegment (n, isFinal) {
372
387
  // Teens: 10-19
373
388
  if (isFinal) {
374
389
  result += ORDINAL_TEENS[ones]
375
- } else {
390
+ }
391
+ else {
376
392
  result += TEENS[ones]
377
393
  }
378
- } else if (tens >= 2 && ones > 0) {
394
+ }
395
+ else if (tens >= 2 && ones > 0) {
379
396
  // Compound: einundzwanzig → einundzwanzigste
380
397
  if (isFinal) {
381
398
  result += (ones === 1 ? EIN : ONES[ones]) + 'und' + ORDINAL_TENS[tens]
382
- } else {
399
+ }
400
+ else {
383
401
  result += (ones === 1 ? EIN : ONES[ones]) + 'und' + TENS[tens]
384
402
  }
385
- } else if (tens >= 2) {
403
+ }
404
+ else if (tens >= 2) {
386
405
  // Just tens: zwanzig → zwanzigste
387
406
  if (isFinal) {
388
407
  result += ORDINAL_TENS[tens]
389
- } else {
408
+ }
409
+ else {
390
410
  result += TENS[tens]
391
411
  }
392
- } else if (ones > 0) {
412
+ }
413
+ else if (ones > 0) {
393
414
  // Just ones: eins → erste
394
415
  if (isFinal) {
395
416
  result += ORDINAL_ONES[ones]
396
- } else {
417
+ }
418
+ else {
397
419
  result += ONES[ones]
398
420
  }
399
421
  }
@@ -408,11 +430,10 @@ function buildOrdinalSegment (n, isFinal) {
408
430
 
409
431
  /**
410
432
  * Converts integer to German ordinal words.
411
- *
412
433
  * @param {bigint} n - Positive integer
413
434
  * @returns {string} German ordinal words
414
435
  */
415
- function integerToOrdinal (n) {
436
+ function integerToOrdinal(n) {
416
437
  // Fast path: numbers < 1000
417
438
  if (n < 1000n) {
418
439
  return buildOrdinalSegment(Number(n), true)
@@ -438,11 +459,10 @@ function integerToOrdinal (n) {
438
459
 
439
460
  /**
440
461
  * Builds ordinal words for numbers >= 1,000,000.
441
- *
442
462
  * @param {bigint} n - Number >= 1,000,000
443
463
  * @returns {string} German ordinal words
444
464
  */
445
- function buildLargeOrdinal (n) {
465
+ function buildLargeOrdinal(n) {
446
466
  const numStr = n.toString()
447
467
  const len = numStr.length
448
468
 
@@ -482,27 +502,32 @@ function buildLargeOrdinal (n) {
482
502
  if (scaleIndex === 0) {
483
503
  // Units segment
484
504
  parts.push({ words: buildOrdinalSegment(segment, true), isScale: false, scaleLevel: 0 })
485
- } else if (scaleIndex === 1) {
505
+ }
506
+ else if (scaleIndex === 1) {
486
507
  // Thousands
487
508
  const segWords = buildSegmentForThousand(segment)
488
509
  if (isLowestSegment) {
489
510
  parts.push({ words: segWords + SCALES[0] + ORDINAL_SUFFIX, isScale: false, scaleLevel: 1 })
490
- } else {
511
+ }
512
+ else {
491
513
  parts.push({ words: segWords + SCALES[0], isScale: false, scaleLevel: 1 })
492
514
  }
493
- } else {
515
+ }
516
+ else {
494
517
  // Million+
495
518
  let segWords
496
519
  if (segment === 1) {
497
520
  segWords = 'eine'
498
- } else {
521
+ }
522
+ else {
499
523
  segWords = buildSegment(segment)
500
524
  }
501
525
  const scaleWord = segment === 1 ? SCALES[scaleIndex - 1] : SCALES_PLURAL[scaleIndex - 1]
502
526
  parts.push({ words: segWords, isScale: false, scaleLevel: scaleIndex })
503
527
  if (isLowestSegment) {
504
528
  parts.push({ words: scaleWord + ORDINAL_SUFFIX, isScale: true, scaleLevel: scaleIndex })
505
- } else {
529
+ }
530
+ else {
506
531
  parts.push({ words: scaleWord, isScale: true, scaleLevel: scaleIndex })
507
532
  }
508
533
  }
@@ -519,12 +544,10 @@ function buildLargeOrdinal (n) {
519
544
  *
520
545
  * German ordinals add -te for 1-19 and -ste for 20+.
521
546
  * Irregular forms: erste (1st), dritte (3rd), siebte (7th), achte (8th).
522
- *
523
547
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
524
548
  * @returns {string} The number as ordinal words
525
549
  * @throws {TypeError} If value is not a valid numeric type
526
550
  * @throws {RangeError} If value is negative, zero, or has a decimal part
527
- *
528
551
  * @example
529
552
  * toOrdinal(1) // 'erste'
530
553
  * toOrdinal(2) // 'zweite'
@@ -533,8 +556,9 @@ function buildLargeOrdinal (n) {
533
556
  * toOrdinal(100) // 'einhundertste'
534
557
  * toOrdinal(1000) // 'eintausendste'
535
558
  */
536
- function toOrdinal (value) {
559
+ function toOrdinal(value) {
537
560
  const integerPart = parseOrdinalValue(value)
561
+ checkMax(integerPart, ordinalMax)
538
562
  return integerToOrdinal(integerPart)
539
563
  }
540
564
 
@@ -542,16 +566,21 @@ function toOrdinal (value) {
542
566
  // CURRENCY: toCurrency(value, options?)
543
567
  // ============================================================================
544
568
 
569
+ /**
570
+ * @typedef {object} CurrencyOptions
571
+ * @property {boolean} [and] - Use "und" between euros and cents
572
+ */
573
+
574
+ /** @type {Required<CurrencyOptions>} */
575
+ export const currencyDefaults = { and: true }
576
+
545
577
  /**
546
578
  * Converts a numeric value to German currency words (Euro).
547
- *
548
579
  * @param {number | string | bigint} value - The currency amount to convert
549
- * @param {Object} [options] - Optional configuration
550
- * @param {boolean} [options.and=true] - Use "und" between euros and cents
580
+ * @param {CurrencyOptions} [options] - Optional configuration
551
581
  * @returns {string} The amount in German currency words
552
582
  * @throws {TypeError} If value is not a valid numeric type
553
583
  * @throws {Error} If value is not a valid number format
554
- *
555
584
  * @example
556
585
  * toCurrency(42.50) // 'zweiundvierzig Euro und fünfzig Cent'
557
586
  * toCurrency(1) // 'ein Euro'
@@ -559,10 +588,10 @@ function toOrdinal (value) {
559
588
  * toCurrency(0.01) // 'ein Cent'
560
589
  * toCurrency(42.50, { and: false }) // 'zweiundvierzig Euro fünfzig Cent'
561
590
  */
562
- function toCurrency (value, options) {
563
- options = validateOptions(options)
591
+ function toCurrency(value, options) {
564
592
  const { isNegative, dollars: euros, cents } = parseCurrencyValue(value)
565
- const { and: useAnd = true } = options
593
+ checkMax(euros, currencyMax)
594
+ const { and: useAnd } = resolveOptions(options, currencyDefaults)
566
595
 
567
596
  // Build result
568
597
  let result = ''
@@ -573,7 +602,8 @@ function toCurrency (value, options) {
573
602
  // Use "ein" instead of "eins" before Euro
574
603
  if (euros === 1n) {
575
604
  result += EIN
576
- } else {
605
+ }
606
+ else {
577
607
  result += integerToWords(euros)
578
608
  }
579
609
  result += ' ' + EURO
@@ -587,7 +617,8 @@ function toCurrency (value, options) {
587
617
  // Use "ein" instead of "eins" before Cent
588
618
  if (cents === 1n) {
589
619
  result += EIN
590
- } else {
620
+ }
621
+ else {
591
622
  result += integerToWords(cents)
592
623
  }
593
624
  result += ' ' + CENT
package/src/el-GR.d.ts CHANGED
@@ -1,11 +1,12 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
1
4
  /**
2
5
  * Converts a numeric value to Greek words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Greek words
6
8
  * @throws {TypeError} If value is not a valid numeric type
7
9
  * @throws {Error} If value is not a valid number format
8
- *
9
10
  * @example
10
11
  * toCardinal(21) // 'είκοσι ένα'
11
12
  * toCardinal(1000) // 'χίλια'
@@ -14,12 +15,10 @@
14
15
  export function toCardinal(value: number | string | bigint): string;
15
16
  /**
16
17
  * Converts a numeric value to Greek ordinal words.
17
- *
18
18
  * @param {number | string | bigint} value - The numeric value to convert
19
19
  * @returns {string} The ordinal in Greek words
20
20
  * @throws {TypeError} If value is not a valid numeric type
21
21
  * @throws {Error} If value is not a positive integer
22
- *
23
22
  * @example
24
23
  * toOrdinal(1) // 'πρώτος'
25
24
  * toOrdinal(21) // 'εικοστός πρώτος'
@@ -27,12 +26,10 @@ export function toCardinal(value: number | string | bigint): string;
27
26
  export function toOrdinal(value: number | string | bigint): string;
28
27
  /**
29
28
  * Converts a numeric value to Greek Euro currency words.
30
- *
31
29
  * @param {number | string | bigint} value - The numeric value to convert
32
30
  * @returns {string} The currency in Greek words
33
31
  * @throws {TypeError} If value is not a valid numeric type
34
32
  * @throws {Error} If value is not a valid number format
35
- *
36
33
  * @example
37
34
  * toCurrency(1) // 'ένα ευρώ'
38
35
  * toCurrency(2.50) // 'δύο ευρώ πενήντα λεπτά'
package/src/el-GR.js CHANGED
@@ -13,6 +13,8 @@
13
13
  import { parseCardinalValue } from './utils/parse-cardinal.js'
14
14
  import { parseCurrencyValue } from './utils/parse-currency.js'
15
15
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
16
+ import { checkMax } from './utils/check-max.js'
17
+ import { bounded, western } from './utils/scale.js'
16
18
 
17
19
  // ============================================================================
18
20
  // Vocabulary (module-level constants)
@@ -36,6 +38,11 @@ const DECIMAL_SEP = 'κόμμα'
36
38
  // Short scale
37
39
  const SCALES = ['εκατομμύριο', 'δισεκατομμύριο', 'τρισεκατομμύριο']
38
40
 
41
+ // Supported magnitude ceiling (checked at the public entry points), derived from the scale table.
42
+ export const cardinalMax = western(SCALES.length + 1)
43
+ export const ordinalMax = bounded(9)
44
+ export const currencyMax = western(SCALES.length + 1)
45
+
39
46
  // Ordinal vocabulary
40
47
  const ORDINAL_ONES = ['', 'πρώτος', 'δεύτερος', 'τρίτος', 'τέταρτος', 'πέμπτος', 'έκτος', 'έβδομος', 'όγδοος', 'ένατος']
41
48
 
@@ -58,8 +65,10 @@ const CENT_FORMS = ['λεπτό', 'λεπτά'] // Singular, plural
58
65
 
59
66
  /**
60
67
  * Builds segment word for 0-999.
68
+ * @param {number} n - Number 0-999
69
+ * @returns {string} Greek words for the segment
61
70
  */
62
- function buildSegment (n) {
71
+ function buildSegment(n) {
63
72
  if (n === 0) return ''
64
73
 
65
74
  const ones = n % 10
@@ -78,13 +87,17 @@ function buildSegment (n) {
78
87
 
79
88
  if (tensOnes === 0) {
80
89
  // Just hundreds
81
- } else if (tensOnes < 10) {
90
+ }
91
+ else if (tensOnes < 10) {
82
92
  parts.push(ONES[ones])
83
- } else if (tensOnes < 20) {
93
+ }
94
+ else if (tensOnes < 20) {
84
95
  parts.push(TEENS[ones])
85
- } else if (ones === 0) {
96
+ }
97
+ else if (ones === 0) {
86
98
  parts.push(TENS[tens])
87
- } else {
99
+ }
100
+ else {
88
101
  parts.push(TENS[tens] + ' ' + ONES[ones])
89
102
  }
90
103
 
@@ -97,11 +110,10 @@ function buildSegment (n) {
97
110
 
98
111
  /**
99
112
  * Converts a non-negative integer to Greek words.
100
- *
101
113
  * @param {bigint} n - Non-negative integer to convert
102
114
  * @returns {string} Greek words
103
115
  */
104
- function integerToWords (n) {
116
+ function integerToWords(n) {
105
117
  if (n === 0n) return ZERO
106
118
 
107
119
  // Fast path: numbers < 1000 (direct lookup)
@@ -118,7 +130,8 @@ function integerToWords (n) {
118
130
  let result
119
131
  if (thousands === 1) {
120
132
  result = THOUSAND
121
- } else {
133
+ }
134
+ else {
122
135
  result = buildSegment(thousands) + ' ' + THOUSAND
123
136
  }
124
137
 
@@ -135,11 +148,10 @@ function integerToWords (n) {
135
148
 
136
149
  /**
137
150
  * Builds words for numbers >= 1,000,000.
138
- *
139
151
  * @param {bigint} n - Number >= 1,000,000
140
152
  * @returns {string} Greek words
141
153
  */
142
- function buildLargeNumberWords (n) {
154
+ function buildLargeNumberWords(n) {
143
155
  const numStr = n.toString()
144
156
  const len = numStr.length
145
157
 
@@ -171,19 +183,23 @@ function buildLargeNumberWords (n) {
171
183
  if (scaleIndex === 0) {
172
184
  // Units segment
173
185
  parts.push(segmentWord)
174
- } else if (scaleIndex === 1) {
186
+ }
187
+ else if (scaleIndex === 1) {
175
188
  // Thousands - omit "ένα" before χίλια
176
189
  if (segment === 1) {
177
190
  parts.push(THOUSAND)
178
- } else {
191
+ }
192
+ else {
179
193
  parts.push(segmentWord + ' ' + THOUSAND)
180
194
  }
181
- } else {
195
+ }
196
+ else {
182
197
  // Millions+ - omit "ένα" before scale words
183
198
  const scaleWord = SCALES[scaleIndex - 2]
184
199
  if (segment === 1) {
185
200
  parts.push(scaleWord)
186
- } else {
201
+ }
202
+ else {
187
203
  parts.push(segmentWord + ' ' + scaleWord)
188
204
  }
189
205
  }
@@ -197,18 +213,18 @@ function buildLargeNumberWords (n) {
197
213
 
198
214
  /**
199
215
  * Converts decimal digits to Greek words (per-digit).
200
- *
201
216
  * @param {string} decimalPart - Decimal digits (without the point)
202
217
  * @returns {string} Greek words for decimal part
203
218
  */
204
- function decimalPartToWords (decimalPart) {
219
+ function decimalPartToWords(decimalPart) {
205
220
  const parts = []
206
221
 
207
222
  for (const digit of decimalPart) {
208
223
  const d = parseInt(digit, 10)
209
224
  if (d === 0) {
210
225
  parts.push(ZERO)
211
- } else {
226
+ }
227
+ else {
212
228
  parts.push(ONES[d])
213
229
  }
214
230
  }
@@ -218,19 +234,18 @@ function decimalPartToWords (decimalPart) {
218
234
 
219
235
  /**
220
236
  * Converts a numeric value to Greek words.
221
- *
222
237
  * @param {number | string | bigint} value - The numeric value to convert
223
238
  * @returns {string} The number in Greek words
224
239
  * @throws {TypeError} If value is not a valid numeric type
225
240
  * @throws {Error} If value is not a valid number format
226
- *
227
241
  * @example
228
242
  * toCardinal(21) // 'είκοσι ένα'
229
243
  * toCardinal(1000) // 'χίλια'
230
244
  * toCardinal('3.14') // 'τρία κόμμα ένα τέσσερα'
231
245
  */
232
- function toCardinal (value) {
246
+ function toCardinal(value) {
233
247
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
248
+ checkMax(integerPart, cardinalMax)
234
249
 
235
250
  let result = ''
236
251
 
@@ -253,11 +268,10 @@ function toCardinal (value) {
253
268
 
254
269
  /**
255
270
  * Builds ordinal for tens and ones (0-99).
256
- *
257
271
  * @param {number} n - Number 0-99
258
272
  * @returns {string} Ordinal word
259
273
  */
260
- function buildOrdinalTensOnes (n) {
274
+ function buildOrdinalTensOnes(n) {
261
275
  if (n === 0) return ''
262
276
  if (n < 10) return ORDINAL_ONES[n]
263
277
  if (n < 20) return ORDINAL_TEENS[n - 10]
@@ -273,11 +287,10 @@ function buildOrdinalTensOnes (n) {
273
287
 
274
288
  /**
275
289
  * Converts a non-negative integer to Greek ordinal words.
276
- *
277
290
  * @param {bigint} n - Non-negative integer to convert
278
291
  * @returns {string} Greek ordinal words
279
292
  */
280
- function integerToOrdinal (n) {
293
+ function integerToOrdinal(n) {
281
294
  if (n === 0n) return ''
282
295
  if (n === 1n) return ORDINAL_ONES[1]
283
296
 
@@ -313,7 +326,8 @@ function integerToOrdinal (n) {
313
326
  let result
314
327
  if (thousands === 1) {
315
328
  result = THOUSAND
316
- } else {
329
+ }
330
+ else {
317
331
  result = buildSegment(thousands) + ' ' + THOUSAND
318
332
  }
319
333
 
@@ -345,7 +359,8 @@ function integerToOrdinal (n) {
345
359
  let result
346
360
  if (millions === 1) {
347
361
  result = SCALES[0]
348
- } else {
362
+ }
363
+ else {
349
364
  result = buildSegment(millions) + ' ' + SCALES[0]
350
365
  }
351
366
 
@@ -354,18 +369,17 @@ function integerToOrdinal (n) {
354
369
 
355
370
  /**
356
371
  * Converts a numeric value to Greek ordinal words.
357
- *
358
372
  * @param {number | string | bigint} value - The numeric value to convert
359
373
  * @returns {string} The ordinal in Greek words
360
374
  * @throws {TypeError} If value is not a valid numeric type
361
375
  * @throws {Error} If value is not a positive integer
362
- *
363
376
  * @example
364
377
  * toOrdinal(1) // 'πρώτος'
365
378
  * toOrdinal(21) // 'εικοστός πρώτος'
366
379
  */
367
- function toOrdinal (value) {
380
+ function toOrdinal(value) {
368
381
  const n = parseOrdinalValue(value)
382
+ checkMax(n, ordinalMax)
369
383
  return integerToOrdinal(n)
370
384
  }
371
385
 
@@ -375,18 +389,17 @@ function toOrdinal (value) {
375
389
 
376
390
  /**
377
391
  * Converts a numeric value to Greek Euro currency words.
378
- *
379
392
  * @param {number | string | bigint} value - The numeric value to convert
380
393
  * @returns {string} The currency in Greek words
381
394
  * @throws {TypeError} If value is not a valid numeric type
382
395
  * @throws {Error} If value is not a valid number format
383
- *
384
396
  * @example
385
397
  * toCurrency(1) // 'ένα ευρώ'
386
398
  * toCurrency(2.50) // 'δύο ευρώ πενήντα λεπτά'
387
399
  */
388
- function toCurrency (value) {
400
+ function toCurrency(value) {
389
401
  const { isNegative, dollars, cents } = parseCurrencyValue(value)
402
+ checkMax(dollars, currencyMax)
390
403
 
391
404
  const parts = []
392
405