n2words 1.23.0 → 1.24.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 (317) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +182 -53
  3. package/dist/languages/ar.js +2 -0
  4. package/dist/languages/ar.js.map +1 -0
  5. package/dist/languages/az.js +2 -0
  6. package/dist/languages/az.js.map +1 -0
  7. package/dist/languages/bn.js +2 -0
  8. package/dist/languages/bn.js.map +1 -0
  9. package/dist/languages/cs.js +2 -0
  10. package/dist/languages/cs.js.map +1 -0
  11. package/dist/languages/da.js +2 -0
  12. package/dist/languages/da.js.map +1 -0
  13. package/dist/languages/de.js +2 -0
  14. package/dist/languages/de.js.map +1 -0
  15. package/dist/languages/el.js +2 -0
  16. package/dist/languages/el.js.map +1 -0
  17. package/dist/languages/en.js +2 -0
  18. package/dist/languages/en.js.map +1 -0
  19. package/dist/languages/es.js +2 -0
  20. package/dist/languages/es.js.map +1 -0
  21. package/dist/languages/fa.js +2 -0
  22. package/dist/languages/fa.js.map +1 -0
  23. package/dist/languages/fil.js +2 -0
  24. package/dist/languages/fil.js.map +1 -0
  25. package/dist/languages/fr-BE.js +2 -0
  26. package/dist/languages/fr-BE.js.map +1 -0
  27. package/dist/languages/fr.js +2 -0
  28. package/dist/languages/fr.js.map +1 -0
  29. package/dist/languages/gu.js +2 -0
  30. package/dist/languages/gu.js.map +1 -0
  31. package/dist/languages/he.js +2 -0
  32. package/dist/languages/he.js.map +1 -0
  33. package/dist/languages/hi.js +2 -0
  34. package/dist/languages/hi.js.map +1 -0
  35. package/dist/languages/hr.js +2 -0
  36. package/dist/languages/hr.js.map +1 -0
  37. package/dist/languages/hu.js +2 -0
  38. package/dist/languages/hu.js.map +1 -0
  39. package/dist/languages/id.js +2 -0
  40. package/dist/languages/id.js.map +1 -0
  41. package/dist/languages/it.js +2 -0
  42. package/dist/languages/it.js.map +1 -0
  43. package/dist/languages/ja.js +2 -0
  44. package/dist/languages/ja.js.map +1 -0
  45. package/dist/languages/kn.js +2 -0
  46. package/dist/languages/kn.js.map +1 -0
  47. package/dist/languages/ko.js +2 -0
  48. package/dist/languages/ko.js.map +1 -0
  49. package/dist/languages/lt.js +2 -0
  50. package/dist/languages/lt.js.map +1 -0
  51. package/dist/languages/lv.js +2 -0
  52. package/dist/languages/lv.js.map +1 -0
  53. package/dist/languages/mr.js +2 -0
  54. package/dist/languages/mr.js.map +1 -0
  55. package/dist/languages/ms.js +2 -0
  56. package/dist/languages/ms.js.map +1 -0
  57. package/dist/languages/nb.js +2 -0
  58. package/dist/languages/nb.js.map +1 -0
  59. package/dist/languages/nl.js +2 -0
  60. package/dist/languages/nl.js.map +1 -0
  61. package/dist/languages/pa-Guru.js +2 -0
  62. package/dist/languages/pa-Guru.js.map +1 -0
  63. package/dist/languages/pl.js +2 -0
  64. package/dist/languages/pl.js.map +1 -0
  65. package/dist/languages/pt.js +2 -0
  66. package/dist/languages/pt.js.map +1 -0
  67. package/dist/languages/ro.js +2 -0
  68. package/dist/languages/ro.js.map +1 -0
  69. package/dist/languages/ru.js +2 -0
  70. package/dist/languages/ru.js.map +1 -0
  71. package/dist/languages/sr-Latn.js +2 -0
  72. package/dist/languages/sr-Latn.js.map +1 -0
  73. package/dist/languages/sv.js +2 -0
  74. package/dist/languages/sv.js.map +1 -0
  75. package/dist/languages/sw.js +2 -0
  76. package/dist/languages/sw.js.map +1 -0
  77. package/dist/languages/ta.js +2 -0
  78. package/dist/languages/ta.js.map +1 -0
  79. package/dist/languages/te.js +2 -0
  80. package/dist/languages/te.js.map +1 -0
  81. package/dist/languages/th.js +2 -0
  82. package/dist/languages/th.js.map +1 -0
  83. package/dist/languages/tr.js +2 -0
  84. package/dist/languages/tr.js.map +1 -0
  85. package/dist/languages/uk.js +2 -0
  86. package/dist/languages/uk.js.map +1 -0
  87. package/dist/languages/ur.js +2 -0
  88. package/dist/languages/ur.js.map +1 -0
  89. package/dist/languages/vi.js +2 -0
  90. package/dist/languages/vi.js.map +1 -0
  91. package/dist/languages/zh-Hans.js +2 -0
  92. package/dist/languages/zh-Hans.js.map +1 -0
  93. package/dist/n2words.js +1 -1
  94. package/dist/n2words.js.map +1 -1
  95. package/lib/classes/abstract-language.js +211 -110
  96. package/lib/classes/greedy-scale-language.js +195 -0
  97. package/lib/classes/slavic-language.js +251 -0
  98. package/lib/classes/south-asian-language.js +161 -0
  99. package/lib/classes/turkic-language.js +63 -0
  100. package/lib/languages/ar.js +243 -0
  101. package/lib/languages/az.js +58 -0
  102. package/lib/languages/bn.js +126 -0
  103. package/lib/languages/cs.js +212 -0
  104. package/lib/languages/da.js +167 -0
  105. package/lib/languages/de.js +135 -0
  106. package/lib/languages/el.js +116 -0
  107. package/lib/languages/en.js +123 -0
  108. package/lib/languages/es.js +153 -0
  109. package/lib/languages/fa.js +127 -0
  110. package/lib/languages/fil.js +162 -0
  111. package/lib/languages/fr-BE.js +61 -0
  112. package/lib/languages/fr.js +145 -0
  113. package/lib/languages/gu.js +156 -0
  114. package/lib/languages/he.js +329 -0
  115. package/lib/languages/hi.js +126 -0
  116. package/lib/languages/hr.js +157 -0
  117. package/lib/languages/hu.js +155 -0
  118. package/lib/languages/id.js +174 -0
  119. package/lib/languages/it.js +148 -0
  120. package/lib/languages/ja.js +190 -0
  121. package/lib/languages/kn.js +71 -0
  122. package/lib/languages/ko.js +83 -0
  123. package/lib/languages/lt.js +171 -0
  124. package/lib/languages/lv.js +153 -0
  125. package/lib/languages/mr.js +156 -0
  126. package/lib/languages/ms.js +146 -0
  127. package/lib/languages/nb.js +120 -0
  128. package/lib/languages/nl.js +206 -0
  129. package/lib/languages/pa-Guru.js +126 -0
  130. package/lib/languages/pl.js +189 -0
  131. package/lib/languages/pt.js +147 -0
  132. package/lib/languages/ro.js +380 -0
  133. package/lib/languages/ru.js +116 -0
  134. package/lib/languages/sr-Latn.js +157 -0
  135. package/lib/languages/sv.js +127 -0
  136. package/lib/languages/sw.js +121 -0
  137. package/lib/languages/ta.js +226 -0
  138. package/lib/languages/te.js +229 -0
  139. package/lib/languages/th.js +123 -0
  140. package/lib/languages/tr.js +83 -0
  141. package/lib/{i18n → languages}/uk.js +50 -23
  142. package/lib/languages/ur.js +126 -0
  143. package/lib/languages/vi.js +193 -0
  144. package/lib/languages/zh-Hans.js +165 -0
  145. package/lib/n2words.js +246 -75
  146. package/package.json +80 -72
  147. package/typings/classes/abstract-language.d.ts +144 -0
  148. package/typings/classes/greedy-scale-language.d.ts +148 -0
  149. package/typings/classes/slavic-language.d.ts +145 -0
  150. package/typings/classes/south-asian-language.d.ts +101 -0
  151. package/typings/classes/turkic-language.d.ts +42 -0
  152. package/typings/languages/ar.d.ts +93 -0
  153. package/typings/languages/az.d.ts +25 -0
  154. package/typings/languages/bn.d.ts +1 -0
  155. package/typings/languages/cs.d.ts +120 -0
  156. package/typings/languages/da.d.ts +53 -0
  157. package/typings/languages/de.d.ts +26 -0
  158. package/typings/languages/el.d.ts +11 -0
  159. package/typings/languages/en.d.ts +30 -0
  160. package/typings/languages/es.d.ts +43 -0
  161. package/typings/languages/fa.d.ts +81 -0
  162. package/typings/languages/fil.d.ts +12 -0
  163. package/typings/languages/fr-BE.d.ts +41 -0
  164. package/typings/languages/fr.d.ts +43 -0
  165. package/typings/languages/gu.d.ts +12 -0
  166. package/typings/languages/he.d.ts +197 -0
  167. package/typings/languages/hi.d.ts +1 -0
  168. package/typings/languages/hr.d.ts +110 -0
  169. package/typings/languages/hu.d.ts +37 -0
  170. package/typings/languages/id.d.ts +69 -0
  171. package/typings/languages/it.d.ts +51 -0
  172. package/typings/languages/ja.d.ts +58 -0
  173. package/typings/languages/kn.d.ts +11 -0
  174. package/typings/languages/ko.d.ts +25 -0
  175. package/typings/languages/lt.d.ts +110 -0
  176. package/typings/languages/lv.d.ts +99 -0
  177. package/typings/languages/mr.d.ts +12 -0
  178. package/typings/languages/ms.d.ts +37 -0
  179. package/typings/languages/nb.d.ts +27 -0
  180. package/typings/languages/nl.d.ts +65 -0
  181. package/typings/languages/pa-Guru.d.ts +1 -0
  182. package/typings/languages/pl.d.ts +116 -0
  183. package/typings/languages/pt.d.ts +39 -0
  184. package/typings/languages/ro.d.ts +229 -0
  185. package/typings/languages/ru.d.ts +108 -0
  186. package/typings/languages/sr-Latn.d.ts +98 -0
  187. package/typings/languages/sv.d.ts +30 -0
  188. package/typings/languages/sw.d.ts +1 -0
  189. package/typings/languages/ta.d.ts +1 -0
  190. package/typings/languages/te.d.ts +1 -0
  191. package/typings/languages/th.d.ts +1 -0
  192. package/typings/languages/tr.d.ts +46 -0
  193. package/typings/languages/uk.d.ts +117 -0
  194. package/typings/languages/ur.d.ts +1 -0
  195. package/typings/languages/vi.d.ts +116 -0
  196. package/typings/languages/zh-Hans.d.ts +57 -0
  197. package/typings/n2words.d.ts +177 -0
  198. package/dist/ar.js +0 -2
  199. package/dist/ar.js.map +0 -1
  200. package/dist/az.js +0 -2
  201. package/dist/az.js.map +0 -1
  202. package/dist/cz.js +0 -2
  203. package/dist/cz.js.map +0 -1
  204. package/dist/de.js +0 -2
  205. package/dist/de.js.map +0 -1
  206. package/dist/dk.js +0 -2
  207. package/dist/dk.js.map +0 -1
  208. package/dist/en.js +0 -2
  209. package/dist/en.js.map +0 -1
  210. package/dist/es.js +0 -2
  211. package/dist/es.js.map +0 -1
  212. package/dist/fa.js +0 -2
  213. package/dist/fa.js.map +0 -1
  214. package/dist/fr-BE.js +0 -2
  215. package/dist/fr-BE.js.map +0 -1
  216. package/dist/fr.js +0 -2
  217. package/dist/fr.js.map +0 -1
  218. package/dist/he.js +0 -2
  219. package/dist/he.js.map +0 -1
  220. package/dist/hr.js +0 -2
  221. package/dist/hr.js.map +0 -1
  222. package/dist/hu.js +0 -2
  223. package/dist/hu.js.map +0 -1
  224. package/dist/id.js +0 -2
  225. package/dist/id.js.map +0 -1
  226. package/dist/it.js +0 -2
  227. package/dist/it.js.map +0 -1
  228. package/dist/ko.js +0 -2
  229. package/dist/ko.js.map +0 -1
  230. package/dist/lt.js +0 -2
  231. package/dist/lt.js.map +0 -1
  232. package/dist/lv.js +0 -2
  233. package/dist/lv.js.map +0 -1
  234. package/dist/n2words.d.ts +0 -2
  235. package/dist/nl.js +0 -2
  236. package/dist/nl.js.map +0 -1
  237. package/dist/no.js +0 -2
  238. package/dist/no.js.map +0 -1
  239. package/dist/pl.js +0 -2
  240. package/dist/pl.js.map +0 -1
  241. package/dist/pt.js +0 -2
  242. package/dist/pt.js.map +0 -1
  243. package/dist/ro.js +0 -2
  244. package/dist/ro.js.map +0 -1
  245. package/dist/ru.js +0 -2
  246. package/dist/ru.js.map +0 -1
  247. package/dist/sr.js +0 -2
  248. package/dist/sr.js.map +0 -1
  249. package/dist/tr.js +0 -2
  250. package/dist/tr.js.map +0 -1
  251. package/dist/uk.js +0 -2
  252. package/dist/uk.js.map +0 -1
  253. package/dist/vi.js +0 -2
  254. package/dist/vi.js.map +0 -1
  255. package/dist/zh.js +0 -2
  256. package/dist/zh.js.map +0 -1
  257. package/lib/classes/abstract-language.d.ts +0 -54
  258. package/lib/classes/base-language.d.ts +0 -58
  259. package/lib/classes/base-language.js +0 -172
  260. package/lib/i18n/ar.d.ts +0 -41
  261. package/lib/i18n/ar.js +0 -209
  262. package/lib/i18n/az.d.ts +0 -15
  263. package/lib/i18n/az.js +0 -66
  264. package/lib/i18n/cz.d.ts +0 -68
  265. package/lib/i18n/cz.js +0 -135
  266. package/lib/i18n/de.d.ts +0 -17
  267. package/lib/i18n/de.js +0 -103
  268. package/lib/i18n/dk.d.ts +0 -14
  269. package/lib/i18n/dk.js +0 -110
  270. package/lib/i18n/en.d.ts +0 -22
  271. package/lib/i18n/en.js +0 -86
  272. package/lib/i18n/es.d.ts +0 -16
  273. package/lib/i18n/es.js +0 -110
  274. package/lib/i18n/fa.d.ts +0 -54
  275. package/lib/i18n/fa.js +0 -106
  276. package/lib/i18n/fr-BE.d.ts +0 -11
  277. package/lib/i18n/fr-BE.js +0 -20
  278. package/lib/i18n/fr.d.ts +0 -15
  279. package/lib/i18n/fr.js +0 -99
  280. package/lib/i18n/he.d.ts +0 -61
  281. package/lib/i18n/he.js +0 -132
  282. package/lib/i18n/hr.d.ts +0 -68
  283. package/lib/i18n/hr.js +0 -129
  284. package/lib/i18n/hu.d.ts +0 -17
  285. package/lib/i18n/hu.js +0 -135
  286. package/lib/i18n/id.d.ts +0 -43
  287. package/lib/i18n/id.js +0 -156
  288. package/lib/i18n/it.d.ts +0 -29
  289. package/lib/i18n/it.js +0 -137
  290. package/lib/i18n/ko.d.ts +0 -15
  291. package/lib/i18n/ko.js +0 -56
  292. package/lib/i18n/lt.d.ts +0 -68
  293. package/lib/i18n/lt.js +0 -138
  294. package/lib/i18n/lv.d.ts +0 -57
  295. package/lib/i18n/lv.js +0 -120
  296. package/lib/i18n/nl.d.ts +0 -20
  297. package/lib/i18n/nl.js +0 -125
  298. package/lib/i18n/no.d.ts +0 -15
  299. package/lib/i18n/no.js +0 -77
  300. package/lib/i18n/pl.d.ts +0 -67
  301. package/lib/i18n/pl.js +0 -126
  302. package/lib/i18n/pt.d.ts +0 -26
  303. package/lib/i18n/pt.js +0 -118
  304. package/lib/i18n/ro.d.ts +0 -109
  305. package/lib/i18n/ro.js +0 -360
  306. package/lib/i18n/ru.d.ts +0 -30
  307. package/lib/i18n/ru.js +0 -198
  308. package/lib/i18n/sr.d.ts +0 -56
  309. package/lib/i18n/sr.js +0 -127
  310. package/lib/i18n/tr.d.ts +0 -15
  311. package/lib/i18n/tr.js +0 -64
  312. package/lib/i18n/uk.d.ts +0 -78
  313. package/lib/i18n/vi.d.ts +0 -70
  314. package/lib/i18n/vi.js +0 -151
  315. package/lib/i18n/zh.d.ts +0 -18
  316. package/lib/i18n/zh.js +0 -78
  317. package/lib/n2words.d.ts +0 -9
@@ -0,0 +1,189 @@
1
+ import SlavicLanguage from '../classes/slavic-language.js'
2
+
3
+ /**
4
+ * @typedef {Object} SlavicOptions
5
+ * @property {boolean} [feminine=false] Use feminine forms for numbers.
6
+ */
7
+
8
+ /**
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.
31
+ */
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ęć'
46
+ }
47
+
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'
59
+ }
60
+
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'
70
+ }
71
+
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'
82
+ }
83
+
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
95
+ }
96
+
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
+ }
113
+
114
+ const lastDigit = n % 10n
115
+ const lastTwoDigits = n % 100n
116
+
117
+ if (lastDigit < 5n && lastDigit > 1n && (lastTwoDigits < 10n || lastTwoDigits > 20n)) {
118
+ return forms[1]
119
+ }
120
+
121
+ return forms[2]
122
+ }
123
+
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]))
172
+ }
173
+ }
174
+ return words.join(' ')
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Converts a number to Polish cardinal (written) form.
180
+ *
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.
186
+ */
187
+ export default function convertToWords (value, options = {}) {
188
+ return new Polish(options).convertToWords(value)
189
+ }
@@ -0,0 +1,147 @@
1
+ import GreedyScaleLanguage from '../classes/greedy-scale-language.js'
2
+
3
+ /**
4
+ * (European) Portuguese language converter.
5
+ *
6
+ * Features:
7
+ * - Gender-aware hundreds (hundredos, duzentos, etc.)
8
+ * - Million/Billion pluralization
9
+ * - "e" (and) conjunction for number combinations
10
+ * - Post-processing to normalize word flow
11
+ */
12
+ export class Portuguese extends GreedyScaleLanguage {
13
+ negativeWord = 'menos'
14
+ decimalSeparatorWord = 'vírgula'
15
+ zeroWord = 'zero'
16
+ scaleWordPairs = [
17
+ [1_000_000_000_000_000_000_000_000n, 'quatrilião'],
18
+ [1_000_000_000_000_000_000n, 'trilião'],
19
+ [1_000_000_000_000n, 'bilião'],
20
+ [1_000_000n, 'milião'],
21
+ [1000n, 'mil'],
22
+ [100n, 'cem'],
23
+ [90n, 'noventa'],
24
+ [80n, 'oitenta'],
25
+ [70n, 'setenta'],
26
+ [60n, 'sessenta'],
27
+ [50n, 'cinquenta'],
28
+ [40n, 'quarenta'],
29
+ [30n, 'trinta'],
30
+ [20n, 'vinte'],
31
+ [19n, 'dezanove'],
32
+ [18n, 'dezoito'],
33
+ [17n, 'dezassete'],
34
+ [16n, 'dezasseis'],
35
+ [15n, 'quinze'],
36
+ [14n, 'catorze'],
37
+ [13n, 'treze'],
38
+ [12n, 'doze'],
39
+ [11n, 'onze'],
40
+ [10n, 'dez'],
41
+ [9n, 'nove'],
42
+ [8n, 'oito'],
43
+ [7n, 'sete'],
44
+ [6n, 'seis'],
45
+ [5n, 'cinco'],
46
+ [4n, 'quatro'],
47
+ [3n, 'três'],
48
+ [2n, 'dois'],
49
+ [1n, 'um'],
50
+ [0n, 'zero']
51
+ ]
52
+
53
+ hundreds = {
54
+ 1: 'cento',
55
+ 2: 'duzentos',
56
+ 3: 'trezentos',
57
+ 4: 'quatrocentos',
58
+ 5: 'quinhentos',
59
+ 6: 'seiscentos',
60
+ 7: 'setecentos',
61
+ 8: 'oitocentos',
62
+ 9: 'novecentos'
63
+ }
64
+
65
+ // Pre-compiled regex patterns for postClean - avoid recompilation
66
+ static POSTCLEAN_REGEX = / e (.*entos?) (?=.*e)/g
67
+
68
+ finalizeWords (words) {
69
+ return words.replaceAll(Portuguese.POSTCLEAN_REGEX, ' $1 ')
70
+ }
71
+
72
+ /**
73
+ * Merges two adjacent word-number pairs according to Portuguese grammar rules.
74
+ *
75
+ * Portuguese-specific rules:
76
+ * - Implicit "um": `mergeScales({ 'um': 1n }, { 'mil': 1000n })` → `{ 'mil': 1000n }`
77
+ * - "e" (and) between most combinations: `mergeScales({ 'vinte': 20n }, { 'três': 3n })` → `{ 'vinte e três': 23n }`
78
+ * - Special handling: "cem" (100) becomes "cento" when followed by non-thousands
79
+ * - Gender-aware hundreds: "duzentos", "trezentos", etc.
80
+ * - Million pluralization: "milhões" instead of "milhão" when coefficient > 1
81
+ * - Post-processing with postClean() to normalize word flow
82
+ *
83
+ * @param {Object} current The left operand as `{ word: BigInt }`.
84
+ * @param {Object} next The right operand as `{ word: BigInt }`.
85
+ * @returns {Object} Merged pair with combined word and resulting numeric value.
86
+ *
87
+ * @example
88
+ * mergeScales({ 'um': 1n }, { 'mil': 1000n }); // { 'mil': 1000n }
89
+ * mergeScales({ 'vinte': 20n }, { 'dois': 2n }); // { 'vinte e dois': 22n }
90
+ */
91
+ mergeScales (current, next) {
92
+ // Extract words and numeric values
93
+ let cText = Object.keys(current)[0]
94
+ let nText = Object.keys(next)[0]
95
+ const cNumber = Object.values(current)[0] // BigInt
96
+ const nNumber = Object.values(next)[0] // BigInt
97
+
98
+ // Implicit "um": omit before millions ("um milhão" → "milhão")
99
+ if (cNumber === 1n) {
100
+ if (nNumber < 1_000_000n) return { [nText]: nNumber }
101
+ cText = 'um'
102
+ } else if (cNumber === 100n && nNumber % 1000n !== 0n) {
103
+ // Special handling: "cem" (100) becomes "cento" when followed by non-thousands
104
+ cText = 'cento'
105
+ }
106
+
107
+ if (nNumber < cNumber) {
108
+ return { [`${cText} e ${nText}`]: cNumber + nNumber }
109
+ }
110
+
111
+ // Handle "milião" -> "milhão" conversion
112
+ if (nText === 'milião') nText = 'milhão'
113
+
114
+ // Pluralization logic for large numbers
115
+ if (cNumber > 1n) {
116
+ if (nNumber % 1_000_000_000n === 0n) {
117
+ nText = nText.replace('bilião', 'biliões')
118
+ } else if (nNumber % 1_000_000n === 0n) {
119
+ nText = nText.replace('milhão', 'milhões')
120
+ }
121
+ }
122
+
123
+ if (nNumber === 100n) {
124
+ cText = this.hundreds[cNumber]
125
+ return { [`${cText}`]: cNumber * nNumber }
126
+ }
127
+
128
+ return { [`${cText} ${nText}`]: cNumber * nNumber }
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Converts a number to Portuguese cardinal (written) form.
134
+ *
135
+ * @param {number|string|bigint} value The number to convert.
136
+ * @param {Object} [options] Conversion options (see Portuguese class options).
137
+ * @returns {string} The number expressed in Portuguese words.
138
+ * @throws {TypeError} If value is NaN or invalid type.
139
+ * @throws {Error} If value is an invalid number string.
140
+ *
141
+ * @example
142
+ * convertToWords(42); // 'quarenta e dois'
143
+ * convertToWords('100.5'); // 'cem vírgula cinco'
144
+ */
145
+ export default function convertToWords (value, options = {}) {
146
+ return new Portuguese(options).convertToWords(value)
147
+ }