n2words 2.0.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 (327) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +86 -188
  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 -0
  8. package/dist/languages/ar.js.map +1 -0
  9. package/dist/languages/az.js +3 -0
  10. package/dist/languages/az.js.map +1 -0
  11. package/dist/languages/bn.js +3 -0
  12. package/dist/languages/bn.js.map +1 -0
  13. package/dist/languages/cs.js +3 -0
  14. package/dist/languages/cs.js.map +1 -0
  15. package/dist/languages/da.js +3 -0
  16. package/dist/languages/da.js.map +1 -0
  17. package/dist/languages/de.js +3 -0
  18. package/dist/languages/de.js.map +1 -0
  19. package/dist/languages/el.js +3 -0
  20. package/dist/languages/el.js.map +1 -0
  21. package/dist/languages/en.js +3 -0
  22. package/dist/languages/en.js.map +1 -0
  23. package/dist/languages/es.js +3 -0
  24. package/dist/languages/es.js.map +1 -0
  25. package/dist/languages/fa.js +3 -0
  26. package/dist/languages/fa.js.map +1 -0
  27. package/dist/languages/fi.js +3 -0
  28. package/dist/languages/fi.js.map +1 -0
  29. package/dist/languages/fil.js +3 -0
  30. package/dist/languages/fil.js.map +1 -0
  31. package/dist/languages/fr-BE.js +3 -0
  32. package/dist/languages/fr-BE.js.map +1 -0
  33. package/dist/languages/fr.js +3 -0
  34. package/dist/languages/fr.js.map +1 -0
  35. package/dist/languages/gu.js +3 -0
  36. package/dist/languages/gu.js.map +1 -0
  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 -0
  42. package/dist/languages/he.js.map +1 -0
  43. package/dist/languages/hi.js +3 -0
  44. package/dist/languages/hi.js.map +1 -0
  45. package/dist/languages/hr.js +3 -0
  46. package/dist/languages/hr.js.map +1 -0
  47. package/dist/languages/hu.js +3 -0
  48. package/dist/languages/hu.js.map +1 -0
  49. package/dist/languages/id.js +3 -0
  50. package/dist/languages/id.js.map +1 -0
  51. package/dist/languages/it.js +3 -0
  52. package/dist/languages/it.js.map +1 -0
  53. package/dist/languages/ja.js +3 -0
  54. package/dist/languages/ja.js.map +1 -0
  55. package/dist/languages/kn.js +3 -0
  56. package/dist/languages/kn.js.map +1 -0
  57. package/dist/languages/ko.js +3 -0
  58. package/dist/languages/ko.js.map +1 -0
  59. package/dist/languages/lt.js +3 -0
  60. package/dist/languages/lt.js.map +1 -0
  61. package/dist/languages/lv.js +3 -0
  62. package/dist/languages/lv.js.map +1 -0
  63. package/dist/languages/mr.js +3 -0
  64. package/dist/languages/mr.js.map +1 -0
  65. package/dist/languages/ms.js +3 -0
  66. package/dist/languages/ms.js.map +1 -0
  67. package/dist/languages/nb.js +3 -0
  68. package/dist/languages/nb.js.map +1 -0
  69. package/dist/languages/nl.js +3 -0
  70. package/dist/languages/nl.js.map +1 -0
  71. package/dist/languages/pa.js +3 -0
  72. package/dist/languages/pa.js.map +1 -0
  73. package/dist/languages/pl.js +3 -0
  74. package/dist/languages/pl.js.map +1 -0
  75. package/dist/languages/pt.js +3 -0
  76. package/dist/languages/pt.js.map +1 -0
  77. package/dist/languages/ro.js +3 -0
  78. package/dist/languages/ro.js.map +1 -0
  79. package/dist/languages/ru.js +3 -0
  80. package/dist/languages/ru.js.map +1 -0
  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 -0
  84. package/dist/languages/sr-Latn.js.map +1 -0
  85. package/dist/languages/sv.js +3 -0
  86. package/dist/languages/sv.js.map +1 -0
  87. package/dist/languages/sw.js +3 -0
  88. package/dist/languages/sw.js.map +1 -0
  89. package/dist/languages/ta.js +3 -0
  90. package/dist/languages/ta.js.map +1 -0
  91. package/dist/languages/te.js +3 -0
  92. package/dist/languages/te.js.map +1 -0
  93. package/dist/languages/th.js +3 -0
  94. package/dist/languages/th.js.map +1 -0
  95. package/dist/languages/tr.js +3 -0
  96. package/dist/languages/tr.js.map +1 -0
  97. package/dist/languages/uk.js +3 -0
  98. package/dist/languages/uk.js.map +1 -0
  99. package/dist/languages/ur.js +3 -0
  100. package/dist/languages/ur.js.map +1 -0
  101. package/dist/languages/vi.js +3 -0
  102. package/dist/languages/vi.js.map +1 -0
  103. package/dist/languages/zh-Hans.js +3 -0
  104. package/dist/languages/zh-Hans.js.map +1 -0
  105. package/dist/languages/zh-Hant.js +3 -0
  106. package/dist/languages/zh-Hant.js.map +1 -0
  107. package/dist/n2words.js +2 -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 +14 -27
  114. package/lib/languages/ar.js +175 -129
  115. package/lib/languages/az.d.ts +4 -9
  116. package/lib/languages/az.js +171 -37
  117. package/lib/languages/bn.d.ts +4 -8
  118. package/lib/languages/bn.js +138 -124
  119. package/lib/languages/cs.d.ts +15 -85
  120. package/lib/languages/cs.js +310 -114
  121. package/lib/languages/da.d.ts +11 -12
  122. package/lib/languages/da.js +276 -101
  123. package/lib/languages/de.d.ts +14 -11
  124. package/lib/languages/de.js +317 -86
  125. package/lib/languages/el.d.ts +11 -11
  126. package/lib/languages/el.js +231 -78
  127. package/lib/languages/en.d.ts +14 -13
  128. package/lib/languages/en.js +242 -72
  129. package/lib/languages/es.d.ts +18 -12
  130. package/lib/languages/es.js +317 -103
  131. package/lib/languages/fa.d.ts +4 -44
  132. package/lib/languages/fa.js +112 -122
  133. package/lib/languages/fi.d.ts +14 -0
  134. package/lib/languages/fi.js +245 -0
  135. package/lib/languages/fil.d.ts +4 -13
  136. package/lib/languages/fil.js +207 -106
  137. package/lib/languages/fr-BE.d.ts +8 -8
  138. package/lib/languages/fr-BE.js +294 -19
  139. package/lib/languages/fr.d.ts +18 -12
  140. package/lib/languages/fr.js +352 -89
  141. package/lib/languages/gu.d.ts +4 -8
  142. package/lib/languages/gu.js +130 -125
  143. package/lib/languages/ha.d.ts +7 -0
  144. package/lib/languages/ha.js +230 -0
  145. package/lib/languages/hbo.d.ts +10 -110
  146. package/lib/languages/hbo.js +263 -214
  147. package/lib/languages/he.d.ts +10 -77
  148. package/lib/languages/he.js +242 -172
  149. package/lib/languages/hi.d.ts +4 -8
  150. package/lib/languages/hi.js +138 -124
  151. package/lib/languages/hr.d.ts +8 -77
  152. package/lib/languages/hr.js +194 -89
  153. package/lib/languages/hu.d.ts +4 -19
  154. package/lib/languages/hu.js +198 -119
  155. package/lib/languages/id.d.ts +4 -34
  156. package/lib/languages/id.js +171 -129
  157. package/lib/languages/it.d.ts +16 -34
  158. package/lib/languages/it.js +339 -94
  159. package/lib/languages/ja.d.ts +14 -14
  160. package/lib/languages/ja.js +233 -111
  161. package/lib/languages/kn.d.ts +4 -8
  162. package/lib/languages/kn.js +130 -35
  163. package/lib/languages/ko.d.ts +11 -11
  164. package/lib/languages/ko.js +257 -49
  165. package/lib/languages/lt.d.ts +15 -67
  166. package/lib/languages/lt.js +296 -122
  167. package/lib/languages/lv.d.ts +15 -67
  168. package/lib/languages/lv.js +297 -106
  169. package/lib/languages/mr.d.ts +4 -8
  170. package/lib/languages/mr.js +130 -125
  171. package/lib/languages/ms.d.ts +4 -28
  172. package/lib/languages/ms.js +171 -116
  173. package/lib/languages/nb.d.ts +11 -9
  174. package/lib/languages/nb.js +282 -87
  175. package/lib/languages/nl.d.ts +23 -13
  176. package/lib/languages/nl.js +317 -133
  177. package/lib/languages/pa.d.ts +4 -8
  178. package/lib/languages/pa.js +156 -124
  179. package/lib/languages/pl.d.ts +19 -77
  180. package/lib/languages/pl.js +307 -87
  181. package/lib/languages/pt.d.ts +14 -26
  182. package/lib/languages/pt.js +286 -92
  183. package/lib/languages/ro.d.ts +15 -155
  184. package/lib/languages/ro.js +219 -235
  185. package/lib/languages/ru.d.ts +8 -82
  186. package/lib/languages/ru.js +222 -78
  187. package/lib/languages/sr-Cyrl.d.ts +8 -77
  188. package/lib/languages/sr-Cyrl.js +191 -89
  189. package/lib/languages/sr-Latn.d.ts +8 -77
  190. package/lib/languages/sr-Latn.js +191 -89
  191. package/lib/languages/sv.d.ts +11 -11
  192. package/lib/languages/sv.js +288 -74
  193. package/lib/languages/sw.d.ts +4 -36
  194. package/lib/languages/sw.js +133 -106
  195. package/lib/languages/ta.d.ts +4 -17
  196. package/lib/languages/ta.js +129 -201
  197. package/lib/languages/te.d.ts +4 -19
  198. package/lib/languages/te.js +141 -196
  199. package/lib/languages/th.d.ts +4 -14
  200. package/lib/languages/th.js +135 -91
  201. package/lib/languages/tr.d.ts +15 -9
  202. package/lib/languages/tr.js +256 -49
  203. package/lib/languages/uk.d.ts +8 -82
  204. package/lib/languages/uk.js +200 -78
  205. package/lib/languages/ur.d.ts +4 -8
  206. package/lib/languages/ur.js +156 -124
  207. package/lib/languages/vi.d.ts +14 -69
  208. package/lib/languages/vi.js +294 -125
  209. package/lib/languages/zh-Hans.d.ts +8 -18
  210. package/lib/languages/zh-Hans.js +163 -92
  211. package/lib/languages/zh-Hant.d.ts +8 -18
  212. package/lib/languages/zh-Hant.js +181 -90
  213. package/lib/n2words.d.ts +53 -209
  214. package/lib/n2words.js +111 -530
  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 +26 -14
  222. package/dist/ArabicConverter.js +0 -3
  223. package/dist/ArabicConverter.js.map +0 -1
  224. package/dist/AzerbaijaniConverter.js +0 -3
  225. package/dist/AzerbaijaniConverter.js.map +0 -1
  226. package/dist/BanglaConverter.js +0 -3
  227. package/dist/BanglaConverter.js.map +0 -1
  228. package/dist/BiblicalHebrewConverter.js +0 -3
  229. package/dist/BiblicalHebrewConverter.js.map +0 -1
  230. package/dist/CroatianConverter.js +0 -3
  231. package/dist/CroatianConverter.js.map +0 -1
  232. package/dist/CzechConverter.js +0 -3
  233. package/dist/CzechConverter.js.map +0 -1
  234. package/dist/DanishConverter.js +0 -3
  235. package/dist/DanishConverter.js.map +0 -1
  236. package/dist/DutchConverter.js +0 -3
  237. package/dist/DutchConverter.js.map +0 -1
  238. package/dist/EnglishConverter.js +0 -3
  239. package/dist/EnglishConverter.js.map +0 -1
  240. package/dist/FilipinoConverter.js +0 -3
  241. package/dist/FilipinoConverter.js.map +0 -1
  242. package/dist/FrenchBelgiumConverter.js +0 -3
  243. package/dist/FrenchBelgiumConverter.js.map +0 -1
  244. package/dist/FrenchConverter.js +0 -3
  245. package/dist/FrenchConverter.js.map +0 -1
  246. package/dist/GermanConverter.js +0 -3
  247. package/dist/GermanConverter.js.map +0 -1
  248. package/dist/GreekConverter.js +0 -3
  249. package/dist/GreekConverter.js.map +0 -1
  250. package/dist/GujaratiConverter.js +0 -3
  251. package/dist/GujaratiConverter.js.map +0 -1
  252. package/dist/HebrewConverter.js +0 -3
  253. package/dist/HebrewConverter.js.map +0 -1
  254. package/dist/HindiConverter.js +0 -3
  255. package/dist/HindiConverter.js.map +0 -1
  256. package/dist/HungarianConverter.js +0 -3
  257. package/dist/HungarianConverter.js.map +0 -1
  258. package/dist/IndonesianConverter.js +0 -3
  259. package/dist/IndonesianConverter.js.map +0 -1
  260. package/dist/ItalianConverter.js +0 -3
  261. package/dist/ItalianConverter.js.map +0 -1
  262. package/dist/JapaneseConverter.js +0 -3
  263. package/dist/JapaneseConverter.js.map +0 -1
  264. package/dist/KannadaConverter.js +0 -3
  265. package/dist/KannadaConverter.js.map +0 -1
  266. package/dist/KoreanConverter.js +0 -3
  267. package/dist/KoreanConverter.js.map +0 -1
  268. package/dist/LatvianConverter.js +0 -3
  269. package/dist/LatvianConverter.js.map +0 -1
  270. package/dist/LithuanianConverter.js +0 -3
  271. package/dist/LithuanianConverter.js.map +0 -1
  272. package/dist/MalayConverter.js +0 -3
  273. package/dist/MalayConverter.js.map +0 -1
  274. package/dist/MarathiConverter.js +0 -3
  275. package/dist/MarathiConverter.js.map +0 -1
  276. package/dist/NorwegianBokmalConverter.js +0 -3
  277. package/dist/NorwegianBokmalConverter.js.map +0 -1
  278. package/dist/PersianConverter.js +0 -3
  279. package/dist/PersianConverter.js.map +0 -1
  280. package/dist/PolishConverter.js +0 -3
  281. package/dist/PolishConverter.js.map +0 -1
  282. package/dist/PortugueseConverter.js +0 -3
  283. package/dist/PortugueseConverter.js.map +0 -1
  284. package/dist/PunjabiConverter.js +0 -3
  285. package/dist/PunjabiConverter.js.map +0 -1
  286. package/dist/RomanianConverter.js +0 -3
  287. package/dist/RomanianConverter.js.map +0 -1
  288. package/dist/RussianConverter.js +0 -3
  289. package/dist/RussianConverter.js.map +0 -1
  290. package/dist/SerbianCyrillicConverter.js +0 -3
  291. package/dist/SerbianCyrillicConverter.js.map +0 -1
  292. package/dist/SerbianLatinConverter.js +0 -3
  293. package/dist/SerbianLatinConverter.js.map +0 -1
  294. package/dist/SimplifiedChineseConverter.js +0 -3
  295. package/dist/SimplifiedChineseConverter.js.map +0 -1
  296. package/dist/SpanishConverter.js +0 -3
  297. package/dist/SpanishConverter.js.map +0 -1
  298. package/dist/SwahiliConverter.js +0 -3
  299. package/dist/SwahiliConverter.js.map +0 -1
  300. package/dist/SwedishConverter.js +0 -3
  301. package/dist/SwedishConverter.js.map +0 -1
  302. package/dist/TamilConverter.js +0 -3
  303. package/dist/TamilConverter.js.map +0 -1
  304. package/dist/TeluguConverter.js +0 -3
  305. package/dist/TeluguConverter.js.map +0 -1
  306. package/dist/ThaiConverter.js +0 -3
  307. package/dist/ThaiConverter.js.map +0 -1
  308. package/dist/TraditionalChineseConverter.js +0 -3
  309. package/dist/TraditionalChineseConverter.js.map +0 -1
  310. package/dist/TurkishConverter.js +0 -3
  311. package/dist/TurkishConverter.js.map +0 -1
  312. package/dist/UkrainianConverter.js +0 -3
  313. package/dist/UkrainianConverter.js.map +0 -1
  314. package/dist/UrduConverter.js +0 -3
  315. package/dist/UrduConverter.js.map +0 -1
  316. package/dist/VietnameseConverter.js +0 -3
  317. package/dist/VietnameseConverter.js.map +0 -1
  318. package/lib/classes/abstract-language.d.ts +0 -178
  319. package/lib/classes/abstract-language.js +0 -268
  320. package/lib/classes/greedy-scale-language.d.ts +0 -109
  321. package/lib/classes/greedy-scale-language.js +0 -201
  322. package/lib/classes/slavic-language.d.ts +0 -148
  323. package/lib/classes/slavic-language.js +0 -281
  324. package/lib/classes/south-asian-language.d.ts +0 -70
  325. package/lib/classes/south-asian-language.js +0 -154
  326. package/lib/classes/turkic-language.d.ts +0 -26
  327. package/lib/classes/turkic-language.js +0 -59
@@ -0,0 +1 @@
1
+ {"version":3,"file":"az.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/az.js"],"sourcesContent":["/**\n * Numeric value parsing utility.\n * Transforms user input (number, string, or bigint) into normalized components.\n * @module parse-numeric\n */\n\n/**\n * Parses a numeric value into its components.\n * @param {number|string|bigint} value\n * @returns {{isNegative: boolean, integerPart: bigint, decimalPart?: string}}\n * @throws {TypeError} If value is not number, string, or bigint\n * @throws {Error} If value is not a valid number format\n */\nexport function parseNumericValue (value) {\n const type = typeof value\n\n // BigInt: simplest case\n if (type === 'bigint') {\n return value < 0n\n ? { isNegative: true, integerPart: -value }\n : { isNegative: false, integerPart: value }\n }\n\n // Number: fast path for safe integers\n if (type === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('Number must be finite (NaN and Infinity are not supported)')\n }\n if (Number.isSafeInteger(value)) {\n return value < 0\n ? { isNegative: true, integerPart: BigInt(-value) }\n : { isNegative: false, integerPart: BigInt(value) }\n }\n return parseNumericString(numberToString(value))\n }\n\n // String input\n if (type === 'string') {\n return parseNumericString(normalizeString(value))\n }\n\n throw new TypeError(\n `Invalid value type: expected number, string, or bigint, received ${type}`\n )\n}\n\n/**\n * Converts a number to decimal string, expanding scientific notation if needed.\n */\nfunction numberToString (value) {\n const str = value.toString()\n return (str.includes('e') || str.includes('E'))\n ? expandScientificNotation(str)\n : str\n}\n\n/**\n * Validates and normalizes a string numeric input.\n */\nfunction normalizeString (value) {\n const trimmed = value.trim()\n if (trimmed.length === 0 || Number.isNaN(Number(trimmed))) {\n throw new Error(`Invalid number format: \"${value}\"`)\n }\n return (trimmed.includes('e') || trimmed.includes('E'))\n ? expandScientificNotation(trimmed)\n : trimmed\n}\n\n/**\n * Parses a normalized numeric string into components.\n */\nfunction parseNumericString (str) {\n const isNegative = str[0] === '-'\n if (isNegative) str = str.slice(1)\n\n const dotIndex = str.indexOf('.')\n if (dotIndex === -1) {\n return { isNegative, integerPart: BigInt(str) }\n }\n\n const integerStr = str.slice(0, dotIndex) || '0'\n const decimalPart = str.slice(dotIndex + 1)\n return { isNegative, integerPart: BigInt(integerStr), decimalPart }\n}\n\n/**\n * Expands scientific notation to decimal form (e.g., \"1e21\" → \"1000...\").\n */\nfunction expandScientificNotation (str) {\n const [mantissa, expStr] = str.toLowerCase().split('e')\n const exp = parseInt(expStr, 10)\n\n const dotIndex = mantissa.indexOf('.')\n const digits = dotIndex === -1\n ? mantissa\n : mantissa.slice(0, dotIndex) + mantissa.slice(dotIndex + 1)\n const integerLength = dotIndex === -1 ? mantissa.length : dotIndex\n const newDotPosition = integerLength + exp\n\n if (newDotPosition >= digits.length) {\n return digits + '0'.repeat(newDotPosition - digits.length)\n }\n if (newDotPosition <= 0) {\n return '0.' + '0'.repeat(-newDotPosition) + digits\n }\n return digits.slice(0, newDotPosition) + '.' + digits.slice(newDotPosition)\n}\n","/**\n * Azerbaijani language converter - Functional Implementation\n *\n * Self-contained converter with precomputed lookup tables.\n *\n * Key features:\n * - Turkic language patterns\n * - Implicit \"bir\" (one) omission before hundreds and thousands\n * - Short scale naming\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary\n// ============================================================================\n\nconst ONES = ['', 'bir', 'iki', 'üç', 'dörd', 'beş', 'altı', 'yeddi', 'səkkiz', 'doqquz']\nconst TEENS = ['on', 'on bir', 'on iki', 'on üç', 'on dörd', 'on beş', 'on altı', 'on yeddi', 'on səkkiz', 'on doqquz']\nconst TENS = ['', '', 'iyirmi', 'otuz', 'qırx', 'əlli', 'altmış', 'yetmiş', 'səksən', 'doxsan']\n\nconst HUNDRED = 'yüz'\nconst THOUSAND = 'min'\n\nconst ZERO = 'sıfır'\nconst NEGATIVE = 'mənfi'\nconst DECIMAL_SEP = 'nöqtə'\n\n// Short scale\nconst SCALE_WORDS = ['', THOUSAND, 'milyon', 'milyar', 'trilyon', 'katrilyon', 'kentilyon']\n\n// ============================================================================\n// Precomputed Lookup Table\n// ============================================================================\n\nfunction buildSegment (n) {\n if (n === 0) return ''\n\n const ones = n % 10\n const tensDigit = Math.floor(n / 10) % 10\n const hundredsDigit = Math.floor(n / 100)\n\n const parts = []\n\n if (hundredsDigit > 0) {\n if (hundredsDigit === 1) {\n parts.push(HUNDRED)\n } else {\n parts.push(ONES[hundredsDigit] + ' ' + HUNDRED)\n }\n }\n\n if (tensDigit === 1) {\n parts.push(TEENS[ones])\n } else {\n if (tensDigit > 1) {\n parts.push(TENS[tensDigit])\n }\n if (ones > 0) {\n parts.push(ONES[ones])\n }\n }\n\n return parts.join(' ')\n}\n\nconst SEGMENTS = new Array(1000)\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS[i] = buildSegment(i)\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\nfunction integerToWords (n) {\n if (n === 0n) return ZERO\n\n if (n < 1000n) {\n return SEGMENTS[Number(n)]\n }\n\n return buildLargeNumberWords(n)\n}\n\nfunction buildLargeNumberWords (n) {\n const numStr = n.toString()\n const len = numStr.length\n\n const segments = []\n const segmentSize = 3\n\n const remainderLen = len % segmentSize\n let pos = 0\n if (remainderLen > 0) {\n segments.push(Number(numStr.slice(0, remainderLen)))\n pos = remainderLen\n }\n while (pos < len) {\n segments.push(Number(numStr.slice(pos, pos + segmentSize)))\n pos += segmentSize\n }\n\n const parts = []\n let scaleIndex = segments.length - 1\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]\n\n if (segment !== 0) {\n const scaleWord = SCALE_WORDS[scaleIndex] || ''\n\n if (scaleIndex === 0) {\n parts.push(SEGMENTS[segment])\n } else if (scaleIndex === 1 && segment === 1) {\n // Omit \"bir\" before thousand\n parts.push(scaleWord)\n } else {\n parts.push(SEGMENTS[segment] + ' ' + scaleWord)\n }\n }\n\n scaleIndex--\n }\n\n return parts.join(' ')\n}\n\nfunction decimalPartToWords (decimalPart) {\n let result = ''\n let i = 0\n\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder))\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Azerbaijani words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in Azerbaijani words\n */\nfunction toWords (value) {\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)\n }\n\n return result\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport { toWords }\n"],"names":["parseNumericString","str","isNegative","slice","dotIndex","indexOf","integerPart","BigInt","integerStr","decimalPart","expandScientificNotation","mantissa","expStr","toLowerCase","split","exp","parseInt","digits","newDotPosition","length","repeat","ONES","TEENS","TENS","ZERO","SCALE_WORDS","buildSegment","n","ones","tensDigit","Math","floor","hundredsDigit","parts","push","join","SEGMENTS","Array","i","integerToWords","Number","numStr","toString","len","segments","remainderLen","pos","scaleIndex","segment","scaleWord","buildLargeNumberWords","value","type","isFinite","Error","isSafeInteger","includes","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","result","NEGATIVE","remainder","decimalPartToWords"],"mappings":";0CAwEA,SAASA,EAAoBC,GAC3B,MAAMC,EAAwB,MAAXD,EAAI,GACnBC,IAAYD,EAAMA,EAAIE,MAAM,IAEhC,MAAMC,EAAWH,EAAII,QAAQ,KAC7B,IAAiB,IAAbD,EACF,MAAO,CAAEF,aAAYI,YAAaC,OAAON,IAG3C,MAAMO,EAAaP,EAAIE,MAAM,EAAGC,IAAa,IACvCK,EAAcR,EAAIE,MAAMC,EAAW,GACzC,MAAO,CAAEF,aAAYI,YAAaC,OAAOC,GAAaC,cACxD,CAKA,SAASC,EAA0BT,GACjC,MAAOU,EAAUC,GAAUX,EAAIY,cAAcC,MAAM,KAC7CC,EAAMC,SAASJ,EAAQ,IAEvBR,EAAWO,EAASN,QAAQ,KAC5BY,OAASb,EACXO,EACAA,EAASR,MAAM,EAAGC,GAAYO,EAASR,MAAMC,EAAW,GAEtDc,IAD6B,IAAbd,EAAkBO,EAASQ,OAASf,GACnBW,EAEvC,OAAIG,GAAkBD,EAAOE,OACpBF,EAAS,IAAIG,OAAOF,EAAiBD,EAAOE,QAEjDD,GAAkB,EACb,KAAO,IAAIE,QAAQF,GAAkBD,EAEvCA,EAAOd,MAAM,EAAGe,GAAkB,IAAMD,EAAOd,MAAMe,EAC9D,CC1FA,MAAMG,EAAO,CAAC,GAAI,MAAO,MAAO,KAAM,OAAQ,MAAO,OAAQ,QAAS,SAAU,UAC1EC,EAAQ,CAAC,KAAM,SAAU,SAAU,QAAS,UAAW,SAAU,UAAW,WAAY,YAAa,aACrGC,EAAO,CAAC,GAAI,GAAI,SAAU,OAAQ,OAAQ,OAAQ,SAAU,SAAU,SAAU,UAKhFC,EAAO,QAKPC,EAAc,CAAC,GAPJ,MAOkB,SAAU,SAAU,UAAW,YAAa,aAM/E,SAASC,EAAcC,GACrB,GAAU,IAANA,EAAS,MAAO,GAEpB,MAAMC,EAAOD,EAAI,GACXE,EAAYC,KAAKC,MAAMJ,EAAI,IAAM,GACjCK,EAAgBF,KAAKC,MAAMJ,EAAI,KAE/BM,EAAQ,GAqBd,OAnBID,EAAgB,GAEhBC,EAAMC,KADc,IAAlBF,EAxBQ,MA2BCX,EAAKW,GAALX,QAIG,IAAdQ,EACFI,EAAMC,KAAKZ,EAAMM,KAEbC,EAAY,GACdI,EAAMC,KAAKX,EAAKM,IAEdD,EAAO,GACTK,EAAMC,KAAKb,EAAKO,KAIbK,EAAME,KAAK,IACpB,CAEA,MAAMC,EAAW,IAAIC,MAAM,KAC3B,IAAK,IAAIC,EAAI,EAAGA,EAAI,IAAMA,IACxBF,EAASE,GAAKZ,EAAaY,GAO7B,SAASC,EAAgBZ,GACvB,OAAU,KAANA,EAAiBH,EAEjBG,EAAI,MACCS,EAASI,OAAOb,IAM3B,SAAgCA,GAC9B,MAAMc,EAASd,EAAEe,WACXC,EAAMF,EAAOtB,OAEbyB,EAAW,GAGXC,EAAeF,EAFD,EAGpB,IAAIG,EAAM,EAKV,IAJID,EAAe,IACjBD,EAASV,KAAKM,OAAOC,EAAOtC,MAAM,EAAG0C,KACrCC,EAAMD,GAEDC,EAAMH,GACXC,EAASV,KAAKM,OAAOC,EAAOtC,MAAM2C,EAAKA,EATrB,KAUlBA,GAVkB,EAapB,MAAMb,EAAQ,GACd,IAAIc,EAAaH,EAASzB,OAAS,EAEnC,IAAK,IAAImB,EAAI,EAAGA,EAAIM,EAASzB,OAAQmB,IAAK,CACxC,MAAMU,EAAUJ,EAASN,GAEzB,GAAgB,IAAZU,EAAe,CACjB,MAAMC,EAAYxB,EAAYsB,IAAe,GAG3Cd,EAAMC,KADW,IAAfa,EACSX,EAASY,GACI,IAAfD,GAAgC,IAAZC,EAElBC,EAEAb,EAASY,GAAW,IAAMC,EAEzC,CAEAF,GACF,CAEA,OAAOd,EAAME,KAAK,IACpB,CA5CSe,CAAsBvB,EAC/B,MAsEA,SAAkBwB,GAChB,MAAMjD,WAAEA,EAAUI,YAAEA,EAAWG,YAAEA,GD7I5B,SAA4B0C,GACjC,MAAMC,SAAcD,EAGpB,GAAa,WAATC,EACF,OAAOD,EAAQ,GACX,CAAEjD,YAAY,EAAMI,aAAc6C,GAClC,CAAEjD,YAAY,EAAOI,YAAa6C,GAIxC,GAAa,WAATC,EAAmB,CACrB,IAAKZ,OAAOa,SAASF,GACnB,MAAM,IAAIG,MAAM,8DAElB,OAAId,OAAOe,cAAcJ,GAChBA,EAAQ,EACX,CAAEjD,YAAY,EAAMI,YAAaC,QAAQ4C,IACzC,CAAEjD,YAAY,EAAOI,YAAaC,OAAO4C,IAExCnD,EAgBX,SAAyBmD,GACvB,MAAMlD,EAAMkD,EAAMT,WAClB,OAAQzC,EAAIuD,SAAS,MAAQvD,EAAIuD,SAAS,KACtC9C,EAAyBT,GACzBA,CACN,CArB8BwD,CAAeN,GAC3C,CAGA,GAAa,WAATC,EACF,OAAOpD,EAqBX,SAA0BmD,GACxB,MAAMO,EAAUP,EAAMQ,OACtB,GAAuB,IAAnBD,EAAQvC,QAAgBqB,OAAOoB,MAAMpB,OAAOkB,IAC9C,MAAM,IAAIJ,MAAM,2BAA2BH,MAE7C,OAAQO,EAAQF,SAAS,MAAQE,EAAQF,SAAS,KAC9C9C,EAAyBgD,GACzBA,CACN,CA7B8BG,CAAgBV,IAG5C,MAAM,IAAIW,UACR,oEAAoEV,IAExE,CC8GmDW,CAAkBZ,GAEnE,IAAIa,EAAS,GAYb,OAVI9D,IACF8D,EAASC,UAGXD,GAAUzB,EAAejC,GAErBG,IACFuD,GAAU,UArCd,SAA6BvD,GAC3B,IAAIuD,EAAS,GACT1B,EAAI,EAER,KAAOA,EAAI7B,EAAYU,QAA6B,MAAnBV,EAAY6B,IACvC0B,IAAQA,GAAU,KACtBA,GAAUxC,EACVc,IAGF,MAAM4B,EAAYzD,EAAYN,MAAMmC,GAMpC,OALI4B,IACEF,IAAQA,GAAU,KACtBA,GAAUzB,EAAehC,OAAO2D,KAG3BF,CACT,CAoBwCG,CAAmB1D,IAGlDuD,CACT"}
@@ -0,0 +1,3 @@
1
+ /*! n2words/bn v3.0.0 | MIT License | github.com/forzagreen/n2words */
2
+ var e,t;e=this,t=function(e){"use strict";function t(e){const t="-"===e[0];t&&(e=e.slice(1));const n=e.indexOf(".");if(-1===n)return{isNegative:t,integerPart:BigInt(e)};const i=e.slice(0,n)||"0",r=e.slice(n+1);return{isNegative:t,integerPart:BigInt(i),decimalPart:r}}function n(e){const[t,n]=e.toLowerCase().split("e"),i=parseInt(n,10),r=t.indexOf("."),s=-1===r?t:t.slice(0,r)+t.slice(r+1),o=(-1===r?t.length:r)+i;return o>=s.length?s+"0".repeat(o-s.length):o<=0?"0."+"0".repeat(-o)+s:s.slice(0,o)+"."+s.slice(o)}const i="শূন্য",r=["শূন্য","এক","দুই","তিন","চার","পাঁচ","ছয়","সাত","আট","নয়","দশ","এগারো","বারো","তেরো","চৌদ্দ","পনেরো","ষোল","সতেরো","আঠারো","উনিশ","বিশ","একুশ","বাইশ","তেইশ","চব্বিশ","পঁচিশ","ছাব্বিশ","সাতাশ","আঠাশ","উনত্রিশ","ত্রিশ","একত্রিশ","বত্রিশ","তেত্রিশ","চৌত্রিশ","পঁয়ত্রিশ","ছত্রিশ","সাঁইত্রিশ","আটত্রিশ","উনচল্লিশ","চল্লিশ","একচল্লিশ","বেয়াল্লিশ","তেতাল্লিশ","চুয়াল্লিশ","পঁয়তাল্লিশ","ছেচল্লিশ","সাতচল্লিশ","আটচল্লিশ","উনপঞ্চাশ","পঞ্চাশ","একান্ন","বাহান্ন","তিপ্পান্ন","চুয়ান্ন","পঞ্চান্ন","ছাপ্পান্ন","সাতান্ন","আটান্ন","উনষাট","ষাট","একষট্টি","বাষট্টি","তেষট্টি","চৌষট্টি","পঁয়ষট্টি","ছেষট্টি","সাতষট্টি","আটষট্টি","ঊনসত্তর","সত্তর","একাত্তর","বাহাত্তর","তেহাত্তর","চুয়াত্তর","পঁচাত্তর","ছিয়াত্তর","সাতাত্তর","আটাত্তর","উনআশি","আশি","একাশি","বিরাশি","তিরাশি","চুরাশি","পঁচাশি","ছিয়াশি","সাতাশি","আটাশি","উননব্বই","নব্বই","একানব্বই","বিরানব্বই","তিরানব্বই","চুরানব্বই","পঁচানব্বই","ছিয়ানব্বই","সাতানব্বই","আটানব্বই","নিরানব্বই"],s=["","হাজার","লাখ","কোটি","আরব","খরব","নীল","পদ্ম","শঙ্খ"];function o(e){if(0===e)return"";if(e<100)return r[e];const t=Math.trunc(e/100),n=e%100;return 0===n?r[t]+" শত":r[t]+" শত "+r[n]}function u(e){if(0n===e)return i;const t=function(e){const t=e.toString();if(t.length<=3)return[Number(t)];const n=[];n.unshift(Number(t.slice(-3)));let i=t.slice(0,-3);for(;i.length>0;)n.unshift(Number(i.slice(-2))),i=i.slice(0,-2);return n}(e),n=t.length,r=[];for(let e=0;e<n;e++){const i=t[e];if(0===i)continue;const u=n-e-1;r.push(o(i)),u>0&&s[u]&&r.push(s[u])}return r.join(" ").trim()}e.bn=function(e){const{isNegative:r,integerPart:s,decimalPart:o}=function(e){const i=typeof e;if("bigint"===i)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===i){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:t(function(e){const t=e.toString();return t.includes("e")||t.includes("E")?n(t):t}(e))}if("string"===i)return t(function(e){const t=e.trim();if(0===t.length||Number.isNaN(Number(t)))throw new Error(`Invalid number format: "${e}"`);return t.includes("e")||t.includes("E")?n(t):t}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${i}`)}(e);let c="";return r&&(c="মাইনাস "),c+=u(s),o&&(c+=" দশমিক "+function(e){let t="",n=0;for(;n<e.length&&"0"===e[n];)t&&(t+=" "),t+=i,n++;const r=e.slice(n);return r&&(t&&(t+=" "),t+=u(BigInt(r))),t}(o)),c}},"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
3
+ //# sourceMappingURL=bn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bn.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/bn.js"],"sourcesContent":["/**\n * Numeric value parsing utility.\n * Transforms user input (number, string, or bigint) into normalized components.\n * @module parse-numeric\n */\n\n/**\n * Parses a numeric value into its components.\n * @param {number|string|bigint} value\n * @returns {{isNegative: boolean, integerPart: bigint, decimalPart?: string}}\n * @throws {TypeError} If value is not number, string, or bigint\n * @throws {Error} If value is not a valid number format\n */\nexport function parseNumericValue (value) {\n const type = typeof value\n\n // BigInt: simplest case\n if (type === 'bigint') {\n return value < 0n\n ? { isNegative: true, integerPart: -value }\n : { isNegative: false, integerPart: value }\n }\n\n // Number: fast path for safe integers\n if (type === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('Number must be finite (NaN and Infinity are not supported)')\n }\n if (Number.isSafeInteger(value)) {\n return value < 0\n ? { isNegative: true, integerPart: BigInt(-value) }\n : { isNegative: false, integerPart: BigInt(value) }\n }\n return parseNumericString(numberToString(value))\n }\n\n // String input\n if (type === 'string') {\n return parseNumericString(normalizeString(value))\n }\n\n throw new TypeError(\n `Invalid value type: expected number, string, or bigint, received ${type}`\n )\n}\n\n/**\n * Converts a number to decimal string, expanding scientific notation if needed.\n */\nfunction numberToString (value) {\n const str = value.toString()\n return (str.includes('e') || str.includes('E'))\n ? expandScientificNotation(str)\n : str\n}\n\n/**\n * Validates and normalizes a string numeric input.\n */\nfunction normalizeString (value) {\n const trimmed = value.trim()\n if (trimmed.length === 0 || Number.isNaN(Number(trimmed))) {\n throw new Error(`Invalid number format: \"${value}\"`)\n }\n return (trimmed.includes('e') || trimmed.includes('E'))\n ? expandScientificNotation(trimmed)\n : trimmed\n}\n\n/**\n * Parses a normalized numeric string into components.\n */\nfunction parseNumericString (str) {\n const isNegative = str[0] === '-'\n if (isNegative) str = str.slice(1)\n\n const dotIndex = str.indexOf('.')\n if (dotIndex === -1) {\n return { isNegative, integerPart: BigInt(str) }\n }\n\n const integerStr = str.slice(0, dotIndex) || '0'\n const decimalPart = str.slice(dotIndex + 1)\n return { isNegative, integerPart: BigInt(integerStr), decimalPart }\n}\n\n/**\n * Expands scientific notation to decimal form (e.g., \"1e21\" → \"1000...\").\n */\nfunction expandScientificNotation (str) {\n const [mantissa, expStr] = str.toLowerCase().split('e')\n const exp = parseInt(expStr, 10)\n\n const dotIndex = mantissa.indexOf('.')\n const digits = dotIndex === -1\n ? mantissa\n : mantissa.slice(0, dotIndex) + mantissa.slice(dotIndex + 1)\n const integerLength = dotIndex === -1 ? mantissa.length : dotIndex\n const newDotPosition = integerLength + exp\n\n if (newDotPosition >= digits.length) {\n return digits + '0'.repeat(newDotPosition - digits.length)\n }\n if (newDotPosition <= 0) {\n return '0.' + '0'.repeat(-newDotPosition) + digits\n }\n return digits.slice(0, newDotPosition) + '.' + digits.slice(newDotPosition)\n}\n","/**\n * Bangla language converter - Functional Implementation\n *\n * Self-contained converter for South Asian numbering.\n *\n * Key features:\n * - Indian numbering system (হাজার, লাখ, কোটি)\n * - Bangla script (Bengali)\n * - 3-2-2 grouping pattern (last 3 digits, then groups of 2)\n * - Complete word forms for 0-99\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary\n// ============================================================================\n\nconst ZERO = 'শূন্য'\nconst NEGATIVE = 'মাইনাস'\nconst DECIMAL_SEP = 'দশমিক'\nconst HUNDRED = 'শত'\n\nconst BELOW_HUNDRED = [\n 'শূন্য', 'এক', 'দুই', 'তিন', 'চার', 'পাঁচ', 'ছয়', 'সাত', 'আট', 'নয়',\n 'দশ', 'এগারো', 'বারো', 'তেরো', 'চৌদ্দ', 'পনেরো', 'ষোল', 'সতেরো', 'আঠারো', 'উনিশ',\n 'বিশ', 'একুশ', 'বাইশ', 'তেইশ', 'চব্বিশ', 'পঁচিশ', 'ছাব্বিশ', 'সাতাশ', 'আঠাশ', 'উনত্রিশ',\n 'ত্রিশ', 'একত্রিশ', 'বত্রিশ', 'তেত্রিশ', 'চৌত্রিশ', 'পঁয়ত্রিশ', 'ছত্রিশ', 'সাঁইত্রিশ', 'আটত্রিশ', 'উনচল্লিশ',\n 'চল্লিশ', 'একচল্লিশ', 'বেয়াল্লিশ', 'তেতাল্লিশ', 'চুয়াল্লিশ', 'পঁয়তাল্লিশ', 'ছেচল্লিশ', 'সাতচল্লিশ', 'আটচল্লিশ', 'উনপঞ্চাশ',\n 'পঞ্চাশ', 'একান্ন', 'বাহান্ন', 'তিপ্পান্ন', 'চুয়ান্ন', 'পঞ্চান্ন', 'ছাপ্পান্ন', 'সাতান্ন', 'আটান্ন', 'উনষাট',\n 'ষাট', 'একষট্টি', 'বাষট্টি', 'তেষট্টি', 'চৌষট্টি', 'পঁয়ষট্টি', 'ছেষট্টি', 'সাতষট্টি', 'আটষট্টি', 'ঊনসত্তর',\n 'সত্তর', 'একাত্তর', 'বাহাত্তর', 'তেহাত্তর', 'চুয়াত্তর', 'পঁচাত্তর', 'ছিয়াত্তর', 'সাতাত্তর', 'আটাত্তর', 'উনআশি',\n 'আশি', 'একাশি', 'বিরাশি', 'তিরাশি', 'চুরাশি', 'পঁচাশি', 'ছিয়াশি', 'সাতাশি', 'আটাশি', 'উননব্বই',\n 'নব্বই', 'একানব্বই', 'বিরানব্বই', 'তিরানব্বই', 'চুরানব্বই', 'পঁচানব্বই', 'ছিয়ানব্বই', 'সাতানব্বই', 'আটানব্বই', 'নিরানব্বই'\n]\n\n// Scale words: index 0 = units (empty), 1 = thousand, 2 = lakh, 3 = crore, etc.\nconst SCALE_WORDS = ['', 'হাজার', 'লাখ', 'কোটি', 'আরব', 'খরব', 'নীল', 'পদ্ম', 'শঙ্খ']\n\n// ============================================================================\n// Segment Splitting (inlined for performance)\n// ============================================================================\n\nfunction groupByThreeThenTwos (n) {\n const numStr = n.toString()\n if (numStr.length <= 3) return [Number(numStr)]\n\n const segments = []\n segments.unshift(Number(numStr.slice(-3)))\n\n let remaining = numStr.slice(0, -3)\n while (remaining.length > 0) {\n segments.unshift(Number(remaining.slice(-2)))\n remaining = remaining.slice(0, -2)\n }\n\n return segments\n}\n\nfunction segmentToWords (n) {\n if (n === 0) return ''\n if (n < 100) return BELOW_HUNDRED[n]\n\n const hundreds = Math.trunc(n / 100)\n const remainder = n % 100\n\n if (remainder === 0) {\n return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED\n }\n return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED + ' ' + BELOW_HUNDRED[remainder]\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\nfunction integerToWords (n) {\n if (n === 0n) return ZERO\n\n const segments = groupByThreeThenTwos(n)\n const segmentCount = segments.length\n const words = []\n\n for (let i = 0; i < segmentCount; i++) {\n const segmentValue = segments[i]\n if (segmentValue === 0) continue\n\n const scaleIndex = segmentCount - i - 1\n words.push(segmentToWords(segmentValue))\n if (scaleIndex > 0 && SCALE_WORDS[scaleIndex]) {\n words.push(SCALE_WORDS[scaleIndex])\n }\n }\n\n return words.join(' ').trim()\n}\n\nfunction decimalPartToWords (decimalPart) {\n let result = ''\n let i = 0\n\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder))\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Bengali words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in Bengali words\n */\nfunction toWords (value) {\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)\n }\n\n return result\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport { toWords }\n"],"names":["parseNumericString","str","isNegative","slice","dotIndex","indexOf","integerPart","BigInt","integerStr","decimalPart","expandScientificNotation","mantissa","expStr","toLowerCase","split","exp","parseInt","digits","newDotPosition","length","repeat","ZERO","BELOW_HUNDRED","SCALE_WORDS","segmentToWords","n","hundreds","Math","trunc","remainder","integerToWords","segments","numStr","toString","Number","unshift","remaining","groupByThreeThenTwos","segmentCount","words","i","segmentValue","scaleIndex","push","join","trim","value","type","isFinite","Error","isSafeInteger","includes","numberToString","trimmed","isNaN","normalizeString","TypeError","parseNumericValue","result","NEGATIVE","decimalPartToWords"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /*! n2words/cs v3.0.0 | MIT License | github.com/forzagreen/n2words */
2
+ var t,e;t=this,e=function(t){"use strict";function e(t){const e="-"===t[0];e&&(t=t.slice(1));const n=t.indexOf(".");if(-1===n)return{isNegative:e,integerPart:BigInt(t)};const i=t.slice(0,n)||"0",r=t.slice(n+1);return{isNegative:e,integerPart:BigInt(i),decimalPart:r}}function n(t){const[e,n]=t.toLowerCase().split("e"),i=parseInt(n,10),r=e.indexOf("."),s=-1===r?e:e.slice(0,r)+e.slice(r+1),o=(-1===r?e.length:r)+i;return o>=s.length?s+"0".repeat(o-s.length):o<=0?"0."+"0".repeat(-o)+s:s.slice(0,o)+"."+s.slice(o)}const i=["","jedna","dva","tři","čtyři","pět","šest","sedm","osm","devět"],r=["deset","jedenáct","dvanáct","třináct","čtrnáct","patnáct","šestnáct","sedmnáct","osmnáct","devatenáct"],s=["","","dvacet","třicet","čtyřicet","padesát","šedesát","sedmdesát","osmdesát","devadesát"],o=["","sto","dvě stě","tři sta","čtyři sta","pět set","šest set","sedm set","osm set","devět set"],u={1:["tisíc","tisíce","tisíc"],2:["milion","miliony","milionů"],3:["miliarda","miliardy","miliard"],4:["bilion","biliony","bilionů"],5:["biliarda","biliardy","biliard"],6:["trilion","triliony","trilionů"],7:["triliarda","triliardy","triliard"],8:["kvadrilion","kvadriliony","kvadrilionů"],9:["kvadriliarda","kvadriliardy","kvadriliard"]},a="nula";function c(t){if(0===t)return"";const e=t%10,n=Math.floor(t/10)%10,u=Math.floor(t/100),a=[];return u>0&&a.push(o[u]),1===n?a.push(r[e]):n>=2?(a.push(s[n]),e>0&&a.push(i[e])):e>0&&a.push(i[e]),a.join(" ")}function l(t){if(0===t)return"";const e=t%10,n=Math.floor(t/10)%10,u=Math.floor(t/100),a=[];return u>0&&a.push(o[u]),1===n?a.push(r[e]):n>=2?(a.push(s[n]),e>0&&a.push(i[e])):e>0&&a.push(i[e]),a.join(" ")}const d=new Array(1e3);for(let t=0;t<1e3;t++)d[t]=c(t);const f=new Array(1e3);for(let t=0;t<1e3;t++)f[t]=l(t);function m(t,e){if(1n===t)return e[0];const n=t%10n,i=t%100n;return n>=2n&&n<=4n&&(i<10n||i>20n)?e[1]:e[2]}function g(t){if(0n===t)return a;if(t<1000n)return d[Number(t)];if(t<1000000n){const e=t/1000n,n=Number(t%1000n),i=m(e,u[1]);let r;return r=1n===e?i:d[Number(e)]+" "+i,n>0&&(r+=" "+f[n]),r}return function(t){const e=[];let n=t;for(;n>0n;)e.push(n%1000n),n/=1000n;let i="";for(let t=e.length-1;t>=0;t--){const n=e[t];if(0n!==n)if(i&&(i+=" "),0===t)i+=f[Number(n)];else{const e=u[t];if(e){const t=m(n,e);i+=1n===n?t:d[Number(n)]+" "+t}else i+=d[Number(n)]}}return i}(t)}t.cs=function(t){const{isNegative:i,integerPart:r,decimalPart:s}=function(t){const i=typeof t;if("bigint"===i)return t<0n?{isNegative:!0,integerPart:-t}:{isNegative:!1,integerPart:t};if("number"===i){if(!Number.isFinite(t))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(t)?t<0?{isNegative:!0,integerPart:BigInt(-t)}:{isNegative:!1,integerPart:BigInt(t)}:e(function(t){const e=t.toString();return e.includes("e")||e.includes("E")?n(e):e}(t))}if("string"===i)return e(function(t){const e=t.trim();if(0===e.length||Number.isNaN(Number(e)))throw new Error(`Invalid number format: "${t}"`);return e.includes("e")||e.includes("E")?n(e):e}(t));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${i}`)}(t);let o="";if(i&&(o="mínus "),o+=g(r),s){const t=function(t){return 0n===t||1n===t?"celá":t>=2n&&t<=4n?"celé":"celých"}(r);o+=" "+t+" "+function(t){let e="",n=0;for(;n<t.length&&"0"===t[n];)e&&(e+=" "),e+=a,n++;const i=t.slice(n);return i&&(e&&(e+=" "),e+=g(BigInt(i))),e}(s)}return o}},"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).n2words=t.n2words||{});
3
+ //# sourceMappingURL=cs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cs.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/cs.js"],"sourcesContent":["/**\n * Numeric value parsing utility.\n * Transforms user input (number, string, or bigint) into normalized components.\n * @module parse-numeric\n */\n\n/**\n * Parses a numeric value into its components.\n * @param {number|string|bigint} value\n * @returns {{isNegative: boolean, integerPart: bigint, decimalPart?: string}}\n * @throws {TypeError} If value is not number, string, or bigint\n * @throws {Error} If value is not a valid number format\n */\nexport function parseNumericValue (value) {\n const type = typeof value\n\n // BigInt: simplest case\n if (type === 'bigint') {\n return value < 0n\n ? { isNegative: true, integerPart: -value }\n : { isNegative: false, integerPart: value }\n }\n\n // Number: fast path for safe integers\n if (type === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('Number must be finite (NaN and Infinity are not supported)')\n }\n if (Number.isSafeInteger(value)) {\n return value < 0\n ? { isNegative: true, integerPart: BigInt(-value) }\n : { isNegative: false, integerPart: BigInt(value) }\n }\n return parseNumericString(numberToString(value))\n }\n\n // String input\n if (type === 'string') {\n return parseNumericString(normalizeString(value))\n }\n\n throw new TypeError(\n `Invalid value type: expected number, string, or bigint, received ${type}`\n )\n}\n\n/**\n * Converts a number to decimal string, expanding scientific notation if needed.\n */\nfunction numberToString (value) {\n const str = value.toString()\n return (str.includes('e') || str.includes('E'))\n ? expandScientificNotation(str)\n : str\n}\n\n/**\n * Validates and normalizes a string numeric input.\n */\nfunction normalizeString (value) {\n const trimmed = value.trim()\n if (trimmed.length === 0 || Number.isNaN(Number(trimmed))) {\n throw new Error(`Invalid number format: \"${value}\"`)\n }\n return (trimmed.includes('e') || trimmed.includes('E'))\n ? expandScientificNotation(trimmed)\n : trimmed\n}\n\n/**\n * Parses a normalized numeric string into components.\n */\nfunction parseNumericString (str) {\n const isNegative = str[0] === '-'\n if (isNegative) str = str.slice(1)\n\n const dotIndex = str.indexOf('.')\n if (dotIndex === -1) {\n return { isNegative, integerPart: BigInt(str) }\n }\n\n const integerStr = str.slice(0, dotIndex) || '0'\n const decimalPart = str.slice(dotIndex + 1)\n return { isNegative, integerPart: BigInt(integerStr), decimalPart }\n}\n\n/**\n * Expands scientific notation to decimal form (e.g., \"1e21\" → \"1000...\").\n */\nfunction expandScientificNotation (str) {\n const [mantissa, expStr] = str.toLowerCase().split('e')\n const exp = parseInt(expStr, 10)\n\n const dotIndex = mantissa.indexOf('.')\n const digits = dotIndex === -1\n ? mantissa\n : mantissa.slice(0, dotIndex) + mantissa.slice(dotIndex + 1)\n const integerLength = dotIndex === -1 ? mantissa.length : dotIndex\n const newDotPosition = integerLength + exp\n\n if (newDotPosition >= digits.length) {\n return digits + '0'.repeat(newDotPosition - digits.length)\n }\n if (newDotPosition <= 0) {\n return '0.' + '0'.repeat(-newDotPosition) + digits\n }\n return digits.slice(0, newDotPosition) + '.' + digits.slice(newDotPosition)\n}\n","/**\n * Czech language converter - Functional Implementation\n *\n * A performance-optimized number-to-words converter using precomputed lookup tables.\n * Self-contained module with its own input validation, ready for subpath exports.\n *\n * Key optimization: Precompute all segment values (0-999) at module load.\n * This eliminates all per-call string manipulation for segment conversion.\n *\n * Czech-specific rules (handled in precomputation):\n * - Three-form pluralization: 1 = singular, 2-4 = few, 5+ = many\n * - Irregular hundreds: sto, dvě stě, tři sta, čtyři sta, pět set...\n * - Gender: dva (masc) vs dvě (fem) for 2\n * - Omit \"one\" before scale words: \"tisíc\" not \"jedna tisíc\"\n * - Dynamic decimal separator: celá/celé/celých based on integer\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary (module-level constants)\n// ============================================================================\n\n// Ones words (masculine form)\nconst ONES = ['', 'jedna', 'dva', 'tři', 'čtyři', 'pět', 'šest', 'sedm', 'osm', 'devět']\n\n// Teens (10-19)\nconst TEENS = ['deset', 'jedenáct', 'dvanáct', 'třináct', 'čtrnáct', 'patnáct', 'šestnáct', 'sedmnáct', 'osmnáct', 'devatenáct']\n\n// Tens (20-90)\nconst TENS = ['', '', 'dvacet', 'třicet', 'čtyřicet', 'padesát', 'šedesát', 'sedmdesát', 'osmdesát', 'devadesát']\n\n// Irregular hundreds\nconst HUNDREDS = ['', 'sto', 'dvě stě', 'tři sta', 'čtyři sta', 'pět set', 'šest set', 'sedm set', 'osm set', 'devět set']\n\n// Scale plural forms [singular, few (2-4), many (5+)]\nconst PLURAL_FORMS = {\n 1: ['tisíc', 'tisíce', 'tisíc'], // 10^3\n 2: ['milion', 'miliony', 'milionů'], // 10^6\n 3: ['miliarda', 'miliardy', 'miliard'], // 10^9\n 4: ['bilion', 'biliony', 'bilionů'], // 10^12\n 5: ['biliarda', 'biliardy', 'biliard'], // 10^15\n 6: ['trilion', 'triliony', 'trilionů'], // 10^18\n 7: ['triliarda', 'triliardy', 'triliard'], // 10^21\n 8: ['kvadrilion', 'kvadriliony', 'kvadrilionů'], // 10^24\n 9: ['kvadriliarda', 'kvadriliardy', 'kvadriliard'] // 10^27\n}\n\nconst ZERO = 'nula'\nconst NEGATIVE = 'mínus'\n\n// ============================================================================\n// Precomputed Lookup Tables (built once at module load)\n// ============================================================================\n\n/**\n * Builds segment word for 0-999 (masculine, default form).\n * Only used during table construction.\n */\nfunction buildSegment (n) {\n if (n === 0) return ''\n\n const ones = n % 10\n const tens = Math.floor(n / 10) % 10\n const hundreds = Math.floor(n / 100)\n\n const parts = []\n\n // Hundreds (irregular)\n if (hundreds > 0) {\n parts.push(HUNDREDS[hundreds])\n }\n\n // Tens and ones\n if (tens === 1) {\n // Teens\n parts.push(TEENS[ones])\n } else if (tens >= 2) {\n parts.push(TENS[tens])\n if (ones > 0) {\n parts.push(ONES[ones])\n }\n } else if (ones > 0) {\n parts.push(ONES[ones])\n }\n\n return parts.join(' ')\n}\n\n/**\n * Builds segment word for 0-999 with feminine hundreds.\n * Hundreds use irregular forms (dvě stě, tři sta) but ones remain masculine.\n */\nfunction buildSegmentWithHundreds (n) {\n if (n === 0) return ''\n\n const ones = n % 10\n const tens = Math.floor(n / 10) % 10\n const hundreds = Math.floor(n / 100)\n\n const parts = []\n\n // Hundreds use the irregular HUNDREDS array (already has \"dvě stě\" etc.)\n if (hundreds > 0) {\n parts.push(HUNDREDS[hundreds])\n }\n\n // Tens and ones use masculine form\n if (tens === 1) {\n parts.push(TEENS[ones])\n } else if (tens >= 2) {\n parts.push(TENS[tens])\n if (ones > 0) {\n parts.push(ONES[ones]) // masculine\n }\n } else if (ones > 0) {\n parts.push(ONES[ones]) // masculine\n }\n\n return parts.join(' ')\n}\n\n// Precompute all 1000 segment words (0-999) - masculine form\nconst SEGMENTS = new Array(1000)\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS[i] = buildSegment(i)\n}\n\n// Precompute all 1000 segment words with hundreds (irregular hundreds forms)\nconst SEGMENTS_WITH_HUNDREDS = new Array(1000)\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS_WITH_HUNDREDS[i] = buildSegmentWithHundreds(i)\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Czech pluralization: 1 = singular, 2-4 = few, else = many.\n * Teens (11-19) always use \"many\" form.\n *\n * @param {bigint} n - The number\n * @param {string[]} forms - [singular, few, many]\n * @returns {string} The appropriate form\n */\nfunction pluralize (n, forms) {\n if (n === 1n) return forms[0]\n\n const lastDigit = n % 10n\n const lastTwoDigits = n % 100n\n\n // 2-4, but not 12-14 (teens use \"many\")\n if (lastDigit >= 2n && lastDigit <= 4n && (lastTwoDigits < 10n || lastTwoDigits > 20n)) {\n return forms[1]\n }\n\n return forms[2]\n}\n\n/**\n * Gets the decimal separator word based on integer part.\n * celá (0-1), celé (2-4), celých (5+)\n */\nfunction getDecimalSeparator (integerPart) {\n if (integerPart === 0n || integerPart === 1n) {\n return 'celá'\n } else if (integerPart >= 2n && integerPart <= 4n) {\n return 'celé'\n } else {\n return 'celých'\n }\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\n/**\n * Converts a non-negative integer to Czech words.\n *\n * @param {bigint} n - Non-negative integer to convert\n * @returns {string} Czech words\n */\nfunction integerToWords (n) {\n if (n === 0n) return ZERO\n\n // Fast path: numbers < 1000 (direct lookup)\n if (n < 1000n) {\n return SEGMENTS[Number(n)]\n }\n\n // Fast path: numbers < 1,000,000 (thousands)\n if (n < 1_000_000n) {\n const thousands = n / 1000n\n const remainder = Number(n % 1000n)\n\n const scaleWord = pluralize(thousands, PLURAL_FORMS[1])\n\n let result\n if (thousands === 1n) {\n // Omit \"one\" before tisíc\n result = scaleWord\n } else {\n result = SEGMENTS[Number(thousands)] + ' ' + scaleWord\n }\n\n if (remainder > 0) {\n // Use form with irregular hundreds (for \"dvě stě\" etc.)\n result += ' ' + SEGMENTS_WITH_HUNDREDS[remainder]\n }\n\n return result\n }\n\n // For numbers >= 1,000,000, use scale decomposition\n return buildLargeNumberWords(n)\n}\n\n/**\n * Builds words for numbers >= 1,000,000.\n * Uses BigInt division for faster segment extraction.\n *\n * @param {bigint} n - Number >= 1,000,000\n * @returns {string} Czech words\n */\nfunction buildLargeNumberWords (n) {\n // Extract segments using BigInt division (faster than string slicing)\n // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)\n const segmentValues = []\n let temp = n\n while (temp > 0n) {\n segmentValues.push(temp % 1000n)\n temp = temp / 1000n\n }\n\n // Build result string directly\n let result = ''\n\n for (let i = segmentValues.length - 1; i >= 0; i--) {\n const segment = segmentValues[i]\n if (segment === 0n) continue\n\n if (result) result += ' '\n\n if (i === 0) {\n // Units segment (no scale word) - use form with irregular hundreds\n result += SEGMENTS_WITH_HUNDREDS[Number(segment)]\n } else {\n // Scale word needed\n const forms = PLURAL_FORMS[i]\n if (forms) {\n const scaleWord = pluralize(segment, forms)\n\n if (segment === 1n) {\n // Omit \"one\" before scale words\n result += scaleWord\n } else {\n // Use masculine form for multiplier before scale words\n result += SEGMENTS[Number(segment)] + ' ' + scaleWord\n }\n } else {\n // Fallback for very large scales without defined forms\n result += SEGMENTS[Number(segment)]\n }\n }\n }\n\n return result\n}\n\n/**\n * Converts decimal digits to Czech words.\n *\n * @param {string} decimalPart - Decimal digits (without the point)\n * @returns {string} Czech words for decimal part\n */\nfunction decimalPartToWords (decimalPart) {\n let result = ''\n\n // Handle leading zeros\n let i = 0\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n // Convert remainder as a single number\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder))\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Czech words.\n *\n * This is the main public API. It accepts any valid numeric input\n * (number, string, or bigint) and handles parsing internally.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in Czech words\n * @throws {TypeError} If value is not a valid numeric type\n * @throws {Error} If value is not a valid number format\n *\n * @example\n * toWords(21) // 'dvacet jedna'\n * toWords(1000) // 'tisíc'\n * toWords(2000) // 'dva tisíce'\n * toWords(5000) // 'pět tisíc'\n */\nfunction toWords (value) {\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart)\n\n if (decimalPart) {\n const separator = getDecimalSeparator(integerPart)\n result += ' ' + separator + ' ' + decimalPartToWords(decimalPart)\n }\n\n return result\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport { toWords }\n"],"names":["parseNumericString","str","isNegative","slice","dotIndex","indexOf","integerPart","BigInt","integerStr","decimalPart","expandScientificNotation","mantissa","expStr","toLowerCase","split","exp","parseInt","digits","newDotPosition","length","repeat","ONES","TEENS","TENS","HUNDREDS","PLURAL_FORMS","ZERO","buildSegment","n","ones","tens","Math","floor","hundreds","parts","push","join","buildSegmentWithHundreds","SEGMENTS","Array","i","SEGMENTS_WITH_HUNDREDS","pluralize","forms","lastDigit","lastTwoDigits","integerToWords","Number","thousands","remainder","scaleWord","result","segmentValues","temp","segment","buildLargeNumberWords","value","type","isFinite","Error","isSafeInteger","toString","includes","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","NEGATIVE","separator","getDecimalSeparator","decimalPartToWords"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /*! n2words/da v3.0.0 | MIT License | github.com/forzagreen/n2words */
2
+ var e,t;e=this,t=function(e){"use strict";function t(e){const t="-"===e[0];t&&(e=e.slice(1));const n=e.indexOf(".");if(-1===n)return{isNegative:t,integerPart:BigInt(e)};const r=e.slice(0,n)||"0",i=e.slice(n+1);return{isNegative:t,integerPart:BigInt(r),decimalPart:i}}function n(e){const[t,n]=e.toLowerCase().split("e"),r=parseInt(n,10),i=t.indexOf("."),o=-1===i?t:t.slice(0,i)+t.slice(i+1),s=(-1===i?t.length:i)+r;return s>=o.length?o+"0".repeat(s-o.length):s<=0?"0."+"0".repeat(-s)+o:o.slice(0,s)+"."+o.slice(s)}const r=["","et","to","tre","fire","fem","seks","syv","otte","ni"],i=["","en","to","tre","fire","fem","seks","syv","otte","ni"],o=["ti","elleve","tolv","tretten","fjorten","femten","seksten","sytten","atten","nitten"],s=["","","tyve","tredive","fyrre","halvtreds","treds","halvfjerds","firs","halvfems"],l="tusind",u="nul",f=["millioner","millarder","billioner","billarder","trillioner","trillarder","quadrillioner","quadrillarder"];function c(e){if(0===e)return"";const t=e%10,n=Math.floor(e/10)%10,l=Math.floor(e/100),u=[];l>0&&u.push(r[l]+"hundrede");const f=e%100;return 0===f||u.push(f<10?r[t]:f<20?o[t]:0===t?s[n]:i[t]+"og"+s[n]),2===u.length?u[0]+" og "+u[1]:u[0]||""}const a=new Array(1e3);for(let e=0;e<1e3;e++)a[e]=c(e);function d(e){if(0n===e)return u;if(e<1000n)return a[Number(e)];if(e<1000000n){const t=Number(e/1000n),n=Number(e%1000n);let r=a[t]+l;return n>0&&(r+="e og "+a[n]),r}return function(e){const t=e.toString(),n=t.length,r=[],i=n%3;let o=0;for(i>0&&(r.push(Number(t.slice(0,i))),o=i);o<n;)r.push(Number(t.slice(o,o+3))),o+=3;const s=[];let c=r.length-1;for(let e=0;e<r.length;e++){const t=r[e];if(0!==t){const e=a[t];if(0===c)s.push({word:e,type:"units"});else if(1===c)s.push({word:e+l,type:"thousand"});else{let n=e;1===t&&(n="en"),s.push({word:n+" "+f[c-2],type:"million"})}}c--}return function(e){if(0===e.length)return u;if(1===e.length)return e[0].word;const t=[];for(let n=0;n<e.length;n++){const r=e[n],i=e[n+1];"thousand"===r.type&&i&&"units"===i.type?(t.push(r.word+"e og "+i.word),n++):"million"===r.type?(t.length>0&&t.push(" "),t.push(r.word),i&&t.push(" ")):(t.length>0&&!t[t.length-1].endsWith(" ")&&t.push(" "),t.push(r.word))}return t.join("")}(s)}(e)}e.da=function(e){const{isNegative:r,integerPart:i,decimalPart:o}=function(e){const r=typeof e;if("bigint"===r)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===r){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:t(function(e){const t=e.toString();return t.includes("e")||t.includes("E")?n(t):t}(e))}if("string"===r)return t(function(e){const t=e.trim();if(0===t.length||Number.isNaN(Number(t)))throw new Error(`Invalid number format: "${e}"`);return t.includes("e")||t.includes("E")?n(t):t}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${r}`)}(e);let s="";return r&&(s="minus "),s+=d(i),o&&(s+=" komma "+function(e){let t="",n=0;for(;n<e.length&&"0"===e[n];)t&&(t+=" "),t+=u,n++;const r=e.slice(n);return r&&(t&&(t+=" "),t+=d(BigInt(r))),t}(o)),s}},"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
3
+ //# sourceMappingURL=da.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"da.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/da.js"],"sourcesContent":["/**\n * Numeric value parsing utility.\n * Transforms user input (number, string, or bigint) into normalized components.\n * @module parse-numeric\n */\n\n/**\n * Parses a numeric value into its components.\n * @param {number|string|bigint} value\n * @returns {{isNegative: boolean, integerPart: bigint, decimalPart?: string}}\n * @throws {TypeError} If value is not number, string, or bigint\n * @throws {Error} If value is not a valid number format\n */\nexport function parseNumericValue (value) {\n const type = typeof value\n\n // BigInt: simplest case\n if (type === 'bigint') {\n return value < 0n\n ? { isNegative: true, integerPart: -value }\n : { isNegative: false, integerPart: value }\n }\n\n // Number: fast path for safe integers\n if (type === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('Number must be finite (NaN and Infinity are not supported)')\n }\n if (Number.isSafeInteger(value)) {\n return value < 0\n ? { isNegative: true, integerPart: BigInt(-value) }\n : { isNegative: false, integerPart: BigInt(value) }\n }\n return parseNumericString(numberToString(value))\n }\n\n // String input\n if (type === 'string') {\n return parseNumericString(normalizeString(value))\n }\n\n throw new TypeError(\n `Invalid value type: expected number, string, or bigint, received ${type}`\n )\n}\n\n/**\n * Converts a number to decimal string, expanding scientific notation if needed.\n */\nfunction numberToString (value) {\n const str = value.toString()\n return (str.includes('e') || str.includes('E'))\n ? expandScientificNotation(str)\n : str\n}\n\n/**\n * Validates and normalizes a string numeric input.\n */\nfunction normalizeString (value) {\n const trimmed = value.trim()\n if (trimmed.length === 0 || Number.isNaN(Number(trimmed))) {\n throw new Error(`Invalid number format: \"${value}\"`)\n }\n return (trimmed.includes('e') || trimmed.includes('E'))\n ? expandScientificNotation(trimmed)\n : trimmed\n}\n\n/**\n * Parses a normalized numeric string into components.\n */\nfunction parseNumericString (str) {\n const isNegative = str[0] === '-'\n if (isNegative) str = str.slice(1)\n\n const dotIndex = str.indexOf('.')\n if (dotIndex === -1) {\n return { isNegative, integerPart: BigInt(str) }\n }\n\n const integerStr = str.slice(0, dotIndex) || '0'\n const decimalPart = str.slice(dotIndex + 1)\n return { isNegative, integerPart: BigInt(integerStr), decimalPart }\n}\n\n/**\n * Expands scientific notation to decimal form (e.g., \"1e21\" → \"1000...\").\n */\nfunction expandScientificNotation (str) {\n const [mantissa, expStr] = str.toLowerCase().split('e')\n const exp = parseInt(expStr, 10)\n\n const dotIndex = mantissa.indexOf('.')\n const digits = dotIndex === -1\n ? mantissa\n : mantissa.slice(0, dotIndex) + mantissa.slice(dotIndex + 1)\n const integerLength = dotIndex === -1 ? mantissa.length : dotIndex\n const newDotPosition = integerLength + exp\n\n if (newDotPosition >= digits.length) {\n return digits + '0'.repeat(newDotPosition - digits.length)\n }\n if (newDotPosition <= 0) {\n return '0.' + '0'.repeat(-newDotPosition) + digits\n }\n return digits.slice(0, newDotPosition) + '.' + digits.slice(newDotPosition)\n}\n","/**\n * Danish language converter - Functional Implementation\n *\n * A performance-optimized number-to-words converter using precomputed lookup tables.\n *\n * Key features:\n * - Vigesimal (base-20) tens naming: halvtreds (50), treds (60), etc.\n * - Units-before-tens: \"enogtyve\" (21) = one-and-twenty\n * - Compound thousands: \"ettusind\", \"firetusinde\"\n * - \"og\" conjunction after hundreds and thousands\n * - Long scale for millions+\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary (module-level constants)\n// ============================================================================\n\nconst ONES = ['', 'et', 'to', 'tre', 'fire', 'fem', 'seks', 'syv', 'otte', 'ni']\n// \"en\" form used in vigesimal pattern (X og Y) and before millions\nconst ONES_VIGESIMAL = ['', 'en', 'to', 'tre', 'fire', 'fem', 'seks', 'syv', 'otte', 'ni']\n\nconst TEENS = ['ti', 'elleve', 'tolv', 'tretten', 'fjorten', 'femten', 'seksten', 'sytten', 'atten', 'nitten']\n\n// Danish vigesimal tens (base-20 derived names)\nconst TENS = ['', '', 'tyve', 'tredive', 'fyrre', 'halvtreds', 'treds', 'halvfjerds', 'firs', 'halvfems']\n\nconst HUNDRED = 'hundrede'\nconst THOUSAND = 'tusind'\n\nconst ZERO = 'nul'\nconst NEGATIVE = 'minus'\nconst DECIMAL_SEP = 'komma'\n\n// Long scale: millioner, millarder, billioner, etc.\nconst SCALES = ['millioner', 'millarder', 'billioner', 'billarder', 'trillioner', 'trillarder', 'quadrillioner', 'quadrillarder']\n\n// ============================================================================\n// Precomputed Lookup Tables (built once at module load)\n// ============================================================================\n\n/**\n * Builds segment word for 0-999.\n */\nfunction buildSegment (n) {\n if (n === 0) return ''\n\n const ones = n % 10\n const tens = Math.floor(n / 10) % 10\n const hundreds = Math.floor(n / 100)\n\n const parts = []\n\n // Hundreds: \"ethundrede\", \"tohundrede\" (compound, no space)\n if (hundreds > 0) {\n parts.push(ONES[hundreds] + HUNDRED)\n }\n\n // Tens and ones\n const tensOnes = n % 100\n\n if (tensOnes === 0) {\n // Just hundreds\n } else if (tensOnes < 10) {\n // Single digit\n parts.push(ONES[ones])\n } else if (tensOnes < 20) {\n // Teens\n parts.push(TEENS[ones])\n } else if (ones === 0) {\n // Even tens\n parts.push(TENS[tens])\n } else {\n // Units-before-tens: \"enogtyve\", \"treogfyrre\"\n parts.push(ONES_VIGESIMAL[ones] + 'og' + TENS[tens])\n }\n\n // Combine with \" og \" between hundreds and remainder\n if (parts.length === 2) {\n return parts[0] + ' og ' + parts[1]\n }\n return parts[0] || ''\n}\n\n// Precompute all 1000 segment words (0-999)\nconst SEGMENTS = new Array(1000)\n\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS[i] = buildSegment(i)\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\n/**\n * Converts a non-negative integer to Danish words.\n *\n * @param {bigint} n - Non-negative integer to convert\n * @returns {string} Danish words\n */\nfunction integerToWords (n) {\n if (n === 0n) return ZERO\n\n // Fast path: numbers < 1000 (direct lookup)\n if (n < 1000n) {\n return SEGMENTS[Number(n)]\n }\n\n // Fast path: numbers < 1,000,000 (thousands)\n if (n < 1_000_000n) {\n const thousands = Number(n / 1000n)\n const remainder = Number(n % 1000n)\n\n // Compound thousands: \"ettusind\", \"firetusind\"\n let result = SEGMENTS[thousands] + THOUSAND\n\n if (remainder > 0) {\n // Add 'e' suffix and \" og \" for remainder: \"firetusinde og ...\"\n result += 'e og ' + SEGMENTS[remainder]\n }\n\n return result\n }\n\n // For numbers >= 1,000,000, use scale decomposition\n return buildLargeNumberWords(n)\n}\n\n/**\n * Builds words for numbers >= 1,000,000.\n *\n * @param {bigint} n - Number >= 1,000,000\n * @returns {string} Danish words\n */\nfunction buildLargeNumberWords (n) {\n const numStr = n.toString()\n const len = numStr.length\n\n // Build segments of 3 digits from right to left\n const segments = []\n const segmentSize = 3\n\n const remainderLen = len % segmentSize\n let pos = 0\n if (remainderLen > 0) {\n segments.push(Number(numStr.slice(0, remainderLen)))\n pos = remainderLen\n }\n while (pos < len) {\n segments.push(Number(numStr.slice(pos, pos + segmentSize)))\n pos += segmentSize\n }\n\n // Convert segments to words with scale tracking\n // scaleIndex: 0 = units, 1 = thousands, 2 = millions, etc.\n const parts = []\n let scaleIndex = segments.length - 1\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]\n\n if (segment !== 0) {\n const segmentWord = SEGMENTS[segment]\n\n if (scaleIndex === 0) {\n // Units segment\n parts.push({ word: segmentWord, type: 'units' })\n } else if (scaleIndex === 1) {\n // Thousands - compound form\n parts.push({ word: segmentWord + THOUSAND, type: 'thousand' })\n } else {\n // Millions+ - space-separated, use \"en\" for 1\n const scaleWord = SCALES[scaleIndex - 2]\n let numWord = segmentWord\n // \"et\" → \"en\" before millions+\n if (segment === 1) {\n numWord = 'en'\n }\n parts.push({ word: numWord + ' ' + scaleWord, type: 'million' })\n }\n }\n\n scaleIndex--\n }\n\n // Join parts with Danish rules\n return joinDanishParts(parts)\n}\n\n/**\n * Joins parts with Danish spacing rules.\n * - After thousands with remainder: \"tusinde og\"\n * - Millions are space-separated\n *\n * @param {Array} parts - Parts with type metadata\n * @returns {string} Joined string\n */\nfunction joinDanishParts (parts) {\n if (parts.length === 0) return ZERO\n if (parts.length === 1) return parts[0].word\n\n const result = []\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]\n const nextPart = parts[i + 1]\n\n if (part.type === 'thousand' && nextPart && nextPart.type === 'units') {\n // Thousands followed by units: add \"e og\"\n result.push(part.word + 'e og ' + nextPart.word)\n i++ // Skip the units part\n } else if (part.type === 'million') {\n if (result.length > 0) {\n result.push(' ')\n }\n result.push(part.word)\n if (nextPart) {\n result.push(' ')\n }\n } else {\n if (result.length > 0 && !result[result.length - 1].endsWith(' ')) {\n result.push(' ')\n }\n result.push(part.word)\n }\n }\n\n return result.join('')\n}\n\n/**\n * Converts decimal digits to Danish words.\n *\n * @param {string} decimalPart - Decimal digits (without the point)\n * @returns {string} Danish words for decimal part\n */\nfunction decimalPartToWords (decimalPart) {\n let result = ''\n\n // Handle leading zeros\n let i = 0\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n // Convert remainder as a single number\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder))\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Danish words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in Danish words\n * @throws {TypeError} If value is not a valid numeric type\n * @throws {Error} If value is not a valid number format\n *\n * @example\n * toWords(21) // 'enogtyve'\n * toWords(1000) // 'ettusind'\n * toWords(1000000) // 'en millioner'\n */\nfunction toWords (value) {\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)\n }\n\n return result\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport { toWords }\n"],"names":["parseNumericString","str","isNegative","slice","dotIndex","indexOf","integerPart","BigInt","integerStr","decimalPart","expandScientificNotation","mantissa","expStr","toLowerCase","split","exp","parseInt","digits","newDotPosition","length","repeat","ONES","ONES_VIGESIMAL","TEENS","TENS","THOUSAND","ZERO","SCALES","buildSegment","n","ones","tens","Math","floor","hundreds","parts","push","tensOnes","SEGMENTS","Array","i","integerToWords","Number","thousands","remainder","result","numStr","toString","len","segments","remainderLen","pos","scaleIndex","segment","segmentWord","word","type","numWord","part","nextPart","endsWith","join","joinDanishParts","buildLargeNumberWords","value","isFinite","Error","isSafeInteger","includes","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","NEGATIVE","decimalPartToWords"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /*! n2words/de v3.0.0 | MIT License | github.com/forzagreen/n2words */
2
+ var e,n;e=this,n=function(e){"use strict";function n(e){const n="-"===e[0];n&&(e=e.slice(1));const i=e.indexOf(".");if(-1===i)return{isNegative:n,integerPart:BigInt(e)};const t=e.slice(0,i)||"0",r=e.slice(i+1);return{isNegative:n,integerPart:BigInt(t),decimalPart:r}}function i(e){const[n,i]=e.toLowerCase().split("e"),t=parseInt(i,10),r=n.indexOf("."),l=-1===r?n:n.slice(0,r)+n.slice(r+1),s=(-1===r?n.length:r)+t;return s>=l.length?l+"0".repeat(s-l.length):s<=0?"0."+"0".repeat(-s)+l:l.slice(0,s)+"."+l.slice(s)}const t=["","eins","zwei","drei","vier","fünf","sechs","sieben","acht","neun"],r="ein",l=["zehn","elf","zwölf","dreizehn","vierzehn","fünfzehn","sechzehn","siebzehn","achtzehn","neunzehn"],s=["","","zwanzig","dreißig","vierzig","fünfzig","sechzig","siebzig","achtzig","neunzig"],o=["tausend","Million","Milliarde","Billion","Billiarde","Trillion","Trilliarde","Quadrillion","Quadrilliarde"],u=["tausend","Millionen","Milliarden","Billionen","Billiarden","Trillionen","Trilliarden","Quadrillionen","Quadrilliarden"],a="hundert",c="null";function f(e){if(0===e)return"";const n=e%10,i=Math.floor(e/10)%10,o=Math.floor(e/100);let u="";return o>0&&(u+=(1===o?r:t[o])+a),1===i?u+=l[n]:i>=2&&n>0?u+=(1===n?r:t[n])+"und"+s[i]:i>=2?u+=s[i]:n>0&&(u+=t[n]),u}function d(e){if(0===e)return"";if(1===e)return r;const n=e%10,i=Math.floor(e/10)%10,o=Math.floor(e/100);let u="";return o>0&&(u+=(1===o?r:t[o])+a),1===i?u+=l[n]:i>=2&&n>0?u+=(1===n?r:t[n])+"und"+s[i]:i>=2?u+=s[i]:(n>0&&o>0||n>0)&&(u+=t[n]),u}const g=new Array(1e3);for(let e=0;e<1e3;e++)g[e]=f(e);const h=new Array(1e3);for(let e=0;e<1e3;e++)h[e]=d(e);function m(e){if(0n===e)return c;if(e<1000n)return g[Number(e)];if(e<1000000n){const n=Number(e/1000n),i=Number(e%1000n);let t=h[n]+o[0];return i>0&&(t+=g[i]),t}return function(e){const n=e.toString(),i=n.length,t=[],r=i%3;let l=0;for(r>0&&(t.push(Number(n.slice(0,r))),l=r);l<i;)t.push(Number(n.slice(l,l+3))),l+=3;const s=[];let a=t.length-1;for(let e=0;e<t.length;e++){const n=t[e];if(0!==n)if(0===a)s.push({words:g[n],isScale:!1,scaleLevel:0});else if(1===a)s.push({words:h[n]+o[0],isScale:!1,scaleLevel:1});else{let e;e=1===n?"eine":g[n];const i=1===n?o[a-1]:u[a-1];s.push({words:e,isScale:!1,scaleLevel:a}),s.push({words:i,isScale:!0,scaleLevel:a})}a--}return function(e){if(0===e.length)return c;let n="";for(let i=0;i<e.length;i++){const t=e[i],r=i>0?e[i-1]:null;i>0&&(t.isScale||r&&r.isScale)&&(n+=" "),n+=t.words}return n}(s)}(e)}e.de=function(e){const{isNegative:t,integerPart:r,decimalPart:l}=function(e){const t=typeof e;if("bigint"===t)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===t){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:n(function(e){const n=e.toString();return n.includes("e")||n.includes("E")?i(n):n}(e))}if("string"===t)return n(function(e){const n=e.trim();if(0===n.length||Number.isNaN(Number(n)))throw new Error(`Invalid number format: "${e}"`);return n.includes("e")||n.includes("E")?i(n):n}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${t}`)}(e);let s="";return t&&(s="minus "),s+=m(r),l&&(s+=" komma "+function(e){let n="",i=0;for(;i<e.length&&"0"===e[i];)n&&(n+=" "),n+=c,i++;const t=e.slice(i);return t&&(n&&(n+=" "),n+=m(BigInt(t))),n}(l)),s}},"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
3
+ //# sourceMappingURL=de.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"de.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/de.js"],"sourcesContent":["/**\n * Numeric value parsing utility.\n * Transforms user input (number, string, or bigint) into normalized components.\n * @module parse-numeric\n */\n\n/**\n * Parses a numeric value into its components.\n * @param {number|string|bigint} value\n * @returns {{isNegative: boolean, integerPart: bigint, decimalPart?: string}}\n * @throws {TypeError} If value is not number, string, or bigint\n * @throws {Error} If value is not a valid number format\n */\nexport function parseNumericValue (value) {\n const type = typeof value\n\n // BigInt: simplest case\n if (type === 'bigint') {\n return value < 0n\n ? { isNegative: true, integerPart: -value }\n : { isNegative: false, integerPart: value }\n }\n\n // Number: fast path for safe integers\n if (type === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('Number must be finite (NaN and Infinity are not supported)')\n }\n if (Number.isSafeInteger(value)) {\n return value < 0\n ? { isNegative: true, integerPart: BigInt(-value) }\n : { isNegative: false, integerPart: BigInt(value) }\n }\n return parseNumericString(numberToString(value))\n }\n\n // String input\n if (type === 'string') {\n return parseNumericString(normalizeString(value))\n }\n\n throw new TypeError(\n `Invalid value type: expected number, string, or bigint, received ${type}`\n )\n}\n\n/**\n * Converts a number to decimal string, expanding scientific notation if needed.\n */\nfunction numberToString (value) {\n const str = value.toString()\n return (str.includes('e') || str.includes('E'))\n ? expandScientificNotation(str)\n : str\n}\n\n/**\n * Validates and normalizes a string numeric input.\n */\nfunction normalizeString (value) {\n const trimmed = value.trim()\n if (trimmed.length === 0 || Number.isNaN(Number(trimmed))) {\n throw new Error(`Invalid number format: \"${value}\"`)\n }\n return (trimmed.includes('e') || trimmed.includes('E'))\n ? expandScientificNotation(trimmed)\n : trimmed\n}\n\n/**\n * Parses a normalized numeric string into components.\n */\nfunction parseNumericString (str) {\n const isNegative = str[0] === '-'\n if (isNegative) str = str.slice(1)\n\n const dotIndex = str.indexOf('.')\n if (dotIndex === -1) {\n return { isNegative, integerPart: BigInt(str) }\n }\n\n const integerStr = str.slice(0, dotIndex) || '0'\n const decimalPart = str.slice(dotIndex + 1)\n return { isNegative, integerPart: BigInt(integerStr), decimalPart }\n}\n\n/**\n * Expands scientific notation to decimal form (e.g., \"1e21\" → \"1000...\").\n */\nfunction expandScientificNotation (str) {\n const [mantissa, expStr] = str.toLowerCase().split('e')\n const exp = parseInt(expStr, 10)\n\n const dotIndex = mantissa.indexOf('.')\n const digits = dotIndex === -1\n ? mantissa\n : mantissa.slice(0, dotIndex) + mantissa.slice(dotIndex + 1)\n const integerLength = dotIndex === -1 ? mantissa.length : dotIndex\n const newDotPosition = integerLength + exp\n\n if (newDotPosition >= digits.length) {\n return digits + '0'.repeat(newDotPosition - digits.length)\n }\n if (newDotPosition <= 0) {\n return '0.' + '0'.repeat(-newDotPosition) + digits\n }\n return digits.slice(0, newDotPosition) + '.' + digits.slice(newDotPosition)\n}\n","/**\n * German language converter - Functional Implementation\n *\n * A performance-optimized number-to-words converter using precomputed lookup tables.\n * Self-contained module with its own input validation, ready for subpath exports.\n *\n * Key optimization: Precompute all segment values (0-999) at module load.\n * This eliminates all per-call string manipulation for segment conversion.\n *\n * German-specific rules (handled in precomputation):\n * - Inverted tens-ones order: \"einundzwanzig\" (one-and-twenty) for 21-99\n * - Compound words without spaces below million level\n * - Three forms of 1: \"eins\" (standalone), \"ein\" (before hundert/tausend), \"eine\" (before Million+)\n * - Scale pluralization: Million → Millionen, Milliarde → Milliarden\n * - Spaces only around million+ scale words\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary (module-level constants)\n// ============================================================================\n\n// Ones words (1-9), index 0 unused\nconst ONES = ['', 'eins', 'zwei', 'drei', 'vier', 'fünf', 'sechs', 'sieben', 'acht', 'neun']\n\n// \"ein\" form for use before hundert/und\nconst EIN = 'ein'\n\n// Teens (10-19)\nconst TEENS = ['zehn', 'elf', 'zwölf', 'dreizehn', 'vierzehn', 'fünfzehn', 'sechzehn', 'siebzehn', 'achtzehn', 'neunzehn']\n\n// Tens (20-90)\nconst TENS = ['', '', 'zwanzig', 'dreißig', 'vierzig', 'fünfzig', 'sechzig', 'siebzig', 'achtzig', 'neunzig']\n\n// Scale words (index 0 = thousand, 1 = million, etc.)\nconst SCALES = ['tausend', 'Million', 'Milliarde', 'Billion', 'Billiarde', 'Trillion', 'Trilliarde', 'Quadrillion', 'Quadrilliarde']\n\n// Pluralized scale words (million+)\nconst SCALES_PLURAL = ['tausend', 'Millionen', 'Milliarden', 'Billionen', 'Billiarden', 'Trillionen', 'Trilliarden', 'Quadrillionen', 'Quadrilliarden']\n\nconst HUNDRED = 'hundert'\nconst ZERO = 'null'\nconst NEGATIVE = 'minus'\nconst DECIMAL_SEP = 'komma'\n\n// ============================================================================\n// Precomputed Lookup Tables (built once at module load)\n// ============================================================================\n\n/**\n * Builds segment word for 0-999 (standalone form, uses \"eins\").\n * German inverts ones and tens: \"einundzwanzig\" = one-and-twenty\n * Only used during table construction.\n */\nfunction buildSegment (n) {\n if (n === 0) return ''\n\n const ones = n % 10\n const tens = Math.floor(n / 10) % 10\n const hundreds = Math.floor(n / 100)\n\n let result = ''\n\n // Hundreds: \"ein\" before hundert, not \"eins\"\n if (hundreds > 0) {\n result += (hundreds === 1 ? EIN : ONES[hundreds]) + HUNDRED\n }\n\n // Tens and ones\n if (tens === 1) {\n // Teens\n result += TEENS[ones]\n } else if (tens >= 2 && ones > 0) {\n // Inverted: \"einundzwanzig\" (one-and-twenty)\n // Use \"ein\" before \"und\", not \"eins\"\n result += (ones === 1 ? EIN : ONES[ones]) + 'und' + TENS[tens]\n } else if (tens >= 2) {\n // Just tens\n result += TENS[tens]\n } else if (ones > 0) {\n // Just ones (no tens, possibly after hundreds)\n // Use \"eins\" for standalone/after hundreds\n result += ONES[ones]\n }\n\n return result\n}\n\n/**\n * Builds segment word for use before \"tausend\".\n * Uses \"ein\" instead of \"eins\" for 1.\n */\nfunction buildSegmentForThousand (n) {\n if (n === 0) return ''\n if (n === 1) return EIN // \"eintausend\"\n\n const ones = n % 10\n const tens = Math.floor(n / 10) % 10\n const hundreds = Math.floor(n / 100)\n\n let result = ''\n\n if (hundreds > 0) {\n result += (hundreds === 1 ? EIN : ONES[hundreds]) + HUNDRED\n }\n\n if (tens === 1) {\n result += TEENS[ones]\n } else if (tens >= 2 && ones > 0) {\n result += (ones === 1 ? EIN : ONES[ones]) + 'und' + TENS[tens]\n } else if (tens >= 2) {\n result += TENS[tens]\n } else if (ones > 0 && hundreds > 0) {\n // After hundreds, ones 1 stays \"eins\" when not followed by scale\n // But we're going to tausend, so this path won't hit for n=1\n result += ONES[ones]\n } else if (ones > 0) {\n result += ONES[ones]\n }\n\n return result\n}\n\n// Precompute all 1000 segment words (0-999) - standalone form\nconst SEGMENTS = new Array(1000)\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS[i] = buildSegment(i)\n}\n\n// Precompute all 1000 segment words for thousand context\nconst SEGMENTS_THOUSAND = new Array(1000)\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS_THOUSAND[i] = buildSegmentForThousand(i)\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\n/**\n * Converts a non-negative integer to German words.\n *\n * @param {bigint} n - Non-negative integer to convert\n * @returns {string} German words\n */\nfunction integerToWords (n) {\n if (n === 0n) return ZERO\n\n // Fast path: numbers < 1000 (direct lookup)\n if (n < 1000n) {\n return SEGMENTS[Number(n)]\n }\n\n // Fast path: numbers < 1,000,000 (thousands)\n if (n < 1_000_000n) {\n const thousands = Number(n / 1000n)\n const remainder = Number(n % 1000n)\n\n // Compound: \"eintausendzweihundert\" (no spaces)\n let result = SEGMENTS_THOUSAND[thousands] + SCALES[0]\n\n if (remainder > 0) {\n result += SEGMENTS[remainder]\n }\n\n return result\n }\n\n // For numbers >= 1,000,000, use scale decomposition\n return buildLargeNumberWords(n)\n}\n\n/**\n * Builds words for numbers >= 1,000,000.\n *\n * @param {bigint} n - Number >= 1,000,000\n * @returns {string} German words\n */\nfunction buildLargeNumberWords (n) {\n const numStr = n.toString()\n const len = numStr.length\n\n // Build segments of 3 digits from right to left\n const segments = []\n const segmentSize = 3\n\n const remainderLen = len % segmentSize\n let pos = 0\n if (remainderLen > 0) {\n segments.push(Number(numStr.slice(0, remainderLen)))\n pos = remainderLen\n }\n while (pos < len) {\n segments.push(Number(numStr.slice(pos, pos + segmentSize)))\n pos += segmentSize\n }\n\n // Convert segments to words\n const parts = []\n let scaleIndex = segments.length - 1\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]\n\n if (segment !== 0) {\n if (scaleIndex === 0) {\n // Units segment (no scale word)\n parts.push({ words: SEGMENTS[segment], isScale: false, scaleLevel: 0 })\n } else if (scaleIndex === 1) {\n // Thousands: compound without space\n const segWords = SEGMENTS_THOUSAND[segment]\n parts.push({ words: segWords + SCALES[0], isScale: false, scaleLevel: 1 })\n } else {\n // Million+ : space around scale word\n let segWords\n if (segment === 1) {\n segWords = 'eine' // \"eine Million\"\n } else {\n segWords = SEGMENTS[segment]\n }\n const scaleWord = segment === 1 ? SCALES[scaleIndex - 1] : SCALES_PLURAL[scaleIndex - 1]\n parts.push({ words: segWords, isScale: false, scaleLevel: scaleIndex })\n parts.push({ words: scaleWord, isScale: true, scaleLevel: scaleIndex })\n }\n }\n\n scaleIndex--\n }\n\n // Join with German spacing rules: space around million+ scale words\n return joinGermanParts(parts)\n}\n\n/**\n * Joins parts with German spacing rules.\n * Spaces only around million+ scale words.\n *\n * @param {Array} parts - Parts with metadata\n * @returns {string} Joined string\n */\nfunction joinGermanParts (parts) {\n if (parts.length === 0) return ZERO\n\n let result = ''\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]\n const prevPart = i > 0 ? parts[i - 1] : null\n\n // Add space before if:\n // - Current is a million+ scale word\n // - Previous was a million+ scale word\n if (i > 0) {\n const needsSpace = part.isScale || (prevPart && prevPart.isScale)\n if (needsSpace) {\n result += ' '\n }\n }\n\n result += part.words\n }\n\n return result\n}\n\n/**\n * Converts decimal digits to German words.\n *\n * @param {string} decimalPart - Decimal digits (without the point)\n * @returns {string} German words for decimal part\n */\nfunction decimalPartToWords (decimalPart) {\n let result = ''\n\n // Handle leading zeros\n let i = 0\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n // Convert remainder as a single number\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder))\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to German words.\n *\n * This is the main public API. It accepts any valid numeric input\n * (number, string, or bigint) and handles parsing internally.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in German words\n * @throws {TypeError} If value is not a valid numeric type\n * @throws {Error} If value is not a valid number format\n *\n * @example\n * toWords(21) // 'einundzwanzig'\n * toWords(1000) // 'eintausend'\n * toWords(1000000) // 'eine Million'\n */\nfunction toWords (value) {\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)\n }\n\n return result\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport { toWords }\n"],"names":["parseNumericString","str","isNegative","slice","dotIndex","indexOf","integerPart","BigInt","integerStr","decimalPart","expandScientificNotation","mantissa","expStr","toLowerCase","split","exp","parseInt","digits","newDotPosition","length","repeat","ONES","EIN","TEENS","TENS","SCALES","SCALES_PLURAL","HUNDRED","ZERO","buildSegment","n","ones","tens","Math","floor","hundreds","result","buildSegmentForThousand","SEGMENTS","Array","i","SEGMENTS_THOUSAND","integerToWords","Number","thousands","remainder","numStr","toString","len","segments","remainderLen","pos","push","parts","scaleIndex","segment","words","isScale","scaleLevel","segWords","scaleWord","part","prevPart","joinGermanParts","buildLargeNumberWords","value","type","isFinite","Error","isSafeInteger","includes","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","NEGATIVE","decimalPartToWords"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /*! n2words/el v3.0.0 | MIT License | github.com/forzagreen/n2words */
2
+ var e,t;e=this,t=function(e){"use strict";function t(e){const t="-"===e[0];t&&(e=e.slice(1));const n=e.indexOf(".");if(-1===n)return{isNegative:t,integerPart:BigInt(e)};const r=e.slice(0,n)||"0",i=e.slice(n+1);return{isNegative:t,integerPart:BigInt(r),decimalPart:i}}function n(e){const[t,n]=e.toLowerCase().split("e"),r=parseInt(n,10),i=t.indexOf("."),o=-1===i?t:t.slice(0,i)+t.slice(i+1),s=(-1===i?t.length:i)+r;return s>=o.length?o+"0".repeat(s-o.length):s<=0?"0."+"0".repeat(-s)+o:o.slice(0,s)+"."+o.slice(s)}const r=["","ένα","δύο","τρία","τέσσερα","πέντε","έξι","επτά","οκτώ","εννέα"],i=["δέκα","έντεκα","δώδεκα","δεκατρία","δεκατέσσερα","δεκαπέντε","δεκαέξι","δεκαεπτά","δεκαοκτώ","δεκαεννέα"],o=["","","είκοσι","τριάντα","σαράντα","πενήντα","εξήντα","εβδομήντα","ογδόντα","ενενήντα"],s=["","εκατό","διακόσια","τριακόσια","τετρακόσια","πεντακόσια","εξακόσια","επτακόσια","οκτακόσια","εννιακόσια"],u="χίλια",c="μηδέν",f=["εκατομμύριο","δισεκατομμύριο","τρισεκατομμύριο"];function l(e){if(0===e)return"";const t=e%10,n=Math.floor(e/10)%10,u=Math.floor(e/100),c=[];u>0&&c.push(s[u]);const f=e%100;return 0===f||c.push(f<10?r[t]:f<20?i[t]:0===t?o[n]:o[n]+" "+r[t]),c.join(" ")}const a=new Array(1e3);for(let e=0;e<1e3;e++)a[e]=l(e);e.el=function(e){const{isNegative:i,integerPart:o,decimalPart:s}=function(e){const r=typeof e;if("bigint"===r)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===r){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:t(function(e){const t=e.toString();return t.includes("e")||t.includes("E")?n(t):t}(e))}if("string"===r)return t(function(e){const t=e.trim();if(0===t.length||Number.isNaN(Number(t)))throw new Error(`Invalid number format: "${e}"`);return t.includes("e")||t.includes("E")?n(t):t}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${r}`)}(e);let l="";return i&&(l="μείον "),l+=function(e){if(0n===e)return c;if(e<1000n)return a[Number(e)];if(e<1000000n){const t=Number(e/1000n),n=Number(e%1000n);let r;return r=1===t?u:a[t]+" "+u,n>0&&(r+=" "+a[n]),r}return function(e){const t=e.toString(),n=t.length,r=[],i=n%3;let o=0;for(i>0&&(r.push(Number(t.slice(0,i))),o=i);o<n;)r.push(Number(t.slice(o,o+3))),o+=3;const s=[];let c=r.length-1;for(let e=0;e<r.length;e++){const t=r[e];if(0!==t){const e=a[t];if(0===c)s.push(e);else if(1===c)s.push(1===t?u:e+" "+u);else{const n=f[c-2];s.push(1===t?n:e+" "+n)}}c--}return s.join(" ")}(e)}(o),s&&(l+=" κόμμα "+function(e){const t=[];for(const n of e){const e=parseInt(n,10);t.push(0===e?c:r[e])}return t.join(" ")}(s)),l}},"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
3
+ //# sourceMappingURL=el.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"el.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/el.js"],"sourcesContent":["/**\n * Numeric value parsing utility.\n * Transforms user input (number, string, or bigint) into normalized components.\n * @module parse-numeric\n */\n\n/**\n * Parses a numeric value into its components.\n * @param {number|string|bigint} value\n * @returns {{isNegative: boolean, integerPart: bigint, decimalPart?: string}}\n * @throws {TypeError} If value is not number, string, or bigint\n * @throws {Error} If value is not a valid number format\n */\nexport function parseNumericValue (value) {\n const type = typeof value\n\n // BigInt: simplest case\n if (type === 'bigint') {\n return value < 0n\n ? { isNegative: true, integerPart: -value }\n : { isNegative: false, integerPart: value }\n }\n\n // Number: fast path for safe integers\n if (type === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('Number must be finite (NaN and Infinity are not supported)')\n }\n if (Number.isSafeInteger(value)) {\n return value < 0\n ? { isNegative: true, integerPart: BigInt(-value) }\n : { isNegative: false, integerPart: BigInt(value) }\n }\n return parseNumericString(numberToString(value))\n }\n\n // String input\n if (type === 'string') {\n return parseNumericString(normalizeString(value))\n }\n\n throw new TypeError(\n `Invalid value type: expected number, string, or bigint, received ${type}`\n )\n}\n\n/**\n * Converts a number to decimal string, expanding scientific notation if needed.\n */\nfunction numberToString (value) {\n const str = value.toString()\n return (str.includes('e') || str.includes('E'))\n ? expandScientificNotation(str)\n : str\n}\n\n/**\n * Validates and normalizes a string numeric input.\n */\nfunction normalizeString (value) {\n const trimmed = value.trim()\n if (trimmed.length === 0 || Number.isNaN(Number(trimmed))) {\n throw new Error(`Invalid number format: \"${value}\"`)\n }\n return (trimmed.includes('e') || trimmed.includes('E'))\n ? expandScientificNotation(trimmed)\n : trimmed\n}\n\n/**\n * Parses a normalized numeric string into components.\n */\nfunction parseNumericString (str) {\n const isNegative = str[0] === '-'\n if (isNegative) str = str.slice(1)\n\n const dotIndex = str.indexOf('.')\n if (dotIndex === -1) {\n return { isNegative, integerPart: BigInt(str) }\n }\n\n const integerStr = str.slice(0, dotIndex) || '0'\n const decimalPart = str.slice(dotIndex + 1)\n return { isNegative, integerPart: BigInt(integerStr), decimalPart }\n}\n\n/**\n * Expands scientific notation to decimal form (e.g., \"1e21\" → \"1000...\").\n */\nfunction expandScientificNotation (str) {\n const [mantissa, expStr] = str.toLowerCase().split('e')\n const exp = parseInt(expStr, 10)\n\n const dotIndex = mantissa.indexOf('.')\n const digits = dotIndex === -1\n ? mantissa\n : mantissa.slice(0, dotIndex) + mantissa.slice(dotIndex + 1)\n const integerLength = dotIndex === -1 ? mantissa.length : dotIndex\n const newDotPosition = integerLength + exp\n\n if (newDotPosition >= digits.length) {\n return digits + '0'.repeat(newDotPosition - digits.length)\n }\n if (newDotPosition <= 0) {\n return '0.' + '0'.repeat(-newDotPosition) + digits\n }\n return digits.slice(0, newDotPosition) + '.' + digits.slice(newDotPosition)\n}\n","/**\n * Greek language converter - Functional Implementation\n *\n * A performance-optimized number-to-words converter using precomputed lookup tables.\n *\n * Key features:\n * - Space-separated number composition\n * - Implicit \"one\" (ένα) omission before scale words\n * - Irregular hundreds (διακόσια, τριακόσια, etc.)\n * - Per-digit decimal reading\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary (module-level constants)\n// ============================================================================\n\nconst ONES = ['', 'ένα', 'δύο', 'τρία', 'τέσσερα', 'πέντε', 'έξι', 'επτά', 'οκτώ', 'εννέα']\n\nconst TEENS = ['δέκα', 'έντεκα', 'δώδεκα', 'δεκατρία', 'δεκατέσσερα', 'δεκαπέντε', 'δεκαέξι', 'δεκαεπτά', 'δεκαοκτώ', 'δεκαεννέα']\n\nconst TENS = ['', '', 'είκοσι', 'τριάντα', 'σαράντα', 'πενήντα', 'εξήντα', 'εβδομήντα', 'ογδόντα', 'ενενήντα']\n\n// Greek has irregular hundreds\nconst HUNDREDS = ['', 'εκατό', 'διακόσια', 'τριακόσια', 'τετρακόσια', 'πεντακόσια', 'εξακόσια', 'επτακόσια', 'οκτακόσια', 'εννιακόσια']\n\nconst THOUSAND = 'χίλια'\n\nconst ZERO = 'μηδέν'\nconst NEGATIVE = 'μείον'\nconst DECIMAL_SEP = 'κόμμα'\n\n// Short scale\nconst SCALES = ['εκατομμύριο', 'δισεκατομμύριο', 'τρισεκατομμύριο']\n\n// ============================================================================\n// Precomputed Lookup Tables (built once at module load)\n// ============================================================================\n\n/**\n * Builds segment word for 0-999.\n */\nfunction buildSegment (n) {\n if (n === 0) return ''\n\n const ones = n % 10\n const tens = Math.floor(n / 10) % 10\n const hundreds = Math.floor(n / 100)\n\n const parts = []\n\n // Hundreds (irregular forms)\n if (hundreds > 0) {\n parts.push(HUNDREDS[hundreds])\n }\n\n // Tens and ones\n const tensOnes = n % 100\n\n if (tensOnes === 0) {\n // Just hundreds\n } else if (tensOnes < 10) {\n parts.push(ONES[ones])\n } else if (tensOnes < 20) {\n parts.push(TEENS[ones])\n } else if (ones === 0) {\n parts.push(TENS[tens])\n } else {\n parts.push(TENS[tens] + ' ' + ONES[ones])\n }\n\n return parts.join(' ')\n}\n\n// Precompute all 1000 segment words (0-999)\nconst SEGMENTS = new Array(1000)\n\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS[i] = buildSegment(i)\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\n/**\n * Converts a non-negative integer to Greek words.\n *\n * @param {bigint} n - Non-negative integer to convert\n * @returns {string} Greek words\n */\nfunction integerToWords (n) {\n if (n === 0n) return ZERO\n\n // Fast path: numbers < 1000 (direct lookup)\n if (n < 1000n) {\n return SEGMENTS[Number(n)]\n }\n\n // Fast path: numbers < 1,000,000 (thousands)\n if (n < 1_000_000n) {\n const thousands = Number(n / 1000n)\n const remainder = Number(n % 1000n)\n\n // Omit \"ένα\" before χίλια\n let result\n if (thousands === 1) {\n result = THOUSAND\n } else {\n result = SEGMENTS[thousands] + ' ' + THOUSAND\n }\n\n if (remainder > 0) {\n result += ' ' + SEGMENTS[remainder]\n }\n\n return result\n }\n\n // For numbers >= 1,000,000, use scale decomposition\n return buildLargeNumberWords(n)\n}\n\n/**\n * Builds words for numbers >= 1,000,000.\n *\n * @param {bigint} n - Number >= 1,000,000\n * @returns {string} Greek words\n */\nfunction buildLargeNumberWords (n) {\n const numStr = n.toString()\n const len = numStr.length\n\n // Build segments of 3 digits from right to left\n const segments = []\n const segmentSize = 3\n\n const remainderLen = len % segmentSize\n let pos = 0\n if (remainderLen > 0) {\n segments.push(Number(numStr.slice(0, remainderLen)))\n pos = remainderLen\n }\n while (pos < len) {\n segments.push(Number(numStr.slice(pos, pos + segmentSize)))\n pos += segmentSize\n }\n\n // Convert segments to words\n const parts = []\n let scaleIndex = segments.length - 1\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]\n\n if (segment !== 0) {\n const segmentWord = SEGMENTS[segment]\n\n if (scaleIndex === 0) {\n // Units segment\n parts.push(segmentWord)\n } else if (scaleIndex === 1) {\n // Thousands - omit \"ένα\" before χίλια\n if (segment === 1) {\n parts.push(THOUSAND)\n } else {\n parts.push(segmentWord + ' ' + THOUSAND)\n }\n } else {\n // Millions+ - omit \"ένα\" before scale words\n const scaleWord = SCALES[scaleIndex - 2]\n if (segment === 1) {\n parts.push(scaleWord)\n } else {\n parts.push(segmentWord + ' ' + scaleWord)\n }\n }\n }\n\n scaleIndex--\n }\n\n return parts.join(' ')\n}\n\n/**\n * Converts decimal digits to Greek words (per-digit).\n *\n * @param {string} decimalPart - Decimal digits (without the point)\n * @returns {string} Greek words for decimal part\n */\nfunction decimalPartToWords (decimalPart) {\n const parts = []\n\n for (const digit of decimalPart) {\n const d = parseInt(digit, 10)\n if (d === 0) {\n parts.push(ZERO)\n } else {\n parts.push(ONES[d])\n }\n }\n\n return parts.join(' ')\n}\n\n/**\n * Converts a numeric value to Greek words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in Greek words\n * @throws {TypeError} If value is not a valid numeric type\n * @throws {Error} If value is not a valid number format\n *\n * @example\n * toWords(21) // 'είκοσι ένα'\n * toWords(1000) // 'χίλια'\n * toWords('3.14') // 'τρία κόμμα ένα τέσσερα'\n */\nfunction toWords (value) {\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)\n }\n\n return result\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport { toWords }\n"],"names":["parseNumericString","str","isNegative","slice","dotIndex","indexOf","integerPart","BigInt","integerStr","decimalPart","expandScientificNotation","mantissa","expStr","toLowerCase","split","exp","parseInt","digits","newDotPosition","length","repeat","ONES","TEENS","TENS","HUNDREDS","THOUSAND","ZERO","SCALES","buildSegment","n","ones","tens","Math","floor","hundreds","parts","push","tensOnes","join","SEGMENTS","Array","i","value","type","Number","isFinite","Error","isSafeInteger","toString","includes","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","result","NEGATIVE","thousands","remainder","numStr","len","segments","remainderLen","pos","scaleIndex","segment","segmentWord","scaleWord","buildLargeNumberWords","integerToWords","digit","d","decimalPartToWords"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /*! n2words/en v3.0.0 | MIT License | github.com/forzagreen/n2words */
2
+ var e,n;e=this,n=function(e){"use strict";function n(e){const n="-"===e[0];n&&(e=e.slice(1));const t=e.indexOf(".");if(-1===t)return{isNegative:n,integerPart:BigInt(e)};const i=e.slice(0,t)||"0",r=e.slice(t+1);return{isNegative:n,integerPart:BigInt(i),decimalPart:r}}function t(e){const[n,t]=e.toLowerCase().split("e"),i=parseInt(t,10),r=n.indexOf("."),o=-1===r?n:n.slice(0,r)+n.slice(r+1),s=(-1===r?n.length:r)+i;return s>=o.length?o+"0".repeat(s-o.length):s<=0?"0."+"0".repeat(-s)+o:o.slice(0,s)+"."+o.slice(s)}const i=["","one","two","three","four","five","six","seven","eight","nine"],r=["ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen","eighteen","nineteen"],o=["","","twenty","thirty","forty","fifty","sixty","seventy","eighty","ninety"],s=["thousand","million","billion","trillion","quadrillion","quintillion","sextillion","septillion","octillion"],u="zero";function l(e){if(0===e)return{word:"",hasHundred:!1};const n=e%10,t=Math.floor(e/10)%10,s=Math.floor(e/100);let u="",l=!1;s>0&&(u=i[s]+" hundred",l=!0);let f="";return 1===t?f=r[n]:t>=2?f=n>0?o[t]+"-"+i[n]:o[t]:n>0&&(f=i[n]),u&&f?{word:u+" and "+f,hasHundred:!0}:{word:u||f,hasHundred:l}}const f=new Array(1e3),a=new Array(1e3);for(let e=0;e<1e3;e++){const n=l(e);f[e]=n.word,a[e]=n.hasHundred}function c(e){if(0n===e)return u;if(e<1000n)return f[Number(e)];if(e<1000000n){const n=Number(e/1000n),t=Number(e%1000n);let i=f[n]+" "+s[0];if(t>0){const e=f[t];i+=a[t]?" "+e:" and "+e}return i}return function(e){const n=[];let t=e;for(;t>0n;)n.push(Number(t%1000n)),t/=1000n;let i=-1;for(let e=0;e<n.length;e++)if(0!==n[e]){i=e;break}let r="",o=!1;for(let e=n.length-1;e>=0;e--){const t=n[e];0!==t&&(r&&e===i&&o&&!a[t]&&(r+=" and"),r&&(r+=" "),r+=f[t],e>0?(r+=" "+s[e-1],o=!0):o=!1)}return r}(e)}e.en=function(e){const{isNegative:i,integerPart:r,decimalPart:o}=function(e){const i=typeof e;if("bigint"===i)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===i){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:n(function(e){const n=e.toString();return n.includes("e")||n.includes("E")?t(n):n}(e))}if("string"===i)return n(function(e){const n=e.trim();if(0===n.length||Number.isNaN(Number(n)))throw new Error(`Invalid number format: "${e}"`);return n.includes("e")||n.includes("E")?t(n):n}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${i}`)}(e);let s="";return i&&(s="minus "),s+=c(r),o&&(s+=" point "+function(e){let n="",t=0;for(;t<e.length&&"0"===e[t];)n&&(n+=" "),n+=u,t++;const i=e.slice(t);return i&&(n&&(n+=" "),n+=c(BigInt(i))),n}(o)),s}},"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
3
+ //# sourceMappingURL=en.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"en.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/en.js"],"sourcesContent":["/**\n * Numeric value parsing utility.\n * Transforms user input (number, string, or bigint) into normalized components.\n * @module parse-numeric\n */\n\n/**\n * Parses a numeric value into its components.\n * @param {number|string|bigint} value\n * @returns {{isNegative: boolean, integerPart: bigint, decimalPart?: string}}\n * @throws {TypeError} If value is not number, string, or bigint\n * @throws {Error} If value is not a valid number format\n */\nexport function parseNumericValue (value) {\n const type = typeof value\n\n // BigInt: simplest case\n if (type === 'bigint') {\n return value < 0n\n ? { isNegative: true, integerPart: -value }\n : { isNegative: false, integerPart: value }\n }\n\n // Number: fast path for safe integers\n if (type === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('Number must be finite (NaN and Infinity are not supported)')\n }\n if (Number.isSafeInteger(value)) {\n return value < 0\n ? { isNegative: true, integerPart: BigInt(-value) }\n : { isNegative: false, integerPart: BigInt(value) }\n }\n return parseNumericString(numberToString(value))\n }\n\n // String input\n if (type === 'string') {\n return parseNumericString(normalizeString(value))\n }\n\n throw new TypeError(\n `Invalid value type: expected number, string, or bigint, received ${type}`\n )\n}\n\n/**\n * Converts a number to decimal string, expanding scientific notation if needed.\n */\nfunction numberToString (value) {\n const str = value.toString()\n return (str.includes('e') || str.includes('E'))\n ? expandScientificNotation(str)\n : str\n}\n\n/**\n * Validates and normalizes a string numeric input.\n */\nfunction normalizeString (value) {\n const trimmed = value.trim()\n if (trimmed.length === 0 || Number.isNaN(Number(trimmed))) {\n throw new Error(`Invalid number format: \"${value}\"`)\n }\n return (trimmed.includes('e') || trimmed.includes('E'))\n ? expandScientificNotation(trimmed)\n : trimmed\n}\n\n/**\n * Parses a normalized numeric string into components.\n */\nfunction parseNumericString (str) {\n const isNegative = str[0] === '-'\n if (isNegative) str = str.slice(1)\n\n const dotIndex = str.indexOf('.')\n if (dotIndex === -1) {\n return { isNegative, integerPart: BigInt(str) }\n }\n\n const integerStr = str.slice(0, dotIndex) || '0'\n const decimalPart = str.slice(dotIndex + 1)\n return { isNegative, integerPart: BigInt(integerStr), decimalPart }\n}\n\n/**\n * Expands scientific notation to decimal form (e.g., \"1e21\" → \"1000...\").\n */\nfunction expandScientificNotation (str) {\n const [mantissa, expStr] = str.toLowerCase().split('e')\n const exp = parseInt(expStr, 10)\n\n const dotIndex = mantissa.indexOf('.')\n const digits = dotIndex === -1\n ? mantissa\n : mantissa.slice(0, dotIndex) + mantissa.slice(dotIndex + 1)\n const integerLength = dotIndex === -1 ? mantissa.length : dotIndex\n const newDotPosition = integerLength + exp\n\n if (newDotPosition >= digits.length) {\n return digits + '0'.repeat(newDotPosition - digits.length)\n }\n if (newDotPosition <= 0) {\n return '0.' + '0'.repeat(-newDotPosition) + digits\n }\n return digits.slice(0, newDotPosition) + '.' + digits.slice(newDotPosition)\n}\n","/**\n * English language converter - Functional Implementation v2\n *\n * A performance-optimized number-to-words converter using precomputed lookup tables.\n * Self-contained module with its own input validation, ready for subpath exports.\n *\n * Key optimization: Precompute all segment values (0-999) at module load.\n * This eliminates all per-call string manipulation for segment conversion.\n *\n * English-specific rules (handled in precomputation):\n * - \"and\" after hundreds: \"one hundred and twenty-three\"\n * - Hyphenated tens-ones: \"twenty-one\", \"forty-two\"\n * - \"and\" before final segment when following scale word\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary (module-level constants)\n// ============================================================================\n\nconst ONES = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine']\nconst TEENS = ['ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen']\nconst TENS = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety']\n\nconst SCALES = ['thousand', 'million', 'billion', 'trillion', 'quadrillion', 'quintillion', 'sextillion', 'septillion', 'octillion']\n\nconst HUNDRED = 'hundred'\nconst ZERO = 'zero'\nconst NEGATIVE = 'minus'\nconst DECIMAL_SEP = 'point'\n\n// ============================================================================\n// Precomputed Lookup Tables (built once at module load)\n// ============================================================================\n\n/**\n * Builds segment word for 0-999.\n * Returns object with word and whether it contains \"hundred\" (for \"and\" logic).\n */\nfunction buildSegment (n) {\n if (n === 0) return { word: '', hasHundred: false }\n\n const ones = n % 10\n const tens = Math.floor(n / 10) % 10\n const hundreds = Math.floor(n / 100)\n\n let result = ''\n let hasHundred = false\n\n // Hundreds\n if (hundreds > 0) {\n result = ONES[hundreds] + ' ' + HUNDRED\n hasHundred = true\n }\n\n // Tens and ones\n let tensOnes = ''\n if (tens === 1) {\n tensOnes = TEENS[ones]\n } else if (tens >= 2) {\n if (ones > 0) {\n tensOnes = TENS[tens] + '-' + ONES[ones]\n } else {\n tensOnes = TENS[tens]\n }\n } else if (ones > 0) {\n tensOnes = ONES[ones]\n }\n\n // Combine with \"and\" after hundreds\n if (result && tensOnes) {\n return { word: result + ' and ' + tensOnes, hasHundred: true }\n }\n\n return { word: result || tensOnes, hasHundred }\n}\n\n// Precompute all 1000 segment words (0-999)\nconst SEGMENTS = new Array(1000)\nconst SEGMENTS_HAS_HUNDRED = new Array(1000)\n\nfor (let i = 0; i < 1000; i++) {\n const result = buildSegment(i)\n SEGMENTS[i] = result.word\n SEGMENTS_HAS_HUNDRED[i] = result.hasHundred\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\n/**\n * Converts a non-negative integer to English words.\n *\n * @param {bigint} n - Non-negative integer to convert\n * @returns {string} English words\n */\nfunction integerToWords (n) {\n if (n === 0n) return ZERO\n\n // Fast path: numbers < 1000 (direct lookup)\n if (n < 1000n) {\n return SEGMENTS[Number(n)]\n }\n\n // Fast path: numbers < 1,000,000 (thousands)\n if (n < 1_000_000n) {\n const thousands = Number(n / 1000n)\n const remainder = Number(n % 1000n)\n\n let result = SEGMENTS[thousands] + ' ' + SCALES[0]\n\n if (remainder > 0) {\n const remainderWord = SEGMENTS[remainder]\n // Insert \"and\" if remainder doesn't have hundred\n if (!SEGMENTS_HAS_HUNDRED[remainder]) {\n result += ' and ' + remainderWord\n } else {\n result += ' ' + remainderWord\n }\n }\n\n return result\n }\n\n // For numbers >= 1,000,000, use scale decomposition\n return buildLargeNumberWords(n)\n}\n\n/**\n * Builds words for numbers >= 1,000,000.\n * Uses BigInt division for faster segment extraction.\n *\n * @param {bigint} n - Number >= 1,000,000\n * @returns {string} English words\n */\nfunction buildLargeNumberWords (n) {\n // Extract segments using BigInt division (faster than string slicing)\n // Segments are stored least-significant first (index 0 = ones, 1 = thousands, etc.)\n const segments = []\n let temp = n\n while (temp > 0n) {\n segments.push(Number(temp % 1000n))\n temp = temp / 1000n\n }\n\n // Find the first (smallest index) non-zero segment - this is processed last\n let firstNonZeroIdx = -1\n for (let i = 0; i < segments.length; i++) {\n if (segments[i] !== 0) {\n firstNonZeroIdx = i\n break\n }\n }\n\n // Build result string directly (avoids intermediate object allocations)\n // Process from most-significant (end) to least-significant (start)\n let result = ''\n let prevWasScale = false\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i]\n if (segment === 0) continue\n\n const segmentWord = SEGMENTS[segment]\n const hasHundred = SEGMENTS_HAS_HUNDRED[segment]\n const isLastSegment = (i === firstNonZeroIdx)\n\n // Add \"and\" only before FINAL segment if it follows scale and doesn't have hundred\n if (result && isLastSegment && prevWasScale && !hasHundred) {\n result += ' and'\n }\n\n // Add segment word\n if (result) result += ' '\n result += segmentWord\n\n // Add scale word (i=0 is units, i=1 is thousands, etc.)\n if (i > 0) {\n result += ' ' + SCALES[i - 1]\n prevWasScale = true\n } else {\n prevWasScale = false\n }\n }\n\n return result\n}\n\n/**\n * Converts decimal digits to English words.\n *\n * @param {string} decimalPart - Decimal digits (without the point)\n * @returns {string} English words for decimal part\n */\nfunction decimalPartToWords (decimalPart) {\n let result = ''\n\n // Handle leading zeros\n let i = 0\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n // Convert remainder as a single number\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder))\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to English words.\n *\n * This is the main public API. It accepts any valid numeric input\n * (number, string, or bigint) and handles parsing internally.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in English words\n * @throws {TypeError} If value is not a valid numeric type\n * @throws {Error} If value is not a valid number format\n *\n * @example\n * toWords(42) // 'forty-two'\n * toWords(-3.14) // 'minus three point one four'\n * toWords('1000000') // 'one million'\n */\nfunction toWords (value) {\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)\n }\n\n return result\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport { toWords }\n"],"names":["parseNumericString","str","isNegative","slice","dotIndex","indexOf","integerPart","BigInt","integerStr","decimalPart","expandScientificNotation","mantissa","expStr","toLowerCase","split","exp","parseInt","digits","newDotPosition","length","repeat","ONES","TEENS","TENS","SCALES","ZERO","buildSegment","n","word","hasHundred","ones","tens","Math","floor","hundreds","result","tensOnes","SEGMENTS","Array","SEGMENTS_HAS_HUNDRED","i","integerToWords","Number","thousands","remainder","remainderWord","segments","temp","push","firstNonZeroIdx","prevWasScale","segment","buildLargeNumberWords","value","type","isFinite","Error","isSafeInteger","toString","includes","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","NEGATIVE","decimalPartToWords"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /*! n2words/es v3.0.0 | MIT License | github.com/forzagreen/n2words */
2
+ var e,n;e=this,n=function(e){"use strict";function n(e){const n="-"===e[0];n&&(e=e.slice(1));const t=e.indexOf(".");if(-1===t)return{isNegative:n,integerPart:BigInt(e)};const i=e.slice(0,t)||"0",r=e.slice(t+1);return{isNegative:n,integerPart:BigInt(i),decimalPart:r}}function t(e){const[n,t]=e.toLowerCase().split("e"),i=parseInt(t,10),r=n.indexOf("."),o=-1===r?n:n.slice(0,r)+n.slice(r+1),s=(-1===r?n.length:r)+i;return s>=o.length?o+"0".repeat(s-o.length):s<=0?"0."+"0".repeat(-s)+o:o.slice(0,s)+"."+o.slice(s)}const i=["","uno","dos","tres","cuatro","cinco","seis","siete","ocho","nueve"],r=["","una","dos","tres","cuatro","cinco","seis","siete","ocho","nueve"],o=["diez","once","doce","trece","catorce","quince","dieciseis","diecisiete","dieciocho","diecinueve"],s=["veinte","veintiuno","veintidós","veintitrés","veinticuatro","veinticinco","veintiséis","veintisiete","veintiocho","veintinueve"],c=["veinte","veintiuna","veintidós","veintitrés","veinticuatro","veinticinco","veintiséis","veintisiete","veintiocho","veintinueve"],u=["","","","treinta","cuarenta","cincuenta","sesenta","setenta","ochenta","noventa"],a=["","ciento","doscientos","trescientos","cuatrocientos","quinientos","seiscientos","setecientos","ochocientos","novecientos"],l=["","cienta","doscientas","trescientas","cuatrocientas","quinientas","seiscientas","setecientas","ochocientas","novecientas"],f=["millón","billón","trillón","cuatrillón"],d=["millones","billones","trillones","cuatrillones"],v="mil",g="cero";function p(e,n){if(0===e)return"";if(100===e)return"cien";const t=e%10,f=Math.floor(e/10)%10,d=Math.floor(e/100),v=e%100,g=[];return d>0&&g.push((n?l:a)[d]),0===v||g.push(v<10?(n?r:i)[v]:v<20?o[t]:v<30?(n?c:s)[t]:0===t?u[f]:u[f]+" y "+(n?r:i)[t]),g.join(" ")}const b=new Array(1e3);for(let e=0;e<1e3;e++)b[e]=p(e,!1);const m=new Array(1e3);for(let e=0;e<1e3;e++)m[e]=p(e,!0);function h(e,n){if(1===e)return v;if(e%2==0){const t=e/2-1,i=f[t];return i?n>1n?d[t]:i:""}{const n=d[(e-1)/2-1];return n?v+" "+n:v}}function N(e,n){if(0n===e)return g;const t=n?m:b;if(e<1000n)return t[Number(e)];if(e<1000000n){const n=Number(e/1000n),i=Number(e%1000n);let r;if(1===n)r=v;else{const e=b[n];r="uno"===e||"una"===e?v:e+" "+v}return i>0&&(r+=" "+t[i]),r}return function(e,n){const t=[];let i=e;for(;i>0n;)t.push(i%1000n),i/=1000n;const r=n?m:b;let o="";for(let e=t.length-1;e>=0;e--){const n=t[e];if(0n===n)continue;const i=e>0?h(e,n):"";o&&(o+=" "),o+=0===e?r[Number(n)]:1===e?1n===n?v:b[Number(n)]+" "+i:e%2==1?1n===n?i:b[Number(n)]+" "+i:1n===n?"un "+i:b[Number(n)]+" "+i}return o}(e,n)}e.es=function(e,i){i=function(e){if(void 0===e)return{};if(function(e){if(null===e||"object"!=typeof e)return!1;const n=Object.getPrototypeOf(e);return null===n||n===Object.prototype}(e))return e;throw new TypeError("Invalid options: expected plain object or undefined, got "+typeof e)}(i);const{isNegative:r,integerPart:o,decimalPart:s}=function(e){const i=typeof e;if("bigint"===i)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===i){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:n(function(e){const n=e.toString();return n.includes("e")||n.includes("E")?t(n):n}(e))}if("string"===i)return n(function(e){const n=e.trim();if(0===n.length||Number.isNaN(Number(n)))throw new Error(`Invalid number format: "${e}"`);return n.includes("e")||n.includes("E")?t(n):n}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${i}`)}(e),c="feminine"===i.gender;let u="";return r&&(u="menos "),u+=N(o,c),s&&(u+=" punto "+function(e,n){let t="",i=0;for(;i<e.length&&"0"===e[i];)t&&(t+=" "),t+=g,i++;const r=e.slice(i);return r&&(t&&(t+=" "),t+=N(BigInt(r),n)),t}(s,c)),u}},"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
3
+ //# sourceMappingURL=es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"es.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/es.js","../../lib/utils/validate-options.js","../../lib/utils/is-plain-object.js"],"sourcesContent":["/**\n * Numeric value parsing utility.\n * Transforms user input (number, string, or bigint) into normalized components.\n * @module parse-numeric\n */\n\n/**\n * Parses a numeric value into its components.\n * @param {number|string|bigint} value\n * @returns {{isNegative: boolean, integerPart: bigint, decimalPart?: string}}\n * @throws {TypeError} If value is not number, string, or bigint\n * @throws {Error} If value is not a valid number format\n */\nexport function parseNumericValue (value) {\n const type = typeof value\n\n // BigInt: simplest case\n if (type === 'bigint') {\n return value < 0n\n ? { isNegative: true, integerPart: -value }\n : { isNegative: false, integerPart: value }\n }\n\n // Number: fast path for safe integers\n if (type === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('Number must be finite (NaN and Infinity are not supported)')\n }\n if (Number.isSafeInteger(value)) {\n return value < 0\n ? { isNegative: true, integerPart: BigInt(-value) }\n : { isNegative: false, integerPart: BigInt(value) }\n }\n return parseNumericString(numberToString(value))\n }\n\n // String input\n if (type === 'string') {\n return parseNumericString(normalizeString(value))\n }\n\n throw new TypeError(\n `Invalid value type: expected number, string, or bigint, received ${type}`\n )\n}\n\n/**\n * Converts a number to decimal string, expanding scientific notation if needed.\n */\nfunction numberToString (value) {\n const str = value.toString()\n return (str.includes('e') || str.includes('E'))\n ? expandScientificNotation(str)\n : str\n}\n\n/**\n * Validates and normalizes a string numeric input.\n */\nfunction normalizeString (value) {\n const trimmed = value.trim()\n if (trimmed.length === 0 || Number.isNaN(Number(trimmed))) {\n throw new Error(`Invalid number format: \"${value}\"`)\n }\n return (trimmed.includes('e') || trimmed.includes('E'))\n ? expandScientificNotation(trimmed)\n : trimmed\n}\n\n/**\n * Parses a normalized numeric string into components.\n */\nfunction parseNumericString (str) {\n const isNegative = str[0] === '-'\n if (isNegative) str = str.slice(1)\n\n const dotIndex = str.indexOf('.')\n if (dotIndex === -1) {\n return { isNegative, integerPart: BigInt(str) }\n }\n\n const integerStr = str.slice(0, dotIndex) || '0'\n const decimalPart = str.slice(dotIndex + 1)\n return { isNegative, integerPart: BigInt(integerStr), decimalPart }\n}\n\n/**\n * Expands scientific notation to decimal form (e.g., \"1e21\" → \"1000...\").\n */\nfunction expandScientificNotation (str) {\n const [mantissa, expStr] = str.toLowerCase().split('e')\n const exp = parseInt(expStr, 10)\n\n const dotIndex = mantissa.indexOf('.')\n const digits = dotIndex === -1\n ? mantissa\n : mantissa.slice(0, dotIndex) + mantissa.slice(dotIndex + 1)\n const integerLength = dotIndex === -1 ? mantissa.length : dotIndex\n const newDotPosition = integerLength + exp\n\n if (newDotPosition >= digits.length) {\n return digits + '0'.repeat(newDotPosition - digits.length)\n }\n if (newDotPosition <= 0) {\n return '0.' + '0'.repeat(-newDotPosition) + digits\n }\n return digits.slice(0, newDotPosition) + '.' + digits.slice(newDotPosition)\n}\n","/**\n * Spanish language converter - Functional Implementation\n *\n * A performance-optimized number-to-words converter using precomputed lookup tables.\n * Self-contained module with its own input validation, ready for subpath exports.\n *\n * Key optimization: Precompute all segment values (0-999) at module load.\n * This eliminates all per-call string manipulation for segment conversion.\n *\n * Spanish-specific rules (handled in precomputation):\n * - Gender agreement: uno/una, veintiuno/veintiuna, hundreds\n * - Special twenties: veinte, veintiuno, veintidós, ... veintinueve\n * - \"y\" conjunction: treinta y uno (only 30-99 with ones)\n * - \"cien\" for exact 100, \"ciento/cienta\" otherwise\n * - Irregular hundreds: quinientos, setecientos, novecientos\n * - Compound long scale: millón, mil millones, billón, mil billones\n * - \"un\" before millón (not \"uno\"), omit before mil\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\nimport { validateOptions } from '../utils/validate-options.js'\n\n// ============================================================================\n// Vocabulary (module-level constants)\n// ============================================================================\n\nconst ONES_MASC = ['', 'uno', 'dos', 'tres', 'cuatro', 'cinco', 'seis', 'siete', 'ocho', 'nueve']\nconst ONES_FEM = ['', 'una', 'dos', 'tres', 'cuatro', 'cinco', 'seis', 'siete', 'ocho', 'nueve']\n\nconst TEENS = ['diez', 'once', 'doce', 'trece', 'catorce', 'quince', 'dieciseis', 'diecisiete', 'dieciocho', 'diecinueve']\n\n// 20-29 have special compound forms\nconst TWENTIES_MASC = ['veinte', 'veintiuno', 'veintidós', 'veintitrés', 'veinticuatro', 'veinticinco', 'veintiséis', 'veintisiete', 'veintiocho', 'veintinueve']\nconst TWENTIES_FEM = ['veinte', 'veintiuna', 'veintidós', 'veintitrés', 'veinticuatro', 'veinticinco', 'veintiséis', 'veintisiete', 'veintiocho', 'veintinueve']\n\nconst TENS = ['', '', '', 'treinta', 'cuarenta', 'cincuenta', 'sesenta', 'setenta', 'ochenta', 'noventa']\n\n// Irregular hundreds\nconst HUNDREDS_MASC = ['', 'ciento', 'doscientos', 'trescientos', 'cuatrocientos', 'quinientos', 'seiscientos', 'setecientos', 'ochocientos', 'novecientos']\nconst HUNDREDS_FEM = ['', 'cienta', 'doscientas', 'trescientas', 'cuatrocientas', 'quinientas', 'seiscientas', 'setecientas', 'ochocientas', 'novecientas']\n\n// Scale words (compound long scale)\nconst SCALES = ['millón', 'billón', 'trillón', 'cuatrillón']\nconst SCALES_PLURAL = ['millones', 'billones', 'trillones', 'cuatrillones']\n\nconst THOUSAND = 'mil'\nconst ZERO = 'cero'\nconst NEGATIVE = 'menos'\nconst DECIMAL_SEP = 'punto'\n\n// ============================================================================\n// Precomputed Lookup Tables (built once at module load)\n// ============================================================================\n\n/**\n * Builds segment word for 0-999.\n * @param {number} n - Segment value\n * @param {boolean} feminine - Use feminine forms\n * @returns {string} Spanish word\n */\nfunction buildSegment (n, feminine) {\n if (n === 0) return ''\n\n // Special case: exact 100 is \"cien\" (no gender)\n if (n === 100) return 'cien'\n\n const ones = n % 10\n const tens = Math.floor(n / 10) % 10\n const hundreds = Math.floor(n / 100)\n const tensOnes = n % 100\n\n const parts = []\n\n // Hundreds\n if (hundreds > 0) {\n const hundredsArr = feminine ? HUNDREDS_FEM : HUNDREDS_MASC\n parts.push(hundredsArr[hundreds])\n }\n\n // Tens and ones\n if (tensOnes === 0) {\n // Just hundreds\n } else if (tensOnes < 10) {\n // Single digit\n const onesArr = feminine ? ONES_FEM : ONES_MASC\n parts.push(onesArr[tensOnes])\n } else if (tensOnes < 20) {\n // 10-19: teens\n parts.push(TEENS[ones])\n } else if (tensOnes < 30) {\n // 20-29: special twenties\n const twentiesArr = feminine ? TWENTIES_FEM : TWENTIES_MASC\n parts.push(twentiesArr[ones])\n } else {\n // 30-99: tens y ones\n if (ones === 0) {\n parts.push(TENS[tens])\n } else {\n const onesArr = feminine ? ONES_FEM : ONES_MASC\n parts.push(TENS[tens] + ' y ' + onesArr[ones])\n }\n }\n\n return parts.join(' ')\n}\n\n// Precompute all 1000 segment words (0-999) for masculine\nconst SEGMENTS_MASC = new Array(1000)\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS_MASC[i] = buildSegment(i, false)\n}\n\n// Precompute all 1000 segment words (0-999) for feminine\nconst SEGMENTS_FEM = new Array(1000)\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS_FEM[i] = buildSegment(i, true)\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Gets scale word for Spanish compound long scale.\n *\n * @param {number} scaleIndex - Scale level (1 = thousand, 2 = million, etc.)\n * @param {bigint} segment - Segment value for pluralization\n * @returns {string} Scale word\n */\nfunction getScaleWord (scaleIndex, segment) {\n if (scaleIndex === 1) return THOUSAND\n\n // Even indices (2, 4, 6, 8): millón, billón, trillón, cuatrillón\n // Odd indices > 1 (3, 5, 7): mil millones, mil billones, mil trillones\n if (scaleIndex % 2 === 0) {\n const arrayIndex = (scaleIndex / 2) - 1\n const baseWord = SCALES[arrayIndex]\n if (!baseWord) return ''\n return segment > 1n ? SCALES_PLURAL[arrayIndex] : baseWord\n } else {\n // Compound: \"mil millones\" pattern\n const arrayIndex = ((scaleIndex - 1) / 2) - 1\n const pluralWord = SCALES_PLURAL[arrayIndex]\n if (!pluralWord) return THOUSAND\n return THOUSAND + ' ' + pluralWord\n }\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\n/**\n * Converts a non-negative integer to Spanish words.\n *\n * @param {bigint} n - Non-negative integer to convert\n * @param {boolean} feminine - Use feminine forms\n * @returns {string} Spanish words\n */\nfunction integerToWords (n, feminine) {\n if (n === 0n) return ZERO\n\n const segments = feminine ? SEGMENTS_FEM : SEGMENTS_MASC\n\n // Fast path: numbers < 1000 (direct lookup)\n if (n < 1000n) {\n return segments[Number(n)]\n }\n\n // Fast path: numbers < 1,000,000 (thousands)\n if (n < 1_000_000n) {\n const thousands = Number(n / 1000n)\n const remainder = Number(n % 1000n)\n\n let result\n if (thousands === 1) {\n // \"mil\" not \"uno mil\"\n result = THOUSAND\n } else {\n // Use masculine for thousands segment, but check for \"uno\" → omit before mil\n const thousandsWord = SEGMENTS_MASC[thousands]\n // \"uno mil\" → \"mil\" (handled in joinSegments equivalent)\n if (thousandsWord === 'uno' || thousandsWord === 'una') {\n result = THOUSAND\n } else {\n result = thousandsWord + ' ' + THOUSAND\n }\n }\n\n if (remainder > 0) {\n result += ' ' + segments[remainder]\n }\n\n return result\n }\n\n // For numbers >= 1,000,000, use scale decomposition\n return buildLargeNumberWords(n, feminine)\n}\n\n/**\n * Builds words for numbers >= 1,000,000.\n * Uses BigInt division for faster segment extraction.\n *\n * @param {bigint} n - Number >= 1,000,000\n * @param {boolean} feminine - Use feminine forms\n * @returns {string} Spanish words\n */\nfunction buildLargeNumberWords (n, feminine) {\n // Extract segments using BigInt division (faster than string slicing)\n // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)\n const segmentValues = []\n let temp = n\n while (temp > 0n) {\n segmentValues.push(temp % 1000n)\n temp = temp / 1000n\n }\n\n const segments = feminine ? SEGMENTS_FEM : SEGMENTS_MASC\n\n // Build result string directly\n let result = ''\n\n for (let i = segmentValues.length - 1; i >= 0; i--) {\n const segment = segmentValues[i]\n if (segment === 0n) continue\n\n const scaleWord = i > 0 ? getScaleWord(i, segment) : ''\n\n if (result) result += ' '\n\n if (i === 0) {\n // Units segment\n result += segments[Number(segment)]\n } else if (i === 1) {\n // Thousands: omit \"uno\" before mil\n if (segment === 1n) {\n result += THOUSAND\n } else {\n result += SEGMENTS_MASC[Number(segment)] + ' ' + scaleWord\n }\n } else if (i % 2 === 1) {\n // Odd scale indices (3, 5, 7): \"mil millones\", \"mil billones\", etc.\n // Omit \"uno\" before these compound scales\n if (segment === 1n) {\n result += scaleWord\n } else {\n result += SEGMENTS_MASC[Number(segment)] + ' ' + scaleWord\n }\n } else {\n // Even scale indices (2, 4, 6): millón, billón, trillón\n if (segment === 1n) {\n // \"un millón\" not \"uno millón\"\n result += 'un ' + scaleWord\n } else {\n // Use masculine for scale segment\n result += SEGMENTS_MASC[Number(segment)] + ' ' + scaleWord\n }\n }\n }\n\n return result\n}\n\n/**\n * Converts decimal digits to Spanish words.\n *\n * @param {string} decimalPart - Decimal digits (without the point)\n * @param {boolean} feminine - Use feminine forms\n * @returns {string} Spanish words for decimal part\n */\nfunction decimalPartToWords (decimalPart, feminine) {\n let result = ''\n\n // Handle leading zeros\n let i = 0\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n // Convert remainder as a single number\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder), feminine)\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Spanish words.\n *\n * This is the main public API. It accepts any valid numeric input\n * (number, string, or bigint) and handles parsing internally.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @param {Object} [options] - Optional configuration\n * @param {('masculine'|'feminine')} [options.gender='masculine'] - Grammatical gender\n * @returns {string} The number in Spanish words\n * @throws {TypeError} If value is not a valid numeric type\n * @throws {Error} If value is not a valid number format\n *\n * @example\n * toWords(21) // 'veintiuno'\n * toWords(21, {gender: 'feminine'}) // 'veintiuna'\n * toWords(1000000) // 'un millón'\n */\nfunction toWords (value, options) {\n options = validateOptions(options)\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n const feminine = options.gender === 'feminine'\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart, feminine)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, feminine)\n }\n\n return result\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport { toWords }\n","import { isPlainObject } from './is-plain-object.js'\n\n/**\n * Validates and normalizes the options parameter.\n *\n * @param {*} options The options value to validate\n * @returns {Object} A valid options object (empty object if undefined)\n * @throws {TypeError} If options is not undefined or a plain object\n */\nexport function validateOptions (options) {\n if (options === undefined) return {}\n if (isPlainObject(options)) return options\n throw new TypeError(\n `Invalid options: expected plain object or undefined, got ${typeof options}`\n )\n}\n","/**\n * Checks if a value is a plain object (not null, array, or other object types).\n *\n * A plain object is one created by:\n * - Object literal: `{}`\n * - Object.create(null): null-prototype object\n *\n * This excludes arrays, class instances, Map, Set, and other object types.\n *\n * @param {*} value Value to check\n * @returns {boolean} True if value is a plain object\n */\nexport function isPlainObject (value) {\n if (value === null || typeof value !== 'object') return false\n const proto = Object.getPrototypeOf(value)\n return proto === null || proto === Object.prototype\n}\n"],"names":["parseNumericString","str","isNegative","slice","dotIndex","indexOf","integerPart","BigInt","integerStr","decimalPart","expandScientificNotation","mantissa","expStr","toLowerCase","split","exp","parseInt","digits","newDotPosition","length","repeat","ONES_MASC","ONES_FEM","TEENS","TWENTIES_MASC","TWENTIES_FEM","TENS","HUNDREDS_MASC","HUNDREDS_FEM","SCALES","SCALES_PLURAL","THOUSAND","ZERO","buildSegment","n","feminine","ones","tens","Math","floor","hundreds","tensOnes","parts","push","join","SEGMENTS_MASC","Array","i","SEGMENTS_FEM","getScaleWord","scaleIndex","segment","arrayIndex","baseWord","pluralWord","integerToWords","segments","Number","thousands","remainder","result","thousandsWord","segmentValues","temp","scaleWord","buildLargeNumberWords","value","options","undefined","proto","Object","getPrototypeOf","prototype","isPlainObject","TypeError","validateOptions","type","isFinite","Error","isSafeInteger","toString","includes","numberToString","trimmed","trim","isNaN","normalizeString","parseNumericValue","gender","NEGATIVE","decimalPartToWords"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /*! n2words/fa v3.0.0 | MIT License | github.com/forzagreen/n2words */
2
+ var e,n;e=this,n=function(e){"use strict";function n(e){const n="-"===e[0];n&&(e=e.slice(1));const t=e.indexOf(".");if(-1===t)return{isNegative:n,integerPart:BigInt(e)};const r=e.slice(0,t)||"0",i=e.slice(t+1);return{isNegative:n,integerPart:BigInt(r),decimalPart:i}}function t(e){const[n,t]=e.toLowerCase().split("e"),r=parseInt(t,10),i=n.indexOf("."),s=-1===i?n:n.slice(0,i)+n.slice(i+1),u=(-1===i?n.length:i)+r;return u>=s.length?s+"0".repeat(u-s.length):u<=0?"0."+"0".repeat(-u)+s:s.slice(0,u)+"."+s.slice(u)}const r={1:"یک",2:"دو",3:"سه",4:"چهار",5:"پنج",6:"شش",7:"هفت",8:"هشت",9:"نه"},i={10:"ده",11:"یازده",12:"دوازده",13:"سیزده",14:"چهارده",15:"پانزده",16:"شانزده",17:"هفده",18:"هجده",19:"نوزده"},s={20:"بیست",30:"سی",40:"چهل",50:"پنجاه",60:"شصت",70:"هفتاد",80:"هشتاد",90:"نود"},u={100:"صد",200:"دویست",300:"سيصد",400:"چهار صد",500:"پانصد",600:"ششصد",700:"هفتصد",800:"هشتصد",900:"نهصد"};function o(e){if(0n===e)return"صفر";if(e<=9n)return r[Number(e)];if(e<=19n)return i[Number(e)];if(e<100n){const n=e%10n,t=e-n;return 0n===n?s[Number(t)]:`${s[Number(t)]} و ${r[Number(n)]}`}if(e<1000n){const n=e/100n*100n,t=e-n;return 0n===t?u[Number(n)]:`${u[Number(n)]} و ${o(t)}`}if(e<1000000n){const n=e/1000n,t=e%1000n;return`${1n===n?"":o(n)+" "}هزار${0n===t?"":" "+o(t)}`}const n=e%1000000n;return`${o(e/1000000n)+" میلیون"}${0n===n?"":" و "+o(n)}`}e.fa=function(e){const{isNegative:r,integerPart:i,decimalPart:s}=function(e){const r=typeof e;if("bigint"===r)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===r){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:n(function(e){const n=e.toString();return n.includes("e")||n.includes("E")?t(n):n}(e))}if("string"===r)return n(function(e){const n=e.trim();if(0===n.length||Number.isNaN(Number(n)))throw new Error(`Invalid number format: "${e}"`);return n.includes("e")||n.includes("E")?t(n):n}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${r}`)}(e);let u="";return r&&(u="منفى "),u+=o(i),s&&(u+=" ممیّز "+function(e){let n="",t=0;for(;t<e.length&&"0"===e[t];)n&&(n+=" "),n+="صفر",t++;const r=e.slice(t);return r&&(n&&(n+=" "),n+=o(BigInt(r))),n}(s)),u}},"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
3
+ //# sourceMappingURL=fa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fa.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/fa.js"],"sourcesContent":["/**\n * Numeric value parsing utility.\n * Transforms user input (number, string, or bigint) into normalized components.\n * @module parse-numeric\n */\n\n/**\n * Parses a numeric value into its components.\n * @param {number|string|bigint} value\n * @returns {{isNegative: boolean, integerPart: bigint, decimalPart?: string}}\n * @throws {TypeError} If value is not number, string, or bigint\n * @throws {Error} If value is not a valid number format\n */\nexport function parseNumericValue (value) {\n const type = typeof value\n\n // BigInt: simplest case\n if (type === 'bigint') {\n return value < 0n\n ? { isNegative: true, integerPart: -value }\n : { isNegative: false, integerPart: value }\n }\n\n // Number: fast path for safe integers\n if (type === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('Number must be finite (NaN and Infinity are not supported)')\n }\n if (Number.isSafeInteger(value)) {\n return value < 0\n ? { isNegative: true, integerPart: BigInt(-value) }\n : { isNegative: false, integerPart: BigInt(value) }\n }\n return parseNumericString(numberToString(value))\n }\n\n // String input\n if (type === 'string') {\n return parseNumericString(normalizeString(value))\n }\n\n throw new TypeError(\n `Invalid value type: expected number, string, or bigint, received ${type}`\n )\n}\n\n/**\n * Converts a number to decimal string, expanding scientific notation if needed.\n */\nfunction numberToString (value) {\n const str = value.toString()\n return (str.includes('e') || str.includes('E'))\n ? expandScientificNotation(str)\n : str\n}\n\n/**\n * Validates and normalizes a string numeric input.\n */\nfunction normalizeString (value) {\n const trimmed = value.trim()\n if (trimmed.length === 0 || Number.isNaN(Number(trimmed))) {\n throw new Error(`Invalid number format: \"${value}\"`)\n }\n return (trimmed.includes('e') || trimmed.includes('E'))\n ? expandScientificNotation(trimmed)\n : trimmed\n}\n\n/**\n * Parses a normalized numeric string into components.\n */\nfunction parseNumericString (str) {\n const isNegative = str[0] === '-'\n if (isNegative) str = str.slice(1)\n\n const dotIndex = str.indexOf('.')\n if (dotIndex === -1) {\n return { isNegative, integerPart: BigInt(str) }\n }\n\n const integerStr = str.slice(0, dotIndex) || '0'\n const decimalPart = str.slice(dotIndex + 1)\n return { isNegative, integerPart: BigInt(integerStr), decimalPart }\n}\n\n/**\n * Expands scientific notation to decimal form (e.g., \"1e21\" → \"1000...\").\n */\nfunction expandScientificNotation (str) {\n const [mantissa, expStr] = str.toLowerCase().split('e')\n const exp = parseInt(expStr, 10)\n\n const dotIndex = mantissa.indexOf('.')\n const digits = dotIndex === -1\n ? mantissa\n : mantissa.slice(0, dotIndex) + mantissa.slice(dotIndex + 1)\n const integerLength = dotIndex === -1 ? mantissa.length : dotIndex\n const newDotPosition = integerLength + exp\n\n if (newDotPosition >= digits.length) {\n return digits + '0'.repeat(newDotPosition - digits.length)\n }\n if (newDotPosition <= 0) {\n return '0.' + '0'.repeat(-newDotPosition) + digits\n }\n return digits.slice(0, newDotPosition) + '.' + digits.slice(newDotPosition)\n}\n","/**\n * Persian language converter - Functional Implementation\n *\n * Self-contained converter using recursive decomposition.\n *\n * Key features:\n * - \"و\" (and) conjunction for compound numbers\n * - Omit \"یک\" (one) before thousand\n * - Pre-composed hundreds (دویست, سيصد, etc.)\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary\n// ============================================================================\n\nconst ONES = { 1: 'یک', 2: 'دو', 3: 'سه', 4: 'چهار', 5: 'پنج', 6: 'شش', 7: 'هفت', 8: 'هشت', 9: 'نه' }\nconst TEENS = { 10: 'ده', 11: 'یازده', 12: 'دوازده', 13: 'سیزده', 14: 'چهارده', 15: 'پانزده', 16: 'شانزده', 17: 'هفده', 18: 'هجده', 19: 'نوزده' }\nconst TENS = { 20: 'بیست', 30: 'سی', 40: 'چهل', 50: 'پنجاه', 60: 'شصت', 70: 'هفتاد', 80: 'هشتاد', 90: 'نود' }\nconst HUNDREDS = { 100: 'صد', 200: 'دویست', 300: 'سيصد', 400: 'چهار صد', 500: 'پانصد', 600: 'ششصد', 700: 'هفتصد', 800: 'هشتصد', 900: 'نهصد' }\n\nconst THOUSAND = 'هزار'\nconst MILLION = 'میلیون'\n\nconst ZERO = 'صفر'\nconst NEGATIVE = 'منفى'\nconst DECIMAL_SEP = 'ممیّز'\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\nfunction integerToWords (n) {\n if (n === 0n) return ZERO\n\n // 1-9\n if (n <= 9n) {\n return ONES[Number(n)]\n }\n\n // 10-19\n if (n <= 19n) {\n return TEENS[Number(n)]\n }\n\n // 20-99\n if (n < 100n) {\n const ones = n % 10n\n const tens = n - ones\n if (ones === 0n) {\n return TENS[Number(tens)]\n }\n return `${TENS[Number(tens)]} و ${ONES[Number(ones)]}`\n }\n\n // 100-999\n if (n < 1000n) {\n const hundreds = 100n * (n / 100n)\n const remainder = n - hundreds\n if (remainder === 0n) {\n return HUNDREDS[Number(hundreds)]\n }\n return `${HUNDREDS[Number(hundreds)]} و ${integerToWords(remainder)}`\n }\n\n // 1000-999999\n if (n < 1_000_000n) {\n const thousandMultiplier = n / 1000n\n // Persian omits \"one\" before thousand: 1000 is just \"هزار\", not \"یک هزار\"\n const thousandPrefix = thousandMultiplier === 1n\n ? ''\n : integerToWords(thousandMultiplier) + ' '\n const remainder = n % 1000n\n const suffix = remainder === 0n ? '' : ' ' + integerToWords(remainder)\n return `${thousandPrefix}${THOUSAND}${suffix}`\n }\n\n // 1000000+\n const millionMultiplier = n / 1_000_000n\n const millionPrefix = integerToWords(millionMultiplier) + ' ' + MILLION\n const remainder = n % 1_000_000n\n const suffix = remainder === 0n ? '' : ' و ' + integerToWords(remainder)\n return `${millionPrefix}${suffix}`\n}\n\nfunction decimalPartToWords (decimalPart) {\n let result = ''\n let i = 0\n\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder))\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Persian words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in Persian words\n */\nfunction toWords (value) {\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)\n }\n\n return result\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport { toWords }\n"],"names":["parseNumericString","str","isNegative","slice","dotIndex","indexOf","integerPart","BigInt","integerStr","decimalPart","expandScientificNotation","mantissa","expStr","toLowerCase","split","exp","parseInt","digits","newDotPosition","length","repeat","ONES","TEENS","TENS","HUNDREDS","integerToWords","n","Number","ones","tens","hundreds","remainder","thousandMultiplier","value","type","isFinite","Error","isSafeInteger","toString","includes","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","result","NEGATIVE","i","decimalPartToWords"],"mappings":";0CAwEA,SAASA,EAAoBC,GAC3B,MAAMC,EAAwB,MAAXD,EAAI,GACnBC,IAAYD,EAAMA,EAAIE,MAAM,IAEhC,MAAMC,EAAWH,EAAII,QAAQ,KAC7B,IAAiB,IAAbD,EACF,MAAO,CAAEF,aAAYI,YAAaC,OAAON,IAG3C,MAAMO,EAAaP,EAAIE,MAAM,EAAGC,IAAa,IACvCK,EAAcR,EAAIE,MAAMC,EAAW,GACzC,MAAO,CAAEF,aAAYI,YAAaC,OAAOC,GAAaC,cACxD,CAKA,SAASC,EAA0BT,GACjC,MAAOU,EAAUC,GAAUX,EAAIY,cAAcC,MAAM,KAC7CC,EAAMC,SAASJ,EAAQ,IAEvBR,EAAWO,EAASN,QAAQ,KAC5BY,OAASb,EACXO,EACAA,EAASR,MAAM,EAAGC,GAAYO,EAASR,MAAMC,EAAW,GAEtDc,IAD6B,IAAbd,EAAkBO,EAASQ,OAASf,GACnBW,EAEvC,OAAIG,GAAkBD,EAAOE,OACpBF,EAAS,IAAIG,OAAOF,EAAiBD,EAAOE,QAEjDD,GAAkB,EACb,KAAO,IAAIE,QAAQF,GAAkBD,EAEvCA,EAAOd,MAAM,EAAGe,GAAkB,IAAMD,EAAOd,MAAMe,EAC9D,CC1FA,MAAMG,EAAO,CAAE,EAAG,KAAM,EAAG,KAAM,EAAG,KAAM,EAAG,OAAQ,EAAG,MAAO,EAAG,KAAM,EAAG,MAAO,EAAG,MAAO,EAAG,MACzFC,EAAQ,CAAE,GAAI,KAAM,GAAI,QAAS,GAAI,SAAU,GAAI,QAAS,GAAI,SAAU,GAAI,SAAU,GAAI,SAAU,GAAI,OAAQ,GAAI,OAAQ,GAAI,SAClIC,EAAO,CAAE,GAAI,OAAQ,GAAI,KAAM,GAAI,MAAO,GAAI,QAAS,GAAI,MAAO,GAAI,QAAS,GAAI,QAAS,GAAI,OAChGC,EAAW,CAAE,IAAK,KAAM,IAAK,QAAS,IAAK,OAAQ,IAAK,UAAW,IAAK,QAAS,IAAK,OAAQ,IAAK,QAAS,IAAK,QAAS,IAAK,QAarI,SAASC,EAAgBC,GACvB,GAAU,KAANA,EAAU,MATH,MAYX,GAAIA,GAAK,GACP,OAAOL,EAAKM,OAAOD,IAIrB,GAAIA,GAAK,IACP,OAAOJ,EAAMK,OAAOD,IAItB,GAAIA,EAAI,KAAM,CACZ,MAAME,EAAOF,EAAI,IACXG,EAAOH,EAAIE,EACjB,OAAa,KAATA,EACKL,EAAKI,OAAOE,IAEd,GAAGN,EAAKI,OAAOE,SAAYR,EAAKM,OAAOC,KAChD,CAGA,GAAIF,EAAI,MAAO,CACb,MAAMI,EAAmBJ,EAAI,KAAZ,KACXK,EAAYL,EAAII,EACtB,OAAkB,KAAdC,EACKP,EAASG,OAAOG,IAElB,GAAGN,EAASG,OAAOG,SAAgBL,EAAeM,IAC3D,CAGA,GAAIL,EAAI,SAAY,CAClB,MAAMM,EAAqBN,EAAI,MAKzBK,EAAYL,EAAI,MAEtB,MAAO,GALuC,KAAvBM,EACnB,GACAP,EAAeO,GAAsB,UAEZ,KAAdD,EAAmB,GAAK,IAAMN,EAAeM,IAE9D,CAGA,MAEMA,EAAYL,EAAI,SAEtB,MAAO,GAHeD,EADIC,EAAI,UACRD,YAEO,KAAdM,EAAmB,GAAK,MAAQN,EAAeM,IAEhE,MA2BA,SAAkBE,GAChB,MAAM/B,WAAEA,EAAUI,YAAEA,EAAWG,YAAEA,GDnG5B,SAA4BwB,GACjC,MAAMC,SAAcD,EAGpB,GAAa,WAATC,EACF,OAAOD,EAAQ,GACX,CAAE/B,YAAY,EAAMI,aAAc2B,GAClC,CAAE/B,YAAY,EAAOI,YAAa2B,GAIxC,GAAa,WAATC,EAAmB,CACrB,IAAKP,OAAOQ,SAASF,GACnB,MAAM,IAAIG,MAAM,8DAElB,OAAIT,OAAOU,cAAcJ,GAChBA,EAAQ,EACX,CAAE/B,YAAY,EAAMI,YAAaC,QAAQ0B,IACzC,CAAE/B,YAAY,EAAOI,YAAaC,OAAO0B,IAExCjC,EAgBX,SAAyBiC,GACvB,MAAMhC,EAAMgC,EAAMK,WAClB,OAAQrC,EAAIsC,SAAS,MAAQtC,EAAIsC,SAAS,KACtC7B,EAAyBT,GACzBA,CACN,CArB8BuC,CAAeP,GAC3C,CAGA,GAAa,WAATC,EACF,OAAOlC,EAqBX,SAA0BiC,GACxB,MAAMQ,EAAUR,EAAMS,OACtB,GAAuB,IAAnBD,EAAQtB,QAAgBQ,OAAOgB,MAAMhB,OAAOc,IAC9C,MAAM,IAAIL,MAAM,2BAA2BH,MAE7C,OAAQQ,EAAQF,SAAS,MAAQE,EAAQF,SAAS,KAC9C7B,EAAyB+B,GACzBA,CACN,CA7B8BG,CAAgBX,IAG5C,MAAM,IAAIY,UACR,oEAAoEX,IAExE,CCoEmDY,CAAkBb,GAEnE,IAAIc,EAAS,GAYb,OAVI7C,IACF6C,EAASC,SAGXD,GAAUtB,EAAenB,GAErBG,IACFsC,GAAU,UArCd,SAA6BtC,GAC3B,IAAIsC,EAAS,GACTE,EAAI,EAER,KAAOA,EAAIxC,EAAYU,QAA6B,MAAnBV,EAAYwC,IACvCF,IAAQA,GAAU,KACtBA,GAnES,MAoETE,IAGF,MAAMlB,EAAYtB,EAAYN,MAAM8C,GAMpC,OALIlB,IACEgB,IAAQA,GAAU,KACtBA,GAAUtB,EAAelB,OAAOwB,KAG3BgB,CACT,CAoBwCG,CAAmBzC,IAGlDsC,CACT"}
@@ -0,0 +1,3 @@
1
+ /*! n2words/fi v3.0.0 | MIT License | github.com/forzagreen/n2words */
2
+ var e,t;e=this,t=function(e){"use strict";function t(e){const t="-"===e[0];t&&(e=e.slice(1));const n=e.indexOf(".");if(-1===n)return{isNegative:t,integerPart:BigInt(e)};const i=e.slice(0,n)||"0",r=e.slice(n+1);return{isNegative:t,integerPart:BigInt(i),decimalPart:r}}function n(e){const[t,n]=e.toLowerCase().split("e"),i=parseInt(n,10),r=t.indexOf("."),s=-1===r?t:t.slice(0,r)+t.slice(r+1),o=(-1===r?t.length:r)+i;return o>=s.length?s+"0".repeat(o-s.length):o<=0?"0."+"0".repeat(-o)+s:s.slice(0,o)+"."+s.slice(o)}const i=["","yksi","kaksi","kolme","neljä","viisi","kuusi","seitsemän","kahdeksan","yhdeksän"],r=["kymmenen","yksitoista","kaksitoista","kolmetoista","neljätoista","viisitoista","kuusitoista","seitsemäntoista","kahdeksantoista","yhdeksäntoista"],s=["","","kaksikymmentä","kolmekymmentä","neljäkymmentä","viisikymmentä","kuusikymmentä","seitsemänkymmentä","kahdeksankymmentä","yhdeksänkymmentä"],o="sata",u="tuhat",a="nolla",l=["miljoona","miljardi","biljoona","triljoona"];function c(e){if(0===e)return"";const t=e%10,n=Math.floor(e/10)%10,u=Math.floor(e/100),a=[];u>0&&a.push(1===u?o:i[u]+" "+o);const l=e%100;return 0===l||a.push(l<10?i[t]:l<20?r[t]:0===t?s[n]:s[n]+i[t]),a.join(" ")}const f=new Array(1e3);for(let e=0;e<1e3;e++)f[e]=c(e);e.fi=function(e){const{isNegative:r,integerPart:s,decimalPart:o}=function(e){const i=typeof e;if("bigint"===i)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===i){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:t(function(e){const t=e.toString();return t.includes("e")||t.includes("E")?n(t):t}(e))}if("string"===i)return t(function(e){const t=e.trim();if(0===t.length||Number.isNaN(Number(t)))throw new Error(`Invalid number format: "${e}"`);return t.includes("e")||t.includes("E")?n(t):t}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${i}`)}(e);let c="";return r&&(c="miinus "),c+=function(e){if(0n===e)return a;if(e<1000n)return f[Number(e)];if(e<1000000n){const t=Number(e/1000n),n=Number(e%1000n);let i;return i=1===t?u:f[t]+" "+u,n>0&&(i+=" "+f[n]),i}return function(e){const t=e.toString(),n=t.length,i=[],r=n%3;let s=0;for(r>0&&(i.push(Number(t.slice(0,r))),s=r);s<n;)i.push(Number(t.slice(s,s+3))),s+=3;const o=[];let a=i.length-1;for(let e=0;e<i.length;e++){const t=i[e];if(0!==t){const e=f[t];o.push(0===a?e:1===a?1===t?u:e+" "+u:e+" "+l[a-2])}a--}return o.join(" ")}(e)}(s),o&&(c+=" pilkku "+function(e){const t=[];for(const n of e){const e=parseInt(n,10);t.push(0===e?a:i[e])}return t.join(" ")}(o)),c}},"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
3
+ //# sourceMappingURL=fi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fi.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/fi.js"],"sourcesContent":["/**\n * Numeric value parsing utility.\n * Transforms user input (number, string, or bigint) into normalized components.\n * @module parse-numeric\n */\n\n/**\n * Parses a numeric value into its components.\n * @param {number|string|bigint} value\n * @returns {{isNegative: boolean, integerPart: bigint, decimalPart?: string}}\n * @throws {TypeError} If value is not number, string, or bigint\n * @throws {Error} If value is not a valid number format\n */\nexport function parseNumericValue (value) {\n const type = typeof value\n\n // BigInt: simplest case\n if (type === 'bigint') {\n return value < 0n\n ? { isNegative: true, integerPart: -value }\n : { isNegative: false, integerPart: value }\n }\n\n // Number: fast path for safe integers\n if (type === 'number') {\n if (!Number.isFinite(value)) {\n throw new Error('Number must be finite (NaN and Infinity are not supported)')\n }\n if (Number.isSafeInteger(value)) {\n return value < 0\n ? { isNegative: true, integerPart: BigInt(-value) }\n : { isNegative: false, integerPart: BigInt(value) }\n }\n return parseNumericString(numberToString(value))\n }\n\n // String input\n if (type === 'string') {\n return parseNumericString(normalizeString(value))\n }\n\n throw new TypeError(\n `Invalid value type: expected number, string, or bigint, received ${type}`\n )\n}\n\n/**\n * Converts a number to decimal string, expanding scientific notation if needed.\n */\nfunction numberToString (value) {\n const str = value.toString()\n return (str.includes('e') || str.includes('E'))\n ? expandScientificNotation(str)\n : str\n}\n\n/**\n * Validates and normalizes a string numeric input.\n */\nfunction normalizeString (value) {\n const trimmed = value.trim()\n if (trimmed.length === 0 || Number.isNaN(Number(trimmed))) {\n throw new Error(`Invalid number format: \"${value}\"`)\n }\n return (trimmed.includes('e') || trimmed.includes('E'))\n ? expandScientificNotation(trimmed)\n : trimmed\n}\n\n/**\n * Parses a normalized numeric string into components.\n */\nfunction parseNumericString (str) {\n const isNegative = str[0] === '-'\n if (isNegative) str = str.slice(1)\n\n const dotIndex = str.indexOf('.')\n if (dotIndex === -1) {\n return { isNegative, integerPart: BigInt(str) }\n }\n\n const integerStr = str.slice(0, dotIndex) || '0'\n const decimalPart = str.slice(dotIndex + 1)\n return { isNegative, integerPart: BigInt(integerStr), decimalPart }\n}\n\n/**\n * Expands scientific notation to decimal form (e.g., \"1e21\" → \"1000...\").\n */\nfunction expandScientificNotation (str) {\n const [mantissa, expStr] = str.toLowerCase().split('e')\n const exp = parseInt(expStr, 10)\n\n const dotIndex = mantissa.indexOf('.')\n const digits = dotIndex === -1\n ? mantissa\n : mantissa.slice(0, dotIndex) + mantissa.slice(dotIndex + 1)\n const integerLength = dotIndex === -1 ? mantissa.length : dotIndex\n const newDotPosition = integerLength + exp\n\n if (newDotPosition >= digits.length) {\n return digits + '0'.repeat(newDotPosition - digits.length)\n }\n if (newDotPosition <= 0) {\n return '0.' + '0'.repeat(-newDotPosition) + digits\n }\n return digits.slice(0, newDotPosition) + '.' + digits.slice(newDotPosition)\n}\n","/**\n * Finnish language converter - Functional Implementation\n *\n * A performance-optimized number-to-words converter using precomputed lookup tables.\n *\n * Key features:\n * - Compound tens+ones without spaces: \"kaksikymmentäyksi\" (21)\n * - Teens with \"-toista\" suffix\n * - Omit \"yksi\" before sata/tuhat but keep before miljoona+\n * - Long scale: miljoona, miljardi, biljoona\n * - Per-digit decimal reading\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary (module-level constants)\n// ============================================================================\n\nconst ONES = ['', 'yksi', 'kaksi', 'kolme', 'neljä', 'viisi', 'kuusi', 'seitsemän', 'kahdeksan', 'yhdeksän']\n\nconst TEENS = ['kymmenen', 'yksitoista', 'kaksitoista', 'kolmetoista', 'neljätoista', 'viisitoista', 'kuusitoista', 'seitsemäntoista', 'kahdeksantoista', 'yhdeksäntoista']\n\n// Tens use \"kymmentä\" suffix\nconst TENS = ['', '', 'kaksikymmentä', 'kolmekymmentä', 'neljäkymmentä', 'viisikymmentä', 'kuusikymmentä', 'seitsemänkymmentä', 'kahdeksankymmentä', 'yhdeksänkymmentä']\n\nconst HUNDRED = 'sata'\nconst THOUSAND = 'tuhat'\n\nconst ZERO = 'nolla'\nconst NEGATIVE = 'miinus'\nconst DECIMAL_SEP = 'pilkku'\n\n// Long scale\nconst SCALES = ['miljoona', 'miljardi', 'biljoona', 'triljoona']\n\n// ============================================================================\n// Precomputed Lookup Tables (built once at module load)\n// ============================================================================\n\n/**\n * Builds segment word for 0-999.\n * Omits \"yksi\" before \"sata\" (hundred).\n */\nfunction buildSegment (n) {\n if (n === 0) return ''\n\n const ones = n % 10\n const tens = Math.floor(n / 10) % 10\n const hundreds = Math.floor(n / 100)\n\n const parts = []\n\n // Hundreds - omit \"yksi\" before sata\n if (hundreds > 0) {\n if (hundreds === 1) {\n parts.push(HUNDRED)\n } else {\n parts.push(ONES[hundreds] + ' ' + HUNDRED)\n }\n }\n\n // Tens and ones\n const tensOnes = n % 100\n\n if (tensOnes === 0) {\n // Just hundreds\n } else if (tensOnes < 10) {\n parts.push(ONES[ones])\n } else if (tensOnes < 20) {\n parts.push(TEENS[ones])\n } else if (ones === 0) {\n parts.push(TENS[tens])\n } else {\n // Compound: \"kaksikymmentäyksi\" (no space)\n parts.push(TENS[tens] + ONES[ones])\n }\n\n return parts.join(' ')\n}\n\n// Precompute all 1000 segment words (0-999)\nconst SEGMENTS = new Array(1000)\n\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS[i] = buildSegment(i)\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\n/**\n * Converts a non-negative integer to Finnish words.\n *\n * @param {bigint} n - Non-negative integer to convert\n * @returns {string} Finnish words\n */\nfunction integerToWords (n) {\n if (n === 0n) return ZERO\n\n // Fast path: numbers < 1000 (direct lookup)\n if (n < 1000n) {\n return SEGMENTS[Number(n)]\n }\n\n // Fast path: numbers < 1,000,000 (thousands)\n if (n < 1_000_000n) {\n const thousands = Number(n / 1000n)\n const remainder = Number(n % 1000n)\n\n // Omit \"yksi\" before tuhat\n let result\n if (thousands === 1) {\n result = THOUSAND\n } else {\n result = SEGMENTS[thousands] + ' ' + THOUSAND\n }\n\n if (remainder > 0) {\n result += ' ' + SEGMENTS[remainder]\n }\n\n return result\n }\n\n // For numbers >= 1,000,000, use scale decomposition\n return buildLargeNumberWords(n)\n}\n\n/**\n * Builds words for numbers >= 1,000,000.\n *\n * @param {bigint} n - Number >= 1,000,000\n * @returns {string} Finnish words\n */\nfunction buildLargeNumberWords (n) {\n const numStr = n.toString()\n const len = numStr.length\n\n // Build segments of 3 digits from right to left\n const segments = []\n const segmentSize = 3\n\n const remainderLen = len % segmentSize\n let pos = 0\n if (remainderLen > 0) {\n segments.push(Number(numStr.slice(0, remainderLen)))\n pos = remainderLen\n }\n while (pos < len) {\n segments.push(Number(numStr.slice(pos, pos + segmentSize)))\n pos += segmentSize\n }\n\n // Convert segments to words\n const parts = []\n let scaleIndex = segments.length - 1\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]\n\n if (segment !== 0) {\n const segmentWord = SEGMENTS[segment]\n\n if (scaleIndex === 0) {\n // Units segment\n parts.push(segmentWord)\n } else if (scaleIndex === 1) {\n // Thousands - omit \"yksi\" before tuhat\n if (segment === 1) {\n parts.push(THOUSAND)\n } else {\n parts.push(segmentWord + ' ' + THOUSAND)\n }\n } else {\n // Millions+ - keep \"yksi\" before scale words\n const scaleWord = SCALES[scaleIndex - 2]\n parts.push(segmentWord + ' ' + scaleWord)\n }\n }\n\n scaleIndex--\n }\n\n return parts.join(' ')\n}\n\n/**\n * Converts decimal digits to Finnish words (per-digit).\n *\n * @param {string} decimalPart - Decimal digits (without the point)\n * @returns {string} Finnish words for decimal part\n */\nfunction decimalPartToWords (decimalPart) {\n const parts = []\n\n for (const digit of decimalPart) {\n const d = parseInt(digit, 10)\n if (d === 0) {\n parts.push(ZERO)\n } else {\n parts.push(ONES[d])\n }\n }\n\n return parts.join(' ')\n}\n\n/**\n * Converts a numeric value to Finnish words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in Finnish words\n * @throws {TypeError} If value is not a valid numeric type\n * @throws {Error} If value is not a valid number format\n *\n * @example\n * toWords(21) // 'kaksikymmentäyksi'\n * toWords(1000) // 'tuhat'\n * toWords('3.14') // 'kolme pilkku yksi neljä'\n */\nfunction toWords (value) {\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)\n }\n\n return result\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport { toWords }\n"],"names":["parseNumericString","str","isNegative","slice","dotIndex","indexOf","integerPart","BigInt","integerStr","decimalPart","expandScientificNotation","mantissa","expStr","toLowerCase","split","exp","parseInt","digits","newDotPosition","length","repeat","ONES","TEENS","TENS","HUNDRED","THOUSAND","ZERO","SCALES","buildSegment","n","ones","tens","Math","floor","hundreds","parts","push","tensOnes","join","SEGMENTS","Array","i","value","type","Number","isFinite","Error","isSafeInteger","toString","includes","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","result","NEGATIVE","thousands","remainder","numStr","len","segments","remainderLen","pos","scaleIndex","segment","segmentWord","buildLargeNumberWords","integerToWords","digit","d","decimalPartToWords"],"mappings":"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"}