n2words 5.0.0 → 5.1.1

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 +133 -40
  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 +33 -24
  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 +106 -43
  266. package/src/sr-Latn-RS.d.ts +35 -15
  267. package/src/sr-Latn-RS.js +106 -43
  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/ko-KR.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 Korean words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Korean 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(10000) // '만'
@@ -14,12 +15,10 @@
14
15
  export function toCardinal(value: number | string | bigint): string;
15
16
  /**
16
17
  * Converts a numeric value to Korean ordinal words.
17
- *
18
18
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
19
19
  * @returns {string} The number as ordinal words
20
20
  * @throws {TypeError} If value is not a valid numeric type
21
21
  * @throws {RangeError} If value is negative, zero, or has a decimal part
22
- *
23
22
  * @example
24
23
  * toOrdinal(1) // '제일'
25
24
  * toOrdinal(2) // '제이'
@@ -31,12 +30,10 @@ export function toOrdinal(value: number | string | bigint): string;
31
30
  *
32
31
  * Korean Won has no subunit (jeon are historical).
33
32
  * Amounts are rounded to whole won.
34
- *
35
33
  * @param {number | string | bigint} value - The currency amount to convert
36
34
  * @returns {string} The amount in Korean currency words
37
35
  * @throws {TypeError} If value is not a valid numeric type
38
36
  * @throws {Error} If value is not a valid number format
39
- *
40
37
  * @example
41
38
  * toCurrency(42) // '사십이원'
42
39
  * toCurrency(1000) // '천원'
package/src/ko-KR.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 { myriad } from './utils/scale.js'
16
18
 
17
19
  // ============================================================================
18
20
  // Vocabulary (module-level constants)
@@ -32,6 +34,13 @@ const DECIMAL_SEP = '점'
32
34
  // 만 (10^4), 억 (10^8), 조 (10^12), 경 (10^16), etc.
33
35
  const SCALES = ['만', '억', '조', '경', '해', '자', '양']
34
36
 
37
+ // Myriad (4-digit) grouping: each scale word covers a power of 10,000, so the
38
+ // first unsupported value is 10^((SCALES.length + 1) * 4). Ordinals and currency
39
+ // build on the cardinal speller, so they share its ceiling.
40
+ export const cardinalMax = myriad(SCALES.length)
41
+ export const ordinalMax = myriad(SCALES.length)
42
+ export const currencyMax = myriad(SCALES.length)
43
+
35
44
  // ============================================================================
36
45
  // Ordinal Vocabulary
37
46
  // ============================================================================
@@ -51,8 +60,10 @@ const WON = '원'
51
60
  /**
52
61
  * Builds segment word for 0-9999 (4-digit myriad segment).
53
62
  * Korean omits "일" before 십, 백, 천.
63
+ * @param {number} n - Segment value (0-9999)
64
+ * @returns {string} Korean words for the segment
54
65
  */
55
- function buildSegment (n) {
66
+ function buildSegment(n) {
56
67
  if (n === 0) return ''
57
68
 
58
69
  const ones = n % 10
@@ -66,7 +77,8 @@ function buildSegment (n) {
66
77
  if (thousands > 0) {
67
78
  if (thousands === 1) {
68
79
  result += THOUSAND
69
- } else {
80
+ }
81
+ else {
70
82
  result += ONES[thousands] + THOUSAND
71
83
  }
72
84
  }
@@ -75,7 +87,8 @@ function buildSegment (n) {
75
87
  if (hundreds > 0) {
76
88
  if (hundreds === 1) {
77
89
  result += HUNDRED
78
- } else {
90
+ }
91
+ else {
79
92
  result += ONES[hundreds] + HUNDRED
80
93
  }
81
94
  }
@@ -84,7 +97,8 @@ function buildSegment (n) {
84
97
  if (tens > 0) {
85
98
  if (tens === 1) {
86
99
  result += TEN
87
- } else {
100
+ }
101
+ else {
88
102
  result += ONES[tens] + TEN
89
103
  }
90
104
  }
@@ -103,11 +117,10 @@ function buildSegment (n) {
103
117
 
104
118
  /**
105
119
  * Converts a non-negative integer to Korean words.
106
- *
107
120
  * @param {bigint} n - Non-negative integer to convert
108
121
  * @returns {string} Korean words
109
122
  */
110
- function integerToWords (n) {
123
+ function integerToWords(n) {
111
124
  if (n === 0n) return ZERO
112
125
 
113
126
  // Fast path: numbers < 10000
@@ -122,11 +135,10 @@ function integerToWords (n) {
122
135
  /**
123
136
  * Builds words for numbers >= 10000.
124
137
  * Uses myriad (만) grouping - 4 digits per segment.
125
- *
126
138
  * @param {bigint} n - Number >= 10000
127
139
  * @returns {string} Korean words
128
140
  */
129
- function buildLargeNumberWords (n) {
141
+ function buildLargeNumberWords(n) {
130
142
  const numStr = n.toString()
131
143
  const len = numStr.length
132
144
 
@@ -156,14 +168,16 @@ function buildLargeNumberWords (n) {
156
168
  if (scaleIndex === 0) {
157
169
  // Units segment (no scale word)
158
170
  parts.push({ word: buildSegment(segment), isScale: false })
159
- } else {
171
+ }
172
+ else {
160
173
  // Segment with scale word
161
174
  const scaleWord = SCALES[scaleIndex - 1]
162
175
 
163
176
  // Korean omits segment when it's 1 before scale words
164
177
  if (segment === 1) {
165
178
  parts.push({ word: scaleWord, isScale: true })
166
- } else {
179
+ }
180
+ else {
167
181
  parts.push({ word: buildSegment(segment), isScale: false })
168
182
  parts.push({ word: scaleWord, isScale: true })
169
183
  }
@@ -181,11 +195,10 @@ function buildLargeNumberWords (n) {
181
195
  * Joins parts with Korean spacing rules.
182
196
  * - Concatenate without spaces within segments
183
197
  * - Space after scale words before next number
184
- *
185
- * @param {Array} parts - Parts with isScale metadata
198
+ * @param {Array<{word: string, isScale: boolean}>} parts - Parts with isScale metadata
186
199
  * @returns {string} Joined string
187
200
  */
188
- function joinKoreanParts (parts) {
201
+ function joinKoreanParts(parts) {
189
202
  if (parts.length === 0) return ZERO
190
203
  if (parts.length === 1) return parts[0].word
191
204
 
@@ -208,11 +221,10 @@ function joinKoreanParts (parts) {
208
221
 
209
222
  /**
210
223
  * Converts decimal digits to Korean words.
211
- *
212
224
  * @param {string} decimalPart - Decimal digits (without the point)
213
225
  * @returns {string} Korean words for decimal part (space-separated)
214
226
  */
215
- function decimalPartToWords (decimalPart) {
227
+ function decimalPartToWords(decimalPart) {
216
228
  const parts = []
217
229
 
218
230
  // Handle leading zeros
@@ -233,19 +245,18 @@ function decimalPartToWords (decimalPart) {
233
245
 
234
246
  /**
235
247
  * Converts a numeric value to Korean words.
236
- *
237
248
  * @param {number | string | bigint} value - The numeric value to convert
238
249
  * @returns {string} The number in Korean words
239
250
  * @throws {TypeError} If value is not a valid numeric type
240
251
  * @throws {Error} If value is not a valid number format
241
- *
242
252
  * @example
243
253
  * toCardinal(21) // '이십일'
244
254
  * toCardinal(10000) // '만'
245
255
  * toCardinal(1000000) // '백만'
246
256
  */
247
- function toCardinal (value) {
257
+ function toCardinal(value) {
248
258
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
259
+ checkMax(integerPart, cardinalMax, decimalPart)
249
260
 
250
261
  const parts = []
251
262
 
@@ -271,29 +282,27 @@ function toCardinal (value) {
271
282
  * Converts a non-negative integer to Korean ordinal words.
272
283
  *
273
284
  * Korean ordinals use "제" prefix + Sino-Korean numeral.
274
- *
275
285
  * @param {bigint} n - Positive integer to convert
276
286
  * @returns {string} Korean ordinal words
277
287
  */
278
- function integerToOrdinal (n) {
288
+ function integerToOrdinal(n) {
279
289
  return ORDINAL_PREFIX + integerToWords(n)
280
290
  }
281
291
 
282
292
  /**
283
293
  * Converts a numeric value to Korean ordinal words.
284
- *
285
294
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
286
295
  * @returns {string} The number as ordinal words
287
296
  * @throws {TypeError} If value is not a valid numeric type
288
297
  * @throws {RangeError} If value is negative, zero, or has a decimal part
289
- *
290
298
  * @example
291
299
  * toOrdinal(1) // '제일'
292
300
  * toOrdinal(2) // '제이'
293
301
  * toOrdinal(10) // '제십'
294
302
  */
295
- function toOrdinal (value) {
303
+ function toOrdinal(value) {
296
304
  const integerPart = parseOrdinalValue(value)
305
+ checkMax(integerPart, ordinalMax)
297
306
  return integerToOrdinal(integerPart)
298
307
  }
299
308
 
@@ -306,19 +315,18 @@ function toOrdinal (value) {
306
315
  *
307
316
  * Korean Won has no subunit (jeon are historical).
308
317
  * Amounts are rounded to whole won.
309
- *
310
318
  * @param {number | string | bigint} value - The currency amount to convert
311
319
  * @returns {string} The amount in Korean currency words
312
320
  * @throws {TypeError} If value is not a valid numeric type
313
321
  * @throws {Error} If value is not a valid number format
314
- *
315
322
  * @example
316
323
  * toCurrency(42) // '사십이원'
317
324
  * toCurrency(1000) // '천원'
318
325
  * toCurrency(-5) // '마이너스 오원'
319
326
  */
320
- function toCurrency (value) {
327
+ function toCurrency(value) {
321
328
  const { isNegative, dollars: won } = parseCurrencyValue(value)
329
+ checkMax(won, currencyMax)
322
330
 
323
331
  let result = ''
324
332
  if (isNegative) {
package/src/lt-LT.d.ts CHANGED
@@ -1,29 +1,41 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CardinalOptions
6
+ * @property {('masculine'|'feminine')} [gender] - Gender for numbers < 1000
7
+ */
8
+ /** @type {Required<CardinalOptions>} */
9
+ export const cardinalDefaults: Required<CardinalOptions>;
10
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
11
+ export const cardinalValues: {
12
+ gender: ReadonlyArray<Required<CardinalOptions>["gender"]>;
13
+ };
14
+ export type CardinalOptions = {
15
+ /**
16
+ * - Gender for numbers < 1000
17
+ */
18
+ gender?: "feminine" | "masculine" | undefined;
19
+ };
1
20
  /**
2
21
  * Converts a numeric value to Lithuanian words.
3
- *
4
22
  * @param {number | string | bigint} value - The numeric value to convert
5
- * @param {Object} [options] - Conversion options
6
- * @param {string} [options.gender='masculine'] - Gender for numbers < 1000
23
+ * @param {CardinalOptions} [options] - Conversion options
7
24
  * @returns {string} The number in Lithuanian words
8
25
  * @throws {TypeError} If value is not a valid numeric type
9
26
  * @throws {Error} If value is not a valid number format
10
- *
11
27
  * @example
12
28
  * toCardinal(42) // 'keturiasdešimt du'
13
29
  * toCardinal(1, { gender: 'feminine' }) // 'viena'
14
30
  * toCardinal(1000000) // 'vienas milijonas'
15
31
  */
16
- export function toCardinal(value: number | string | bigint, options?: {
17
- gender?: string | undefined;
18
- }): string;
32
+ export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
19
33
  /**
20
34
  * Converts a numeric value to Lithuanian ordinal words (masculine nominative).
21
- *
22
35
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
23
36
  * @returns {string} The number as ordinal words
24
37
  * @throws {TypeError} If value is not a valid numeric type
25
38
  * @throws {RangeError} If value is negative, zero, or has a decimal part
26
- *
27
39
  * @example
28
40
  * toOrdinal(1) // 'pirmas'
29
41
  * toOrdinal(2) // 'antras'
@@ -34,12 +46,10 @@ export function toCardinal(value: number | string | bigint, options?: {
34
46
  export function toOrdinal(value: number | string | bigint): string;
35
47
  /**
36
48
  * Converts a numeric value to Lithuanian currency words (Euro).
37
- *
38
49
  * @param {number | string | bigint} value - The currency amount to convert
39
50
  * @returns {string} The amount in Lithuanian currency words
40
51
  * @throws {TypeError} If value is not a valid numeric type
41
52
  * @throws {Error} If value is not a valid number format
42
- *
43
53
  * @example
44
54
  * toCurrency(42) // 'keturiasdešimt du eurai'
45
55
  * toCurrency(1) // 'vienas euras'
package/src/lt-LT.js CHANGED
@@ -13,7 +13,9 @@
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 { validateOptions } from './utils/validate-options.js'
16
+ import { checkMax } from './utils/check-max.js'
17
+ import { western } from './utils/scale.js'
18
+ import { resolveOptions } from './utils/resolve-options.js'
17
19
 
18
20
  // ============================================================================
19
21
  // Vocabulary (module-level constants)
@@ -71,17 +73,28 @@ const SCALE_FORMS = [
71
73
  ['kvintilijonas', 'kvintilijonai', 'kvintilijonų'],
72
74
  ['sikstilijonas', 'sikstilijonai', 'sikstilijonų'],
73
75
  ['septilijonas', 'septilijonai', 'septilijonų'],
74
- ['oktilijonas', 'oktilijonai', 'oktilijonų']
76
+ ['oktilijonas', 'oktilijonai', 'oktilijonų'],
75
77
  ]
76
78
 
79
+ // Supported magnitude ceilings (checked at the public entry points). Both
80
+ // tables are indexed [scaleIndex - 1] (units separate), so the cardinal/
81
+ // currency ceiling is 10^((SCALE_FORMS.length + 1) * 3) = 10^30, and the
82
+ // shorter ORDINAL_SCALES bounds ordinals at 10^((ORDINAL_SCALES.length + 1) *
83
+ // 3) = 10^15.
84
+ export const cardinalMax = western(SCALE_FORMS.length)
85
+ export const ordinalMax = western(ORDINAL_SCALES.length)
86
+ export const currencyMax = western(SCALE_FORMS.length)
87
+
77
88
  // ============================================================================
78
89
  // Segment Building
79
90
  // ============================================================================
80
91
 
81
92
  /**
82
93
  * Builds segment word for 0-999 (masculine form).
94
+ * @param {number} n - Segment value (0-999)
95
+ * @returns {string} Segment words
83
96
  */
84
- function buildSegment (n) {
97
+ function buildSegment(n) {
85
98
  if (n === 0) return ''
86
99
 
87
100
  const ones = n % 10
@@ -104,7 +117,8 @@ function buildSegment (n) {
104
117
  // Teens or ones
105
118
  if (tens === 1) {
106
119
  parts.push(TEENS[ones])
107
- } else if (ones > 0) {
120
+ }
121
+ else if (ones > 0) {
108
122
  parts.push(ONES_MASC[ones])
109
123
  }
110
124
 
@@ -113,8 +127,10 @@ function buildSegment (n) {
113
127
 
114
128
  /**
115
129
  * Builds segment word for 0-999 (feminine form - only differs in ones).
130
+ * @param {number} n - Segment value (0-999)
131
+ * @returns {string} Segment words
116
132
  */
117
- function buildSegmentFeminine (n) {
133
+ function buildSegmentFeminine(n) {
118
134
  if (n === 0) return ''
119
135
 
120
136
  const ones = n % 10
@@ -137,7 +153,8 @@ function buildSegmentFeminine (n) {
137
153
  // Teens or ones - feminine for ones only
138
154
  if (tens === 1) {
139
155
  parts.push(TEENS[ones])
140
- } else if (ones > 0) {
156
+ }
157
+ else if (ones > 0) {
141
158
  parts.push(ONES_FEM[ones])
142
159
  }
143
160
 
@@ -153,12 +170,11 @@ function buildSegmentFeminine (n) {
153
170
  * - Singular: ends in 1 (except 11)
154
171
  * - Plural: ends in 2-9 (except 12-19)
155
172
  * - Genitive: 0, 10-19, or ends in 0
156
- *
157
173
  * @param {number} n - The segment value
158
174
  * @param {string[]} forms - [singular, plural, genitive]
159
175
  * @returns {string} The appropriate form
160
176
  */
161
- function pluralize (n, forms) {
177
+ function pluralize(n, forms) {
162
178
  if (n === 0) return forms[2]
163
179
 
164
180
  const lastDigit = n % 10
@@ -189,12 +205,11 @@ function pluralize (n, forms) {
189
205
 
190
206
  /**
191
207
  * Converts a non-negative integer to Lithuanian words.
192
- *
193
208
  * @param {bigint} n - Non-negative integer to convert
194
- * @param {Object} options - Conversion options
209
+ * @param {string} gender - Gender for numbers < 1000 ('masculine' or 'feminine')
195
210
  * @returns {string} Lithuanian words
196
211
  */
197
- function integerToWords (n, gender) {
212
+ function integerToWords(n, gender) {
198
213
  if (n === 0n) return ZERO
199
214
 
200
215
  // Fast path: numbers < 1000
@@ -206,17 +221,15 @@ function integerToWords (n, gender) {
206
221
  // For numbers >= 1000, feminine only applies to final segment if < 1000
207
222
  // But the fixture shows feminine NOT applying for n >= 1000
208
223
  // So we use masculine for all segments when n >= 1000
209
- return buildLargeNumberWords(n, gender)
224
+ return buildLargeNumberWords(n)
210
225
  }
211
226
 
212
227
  /**
213
228
  * Builds words for numbers >= 1000.
214
- *
215
229
  * @param {bigint} n - Number >= 1000
216
- * @param {Object} options - Conversion options
217
230
  * @returns {string} Lithuanian words
218
231
  */
219
- function buildLargeNumberWords (n, gender) {
232
+ function buildLargeNumberWords(n) {
220
233
  const numStr = n.toString()
221
234
  const len = numStr.length
222
235
 
@@ -248,7 +261,8 @@ function buildLargeNumberWords (n, gender) {
248
261
  if (scaleIndex === 0) {
249
262
  // Units segment - use masculine (feminine doesn't apply when n >= 1000)
250
263
  parts.push(segmentWord)
251
- } else {
264
+ }
265
+ else {
252
266
  // Segment with scale word
253
267
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
254
268
  const scaleWord = pluralize(segment, scaleForms)
@@ -264,12 +278,11 @@ function buildLargeNumberWords (n, gender) {
264
278
 
265
279
  /**
266
280
  * Converts decimal digits to Lithuanian words.
267
- *
268
281
  * @param {string} decimalPart - Decimal digits (without the point)
269
- * @param {Object} options - Conversion options
282
+ * @param {string} gender - Gender for numbers < 1000 ('masculine' or 'feminine')
270
283
  * @returns {string} Lithuanian words for decimal part
271
284
  */
272
- function decimalPartToWords (decimalPart, gender) {
285
+ function decimalPartToWords(decimalPart, gender) {
273
286
  let result = ''
274
287
 
275
288
  // Handle leading zeros
@@ -290,27 +303,37 @@ function decimalPartToWords (decimalPart, gender) {
290
303
  return result
291
304
  }
292
305
 
306
+ /**
307
+ * @typedef {object} CardinalOptions
308
+ * @property {('masculine'|'feminine')} [gender] - Gender for numbers < 1000
309
+ */
310
+
311
+ /** @type {Required<CardinalOptions>} */
312
+ export const cardinalDefaults = { gender: 'masculine' }
313
+
314
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
315
+ export const cardinalValues = { gender: ['masculine', 'feminine'] }
316
+
293
317
  /**
294
318
  * Converts a numeric value to Lithuanian words.
295
- *
296
319
  * @param {number | string | bigint} value - The numeric value to convert
297
- * @param {Object} [options] - Conversion options
298
- * @param {string} [options.gender='masculine'] - Gender for numbers < 1000
320
+ * @param {CardinalOptions} [options] - Conversion options
299
321
  * @returns {string} The number in Lithuanian words
300
322
  * @throws {TypeError} If value is not a valid numeric type
301
323
  * @throws {Error} If value is not a valid number format
302
- *
303
324
  * @example
304
325
  * toCardinal(42) // 'keturiasdešimt du'
305
326
  * toCardinal(1, { gender: 'feminine' }) // 'viena'
306
327
  * toCardinal(1000000) // 'vienas milijonas'
307
328
  */
308
- function toCardinal (value, options) {
309
- options = validateOptions(options)
329
+ function toCardinal(value, options) {
310
330
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
331
+ // Both the integer part and the decimal's significant digits are spelled via
332
+ // the scale builder, so both must clear the ceiling.
333
+ checkMax(integerPart, cardinalMax, decimalPart)
311
334
 
312
335
  // Apply option defaults
313
- const { gender = 'masculine' } = options
336
+ const { gender } = resolveOptions(options, cardinalDefaults, cardinalValues)
314
337
 
315
338
  let result = ''
316
339
 
@@ -333,11 +356,10 @@ function toCardinal (value, options) {
333
356
 
334
357
  /**
335
358
  * Builds ordinal for a 0-99 segment when it's the final (ordinal) part.
336
- *
337
359
  * @param {number} n - Number 0-99
338
360
  * @returns {string} Ordinal words
339
361
  */
340
- function buildOrdinalTensOnes (n) {
362
+ function buildOrdinalTensOnes(n) {
341
363
  if (n === 0) return ''
342
364
 
343
365
  const onesDigit = n % 10
@@ -360,11 +382,10 @@ function buildOrdinalTensOnes (n) {
360
382
 
361
383
  /**
362
384
  * Converts a positive integer to Lithuanian ordinal words (masculine nominative).
363
- *
364
385
  * @param {bigint} n - Positive integer to convert
365
386
  * @returns {string} Ordinal Lithuanian words
366
387
  */
367
- function integerToOrdinal (n) {
388
+ function integerToOrdinal(n) {
368
389
  if (n < 100n) {
369
390
  return buildOrdinalTensOnes(Number(n))
370
391
  }
@@ -406,11 +427,10 @@ function integerToOrdinal (n) {
406
427
 
407
428
  /**
408
429
  * Builds ordinal words for numbers >= 1,000,000.
409
- *
410
430
  * @param {bigint} n - Number >= 1,000,000
411
431
  * @returns {string} Ordinal Lithuanian words
412
432
  */
413
- function buildLargeOrdinal (n) {
433
+ function buildLargeOrdinal(n) {
414
434
  const numStr = n.toString()
415
435
  const len = numStr.length
416
436
 
@@ -445,20 +465,24 @@ function buildLargeOrdinal (n) {
445
465
  if (scaleIndex === 0) {
446
466
  if (isLastNonZero) {
447
467
  parts.push(integerToOrdinal(BigInt(segment)))
448
- } else {
468
+ }
469
+ else {
449
470
  parts.push(buildSegment(segment))
450
471
  }
451
- } else {
472
+ }
473
+ else {
452
474
  if (isLastNonZero) {
453
475
  if (segment === 1) {
454
476
  parts.push(ORDINAL_SCALES[scaleIndex - 1])
455
- } else {
477
+ }
478
+ else {
456
479
  // For 2+, include cardinal scale word before ordinal
457
480
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
458
481
  const cardinalScaleWord = pluralize(segment, scaleForms)
459
482
  parts.push(buildSegment(segment) + ' ' + cardinalScaleWord + ' ' + ORDINAL_SCALES[scaleIndex - 1])
460
483
  }
461
- } else {
484
+ }
485
+ else {
462
486
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
463
487
  const scaleWord = pluralize(segment, scaleForms)
464
488
  parts.push(buildSegment(segment) + ' ' + scaleWord)
@@ -474,12 +498,10 @@ function buildLargeOrdinal (n) {
474
498
 
475
499
  /**
476
500
  * Converts a numeric value to Lithuanian ordinal words (masculine nominative).
477
- *
478
501
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
479
502
  * @returns {string} The number as ordinal words
480
503
  * @throws {TypeError} If value is not a valid numeric type
481
504
  * @throws {RangeError} If value is negative, zero, or has a decimal part
482
- *
483
505
  * @example
484
506
  * toOrdinal(1) // 'pirmas'
485
507
  * toOrdinal(2) // 'antras'
@@ -487,8 +509,9 @@ function buildLargeOrdinal (n) {
487
509
  * toOrdinal(100) // 'šimtasis'
488
510
  * toOrdinal(1000) // 'tūkstantasis'
489
511
  */
490
- function toOrdinal (value) {
512
+ function toOrdinal(value) {
491
513
  const integerPart = parseOrdinalValue(value)
514
+ checkMax(integerPart, ordinalMax)
492
515
  return integerToOrdinal(integerPart)
493
516
  }
494
517
 
@@ -498,20 +521,19 @@ function toOrdinal (value) {
498
521
 
499
522
  /**
500
523
  * Converts a numeric value to Lithuanian currency words (Euro).
501
- *
502
524
  * @param {number | string | bigint} value - The currency amount to convert
503
525
  * @returns {string} The amount in Lithuanian currency words
504
526
  * @throws {TypeError} If value is not a valid numeric type
505
527
  * @throws {Error} If value is not a valid number format
506
- *
507
528
  * @example
508
529
  * toCurrency(42) // 'keturiasdešimt du eurai'
509
530
  * toCurrency(1) // 'vienas euras'
510
531
  * toCurrency(1.50) // 'vienas euras penkiasdešimt centų'
511
532
  * toCurrency(-5) // 'minus penki eurai'
512
533
  */
513
- function toCurrency (value) {
534
+ function toCurrency(value) {
514
535
  const { isNegative, dollars: euros, cents } = parseCurrencyValue(value)
536
+ checkMax(euros, currencyMax)
515
537
 
516
538
  let result = ''
517
539
  if (isNegative) {
package/src/lv-LV.d.ts CHANGED
@@ -1,29 +1,41 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
4
+ /**
5
+ * @typedef {object} CardinalOptions
6
+ * @property {('masculine'|'feminine')} [gender] - Gender for numbers < 1000
7
+ */
8
+ /** @type {Required<CardinalOptions>} */
9
+ export const cardinalDefaults: Required<CardinalOptions>;
10
+ /** @type {{ gender: ReadonlyArray<Required<CardinalOptions>['gender']> }} */
11
+ export const cardinalValues: {
12
+ gender: ReadonlyArray<Required<CardinalOptions>["gender"]>;
13
+ };
14
+ export type CardinalOptions = {
15
+ /**
16
+ * - Gender for numbers < 1000
17
+ */
18
+ gender?: "feminine" | "masculine" | undefined;
19
+ };
1
20
  /**
2
21
  * Converts a numeric value to Latvian words.
3
- *
4
22
  * @param {number | string | bigint} value - The numeric value to convert
5
- * @param {Object} [options] - Conversion options
6
- * @param {string} [options.gender='masculine'] - Gender for numbers < 1000
23
+ * @param {CardinalOptions} [options] - Conversion options
7
24
  * @returns {string} The number in Latvian words
8
25
  * @throws {TypeError} If value is not a valid numeric type
9
26
  * @throws {Error} If value is not a valid number format
10
- *
11
27
  * @example
12
28
  * toCardinal(42) // 'četrdesmit divi'
13
29
  * toCardinal(1, { gender: 'feminine' }) // 'viena'
14
30
  * toCardinal(1000) // 'tūkstotis'
15
31
  */
16
- export function toCardinal(value: number | string | bigint, options?: {
17
- gender?: string | undefined;
18
- }): string;
32
+ export function toCardinal(value: number | string | bigint, options?: CardinalOptions): string;
19
33
  /**
20
34
  * Converts a numeric value to Latvian ordinal words (masculine nominative).
21
- *
22
35
  * @param {number | string | bigint} value - The numeric value to convert (must be a positive integer)
23
36
  * @returns {string} The number as ordinal words
24
37
  * @throws {TypeError} If value is not a valid numeric type
25
38
  * @throws {RangeError} If value is negative, zero, or has a decimal part
26
- *
27
39
  * @example
28
40
  * toOrdinal(1) // 'pirmais'
29
41
  * toOrdinal(2) // 'otrais'
@@ -34,12 +46,10 @@ export function toCardinal(value: number | string | bigint, options?: {
34
46
  export function toOrdinal(value: number | string | bigint): string;
35
47
  /**
36
48
  * Converts a numeric value to Latvian currency words (Euro).
37
- *
38
49
  * @param {number | string | bigint} value - The currency amount to convert
39
50
  * @returns {string} The amount in Latvian currency words
40
51
  * @throws {TypeError} If value is not a valid numeric type
41
52
  * @throws {Error} If value is not a valid number format
42
- *
43
53
  * @example
44
54
  * toCurrency(42) // 'četrdesmit divi eiro'
45
55
  * toCurrency(1) // 'viens eiro'