n2words 4.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 +53 -0
  2. package/README.md +14 -12
  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 -0
  112. package/dist/pt-BR.umd.js +2 -0
  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 +53 -36
  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 +42 -0
  257. package/src/pt-BR.js +574 -0
  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/ja-JP.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)
@@ -40,9 +42,16 @@ const SCALES = [
40
42
  '阿僧祇', // 10^56 (asōgi)
41
43
  '那由他', // 10^60 (nayuta)
42
44
  '不可思議', // 10^64 (fukashigi)
43
- '無量大数' // 10^68 (muryōtaisū)
45
+ '無量大数', // 10^68 (muryōtaisū)
44
46
  ]
45
47
 
48
+ // Myriad (4-digit) grouping: each scale word covers a power of 10,000, so the
49
+ // first unsupported value is 10^((SCALES.length + 1) * 4). Ordinals (prefix) and
50
+ // currency build on the cardinal speller, so they share its ceiling.
51
+ export const cardinalMax = myriad(SCALES.length)
52
+ export const ordinalMax = myriad(SCALES.length)
53
+ export const currencyMax = myriad(SCALES.length)
54
+
46
55
  const ZERO = '零'
47
56
  const NEGATIVE = 'マイナス'
48
57
  const DECIMAL_SEP = '点'
@@ -76,8 +85,10 @@ const THOUSAND = '千'
76
85
  /**
77
86
  * Builds segment word for 0-9999 with 一 omission rules.
78
87
  * - Omit 一 before 十, 百, 千
88
+ * @param {number} n - Segment value (0-9999)
89
+ * @returns {string} Japanese kanji words for the segment
79
90
  */
80
- function buildSegment (n) {
91
+ function buildSegment(n) {
81
92
  if (n === 0) return ''
82
93
 
83
94
  const ones = n % 10
@@ -91,7 +102,8 @@ function buildSegment (n) {
91
102
  if (thousands > 0) {
92
103
  if (thousands === 1) {
93
104
  result += THOUSAND
94
- } else {
105
+ }
106
+ else {
95
107
  result += ONES[thousands] + THOUSAND
96
108
  }
97
109
  }
@@ -100,7 +112,8 @@ function buildSegment (n) {
100
112
  if (hundreds > 0) {
101
113
  if (hundreds === 1) {
102
114
  result += HUNDRED
103
- } else {
115
+ }
116
+ else {
104
117
  result += ONES[hundreds] + HUNDRED
105
118
  }
106
119
  }
@@ -109,7 +122,8 @@ function buildSegment (n) {
109
122
  if (tens > 0) {
110
123
  if (tens === 1) {
111
124
  result += TEN
112
- } else {
125
+ }
126
+ else {
113
127
  result += ONES[tens] + TEN
114
128
  }
115
129
  }
@@ -128,11 +142,10 @@ function buildSegment (n) {
128
142
 
129
143
  /**
130
144
  * Converts a non-negative integer to Japanese words.
131
- *
132
145
  * @param {bigint} n - Non-negative integer to convert
133
146
  * @returns {string} Japanese kanji words
134
147
  */
135
- function integerToWords (n) {
148
+ function integerToWords(n) {
136
149
  if (n === 0n) return ZERO
137
150
 
138
151
  // Fast path: numbers < 10000
@@ -149,7 +162,8 @@ function integerToWords (n) {
149
162
  let result
150
163
  if (man === 1) {
151
164
  result = '一' + SCALES[0] // 一万
152
- } else {
165
+ }
166
+ else {
153
167
  result = buildSegment(man) + SCALES[0]
154
168
  }
155
169
 
@@ -167,11 +181,10 @@ function integerToWords (n) {
167
181
  /**
168
182
  * Builds words for numbers >= 100,000,000.
169
183
  * Uses BigInt modulo for 4-digit (myriad) segment extraction.
170
- *
171
184
  * @param {bigint} n - Number >= 100,000,000
172
185
  * @returns {string} Japanese kanji words
173
186
  */
174
- function buildLargeNumberWords (n) {
187
+ function buildLargeNumberWords(n) {
175
188
  // Extract segments using BigInt modulo (faster than string slicing)
176
189
  // Segments stored least-significant first (index 0 = units, 1 = 万, etc.)
177
190
  const segments = []
@@ -192,10 +205,12 @@ function buildLargeNumberWords (n) {
192
205
  // For scales >= 万, we need 一 before scale word when segment is 1
193
206
  if (segment === 1) {
194
207
  result += '一' + SCALES[i - 1]
195
- } else {
208
+ }
209
+ else {
196
210
  result += buildSegment(segment) + SCALES[i - 1]
197
211
  }
198
- } else {
212
+ }
213
+ else {
199
214
  // Units segment (no scale word)
200
215
  result += buildSegment(segment)
201
216
  }
@@ -206,18 +221,18 @@ function buildLargeNumberWords (n) {
206
221
 
207
222
  /**
208
223
  * Converts decimal digits to Japanese words (digit by digit).
209
- *
210
224
  * @param {string} decimalPart - Decimal digits (without the point)
211
225
  * @returns {string} Japanese kanji words for decimal part
212
226
  */
213
- function decimalPartToWords (decimalPart) {
227
+ function decimalPartToWords(decimalPart) {
214
228
  let result = ''
215
229
 
216
230
  for (let i = 0; i < decimalPart.length; i++) {
217
231
  const digit = parseInt(decimalPart[i], 10)
218
232
  if (digit === 0) {
219
233
  result += ZERO
220
- } else {
234
+ }
235
+ else {
221
236
  result += ONES[digit]
222
237
  }
223
238
  }
@@ -230,19 +245,19 @@ function decimalPartToWords (decimalPart) {
230
245
  *
231
246
  * This is the main public API. It accepts any valid numeric input
232
247
  * (number, string, or bigint) and handles parsing internally.
233
- *
234
248
  * @param {number | string | bigint} value - The numeric value to convert
235
249
  * @returns {string} The number in Japanese kanji words
236
250
  * @throws {TypeError} If value is not a valid numeric type
237
251
  * @throws {Error} If value is not a valid number format
238
- *
239
252
  * @example
240
253
  * toCardinal(42) // '四十二'
241
254
  * toCardinal(10000) // '一万'
242
255
  * toCardinal(100000000) // '一億'
243
256
  */
244
- function toCardinal (value) {
257
+ function toCardinal(value) {
245
258
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
259
+ // The fraction is spelled digit by digit, so only the integer part has a ceiling.
260
+ checkMax(integerPart, cardinalMax)
246
261
 
247
262
  let result = ''
248
263
 
@@ -267,29 +282,28 @@ function toCardinal (value) {
267
282
  * Converts a positive integer to Japanese ordinal words.
268
283
  *
269
284
  * Japanese ordinals: 第 prefix + cardinal number.
270
- *
271
285
  * @param {bigint} n - Positive integer to convert
272
286
  * @returns {string} Japanese ordinal words
273
287
  */
274
- function integerToOrdinal (n) {
288
+ function integerToOrdinal(n) {
275
289
  return ORDINAL_PREFIX + integerToWords(n)
276
290
  }
277
291
 
278
292
  /**
279
293
  * Converts a numeric value to Japanese ordinal words.
280
- *
281
294
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
282
295
  * @returns {string} The number as ordinal words
283
296
  * @throws {TypeError} If value is not a valid numeric type
284
297
  * @throws {RangeError} If value is negative, zero, or has a decimal part
285
- *
286
298
  * @example
287
299
  * toOrdinal(1) // '第一'
288
300
  * toOrdinal(10) // '第十'
289
301
  * toOrdinal(100) // '第百'
290
302
  */
291
- function toOrdinal (value) {
303
+ function toOrdinal(value) {
292
304
  const integerPart = parseOrdinalValue(value)
305
+ // Ordinals prefix the cardinal speller, so they share its ceiling.
306
+ checkMax(integerPart, ordinalMax)
293
307
  return integerToOrdinal(integerPart)
294
308
  }
295
309
 
@@ -302,20 +316,19 @@ function toOrdinal (value) {
302
316
  *
303
317
  * Note: Sen (銭, 1/100 yen) is included for completeness but is rarely used
304
318
  * in modern Japan. Most transactions are in whole yen.
305
- *
306
319
  * @param {number | string | bigint} value - The currency amount to convert
307
320
  * @returns {string} The amount in Japanese currency words
308
321
  * @throws {TypeError} If value is not a valid numeric type
309
322
  * @throws {Error} If value is not a valid number format
310
- *
311
323
  * @example
312
324
  * toCurrency(42) // '四十二円'
313
325
  * toCurrency(1) // '一円'
314
326
  * toCurrency(0.50) // '五十銭'
315
327
  * toCurrency(42.50) // '四十二円五十銭'
316
328
  */
317
- function toCurrency (value) {
329
+ function toCurrency(value) {
318
330
  const { isNegative, dollars: yen, cents: sen } = parseCurrencyValue(value)
331
+ checkMax(yen, currencyMax)
319
332
 
320
333
  // Build result
321
334
  let result = ''
package/src/ka-GE.d.ts CHANGED
@@ -1,14 +1,15 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
1
4
  /**
2
5
  * Converts a numeric value to Georgian words.
3
6
  *
4
7
  * This is the main public API. It accepts any valid numeric input
5
8
  * (number, string, or bigint) and handles parsing internally.
6
- *
7
9
  * @param {number | string | bigint} value - The numeric value to convert
8
10
  * @returns {string} The number in Georgian words
9
11
  * @throws {TypeError} If value is not a valid numeric type
10
12
  * @throws {Error} If value is not a valid number format
11
- *
12
13
  * @example
13
14
  * toCardinal(21) // 'ოცდაერთი'
14
15
  * toCardinal(100) // 'ასი'
@@ -17,12 +18,10 @@
17
18
  export function toCardinal(value: number | string | bigint): string;
18
19
  /**
19
20
  * Converts a numeric value to Georgian ordinal words.
20
- *
21
21
  * @param {number | string | bigint} value - The numeric value to convert
22
22
  * @returns {string} The ordinal in Georgian words
23
23
  * @throws {TypeError} If value is not a valid numeric type
24
24
  * @throws {Error} If value is not a positive integer
25
- *
26
25
  * @example
27
26
  * toOrdinal(1) // 'პირველი'
28
27
  * toOrdinal(10) // 'მეათე'
@@ -31,12 +30,10 @@ export function toCardinal(value: number | string | bigint): string;
31
30
  export function toOrdinal(value: number | string | bigint): string;
32
31
  /**
33
32
  * Converts a numeric value to Georgian Lari currency words.
34
- *
35
33
  * @param {number | string | bigint} value - The numeric value to convert
36
34
  * @returns {string} The currency in Georgian 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(1) // 'ერთი ლარი'
42
39
  * toCurrency(2.50) // 'ორი ლარი ორმოცდაათი თეთრი'
package/src/ka-GE.js CHANGED
@@ -15,6 +15,8 @@
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 { checkMax } from './utils/check-max.js'
19
+ import { western } from './utils/scale.js'
18
20
 
19
21
  // ============================================================================
20
22
  // Vocabulary (module-level constants)
@@ -41,6 +43,13 @@ const THOUSAND_STEM = 'ათას' // Without final vowel
41
43
  // Scale words (short scale) - indexed by segment position
42
44
  const SCALES = ['', '', 'მილიონი', 'მილიარდი', 'ტრილიონი', 'კვადრილიონი', 'კვინტილიონი', 'სექსტილიონი']
43
45
 
46
+ // 3-digit grouping; the table tops out at sextillion. Past it the builder clamps
47
+ // to the last scale word (`SCALES[scaleIndex] || SCALES[SCALES.length - 1]`),
48
+ // silently collapsing the magnitude — so cap there.
49
+ export const cardinalMax = western(SCALES.length - 1)
50
+ export const ordinalMax = western(SCALES.length - 1)
51
+ export const currencyMax = western(SCALES.length - 1)
52
+
44
53
  const ZERO = 'ნული'
45
54
  const NEGATIVE = 'მინუს'
46
55
  const DECIMAL_SEP = 'მთელი'
@@ -64,7 +73,7 @@ const TETRI = 'თეთრი'
64
73
  * @param {number} n - Number 0-99
65
74
  * @returns {string} Georgian word
66
75
  */
67
- function buildTens (n) {
76
+ function buildTens(n) {
68
77
  if (n < 10) return ONES[n]
69
78
  if (n < 20) return TEENS[n - 10]
70
79
 
@@ -89,7 +98,7 @@ function buildTens (n) {
89
98
  * @param {number} n - Number 0-999
90
99
  * @returns {{full: string, stem: string}} Georgian words
91
100
  */
92
- function buildSegment (n) {
101
+ function buildSegment(n) {
93
102
  if (n === 0) return { full: '', stem: '' }
94
103
  if (n < 100) {
95
104
  const word = buildTens(n)
@@ -106,7 +115,8 @@ function buildSegment (n) {
106
115
  let hundredWord
107
116
  if (hundreds === 1) {
108
117
  hundredWord = remainder > 0 ? HUNDRED_STEM : HUNDRED
109
- } else {
118
+ }
119
+ else {
110
120
  hundredWord = HUNDRED_PREFIXES[hundreds] + (remainder > 0 ? HUNDRED_STEM : HUNDRED)
111
121
  }
112
122
 
@@ -129,11 +139,10 @@ function buildSegment (n) {
129
139
 
130
140
  /**
131
141
  * Converts a non-negative integer to Georgian words.
132
- *
133
142
  * @param {bigint} n - Non-negative integer to convert
134
143
  * @returns {string} Georgian words
135
144
  */
136
- function integerToWords (n) {
145
+ function integerToWords(n) {
137
146
  if (n === 0n) return ZERO
138
147
 
139
148
  // Fast path: numbers < 1000
@@ -151,7 +160,8 @@ function integerToWords (n) {
151
160
  if (thousands === 1) {
152
161
  // "ათასი" not "ერთი ათასი"
153
162
  result = remainder > 0 ? THOUSAND_STEM : THOUSAND
154
- } else {
163
+ }
164
+ else {
155
165
  // Use stem form before ათასი
156
166
  const { stem: thousandsPart } = buildSegment(thousands)
157
167
  result = thousandsPart + ' ' + (remainder > 0 ? THOUSAND_STEM : THOUSAND)
@@ -171,11 +181,10 @@ function integerToWords (n) {
171
181
 
172
182
  /**
173
183
  * Builds words for numbers >= 1,000,000.
174
- *
175
184
  * @param {bigint} n - Number >= 1,000,000
176
185
  * @returns {string} Georgian words
177
186
  */
178
- function buildLargeNumberWords (n) {
187
+ function buildLargeNumberWords(n) {
179
188
  const numStr = n.toString()
180
189
  const len = numStr.length
181
190
 
@@ -206,23 +215,27 @@ function buildLargeNumberWords (n) {
206
215
  // Units (no scale)
207
216
  const { full } = buildSegment(segment)
208
217
  parts.push(full)
209
- } else if (scaleIndex === 1) {
218
+ }
219
+ else if (scaleIndex === 1) {
210
220
  // Thousands - check if there's a remainder
211
221
  const hasRemainder = segments.slice(i + 1).some(s => s !== 0)
212
222
  const thousandWord = hasRemainder ? THOUSAND_STEM : THOUSAND
213
223
 
214
224
  if (segment === 1) {
215
225
  parts.push(thousandWord)
216
- } else {
226
+ }
227
+ else {
217
228
  const { stem } = buildSegment(segment)
218
229
  parts.push(stem + ' ' + thousandWord)
219
230
  }
220
- } else {
231
+ }
232
+ else {
221
233
  // Million and above
222
234
  const scaleWord = SCALES[scaleIndex] || SCALES[SCALES.length - 1]
223
235
  if (segment === 1) {
224
236
  parts.push('ერთი ' + scaleWord)
225
- } else {
237
+ }
238
+ else {
226
239
  const { full } = buildSegment(segment)
227
240
  parts.push(full + ' ' + scaleWord)
228
241
  }
@@ -237,11 +250,10 @@ function buildLargeNumberWords (n) {
237
250
 
238
251
  /**
239
252
  * Converts decimal digits to Georgian words.
240
- *
241
253
  * @param {string} decimalPart - Decimal digits (without the point)
242
254
  * @returns {string} Georgian words for decimal part
243
255
  */
244
- function decimalPartToWords (decimalPart) {
256
+ function decimalPartToWords(decimalPart) {
245
257
  let result = ''
246
258
 
247
259
  // Handle leading zeros
@@ -267,19 +279,20 @@ function decimalPartToWords (decimalPart) {
267
279
  *
268
280
  * This is the main public API. It accepts any valid numeric input
269
281
  * (number, string, or bigint) and handles parsing internally.
270
- *
271
282
  * @param {number | string | bigint} value - The numeric value to convert
272
283
  * @returns {string} The number in Georgian words
273
284
  * @throws {TypeError} If value is not a valid numeric type
274
285
  * @throws {Error} If value is not a valid number format
275
- *
276
286
  * @example
277
287
  * toCardinal(21) // 'ოცდაერთი'
278
288
  * toCardinal(100) // 'ასი'
279
289
  * toCardinal(1000) // 'ათასი'
280
290
  */
281
- function toCardinal (value) {
291
+ function toCardinal(value) {
282
292
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
293
+ // Both the integer part and the decimal's significant digits are spelled via
294
+ // the scale builder, so both must clear the ceiling.
295
+ checkMax(integerPart, cardinalMax, decimalPart)
283
296
 
284
297
  let result = ''
285
298
 
@@ -305,11 +318,10 @@ function toCardinal (value) {
305
318
  * Georgian ordinals are formed by:
306
319
  * - 1-9: special forms (პირველი, მეორე, მესამე, etc.)
307
320
  * - 10+: მე- prefix + cardinal + -ე suffix
308
- *
309
321
  * @param {bigint} n - Non-negative integer to convert
310
322
  * @returns {string} Georgian ordinal words
311
323
  */
312
- function integerToOrdinal (n) {
324
+ function integerToOrdinal(n) {
313
325
  if (n === 0n) return ''
314
326
  if (n <= 9n) return ORDINAL_ONES[Number(n)]
315
327
 
@@ -321,7 +333,8 @@ function integerToOrdinal (n) {
321
333
  let stem
322
334
  if (lastChar === 'ი' || lastChar === 'ა') {
323
335
  stem = cardinal.slice(0, -1)
324
- } else {
336
+ }
337
+ else {
325
338
  stem = cardinal
326
339
  }
327
340
 
@@ -330,19 +343,19 @@ function integerToOrdinal (n) {
330
343
 
331
344
  /**
332
345
  * Converts a numeric value to Georgian ordinal words.
333
- *
334
346
  * @param {number | string | bigint} value - The numeric value to convert
335
347
  * @returns {string} The ordinal in Georgian words
336
348
  * @throws {TypeError} If value is not a valid numeric type
337
349
  * @throws {Error} If value is not a positive integer
338
- *
339
350
  * @example
340
351
  * toOrdinal(1) // 'პირველი'
341
352
  * toOrdinal(10) // 'მეათე'
342
353
  * toOrdinal(21) // 'მეოცდაერთე'
343
354
  */
344
- function toOrdinal (value) {
355
+ function toOrdinal(value) {
345
356
  const n = parseOrdinalValue(value)
357
+ // Ordinals build on the cardinal speller, so they share its ceiling.
358
+ checkMax(n, ordinalMax)
346
359
  return integerToOrdinal(n)
347
360
  }
348
361
 
@@ -352,18 +365,17 @@ function toOrdinal (value) {
352
365
 
353
366
  /**
354
367
  * Converts a numeric value to Georgian Lari currency words.
355
- *
356
368
  * @param {number | string | bigint} value - The numeric value to convert
357
369
  * @returns {string} The currency in Georgian words
358
370
  * @throws {TypeError} If value is not a valid numeric type
359
371
  * @throws {Error} If value is not a valid number format
360
- *
361
372
  * @example
362
373
  * toCurrency(1) // 'ერთი ლარი'
363
374
  * toCurrency(2.50) // 'ორი ლარი ორმოცდაათი თეთრი'
364
375
  */
365
- function toCurrency (value) {
376
+ function toCurrency(value) {
366
377
  const { isNegative, dollars, cents } = parseCurrencyValue(value)
378
+ checkMax(dollars, currencyMax)
367
379
 
368
380
  const parts = []
369
381
 
package/src/kn-IN.d.ts CHANGED
@@ -1,18 +1,18 @@
1
+ export const cardinalMax: bigint;
2
+ export const ordinalMax: bigint;
3
+ export const currencyMax: bigint;
1
4
  /**
2
5
  * Converts a numeric value to Kannada words.
3
- *
4
6
  * @param {number | string | bigint} value - The numeric value to convert
5
7
  * @returns {string} The number in Kannada words
6
8
  */
7
9
  export function toCardinal(value: number | string | bigint): string;
8
10
  /**
9
11
  * Converts a numeric value to Kannada ordinal words.
10
- *
11
12
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
12
13
  * @returns {string} The number as ordinal words
13
14
  * @throws {TypeError} If value is not a valid numeric type
14
15
  * @throws {RangeError} If value is negative, zero, or has a decimal part
15
- *
16
16
  * @example
17
17
  * toOrdinal(1) // 'ಮೊದಲನೇ'
18
18
  * toOrdinal(2) // 'ಎರಡನೇ'
@@ -21,12 +21,10 @@ export function toCardinal(value: number | string | bigint): string;
21
21
  export function toOrdinal(value: number | string | bigint): string;
22
22
  /**
23
23
  * Converts a numeric value to Kannada currency words (Indian Rupee).
24
- *
25
24
  * @param {number | string | bigint} value - The currency amount to convert
26
25
  * @returns {string} The amount in Kannada currency words
27
26
  * @throws {TypeError} If value is not a valid numeric type
28
27
  * @throws {Error} If value is not a valid number format
29
- *
30
28
  * @example
31
29
  * toCurrency(42.50) // 'ನಲವತ್ತೆರಡು ರೂಪಾಯಿಗಳು ಐವತ್ತು ಪೈಸೆಗಳು'
32
30
  * toCurrency(1) // 'ಒಂದು ರೂಪಾಯಿ'
package/src/kn-IN.js CHANGED
@@ -14,6 +14,8 @@
14
14
  import { parseCardinalValue } from './utils/parse-cardinal.js'
15
15
  import { parseCurrencyValue } from './utils/parse-currency.js'
16
16
  import { parseOrdinalValue } from './utils/parse-ordinal.js'
17
+ import { checkMax } from './utils/check-max.js'
18
+ import { indian } from './utils/scale.js'
17
19
 
18
20
  // ============================================================================
19
21
  // Vocabulary
@@ -56,20 +58,29 @@ const BELOW_HUNDRED = [
56
58
  'ಅರವತ್ತು', 'ಅರವತ್ತೊಂದು', 'ಅರವತ್ತೆರಡು', 'ಅರವತ್ತಮೂರು', 'ಅರವತ್ತನಾಲ್ಕು', 'ಅರವತ್ತೈದು', 'ಅರವತ್ತಾರು', 'ಅರವತ್ತೇಳು', 'ಅರವತ್ತೆಂಟು', 'ಅರವತ್ತೊಂಬತ್ತು',
57
59
  'ಎಪ್ಪತ್ತು', 'ಎಪ್ಪತ್ತೊಂದು', 'ಎಪ್ಪತ್ತೆರಡು', 'ಎಪ್ಪತ್ತಮೂರು', 'ಎಪ್ಪತ್ತನಾಲ್ಕು', 'ಎಪ್ಪತ್ತೈದು', 'ಎಪ್ಪತ್ತಾರು', 'ಎಪ್ಪತ್ತೇಳು', 'ಎಪ್ಪತ್ತೆಂಟು', 'ಎಪ್ಪತ್ತೊಂಬತ್ತು',
58
60
  'ಎಂಬತ್ತು', 'ಎಂಬತ್ತೊಂದು', 'ಎಂಬತ್ತೆರಡು', 'ಎಂಬತ್ತಮೂರು', 'ಎಂಬತ್ತನಾಲ್ಕು', 'ಎಂಬತ್ತೈದು', 'ಎಂಬತ್ತಾರು', 'ಎಂಬತ್ತೇಳು', 'ಎಂಬತ್ತೆಂಟು', 'ಎಂಬತ್ತೊಂಬತ್ತು',
59
- 'ತೊಂಬತ್ತು', 'ತೊಂಬತ್ತೊಂದು', 'ತೊಂಬತ್ತೆರಡು', 'ತೊಂಬತ್ತಮೂರು', 'ತೊಂಬತ್ತನಾಲ್ಕು', 'ತೊಂಬತ್ತೈದು', 'ತೊಂಬತ್ತಾರು', 'ತೊಂಬತ್ತೇಳು', 'ತೊಂಬತ್ತೆಂಟು', 'ತೊಂಬತ್ತೊಂಬತ್ತು'
61
+ 'ತೊಂಬತ್ತು', 'ತೊಂಬತ್ತೊಂದು', 'ತೊಂಬತ್ತೆರಡು', 'ತೊಂಬತ್ತಮೂರು', 'ತೊಂಬತ್ತನಾಲ್ಕು', 'ತೊಂಬತ್ತೈದು', 'ತೊಂಬತ್ತಾರು', 'ತೊಂಬತ್ತೇಳು', 'ತೊಂಬತ್ತೆಂಟು', 'ತೊಂಬತ್ತೊಂಬತ್ತು',
60
62
  ]
61
63
 
62
64
  // Scale words: index 0 = units (empty), 1 = thousand, 2 = lakh, 3 = crore, etc.
63
65
  const SCALE_WORDS = ['', 'ಸಾವಿರ', 'ಲಕ್ಷ', 'ಕೋಟಿ', 'ಅಬ್ಜ', 'ಖರ್ವ', 'ನೀಲ', 'ಪದ್ಮ', 'ಶಂಖ']
64
66
 
67
+ // 3-2-2 Indian grouping: a 3-digit base segment, then 2 digits per scale word
68
+ // (SCALE_WORDS[0] = '' is the units slot). Past the table the scale word is
69
+ // dropped, which collapses the magnitude — so cap there.
70
+ export const cardinalMax = indian(SCALE_WORDS.length)
71
+ export const ordinalMax = indian(SCALE_WORDS.length)
72
+ export const currencyMax = indian(SCALE_WORDS.length)
73
+
65
74
  // ============================================================================
66
75
  // Segment Building
67
76
  // ============================================================================
68
77
 
69
78
  /**
70
79
  * Builds words for a 0-999 segment.
80
+ * @param {number} n - Segment value (0-999)
81
+ * @returns {string} Kannada words for the segment
71
82
  */
72
- function buildSegment (n) {
83
+ function buildSegment(n) {
73
84
  if (n === 0) return ''
74
85
  if (n < 100) return BELOW_HUNDRED[n]
75
86
 
@@ -91,11 +102,10 @@ function buildSegment (n) {
91
102
  *
92
103
  * Uses BigInt modulo for segment extraction (faster than string slicing).
93
104
  * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
94
- *
95
105
  * @param {bigint} n - Non-negative integer to convert
96
106
  * @returns {string} Kannada words
97
107
  */
98
- function integerToWords (n) {
108
+ function integerToWords(n) {
99
109
  if (n === 0n) return ZERO
100
110
 
101
111
  // Fast path: numbers < 1000 (direct lookup)
@@ -121,7 +131,8 @@ function integerToWords (n) {
121
131
 
122
132
  if (i === 0) {
123
133
  words.push(buildSegment(segment))
124
- } else {
134
+ }
135
+ else {
125
136
  words.push(BELOW_HUNDRED[segment])
126
137
  }
127
138
 
@@ -133,7 +144,12 @@ function integerToWords (n) {
133
144
  return words.join(' ')
134
145
  }
135
146
 
136
- function decimalPartToWords (decimalPart) {
147
+ /**
148
+ * Reads a decimal-fraction digit string per-digit in Kannada.
149
+ * @param {string} decimalPart - Digits after the decimal separator
150
+ * @returns {string} Per-digit Kannada words
151
+ */
152
+ function decimalPartToWords(decimalPart) {
137
153
  // Per-digit decimal reading
138
154
  const digits = []
139
155
  for (const char of decimalPart) {
@@ -145,12 +161,13 @@ function decimalPartToWords (decimalPart) {
145
161
 
146
162
  /**
147
163
  * Converts a numeric value to Kannada words.
148
- *
149
164
  * @param {number | string | bigint} value - The numeric value to convert
150
165
  * @returns {string} The number in Kannada words
151
166
  */
152
- function toCardinal (value) {
167
+ function toCardinal(value) {
153
168
  const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
169
+ // The fraction is spelled digit by digit, so only the integer part has a ceiling.
170
+ checkMax(integerPart, cardinalMax)
154
171
 
155
172
  let result = ''
156
173
 
@@ -175,11 +192,10 @@ function toCardinal (value) {
175
192
  * Converts a positive integer to Kannada ordinal words.
176
193
  *
177
194
  * Kannada ordinals: First 6 are special, then add -ನೇ suffix.
178
- *
179
195
  * @param {bigint} n - Positive integer to convert
180
196
  * @returns {string} Kannada ordinal words
181
197
  */
182
- function integerToOrdinal (n) {
198
+ function integerToOrdinal(n) {
183
199
  // Special ordinals for 1-6
184
200
  if (n >= 1n && n <= 6n) {
185
201
  return ORDINAL_SPECIAL[Number(n)]
@@ -192,19 +208,19 @@ function integerToOrdinal (n) {
192
208
 
193
209
  /**
194
210
  * Converts a numeric value to Kannada ordinal words.
195
- *
196
211
  * @param {number | string | bigint} value - The numeric value to convert (positive integer)
197
212
  * @returns {string} The number as ordinal words
198
213
  * @throws {TypeError} If value is not a valid numeric type
199
214
  * @throws {RangeError} If value is negative, zero, or has a decimal part
200
- *
201
215
  * @example
202
216
  * toOrdinal(1) // 'ಮೊದಲನೇ'
203
217
  * toOrdinal(2) // 'ಎರಡನೇ'
204
218
  * toOrdinal(10) // 'ಹತ್ತುನೇ'
205
219
  */
206
- function toOrdinal (value) {
220
+ function toOrdinal(value) {
207
221
  const integerPart = parseOrdinalValue(value)
222
+ // Ordinals build on the cardinal speller, so they share its ceiling.
223
+ checkMax(integerPart, ordinalMax)
208
224
  return integerToOrdinal(integerPart)
209
225
  }
210
226
 
@@ -214,19 +230,18 @@ function toOrdinal (value) {
214
230
 
215
231
  /**
216
232
  * Converts a numeric value to Kannada currency words (Indian Rupee).
217
- *
218
233
  * @param {number | string | bigint} value - The currency amount to convert
219
234
  * @returns {string} The amount in Kannada currency words
220
235
  * @throws {TypeError} If value is not a valid numeric type
221
236
  * @throws {Error} If value is not a valid number format
222
- *
223
237
  * @example
224
238
  * toCurrency(42.50) // 'ನಲವತ್ತೆರಡು ರೂಪಾಯಿಗಳು ಐವತ್ತು ಪೈಸೆಗಳು'
225
239
  * toCurrency(1) // 'ಒಂದು ರೂಪಾಯಿ'
226
240
  * toCurrency(0.01) // 'ಒಂದು ಪೈಸೆ'
227
241
  */
228
- function toCurrency (value) {
242
+ function toCurrency(value) {
229
243
  const { isNegative, dollars: rupees, cents: paise } = parseCurrencyValue(value)
244
+ checkMax(rupees, currencyMax)
230
245
 
231
246
  // Build result
232
247
  let result = ''