n2words 1.24.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (280) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +183 -156
  3. package/dist/languages/am-Latn.js +3 -0
  4. package/dist/languages/am-Latn.js.map +1 -0
  5. package/dist/languages/am.js +3 -0
  6. package/dist/languages/am.js.map +1 -0
  7. package/dist/languages/ar.js +3 -2
  8. package/dist/languages/ar.js.map +1 -1
  9. package/dist/languages/az.js +3 -2
  10. package/dist/languages/az.js.map +1 -1
  11. package/dist/languages/bn.js +3 -2
  12. package/dist/languages/bn.js.map +1 -1
  13. package/dist/languages/cs.js +3 -2
  14. package/dist/languages/cs.js.map +1 -1
  15. package/dist/languages/da.js +3 -2
  16. package/dist/languages/da.js.map +1 -1
  17. package/dist/languages/de.js +3 -2
  18. package/dist/languages/de.js.map +1 -1
  19. package/dist/languages/el.js +3 -2
  20. package/dist/languages/el.js.map +1 -1
  21. package/dist/languages/en.js +3 -2
  22. package/dist/languages/en.js.map +1 -1
  23. package/dist/languages/es.js +3 -2
  24. package/dist/languages/es.js.map +1 -1
  25. package/dist/languages/fa.js +3 -2
  26. package/dist/languages/fa.js.map +1 -1
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -2
  30. package/dist/languages/fil.js.map +1 -1
  31. package/dist/languages/fr-BE.js +3 -2
  32. package/dist/languages/fr-BE.js.map +1 -1
  33. package/dist/languages/fr.js +3 -2
  34. package/dist/languages/fr.js.map +1 -1
  35. package/dist/languages/gu.js +3 -2
  36. package/dist/languages/gu.js.map +1 -1
  37. package/dist/languages/ha.js +3 -0
  38. package/dist/languages/ha.js.map +1 -0
  39. package/dist/languages/hbo.js +3 -0
  40. package/dist/languages/hbo.js.map +1 -0
  41. package/dist/languages/he.js +3 -2
  42. package/dist/languages/he.js.map +1 -1
  43. package/dist/languages/hi.js +3 -2
  44. package/dist/languages/hi.js.map +1 -1
  45. package/dist/languages/hr.js +3 -2
  46. package/dist/languages/hr.js.map +1 -1
  47. package/dist/languages/hu.js +3 -2
  48. package/dist/languages/hu.js.map +1 -1
  49. package/dist/languages/id.js +3 -2
  50. package/dist/languages/id.js.map +1 -1
  51. package/dist/languages/it.js +3 -2
  52. package/dist/languages/it.js.map +1 -1
  53. package/dist/languages/ja.js +3 -2
  54. package/dist/languages/ja.js.map +1 -1
  55. package/dist/languages/kn.js +3 -2
  56. package/dist/languages/kn.js.map +1 -1
  57. package/dist/languages/ko.js +3 -2
  58. package/dist/languages/ko.js.map +1 -1
  59. package/dist/languages/lt.js +3 -2
  60. package/dist/languages/lt.js.map +1 -1
  61. package/dist/languages/lv.js +3 -2
  62. package/dist/languages/lv.js.map +1 -1
  63. package/dist/languages/mr.js +3 -2
  64. package/dist/languages/mr.js.map +1 -1
  65. package/dist/languages/ms.js +3 -2
  66. package/dist/languages/ms.js.map +1 -1
  67. package/dist/languages/nb.js +3 -2
  68. package/dist/languages/nb.js.map +1 -1
  69. package/dist/languages/nl.js +3 -2
  70. package/dist/languages/nl.js.map +1 -1
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -2
  74. package/dist/languages/pl.js.map +1 -1
  75. package/dist/languages/pt.js +3 -2
  76. package/dist/languages/pt.js.map +1 -1
  77. package/dist/languages/ro.js +3 -2
  78. package/dist/languages/ro.js.map +1 -1
  79. package/dist/languages/ru.js +3 -2
  80. package/dist/languages/ru.js.map +1 -1
  81. package/dist/languages/sr-Cyrl.js +3 -0
  82. package/dist/languages/sr-Cyrl.js.map +1 -0
  83. package/dist/languages/sr-Latn.js +3 -2
  84. package/dist/languages/sr-Latn.js.map +1 -1
  85. package/dist/languages/sv.js +3 -2
  86. package/dist/languages/sv.js.map +1 -1
  87. package/dist/languages/sw.js +3 -2
  88. package/dist/languages/sw.js.map +1 -1
  89. package/dist/languages/ta.js +3 -2
  90. package/dist/languages/ta.js.map +1 -1
  91. package/dist/languages/te.js +3 -2
  92. package/dist/languages/te.js.map +1 -1
  93. package/dist/languages/th.js +3 -2
  94. package/dist/languages/th.js.map +1 -1
  95. package/dist/languages/tr.js +3 -2
  96. package/dist/languages/tr.js.map +1 -1
  97. package/dist/languages/uk.js +3 -2
  98. package/dist/languages/uk.js.map +1 -1
  99. package/dist/languages/ur.js +3 -2
  100. package/dist/languages/ur.js.map +1 -1
  101. package/dist/languages/vi.js +3 -2
  102. package/dist/languages/vi.js.map +1 -1
  103. package/dist/languages/zh-Hans.js +3 -2
  104. package/dist/languages/zh-Hans.js.map +1 -1
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +3 -2
  108. package/dist/n2words.js.map +1 -1
  109. package/lib/languages/am-Latn.d.ts +7 -0
  110. package/lib/languages/am-Latn.js +164 -0
  111. package/lib/languages/am.d.ts +7 -0
  112. package/lib/languages/am.js +164 -0
  113. package/lib/languages/ar.d.ts +17 -0
  114. package/lib/languages/ar.js +171 -209
  115. package/lib/languages/az.d.ts +7 -0
  116. package/lib/languages/az.js +167 -49
  117. package/lib/languages/bn.d.ts +7 -0
  118. package/lib/languages/bn.js +142 -123
  119. package/lib/languages/cs.d.ts +18 -0
  120. package/lib/languages/cs.js +303 -176
  121. package/lib/languages/da.d.ts +14 -0
  122. package/lib/languages/da.js +267 -139
  123. package/lib/languages/de.d.ts +17 -0
  124. package/lib/languages/de.js +310 -113
  125. package/lib/languages/el.d.ts +14 -0
  126. package/lib/languages/el.js +225 -98
  127. package/lib/languages/en.d.ts +17 -0
  128. package/lib/languages/en.js +235 -102
  129. package/lib/languages/es.d.ts +21 -0
  130. package/lib/languages/es.js +307 -125
  131. package/lib/languages/fa.d.ts +7 -0
  132. package/lib/languages/fa.js +115 -108
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +7 -0
  136. package/lib/languages/fil.js +199 -139
  137. package/lib/languages/fr-BE.d.ts +11 -0
  138. package/lib/languages/fr-BE.js +287 -48
  139. package/lib/languages/fr.d.ts +21 -0
  140. package/lib/languages/fr.js +343 -119
  141. package/lib/languages/gu.d.ts +7 -0
  142. package/lib/languages/gu.js +125 -144
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +13 -0
  146. package/lib/languages/hbo.js +300 -0
  147. package/lib/languages/he.d.ts +13 -0
  148. package/lib/languages/he.js +230 -283
  149. package/lib/languages/hi.d.ts +7 -0
  150. package/lib/languages/hi.js +142 -123
  151. package/lib/languages/hr.d.ts +11 -0
  152. package/lib/languages/hr.js +190 -129
  153. package/lib/languages/hu.d.ts +7 -0
  154. package/lib/languages/hu.js +194 -133
  155. package/lib/languages/id.d.ts +7 -0
  156. package/lib/languages/id.js +167 -140
  157. package/lib/languages/it.d.ts +19 -0
  158. package/lib/languages/it.js +337 -108
  159. package/lib/languages/ja.d.ts +17 -0
  160. package/lib/languages/ja.js +224 -155
  161. package/lib/languages/kn.d.ts +7 -0
  162. package/lib/languages/kn.js +128 -62
  163. package/lib/languages/ko.d.ts +14 -0
  164. package/lib/languages/ko.js +250 -70
  165. package/lib/languages/lt.d.ts +18 -0
  166. package/lib/languages/lt.js +287 -148
  167. package/lib/languages/lv.d.ts +18 -0
  168. package/lib/languages/lv.js +291 -123
  169. package/lib/languages/mr.d.ts +7 -0
  170. package/lib/languages/mr.js +125 -144
  171. package/lib/languages/ms.d.ts +7 -0
  172. package/lib/languages/ms.js +171 -112
  173. package/lib/languages/nb.d.ts +14 -0
  174. package/lib/languages/nb.js +275 -100
  175. package/lib/languages/nl.d.ts +26 -0
  176. package/lib/languages/nl.js +307 -174
  177. package/lib/languages/pa.d.ts +7 -0
  178. package/lib/languages/pa.js +163 -0
  179. package/lib/languages/pl.d.ts +22 -0
  180. package/lib/languages/pl.js +299 -158
  181. package/lib/languages/pt.d.ts +17 -0
  182. package/lib/languages/pt.js +279 -120
  183. package/lib/languages/ro.d.ts +18 -0
  184. package/lib/languages/ro.js +214 -337
  185. package/lib/languages/ru.d.ts +11 -0
  186. package/lib/languages/ru.js +219 -95
  187. package/lib/languages/sr-Cyrl.d.ts +11 -0
  188. package/lib/languages/sr-Cyrl.js +215 -0
  189. package/lib/languages/sr-Latn.d.ts +11 -0
  190. package/lib/languages/sr-Latn.js +190 -132
  191. package/lib/languages/sv.d.ts +14 -0
  192. package/lib/languages/sv.js +280 -103
  193. package/lib/languages/sw.d.ts +7 -0
  194. package/lib/languages/sw.js +135 -103
  195. package/lib/languages/ta.d.ts +7 -0
  196. package/lib/languages/ta.js +133 -205
  197. package/lib/languages/te.d.ts +7 -0
  198. package/lib/languages/te.js +148 -213
  199. package/lib/languages/th.d.ts +7 -0
  200. package/lib/languages/th.js +139 -101
  201. package/lib/languages/tr.d.ts +18 -0
  202. package/lib/languages/tr.js +246 -66
  203. package/lib/languages/uk.d.ts +11 -0
  204. package/lib/languages/uk.js +197 -101
  205. package/lib/languages/ur.d.ts +7 -0
  206. package/lib/languages/ur.js +160 -123
  207. package/lib/languages/vi.d.ts +17 -0
  208. package/lib/languages/vi.js +287 -164
  209. package/lib/languages/zh-Hans.d.ts +11 -0
  210. package/lib/languages/zh-Hans.js +159 -142
  211. package/lib/languages/zh-Hant.d.ts +11 -0
  212. package/lib/languages/zh-Hant.js +202 -0
  213. package/lib/n2words.d.ts +53 -0
  214. package/lib/n2words.js +91 -227
  215. package/lib/utils/is-plain-object.d.ts +13 -0
  216. package/lib/utils/is-plain-object.js +17 -0
  217. package/lib/utils/parse-numeric.d.ts +17 -0
  218. package/lib/utils/parse-numeric.js +108 -0
  219. package/lib/utils/validate-options.d.ts +8 -0
  220. package/lib/utils/validate-options.js +16 -0
  221. package/package.json +118 -67
  222. package/dist/languages/pa-Guru.js +0 -2
  223. package/dist/languages/pa-Guru.js.map +0 -1
  224. package/lib/classes/abstract-language.js +0 -261
  225. package/lib/classes/greedy-scale-language.js +0 -195
  226. package/lib/classes/slavic-language.js +0 -251
  227. package/lib/classes/south-asian-language.js +0 -161
  228. package/lib/classes/turkic-language.js +0 -63
  229. package/lib/languages/pa-Guru.js +0 -126
  230. package/typings/classes/abstract-language.d.ts +0 -144
  231. package/typings/classes/greedy-scale-language.d.ts +0 -148
  232. package/typings/classes/slavic-language.d.ts +0 -145
  233. package/typings/classes/south-asian-language.d.ts +0 -101
  234. package/typings/classes/turkic-language.d.ts +0 -42
  235. package/typings/languages/ar.d.ts +0 -93
  236. package/typings/languages/az.d.ts +0 -25
  237. package/typings/languages/bn.d.ts +0 -1
  238. package/typings/languages/cs.d.ts +0 -120
  239. package/typings/languages/da.d.ts +0 -53
  240. package/typings/languages/de.d.ts +0 -26
  241. package/typings/languages/el.d.ts +0 -11
  242. package/typings/languages/en.d.ts +0 -30
  243. package/typings/languages/es.d.ts +0 -43
  244. package/typings/languages/fa.d.ts +0 -81
  245. package/typings/languages/fil.d.ts +0 -12
  246. package/typings/languages/fr-BE.d.ts +0 -41
  247. package/typings/languages/fr.d.ts +0 -43
  248. package/typings/languages/gu.d.ts +0 -12
  249. package/typings/languages/he.d.ts +0 -197
  250. package/typings/languages/hi.d.ts +0 -1
  251. package/typings/languages/hr.d.ts +0 -110
  252. package/typings/languages/hu.d.ts +0 -37
  253. package/typings/languages/id.d.ts +0 -69
  254. package/typings/languages/it.d.ts +0 -51
  255. package/typings/languages/ja.d.ts +0 -58
  256. package/typings/languages/kn.d.ts +0 -11
  257. package/typings/languages/ko.d.ts +0 -25
  258. package/typings/languages/lt.d.ts +0 -110
  259. package/typings/languages/lv.d.ts +0 -99
  260. package/typings/languages/mr.d.ts +0 -12
  261. package/typings/languages/ms.d.ts +0 -37
  262. package/typings/languages/nb.d.ts +0 -27
  263. package/typings/languages/nl.d.ts +0 -65
  264. package/typings/languages/pa-Guru.d.ts +0 -1
  265. package/typings/languages/pl.d.ts +0 -116
  266. package/typings/languages/pt.d.ts +0 -39
  267. package/typings/languages/ro.d.ts +0 -229
  268. package/typings/languages/ru.d.ts +0 -108
  269. package/typings/languages/sr-Latn.d.ts +0 -98
  270. package/typings/languages/sv.d.ts +0 -30
  271. package/typings/languages/sw.d.ts +0 -1
  272. package/typings/languages/ta.d.ts +0 -1
  273. package/typings/languages/te.d.ts +0 -1
  274. package/typings/languages/th.d.ts +0 -1
  275. package/typings/languages/tr.d.ts +0 -46
  276. package/typings/languages/uk.d.ts +0 -117
  277. package/typings/languages/ur.d.ts +0 -1
  278. package/typings/languages/vi.d.ts +0 -116
  279. package/typings/languages/zh-Hans.d.ts +0 -57
  280. package/typings/n2words.d.ts +0 -177
@@ -1,212 +1,339 @@
1
- import SlavicLanguage from '../classes/slavic-language.js'
1
+ /**
2
+ * Czech language converter - Functional Implementation
3
+ *
4
+ * A performance-optimized number-to-words converter using precomputed lookup tables.
5
+ * Self-contained module with its own input validation, ready for subpath exports.
6
+ *
7
+ * Key optimization: Precompute all segment values (0-999) at module load.
8
+ * This eliminates all per-call string manipulation for segment conversion.
9
+ *
10
+ * Czech-specific rules (handled in precomputation):
11
+ * - Three-form pluralization: 1 = singular, 2-4 = few, 5+ = many
12
+ * - Irregular hundreds: sto, dvě stě, tři sta, čtyři sta, pět set...
13
+ * - Gender: dva (masc) vs dvě (fem) for 2
14
+ * - Omit "one" before scale words: "tisíc" not "jedna tisíc"
15
+ * - Dynamic decimal separator: celá/celé/celých based on integer
16
+ */
17
+
18
+ import { parseNumericValue } from '../utils/parse-numeric.js'
19
+
20
+ // ============================================================================
21
+ // Vocabulary (module-level constants)
22
+ // ============================================================================
23
+
24
+ // Ones words (masculine form)
25
+ const ONES = ['', 'jedna', 'dva', 'tři', 'čtyři', 'pět', 'šest', 'sedm', 'osm', 'devět']
26
+
27
+ // Teens (10-19)
28
+ const TEENS = ['deset', 'jedenáct', 'dvanáct', 'třináct', 'čtrnáct', 'patnáct', 'šestnáct', 'sedmnáct', 'osmnáct', 'devatenáct']
29
+
30
+ // Tens (20-90)
31
+ const TENS = ['', '', 'dvacet', 'třicet', 'čtyřicet', 'padesát', 'šedesát', 'sedmdesát', 'osmdesát', 'devadesát']
32
+
33
+ // Irregular hundreds
34
+ const HUNDREDS = ['', 'sto', 'dvě stě', 'tři sta', 'čtyři sta', 'pět set', 'šest set', 'sedm set', 'osm set', 'devět set']
35
+
36
+ // Scale plural forms [singular, few (2-4), many (5+)]
37
+ const PLURAL_FORMS = {
38
+ 1: ['tisíc', 'tisíce', 'tisíc'], // 10^3
39
+ 2: ['milion', 'miliony', 'milionů'], // 10^6
40
+ 3: ['miliarda', 'miliardy', 'miliard'], // 10^9
41
+ 4: ['bilion', 'biliony', 'bilionů'], // 10^12
42
+ 5: ['biliarda', 'biliardy', 'biliard'], // 10^15
43
+ 6: ['trilion', 'triliony', 'trilionů'], // 10^18
44
+ 7: ['triliarda', 'triliardy', 'triliard'], // 10^21
45
+ 8: ['kvadrilion', 'kvadriliony', 'kvadrilionů'], // 10^24
46
+ 9: ['kvadriliarda', 'kvadriliardy', 'kvadriliard'] // 10^27
47
+ }
48
+
49
+ const ZERO = 'nula'
50
+ const NEGATIVE = 'mínus'
51
+
52
+ // ============================================================================
53
+ // Precomputed Lookup Tables (built once at module load)
54
+ // ============================================================================
2
55
 
3
56
  /**
4
- * @typedef {Object} SlavicOptions
5
- * @property {boolean} [feminine=false] Use feminine forms for numbers.
57
+ * Builds segment word for 0-999 (masculine, default form).
58
+ * Only used during table construction.
6
59
  */
60
+ function buildSegment (n) {
61
+ if (n === 0) return ''
62
+
63
+ const ones = n % 10
64
+ const tens = Math.floor(n / 10) % 10
65
+ const hundreds = Math.floor(n / 100)
66
+
67
+ const parts = []
68
+
69
+ // Hundreds (irregular)
70
+ if (hundreds > 0) {
71
+ parts.push(HUNDREDS[hundreds])
72
+ }
73
+
74
+ // Tens and ones
75
+ if (tens === 1) {
76
+ // Teens
77
+ parts.push(TEENS[ones])
78
+ } else if (tens >= 2) {
79
+ parts.push(TENS[tens])
80
+ if (ones > 0) {
81
+ parts.push(ONES[ones])
82
+ }
83
+ } else if (ones > 0) {
84
+ parts.push(ONES[ones])
85
+ }
86
+
87
+ return parts.join(' ')
88
+ }
7
89
 
8
90
  /**
9
- * Czech language converter.
10
- *
11
- * Implements Czech number words using the Slavic language pattern:
12
- * - Czech number words (jedna, dva, tři, čtyři, pět...)
13
- * - Slavic three-form pluralization (tisíc/tisíce/tisíc)
14
- * - Gender agreement for numbers 1-2
15
- * - Czech-specific number word endings
16
- *
17
- * Key Features:
18
- * - Three-form pluralization system shared across Slavic languages
19
- * * Form 1 (singular): 1 (e.g., "tisíc")
20
- * * Form 2 (few): 2-4, 22-24, 32-34... excluding teens (e.g., "tisíce")
21
- * * Form 3 (many): all other numbers (e.g., "tisíc")
22
- * - Chunk-based decomposition (splits into groups of 3 digits: ones, thousands, millions, etc.)
23
- * - Large number handling via thousands[] array with indexed [singular, few, many] forms
24
- *
25
- * Inherits from SlavicLanguage:
26
- * - Complex pluralization rules (one/few/many forms)
27
- * - Group-based large number handling (chunk decomposition via splitByX())
28
- * - Proper declension patterns via pluralize() method
91
+ * Builds segment word for 0-999 with feminine hundreds.
92
+ * Hundreds use irregular forms (dvě stě, tři sta) but ones remain masculine.
29
93
  */
30
- export class Czech extends SlavicLanguage {
31
- negativeWord = 'mínus'
32
- zeroWord = 'nula'
33
- ones = {
34
- 1: 'jedna',
35
- 2: 'dva',
36
- 3: 'tři',
37
- 4: 'čtyři',
38
- 5: 'pět',
39
- 6: 'šest',
40
- 7: 'sedm',
41
- 8: 'osm',
42
- 9: 'devět'
94
+ function buildSegmentWithHundreds (n) {
95
+ if (n === 0) return ''
96
+
97
+ const ones = n % 10
98
+ const tens = Math.floor(n / 10) % 10
99
+ const hundreds = Math.floor(n / 100)
100
+
101
+ const parts = []
102
+
103
+ // Hundreds use the irregular HUNDREDS array (already has "dvě stě" etc.)
104
+ if (hundreds > 0) {
105
+ parts.push(HUNDREDS[hundreds])
43
106
  }
44
107
 
45
- tens = {
46
- 0: 'deset',
47
- 1: 'jedenáct',
48
- 2: 'dvanáct',
49
- 3: 'třináct',
50
- 4: 'čtrnáct',
51
- 5: 'patnáct',
52
- 6: 'šestnáct',
53
- 7: 'sedmnáct',
54
- 8: 'osmnáct',
55
- 9: 'devatenáct'
108
+ // Tens and ones use masculine form
109
+ if (tens === 1) {
110
+ parts.push(TEENS[ones])
111
+ } else if (tens >= 2) {
112
+ parts.push(TENS[tens])
113
+ if (ones > 0) {
114
+ parts.push(ONES[ones]) // masculine
115
+ }
116
+ } else if (ones > 0) {
117
+ parts.push(ONES[ones]) // masculine
56
118
  }
57
119
 
58
- twenties = {
59
- 2: 'dvacet',
60
- 3: 'třicet',
61
- 4: 'čtyřicet',
62
- 5: 'padesát',
63
- 6: 'šedesát',
64
- 7: 'sedmdesát',
65
- 8: 'osmdesát',
66
- 9: 'devadesát'
120
+ return parts.join(' ')
121
+ }
122
+
123
+ // Precompute all 1000 segment words (0-999) - masculine form
124
+ const SEGMENTS = new Array(1000)
125
+ for (let i = 0; i < 1000; i++) {
126
+ SEGMENTS[i] = buildSegment(i)
127
+ }
128
+
129
+ // Precompute all 1000 segment words with hundreds (irregular hundreds forms)
130
+ const SEGMENTS_WITH_HUNDREDS = new Array(1000)
131
+ for (let i = 0; i < 1000; i++) {
132
+ SEGMENTS_WITH_HUNDREDS[i] = buildSegmentWithHundreds(i)
133
+ }
134
+
135
+ // ============================================================================
136
+ // Helper Functions
137
+ // ============================================================================
138
+
139
+ /**
140
+ * Czech pluralization: 1 = singular, 2-4 = few, else = many.
141
+ * Teens (11-19) always use "many" form.
142
+ *
143
+ * @param {bigint} n - The number
144
+ * @param {string[]} forms - [singular, few, many]
145
+ * @returns {string} The appropriate form
146
+ */
147
+ function pluralize (n, forms) {
148
+ if (n === 1n) return forms[0]
149
+
150
+ const lastDigit = n % 10n
151
+ const lastTwoDigits = n % 100n
152
+
153
+ // 2-4, but not 12-14 (teens use "many")
154
+ if (lastDigit >= 2n && lastDigit <= 4n && (lastTwoDigits < 10n || lastTwoDigits > 20n)) {
155
+ return forms[1]
67
156
  }
68
157
 
69
- hundreds = {
70
- 1: 'sto',
71
- 2: 'dvě stě',
72
- 3: 'tři sta',
73
- 4: 'čtyři sta',
74
- 5: 'pět set',
75
- 6: 'šest set',
76
- 7: 'sedm set',
77
- 8: 'osm set',
78
- 9: 'devět set'
158
+ return forms[2]
159
+ }
160
+
161
+ /**
162
+ * Gets the decimal separator word based on integer part.
163
+ * celá (0-1), celé (2-4), celých (5+)
164
+ */
165
+ function getDecimalSeparator (integerPart) {
166
+ if (integerPart === 0n || integerPart === 1n) {
167
+ return 'celá'
168
+ } else if (integerPart >= 2n && integerPart <= 4n) {
169
+ return 'celé'
170
+ } else {
171
+ return 'celých'
79
172
  }
173
+ }
174
+
175
+ // ============================================================================
176
+ // Conversion Functions
177
+ // ============================================================================
178
+
179
+ /**
180
+ * Converts a non-negative integer to Czech words.
181
+ *
182
+ * @param {bigint} n - Non-negative integer to convert
183
+ * @returns {string} Czech words
184
+ */
185
+ function integerToWords (n) {
186
+ if (n === 0n) return ZERO
80
187
 
81
- thousands = {
82
- 1: ['tisíc', 'tisíce', 'tisíc'], // 10^ 3
83
- 2: ['milion', 'miliony', 'milionů'], // 10^ 6
84
- 3: ['miliarda', 'miliardy', 'miliard'], // 10^ 9
85
- 4: ['bilion', 'biliony', 'bilionů'], // 10^ 12
86
- 5: ['biliarda', 'biliardy', 'biliard'], // 10^ 15
87
- 6: ['trilion', 'triliony', 'trilionů'], // 10^ 18
88
- 7: ['triliarda', 'triliardy', 'triliard'], // 10^ 21
89
- 8: ['kvadrilion', 'kvadriliony', 'kvadrilionů'], // 10^ 24
90
- 9: ['kvadriliarda', 'kvadriliardy', 'kvadriliard'], // 10^ 27
91
- 10: ['quintillion', 'quintilliony', 'quintillionů'] // 10^ 30
188
+ // Fast path: numbers < 1000 (direct lookup)
189
+ if (n < 1000n) {
190
+ return SEGMENTS[Number(n)]
92
191
  }
93
192
 
94
- /**
95
- * Returns the Czech word for the decimal separator based on the whole number.
96
- *
97
- * @returns {string} The Czech word for the decimal separator.
98
- */
99
- get decimalSeparatorWord () {
100
- if (this.cachedWholeNumber === 0n || this.cachedWholeNumber === 1n) {
101
- return 'celá'
102
- } else if (this.cachedWholeNumber >= 2n && this.cachedWholeNumber <= 4n) {
103
- return 'celé'
193
+ // Fast path: numbers < 1,000,000 (thousands)
194
+ if (n < 1_000_000n) {
195
+ const thousands = n / 1000n
196
+ const remainder = Number(n % 1000n)
197
+
198
+ const scaleWord = pluralize(thousands, PLURAL_FORMS[1])
199
+
200
+ let result
201
+ if (thousands === 1n) {
202
+ // Omit "one" before tisíc
203
+ result = scaleWord
104
204
  } else {
105
- return 'celých'
205
+ result = SEGMENTS[Number(thousands)] + ' ' + scaleWord
106
206
  }
207
+
208
+ if (remainder > 0) {
209
+ // Use form with irregular hundreds (for "dvě stě" etc.)
210
+ result += ' ' + SEGMENTS_WITH_HUNDREDS[remainder]
211
+ }
212
+
213
+ return result
107
214
  }
108
215
 
109
- /**
110
- * Initializes the Czech converter with language-specific options.
111
- *
112
- * @param {SlavicOptions} [options={}] Configuration options (inherited from SlavicLanguage).
113
- */
114
- constructor (options = {}) {
115
- super(options)
216
+ // For numbers >= 1,000,000, use scale decomposition
217
+ return buildLargeNumberWords(n)
218
+ }
116
219
 
117
- // Remove the inherited decimalSeparatorWord property so our getter works
118
- delete this.decimalSeparatorWord
220
+ /**
221
+ * Builds words for numbers >= 1,000,000.
222
+ * Uses BigInt division for faster segment extraction.
223
+ *
224
+ * @param {bigint} n - Number >= 1,000,000
225
+ * @returns {string} Czech words
226
+ */
227
+ function buildLargeNumberWords (n) {
228
+ // Extract segments using BigInt division (faster than string slicing)
229
+ // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
230
+ const segmentValues = []
231
+ let temp = n
232
+ while (temp > 0n) {
233
+ segmentValues.push(temp % 1000n)
234
+ temp = temp / 1000n
119
235
  }
120
236
 
121
- /**
122
- * Implements Czech-specific three-form pluralization rules.
123
- *
124
- * Czech three-form system:
125
- * - Form 1 (singular): exactly n=1 (e.g., "tisíc")
126
- * - Form 2 (few): n ends in 2-4, excluding teens (22-24, 32-34...) (e.g., "tisíce")
127
- * - Form 3 (many): all other numbers (e.g., "tisíc" for 0, 5+)
128
- *
129
- * @param {bigint} n The number to classify.
130
- * @param {Array<string>} forms Array of [singular, few, many] word forms.
131
- * @returns {string} The appropriate form for the number n.
132
- */
133
- pluralize (n, forms) {
134
- if (n === 1n) {
135
- return forms[0]
136
- }
237
+ // Build result string directly
238
+ let result = ''
137
239
 
138
- // Check if n is in the "few" form range (2-4, 22-24, 32-34, etc., excluding teens)
139
- const lastDigit = n % 10n
140
- const lastTwoDigits = n % 100n
240
+ for (let i = segmentValues.length - 1; i >= 0; i--) {
241
+ const segment = segmentValues[i]
242
+ if (segment === 0n) continue
141
243
 
142
- if (lastDigit > 1n && lastDigit < 5n && (lastTwoDigits < 10n || lastTwoDigits > 20n)) {
143
- return forms[1]
144
- }
244
+ if (result) result += ' '
145
245
 
146
- return forms[2]
147
- }
246
+ if (i === 0) {
247
+ // Units segment (no scale word) - use form with irregular hundreds
248
+ result += SEGMENTS_WITH_HUNDREDS[Number(segment)]
249
+ } else {
250
+ // Scale word needed
251
+ const forms = PLURAL_FORMS[i]
252
+ if (forms) {
253
+ const scaleWord = pluralize(segment, forms)
148
254
 
149
- /**
150
- * Converts a whole number to Czech cardinal form.
151
- *
152
- * Algorithm (chunk-based decomposition):
153
- * 1. Split number into chunks of 3 digits (right-to-left): ones, thousands, millions, etc.
154
- * 2. For each non-zero chunk:
155
- * a. Extract hundreds digit (n3), tens digit (n2), ones digit (n1)
156
- * b. Add hundreds word (e.g., "sto", "dvě stě")
157
- * c. Add tens/ones (handles teens 10-19 separately from compound tens)
158
- * d. Add pluralized magnitude word (e.g., "tisíce", "miliony")
159
- * 3. Join all words with spaces
160
- *
161
- * Example: 1234 → chunks [1, 234] → "jeden tisíc dvěstěčtyřicet"
162
- * - Chunk 1 (index=1, thousands): "jeden" + "tisíc"
163
- * - Chunk 234 (index=0): "dvě stě" + "třicet" + "čtyři"
164
- *
165
- * Special case: When chunk=1 at thousands+ levels, omit the ones word to avoid
166
- * redundancy with the pluralized magnitude (e.g., just "tisíc" not "jeden tisíc").
167
- *
168
- * @param {bigint} number The whole number to convert.
169
- * @returns {string} The number expressed in Czech words.
170
- */
171
- convertWholePart (number) {
172
- if (number === 0n) {
173
- return this.zeroWord
174
- }
175
- const words = []
176
- const chunks = this.splitByX(number.toString(), 3)
177
- let index = chunks.length
178
- for (const x of chunks) {
179
- index--
180
- if (x === 0n) continue
181
- const [n1, n2, n3] = this.getDigits(x)
182
- if (n3 > 0n) {
183
- words.push(this.hundreds[n3])
184
- }
185
- if (n2 > 1n) {
186
- words.push(this.twenties[n2])
187
- }
188
- if (n2 === 1n) {
189
- words.push(this.tens[n1])
190
- } else if (n1 > 0n && !(index > 0 && x === 1n)) {
191
- words.push(this.ones[n1])
192
- }
193
- if (index > 0) {
194
- words.push(this.pluralize(x, this.thousands[index]))
255
+ if (segment === 1n) {
256
+ // Omit "one" before scale words
257
+ result += scaleWord
258
+ } else {
259
+ // Use masculine form for multiplier before scale words
260
+ result += SEGMENTS[Number(segment)] + ' ' + scaleWord
261
+ }
262
+ } else {
263
+ // Fallback for very large scales without defined forms
264
+ result += SEGMENTS[Number(segment)]
195
265
  }
196
266
  }
197
- return words.join(' ')
198
267
  }
268
+
269
+ return result
199
270
  }
200
271
 
201
272
  /**
202
- * Converts a number to Czech cardinal (written) form.
273
+ * Converts decimal digits to Czech words.
203
274
  *
204
- * @param {number|string|bigint} value The number to convert.
205
- * @param {SlavicOptions} [options={}] Configuration options.
206
- * @returns {string} The number expressed in Czech words.
207
- * @throws {TypeError} If value is NaN or invalid type.
208
- * @throws {Error} If value is an invalid number string.
275
+ * @param {string} decimalPart - Decimal digits (without the point)
276
+ * @returns {string} Czech words for decimal part
209
277
  */
210
- export default function convertToWords (value, options = {}) {
211
- return new Czech(options).convertToWords(value)
278
+ function decimalPartToWords (decimalPart) {
279
+ let result = ''
280
+
281
+ // Handle leading zeros
282
+ let i = 0
283
+ while (i < decimalPart.length && decimalPart[i] === '0') {
284
+ if (result) result += ' '
285
+ result += ZERO
286
+ i++
287
+ }
288
+
289
+ // Convert remainder as a single number
290
+ const remainder = decimalPart.slice(i)
291
+ if (remainder) {
292
+ if (result) result += ' '
293
+ result += integerToWords(BigInt(remainder))
294
+ }
295
+
296
+ return result
212
297
  }
298
+
299
+ /**
300
+ * Converts a numeric value to Czech words.
301
+ *
302
+ * This is the main public API. It accepts any valid numeric input
303
+ * (number, string, or bigint) and handles parsing internally.
304
+ *
305
+ * @param {number | string | bigint} value - The numeric value to convert
306
+ * @returns {string} The number in Czech words
307
+ * @throws {TypeError} If value is not a valid numeric type
308
+ * @throws {Error} If value is not a valid number format
309
+ *
310
+ * @example
311
+ * toWords(21) // 'dvacet jedna'
312
+ * toWords(1000) // 'tisíc'
313
+ * toWords(2000) // 'dva tisíce'
314
+ * toWords(5000) // 'pět tisíc'
315
+ */
316
+ function toWords (value) {
317
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
318
+
319
+ let result = ''
320
+
321
+ if (isNegative) {
322
+ result = NEGATIVE + ' '
323
+ }
324
+
325
+ result += integerToWords(integerPart)
326
+
327
+ if (decimalPart) {
328
+ const separator = getDecimalSeparator(integerPart)
329
+ result += ' ' + separator + ' ' + decimalPartToWords(decimalPart)
330
+ }
331
+
332
+ return result
333
+ }
334
+
335
+ // ============================================================================
336
+ // Public API
337
+ // ============================================================================
338
+
339
+ export { toWords }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Converts a numeric value to Danish words.
3
+ *
4
+ * @param {number | string | bigint} value - The numeric value to convert
5
+ * @returns {string} The number in Danish words
6
+ * @throws {TypeError} If value is not a valid numeric type
7
+ * @throws {Error} If value is not a valid number format
8
+ *
9
+ * @example
10
+ * toWords(21) // 'enogtyve'
11
+ * toWords(1000) // 'ettusind'
12
+ * toWords(1000000) // 'en millioner'
13
+ */
14
+ export function toWords(value: number | string | bigint): string;