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,189 +1,330 @@
1
- import SlavicLanguage from '../classes/slavic-language.js'
1
+ /**
2
+ * Polish 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
+ * Polish-specific rules (handled in precomputation):
11
+ * - Three-form pluralization: 1 = singular, 2-4 = few, 5+ = many
12
+ * - Gender agreement (masculine/feminine for numbers < 1000)
13
+ * - Omit "jeden" before scale words (tysiąc, milion, etc.)
14
+ * - Irregular hundreds: dwieście, trzysta, czterysta, pięćset...
15
+ * - Long scale with -ard forms: miliard, biliard, tryliard
16
+ */
17
+
18
+ import { parseNumericValue } from '../utils/parse-numeric.js'
19
+ import { validateOptions } from '../utils/validate-options.js'
20
+
21
+ // ============================================================================
22
+ // Vocabulary (module-level constants)
23
+ // ============================================================================
24
+
25
+ const ONES_MASC = ['', 'jeden', 'dwa', 'trzy', 'cztery', 'pięć', 'sześć', 'siedem', 'osiem', 'dziewięć']
26
+ const ONES_FEM = ['', 'jedna', 'dwie', 'trzy', 'cztery', 'pięć', 'sześć', 'siedem', 'osiem', 'dziewięć']
27
+
28
+ const TEENS = ['dziesięć', 'jedenaście', 'dwanaście', 'trzynaście', 'czternaście', 'piętnaście', 'szesnaście', 'siedemnaście', 'osiemnaście', 'dziewiętnaście']
29
+
30
+ const TENS = ['', '', 'dwadzieścia', 'trzydzieści', 'czterdzieści', 'pięćdziesiąt', 'sześćdziesiąt', 'siedemdziesiąt', 'osiemdziesiąt', 'dziewięćdziesiąt']
31
+
32
+ // Irregular hundreds
33
+ const HUNDREDS = ['', 'sto', 'dwieście', 'trzysta', 'czterysta', 'pięćset', 'sześćset', 'siedemset', 'osiemset', 'dziewięćset']
34
+
35
+ // Scale words: [singular, few (2-4), many (5+)]
36
+ const PLURAL_FORMS = {
37
+ 1: ['tysiąc', 'tysiące', 'tysięcy'],
38
+ 2: ['milion', 'miliony', 'milionów'],
39
+ 3: ['miliard', 'miliardy', 'miliardów'],
40
+ 4: ['bilion', 'biliony', 'bilionów'],
41
+ 5: ['biliard', 'biliardy', 'biliardów'],
42
+ 6: ['trylion', 'tryliony', 'trylionów'],
43
+ 7: ['tryliard', 'tryliardy', 'tryliardów'],
44
+ 8: ['kwadrylion', 'kwadryliony', 'kwadrylionów'],
45
+ 9: ['kwaryliard', 'kwadryliardy', 'kwadryliardów'],
46
+ 10: ['kwintylion', 'kwintyliony', 'kwintylionów']
47
+ }
48
+
49
+ const ZERO = 'zero'
50
+ const NEGATIVE = 'minus'
51
+ const DECIMAL_SEP = 'przecinek'
52
+
53
+ // ============================================================================
54
+ // Precomputed Lookup Tables (built once at module load)
55
+ // ============================================================================
2
56
 
3
57
  /**
4
- * @typedef {Object} SlavicOptions
5
- * @property {boolean} [feminine=false] Use feminine forms for numbers.
58
+ * Builds segment word for 0-999 (masculine form).
59
+ * @param {number} n - Segment value
60
+ * @returns {string} Polish word
6
61
  */
62
+ function buildSegment (n) {
63
+ if (n === 0) return ''
64
+
65
+ const ones = n % 10
66
+ const tens = Math.floor(n / 10) % 10
67
+ const hundreds = Math.floor(n / 100)
68
+
69
+ const parts = []
70
+
71
+ // Hundreds
72
+ if (hundreds > 0) {
73
+ parts.push(HUNDREDS[hundreds])
74
+ }
75
+
76
+ // Tens and ones
77
+ if (tens === 1) {
78
+ // Teens (10-19)
79
+ parts.push(TEENS[ones])
80
+ } else {
81
+ if (tens >= 2) {
82
+ parts.push(TENS[tens])
83
+ }
84
+ if (ones > 0) {
85
+ parts.push(ONES_MASC[ones])
86
+ }
87
+ }
88
+
89
+ return parts.join(' ')
90
+ }
7
91
 
8
92
  /**
9
- * Polish language converter.
10
- *
11
- * Implements Polish number words using the Slavic language pattern:
12
- * - Polish number words (jeden, dwa, trzy, cztery, pięć...)
13
- * - Complex Slavic three-form pluralization (tysiąc/tysiące/tysięcy)
14
- * - Polish-specific declension patterns
15
- * - Distinctive Polish phonology and orthography
16
- *
17
- * Key Features:
18
- * - Three-form pluralization system shared across Slavic languages
19
- * * Form 1 (singular): 1 (e.g., "tysiąc")
20
- * * Form 2 (few): 2-4, 22-24, 32-34... excluding teens (e.g., "tysiące")
21
- * * Form 3 (many): all other numbers (e.g., "tysięcy")
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
- * Features:
26
- * - Polish diacritical marks (ą, ć, ę, ł, ń, ś, ź, ż)
27
- * - Gender and case agreement
28
- * - Polish-specific number word endings
29
- *
30
- * Inherits from SlavicLanguage for complex pluralization algorithms.
93
+ * Builds segment word for 0-999 (feminine form - only differs in ones).
94
+ * @param {number} n - Segment value
95
+ * @returns {string} Polish word
31
96
  */
32
- export class Polish extends SlavicLanguage {
33
- negativeWord = 'minus'
34
- decimalSeparatorWord = 'przecinek'
35
- zeroWord = 'zero'
36
- ones = {
37
- 1: 'jeden',
38
- 2: 'dwa',
39
- 3: 'trzy',
40
- 4: 'cztery',
41
- 5: 'pięć',
42
- 6: 'sześć',
43
- 7: 'siedem',
44
- 8: 'osiem',
45
- 9: 'dziewięć'
97
+ function buildSegmentFeminine (n) {
98
+ if (n === 0) return ''
99
+
100
+ const ones = n % 10
101
+ const tens = Math.floor(n / 10) % 10
102
+ const hundreds = Math.floor(n / 100)
103
+
104
+ const parts = []
105
+
106
+ // Hundreds
107
+ if (hundreds > 0) {
108
+ parts.push(HUNDREDS[hundreds])
46
109
  }
47
110
 
48
- tens = {
49
- 0: 'dziesięć',
50
- 1: 'jedenaście',
51
- 2: 'dwanaście',
52
- 3: 'trzynaście',
53
- 4: 'czternaście',
54
- 5: 'piętnaście',
55
- 6: 'szesnaście',
56
- 7: 'siedemnaście',
57
- 8: 'osiemnaście',
58
- 9: 'dziewiętnaście'
111
+ // Tens and ones - feminine for ones only
112
+ if (tens === 1) {
113
+ parts.push(TEENS[ones])
114
+ } else {
115
+ if (tens >= 2) {
116
+ parts.push(TENS[tens])
117
+ }
118
+ if (ones > 0) {
119
+ parts.push(ONES_FEM[ones])
120
+ }
59
121
  }
60
122
 
61
- twenties = {
62
- 2: 'dwadzieścia',
63
- 3: 'trzydzieści',
64
- 4: 'czterdzieści',
65
- 5: 'pięćdziesiąt',
66
- 6: 'sześćdziesiąt',
67
- 7: 'siedemdziesiąt',
68
- 8: 'osiemdziesiąt',
69
- 9: 'dziewięćdziesiąt'
123
+ return parts.join(' ')
124
+ }
125
+
126
+ // Precompute all 1000 segment words (0-999)
127
+ const SEGMENTS_MASC = new Array(1000)
128
+ const SEGMENTS_FEM = new Array(1000)
129
+ for (let i = 0; i < 1000; i++) {
130
+ SEGMENTS_MASC[i] = buildSegment(i)
131
+ SEGMENTS_FEM[i] = buildSegmentFeminine(i)
132
+ }
133
+
134
+ // ============================================================================
135
+ // Helper Functions
136
+ // ============================================================================
137
+
138
+ /**
139
+ * Polish pluralization: 1 = singular, 2-4 = few, else = many.
140
+ * Special case: 11-19 always use many form.
141
+ *
142
+ * @param {bigint} n - Number to pluralize
143
+ * @param {string[]} forms - [singular, few, many]
144
+ * @returns {string} Correct plural form
145
+ */
146
+ function pluralize (n, forms) {
147
+ if (n === 1n) {
148
+ return forms[0]
70
149
  }
71
150
 
72
- hundreds = {
73
- 1: 'sto',
74
- 2: 'dwieście',
75
- 3: 'trzysta',
76
- 4: 'czterysta',
77
- 5: 'pięćset',
78
- 6: 'sześćset',
79
- 7: 'siedemset',
80
- 8: 'osiemset',
81
- 9: 'dziewięćset'
151
+ const lastDigit = n % 10n
152
+ const lastTwoDigits = n % 100n
153
+
154
+ // Teens (11-19) always use many form
155
+ // 2-4 use few form (but not 12-14)
156
+ if (lastDigit >= 2n && lastDigit <= 4n && (lastTwoDigits < 10n || lastTwoDigits > 20n)) {
157
+ return forms[1]
82
158
  }
83
159
 
84
- thousands = {
85
- 1: ['tysiąc', 'tysiące', 'tysięcy'], // 10^ 3
86
- 2: ['milion', 'miliony', 'milionów'], // 10^ 6
87
- 3: ['miliard', 'miliardy', 'miliardów'], // 10^ 9
88
- 4: ['bilion', 'biliony', 'bilionów'], // 10^ 12
89
- 5: ['biliard', 'biliardy', 'biliardów'], // 10^ 15
90
- 6: ['trylion', 'tryliony', 'trylionów'], // 10^ 18
91
- 7: ['tryliard', 'tryliardy', 'tryliardów'], // 10^ 21
92
- 8: ['kwadrylion', 'kwadryliony', 'kwadrylionów'], // 10^ 24
93
- 9: ['kwaryliard', 'kwadryliardy', 'kwadryliardów'], // 10^ 27
94
- 10: ['kwintylion', 'kwintyliony', 'kwintylionów'] // 10^ 30
160
+ return forms[2]
161
+ }
162
+
163
+ // ============================================================================
164
+ // Conversion Functions
165
+ // ============================================================================
166
+
167
+ /**
168
+ * Converts a non-negative integer to Polish words.
169
+ *
170
+ * @param {bigint} n - Non-negative integer to convert
171
+ * @param {Object} options - Conversion options
172
+ * @returns {string} Polish words
173
+ */
174
+ function integerToWords (n, options = {}) {
175
+ if (n === 0n) return ZERO
176
+
177
+ // Fast path: numbers < 1000 (direct lookup)
178
+ if (n < 1000n) {
179
+ const segments = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
180
+ return segments[Number(n)]
95
181
  }
96
182
 
97
- /**
98
- * Implements Polish-specific three-form pluralization rules.
99
- *
100
- * Polish three-form system:
101
- * - Form 1 (singular): exactly n=1 (e.g., "tysiąc")
102
- * - Form 2 (few): n ends in 2-4, excluding teens (22-24, 32-34...) (e.g., "tysiące")
103
- * - Form 3 (many): all other numbers (e.g., "tysięcy")
104
- *
105
- * @param {bigint} n The number to classify.
106
- * @param {Array<string>} forms Array of [singular, few, many] word forms.
107
- * @returns {string} The appropriate form for the number n.
108
- */
109
- pluralize (n, forms) {
110
- if (n === 1n) {
111
- return forms[0]
112
- }
183
+ // Fast path: numbers < 1,000,000 (thousands)
184
+ if (n < 1_000_000n) {
185
+ const thousands = Number(n / 1000n)
186
+ const remainder = Number(n % 1000n)
187
+
188
+ const scaleWord = pluralize(BigInt(thousands), PLURAL_FORMS[1])
113
189
 
114
- const lastDigit = n % 10n
115
- const lastTwoDigits = n % 100n
190
+ let result
191
+ if (thousands === 1) {
192
+ // Omit "jeden" before tysiąc
193
+ result = scaleWord
194
+ } else {
195
+ result = SEGMENTS_MASC[thousands] + ' ' + scaleWord
196
+ }
116
197
 
117
- if (lastDigit < 5n && lastDigit > 1n && (lastTwoDigits < 10n || lastTwoDigits > 20n)) {
118
- return forms[1]
198
+ if (remainder > 0) {
199
+ result += ' ' + SEGMENTS_MASC[remainder]
119
200
  }
120
201
 
121
- return forms[2]
202
+ return result
122
203
  }
123
204
 
124
- /**
125
- * Converts a whole number to Polish cardinal form.
126
- *
127
- * Algorithm (chunk-based decomposition):
128
- * 1. Split number into chunks of 3 digits (right-to-left): ones, thousands, millions, etc.
129
- * 2. For each non-zero chunk:
130
- * a. Extract hundreds digit (n3), tens digit (n2), ones digit (n1)
131
- * b. Add hundreds word (e.g., "sto", "dwieście")
132
- * c. Add tens/ones (handles teens 10-19 separately from compound tens)
133
- * d. Add pluralized magnitude word (e.g., "tysiące", "miliony")
134
- * 3. Join all words with spaces
135
- *
136
- * Example: 1234 → chunks [1, 234] → "jeden tysiąc dwieście trzydzieści cztery"
137
- * - Chunk 1 (index=1, thousands): "jeden" + "tysiąc"
138
- * - Chunk 234 (index=0): "dwieście" + "trzydzieści" + "cztery"
139
- *
140
- * Special case: When chunk=1 at thousands+ levels, omit the ones word to avoid
141
- * redundancy with the pluralized magnitude (e.g., just "tysiąc" not "jeden tysiąc").
142
- *
143
- * @param {bigint} number The whole number to convert.
144
- * @returns {string} The number expressed in Polish words.
145
- */
146
- convertWholePart (number) {
147
- if (number === 0n) {
148
- return this.zeroWord
149
- }
150
- const words = []
151
- const chunks = this.splitByX(number.toString(), 3)
152
- let index = chunks.length
153
- for (const x of chunks) {
154
- index = index - 1
155
- if (x === 0n) {
156
- continue
157
- }
158
- const [n1, n2, n3] = this.getDigits(x)
159
- if (n3 > 0n) {
160
- words.push(this.hundreds[n3])
161
- }
162
- if (n2 > 1n) {
163
- words.push(this.twenties[n2])
164
- }
165
- if (n2 === 1n) {
166
- words.push(this.tens[n1])
167
- } else if (n1 > 0n && !(index > 0 && x === 1n)) {
168
- words.push(this.ones[n1])
169
- }
170
- if (index > 0) {
171
- words.push(this.pluralize(x, this.thousands[index]))
205
+ // For numbers >= 1,000,000, use scale decomposition
206
+ return buildLargeNumberWords(n, options)
207
+ }
208
+
209
+ /**
210
+ * Builds words for numbers >= 1,000,000.
211
+ * Uses BigInt division for faster segment extraction.
212
+ *
213
+ * @param {bigint} n - Number >= 1,000,000
214
+ * @param {Object} options - Conversion options
215
+ * @returns {string} Polish words
216
+ */
217
+ function buildLargeNumberWords (n, options) {
218
+ // Extract segments using BigInt division (faster than string slicing)
219
+ // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)
220
+ const segmentValues = []
221
+ let temp = n
222
+ while (temp > 0n) {
223
+ segmentValues.push(temp % 1000n)
224
+ temp = temp / 1000n
225
+ }
226
+
227
+ // Build result string directly
228
+ let result = ''
229
+
230
+ for (let i = segmentValues.length - 1; i >= 0; i--) {
231
+ const segment = segmentValues[i]
232
+ if (segment === 0n) continue
233
+
234
+ const segmentWord = SEGMENTS_MASC[Number(segment)]
235
+
236
+ if (result) result += ' '
237
+
238
+ if (i === 0) {
239
+ // Units segment
240
+ result += segmentWord
241
+ } else {
242
+ // Scale word needed
243
+ const forms = PLURAL_FORMS[i]
244
+ if (forms) {
245
+ const scaleWord = pluralize(segment, forms)
246
+
247
+ if (segment === 1n) {
248
+ // Omit "jeden" before scale words
249
+ result += scaleWord
250
+ } else {
251
+ result += segmentWord + ' ' + scaleWord
252
+ }
172
253
  }
173
254
  }
174
- return words.join(' ')
175
255
  }
256
+
257
+ return result
176
258
  }
177
259
 
178
260
  /**
179
- * Converts a number to Polish cardinal (written) form.
261
+ * Converts decimal digits to Polish words.
180
262
  *
181
- * @param {number|string|bigint} value The number to convert.
182
- * @param {SlavicOptions} [options={}] Configuration options.
183
- * @returns {string} The number expressed in Polish words.
184
- * @throws {TypeError} If value is NaN or invalid type.
185
- * @throws {Error} If value is an invalid number string.
263
+ * @param {string} decimalPart - Decimal digits (without the point)
264
+ * @param {Object} options - Conversion options
265
+ * @returns {string} Polish words for decimal part
186
266
  */
187
- export default function convertToWords (value, options = {}) {
188
- return new Polish(options).convertToWords(value)
267
+ function decimalPartToWords (decimalPart, options) {
268
+ let result = ''
269
+
270
+ // Handle leading zeros
271
+ let i = 0
272
+ while (i < decimalPart.length && decimalPart[i] === '0') {
273
+ if (result) result += ' '
274
+ result += ZERO
275
+ i++
276
+ }
277
+
278
+ // Convert remainder as a single number
279
+ const remainder = decimalPart.slice(i)
280
+ if (remainder) {
281
+ if (result) result += ' '
282
+ result += integerToWords(BigInt(remainder), options)
283
+ }
284
+
285
+ return result
189
286
  }
287
+
288
+ /**
289
+ * Converts a numeric value to Polish words.
290
+ *
291
+ * This is the main public API. It accepts any valid numeric input
292
+ * (number, string, or bigint) and handles parsing internally.
293
+ *
294
+ * @param {number | string | bigint} value - The numeric value to convert
295
+ * @param {Object} [options] - Conversion options
296
+ * @param {string} [options.gender='masculine'] - Gender for numbers < 1000
297
+ * @returns {string} The number in Polish words
298
+ * @throws {TypeError} If value is not a valid numeric type
299
+ * @throws {Error} If value is not a valid number format
300
+ *
301
+ * @example
302
+ * toWords(1) // 'jeden'
303
+ * toWords(1, { gender: 'feminine' }) // 'jedna'
304
+ * toWords(1000) // 'tysiąc'
305
+ * toWords(2000) // 'dwa tysiące'
306
+ */
307
+ function toWords (value, options) {
308
+ options = validateOptions(options)
309
+ const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
310
+
311
+ let result = ''
312
+
313
+ if (isNegative) {
314
+ result = NEGATIVE + ' '
315
+ }
316
+
317
+ result += integerToWords(integerPart, options)
318
+
319
+ if (decimalPart) {
320
+ result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, options)
321
+ }
322
+
323
+ return result
324
+ }
325
+
326
+ // ============================================================================
327
+ // Public API
328
+ // ============================================================================
329
+
330
+ export { toWords }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Converts a numeric value to Portuguese words.
3
+ *
4
+ * This is the main public API. It accepts any valid numeric input
5
+ * (number, string, or bigint) and handles parsing internally.
6
+ *
7
+ * @param {number | string | bigint} value - The numeric value to convert
8
+ * @returns {string} The number in Portuguese words
9
+ * @throws {TypeError} If value is not a valid numeric type
10
+ * @throws {Error} If value is not a valid number format
11
+ *
12
+ * @example
13
+ * toWords(21) // 'vinte e um'
14
+ * toWords(100) // 'cem'
15
+ * toWords(1000000) // 'um milhão'
16
+ */
17
+ export function toWords(value: number | string | bigint): string;