n2words 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/languages/am-Latn.js +2 -2
  3. package/dist/languages/am-Latn.js.map +1 -1
  4. package/dist/languages/am.js +2 -2
  5. package/dist/languages/am.js.map +1 -1
  6. package/dist/languages/ar.js +1 -1
  7. package/dist/languages/az.js +2 -2
  8. package/dist/languages/az.js.map +1 -1
  9. package/dist/languages/bn.js +2 -2
  10. package/dist/languages/bn.js.map +1 -1
  11. package/dist/languages/cs.js +2 -2
  12. package/dist/languages/cs.js.map +1 -1
  13. package/dist/languages/da.js +2 -2
  14. package/dist/languages/da.js.map +1 -1
  15. package/dist/languages/de.js +2 -2
  16. package/dist/languages/de.js.map +1 -1
  17. package/dist/languages/el.js +2 -2
  18. package/dist/languages/el.js.map +1 -1
  19. package/dist/languages/en.js +2 -2
  20. package/dist/languages/en.js.map +1 -1
  21. package/dist/languages/es.js +2 -2
  22. package/dist/languages/es.js.map +1 -1
  23. package/dist/languages/fa.js +1 -1
  24. package/dist/languages/fi.js +2 -2
  25. package/dist/languages/fi.js.map +1 -1
  26. package/dist/languages/fil.js +2 -2
  27. package/dist/languages/fil.js.map +1 -1
  28. package/dist/languages/fr-BE.js +2 -2
  29. package/dist/languages/fr-BE.js.map +1 -1
  30. package/dist/languages/fr.js +2 -2
  31. package/dist/languages/fr.js.map +1 -1
  32. package/dist/languages/gu.js +2 -2
  33. package/dist/languages/gu.js.map +1 -1
  34. package/dist/languages/ha.js +2 -2
  35. package/dist/languages/ha.js.map +1 -1
  36. package/dist/languages/hbo.js +2 -2
  37. package/dist/languages/hbo.js.map +1 -1
  38. package/dist/languages/he.js +2 -2
  39. package/dist/languages/he.js.map +1 -1
  40. package/dist/languages/hi.js +2 -2
  41. package/dist/languages/hi.js.map +1 -1
  42. package/dist/languages/hr.js +2 -2
  43. package/dist/languages/hr.js.map +1 -1
  44. package/dist/languages/hu.js +1 -1
  45. package/dist/languages/id.js +2 -2
  46. package/dist/languages/id.js.map +1 -1
  47. package/dist/languages/it.js +2 -2
  48. package/dist/languages/it.js.map +1 -1
  49. package/dist/languages/ja.js +2 -2
  50. package/dist/languages/ja.js.map +1 -1
  51. package/dist/languages/ka.js +3 -0
  52. package/dist/languages/ka.js.map +1 -0
  53. package/dist/languages/kn.js +2 -2
  54. package/dist/languages/kn.js.map +1 -1
  55. package/dist/languages/ko.js +2 -2
  56. package/dist/languages/ko.js.map +1 -1
  57. package/dist/languages/lt.js +2 -2
  58. package/dist/languages/lt.js.map +1 -1
  59. package/dist/languages/lv.js +2 -2
  60. package/dist/languages/lv.js.map +1 -1
  61. package/dist/languages/mr.js +2 -2
  62. package/dist/languages/mr.js.map +1 -1
  63. package/dist/languages/ms.js +2 -2
  64. package/dist/languages/ms.js.map +1 -1
  65. package/dist/languages/nb.js +2 -2
  66. package/dist/languages/nb.js.map +1 -1
  67. package/dist/languages/nl.js +2 -2
  68. package/dist/languages/nl.js.map +1 -1
  69. package/dist/languages/pa.js +2 -2
  70. package/dist/languages/pa.js.map +1 -1
  71. package/dist/languages/pl.js +2 -2
  72. package/dist/languages/pl.js.map +1 -1
  73. package/dist/languages/pt.js +2 -2
  74. package/dist/languages/pt.js.map +1 -1
  75. package/dist/languages/ro.js +1 -1
  76. package/dist/languages/ru.js +2 -2
  77. package/dist/languages/ru.js.map +1 -1
  78. package/dist/languages/sr-Cyrl.js +2 -2
  79. package/dist/languages/sr-Cyrl.js.map +1 -1
  80. package/dist/languages/sr-Latn.js +2 -2
  81. package/dist/languages/sr-Latn.js.map +1 -1
  82. package/dist/languages/sv.js +2 -2
  83. package/dist/languages/sv.js.map +1 -1
  84. package/dist/languages/sw.js +1 -1
  85. package/dist/languages/ta.js +2 -2
  86. package/dist/languages/ta.js.map +1 -1
  87. package/dist/languages/te.js +2 -2
  88. package/dist/languages/te.js.map +1 -1
  89. package/dist/languages/th.js +1 -1
  90. package/dist/languages/tr.js +2 -2
  91. package/dist/languages/tr.js.map +1 -1
  92. package/dist/languages/uk.js +2 -2
  93. package/dist/languages/uk.js.map +1 -1
  94. package/dist/languages/ur.js +2 -2
  95. package/dist/languages/ur.js.map +1 -1
  96. package/dist/languages/vi.js +2 -2
  97. package/dist/languages/vi.js.map +1 -1
  98. package/dist/languages/yo.js +3 -0
  99. package/dist/languages/yo.js.map +1 -0
  100. package/dist/languages/zh-Hans.js +1 -1
  101. package/dist/languages/zh-Hant.js +1 -1
  102. package/dist/n2words.js +2 -2
  103. package/dist/n2words.js.map +1 -1
  104. package/lib/languages/am-Latn.js +4 -9
  105. package/lib/languages/am.js +4 -9
  106. package/lib/languages/az.js +4 -9
  107. package/lib/languages/bn.js +51 -30
  108. package/lib/languages/cs.js +9 -26
  109. package/lib/languages/da.js +6 -13
  110. package/lib/languages/de.js +21 -33
  111. package/lib/languages/el.js +6 -13
  112. package/lib/languages/en.js +44 -58
  113. package/lib/languages/es.js +10 -30
  114. package/lib/languages/fi.js +6 -13
  115. package/lib/languages/fil.js +6 -17
  116. package/lib/languages/fr-BE.js +17 -26
  117. package/lib/languages/fr.js +18 -31
  118. package/lib/languages/gu.js +43 -30
  119. package/lib/languages/ha.js +4 -9
  120. package/lib/languages/hbo.js +7 -25
  121. package/lib/languages/he.js +7 -18
  122. package/lib/languages/hi.js +55 -30
  123. package/lib/languages/hr.js +61 -55
  124. package/lib/languages/id.js +8 -13
  125. package/lib/languages/it.js +64 -99
  126. package/lib/languages/ja.js +8 -20
  127. package/lib/languages/ka.d.ts +17 -0
  128. package/lib/languages/ka.js +291 -0
  129. package/lib/languages/kn.js +43 -30
  130. package/lib/languages/ko.js +6 -13
  131. package/lib/languages/lt.js +6 -15
  132. package/lib/languages/lv.js +6 -15
  133. package/lib/languages/mr.js +43 -30
  134. package/lib/languages/ms.js +8 -13
  135. package/lib/languages/nb.js +13 -23
  136. package/lib/languages/nl.js +11 -29
  137. package/lib/languages/pa.js +32 -37
  138. package/lib/languages/pl.js +7 -20
  139. package/lib/languages/pt.js +17 -31
  140. package/lib/languages/ru.js +64 -59
  141. package/lib/languages/sr-Cyrl.js +58 -52
  142. package/lib/languages/sr-Latn.js +58 -52
  143. package/lib/languages/sv.js +14 -24
  144. package/lib/languages/ta.js +55 -42
  145. package/lib/languages/te.js +33 -41
  146. package/lib/languages/tr.js +7 -18
  147. package/lib/languages/uk.js +61 -55
  148. package/lib/languages/ur.js +32 -37
  149. package/lib/languages/vi.js +23 -43
  150. package/lib/languages/yo.d.ts +7 -0
  151. package/lib/languages/yo.js +303 -0
  152. package/lib/n2words.d.ts +3 -1
  153. package/lib/n2words.js +4 -0
  154. package/package.json +1 -1
@@ -1 +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":";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,CCjFA,MAAMG,EAAY,CAAC,GAAI,MAAO,MAAO,OAAQ,SAAU,QAAS,OAAQ,QAAS,OAAQ,SACnFC,EAAW,CAAC,GAAI,MAAO,MAAO,OAAQ,SAAU,QAAS,OAAQ,QAAS,OAAQ,SAElFC,EAAQ,CAAC,OAAQ,OAAQ,OAAQ,QAAS,UAAW,SAAU,YAAa,aAAc,YAAa,cAGvGC,EAAgB,CAAC,SAAU,YAAa,YAAa,aAAc,eAAgB,cAAe,aAAc,cAAe,aAAc,eAC7IC,EAAe,CAAC,SAAU,YAAa,YAAa,aAAc,eAAgB,cAAe,aAAc,cAAe,aAAc,eAE5IC,EAAO,CAAC,GAAI,GAAI,GAAI,UAAW,WAAY,YAAa,UAAW,UAAW,UAAW,WAGzFC,EAAgB,CAAC,GAAI,SAAU,aAAc,cAAe,gBAAiB,aAAc,cAAe,cAAe,cAAe,eACxIC,EAAe,CAAC,GAAI,SAAU,aAAc,cAAe,gBAAiB,aAAc,cAAe,cAAe,cAAe,eAGvIC,EAAS,CAAC,SAAU,SAAU,UAAW,cACzCC,EAAgB,CAAC,WAAY,WAAY,YAAa,gBAEtDC,EAAW,MACXC,EAAO,OAcb,SAASC,EAAcC,EAAGC,GACxB,GAAU,IAAND,EAAS,MAAO,GAGpB,GAAU,MAANA,EAAW,MAAO,OAEtB,MAAME,EAAOF,EAAI,GACXG,EAAOC,KAAKC,MAAML,EAAI,IAAM,GAC5BM,EAAWF,KAAKC,MAAML,EAAI,KAC1BO,EAAWP,EAAI,IAEfQ,EAAQ,GAgCd,OA7BIF,EAAW,GAEbE,EAAMC,MADcR,EAAWP,EAAeD,GACvBa,IAIR,IAAbC,GAKFC,EAAMC,KAHGF,EAAW,IAEJN,EAAWb,EAAWD,GACnBoB,GACVA,EAAW,GAETlB,EAAMa,GACRK,EAAW,IAEAN,EAAWV,EAAeD,GACvBY,GAGV,IAATA,EACSV,EAAKW,GAGLX,EAAKW,GAAQ,OADRF,EAAWb,EAAWD,GACEe,IAIrCM,EAAME,KAAK,IACpB,CAGA,MAAMC,EAAgB,IAAIC,MAAM,KAChC,IAAK,IAAIC,EAAI,EAAGA,EAAI,IAAMA,IACxBF,EAAcE,GAAKd,EAAac,GAAG,GAIrC,MAAMC,EAAe,IAAIF,MAAM,KAC/B,IAAK,IAAIC,EAAI,EAAGA,EAAI,IAAMA,IACxBC,EAAaD,GAAKd,EAAac,GAAG,GAcpC,SAASE,EAAcC,EAAYC,GACjC,GAAmB,IAAfD,EAAkB,OAAOnB,EAI7B,GAAImB,EAAa,GAAM,EAAG,CACxB,MAAME,EAAcF,EAAa,EAAK,EAChCG,EAAWxB,EAAOuB,GACxB,OAAKC,EACEF,EAAU,GAAKrB,EAAcsB,GAAcC,EAD5B,EAExB,CAAO,CAEL,MACMC,EAAaxB,GADEoB,EAAa,GAAK,EAAK,GAE5C,OAAKI,EACEvB,EAAW,IAAMuB,EADAvB,CAE1B,CACF,CAaA,SAASwB,EAAgBrB,EAAGC,GAC1B,GAAU,KAAND,EAAU,OAAOF,EAErB,MAAMwB,EAAWrB,EAAWa,EAAeH,EAG3C,GAAIX,EAAI,MACN,OAAOsB,EAASC,OAAOvB,IAIzB,GAAIA,EAAI,SAAY,CAClB,MAAMwB,EAAYD,OAAOvB,EAAI,OACvByB,EAAYF,OAAOvB,EAAI,OAE7B,IAAI0B,EACJ,GAAkB,IAAdF,EAEFE,EAAS7B,MACJ,CAEL,MAAM8B,EAAgBhB,EAAca,GAGlCE,EADoB,QAAlBC,GAA6C,QAAlBA,EACpB9B,EAEA8B,EAAgB,IAAM9B,CAEnC,CAMA,OAJI4B,EAAY,IACdC,GAAU,IAAMJ,EAASG,IAGpBC,CACT,CAGA,OAWF,SAAgC1B,EAAGC,GAGjC,MAAM2B,EAAgB,GACtB,IAAIC,EAAO7B,EACX,KAAO6B,EAAO,IACZD,EAAcnB,KAAKoB,EAAO,OAC1BA,GAAc,MAGhB,MAAMP,EAAWrB,EAAWa,EAAeH,EAG3C,IAAIe,EAAS,GAEb,IAAK,IAAIb,EAAIe,EAAc3C,OAAS,EAAG4B,GAAK,EAAGA,IAAK,CAClD,MAAMI,EAAUW,EAAcf,GAC9B,GAAgB,KAAZI,EAAgB,SAEpB,MAAMa,EAAYjB,EAAI,EAAIE,EAAaF,EAAGI,GAAW,GAEjDS,IAAQA,GAAU,KAIpBA,GAFQ,IAANb,EAEQS,EAASC,OAAON,IACX,IAANJ,EAEO,KAAZI,EACQpB,EAEAc,EAAcY,OAAON,IAAY,IAAMa,EAE1CjB,EAAI,GAAM,EAGH,KAAZI,EACQa,EAEAnB,EAAcY,OAAON,IAAY,IAAMa,EAInC,KAAZb,EAEQ,MAAQa,EAGRnB,EAAcY,OAAON,IAAY,IAAMa,CAGvD,CAEA,OAAOJ,CACT,CAjESK,CAAsB/B,EAAGC,EAClC,MAgHA,SAAkB+B,EAAOC,GACvBA,EC9SK,SAA0BA,GAC/B,QAAgBC,IAAZD,EAAuB,MAAO,CAAA,EAClC,GCCK,SAAwBD,GAC7B,GAAc,OAAVA,GAAmC,iBAAVA,EAAoB,OAAO,EACxD,MAAMG,EAAQC,OAAOC,eAAeL,GACpC,OAAiB,OAAVG,GAAkBA,IAAUC,OAAOE,SAC5C,CDLMC,CAAcN,GAAU,OAAOA,EACnC,MAAM,IAAIO,UACR,mEAAmEP,EAEvE,CDwSYQ,CAAgBR,GAC1B,MAAMjE,WAAEA,EAAUI,YAAEA,EAAWG,YAAEA,GD3S5B,SAA4ByD,GACjC,MAAMU,SAAcV,EAGpB,GAAa,WAATU,EACF,OAAOV,EAAQ,GACX,CAAEhE,YAAY,EAAMI,aAAc4D,GAClC,CAAEhE,YAAY,EAAOI,YAAa4D,GAIxC,GAAa,WAATU,EAAmB,CACrB,IAAKnB,OAAOoB,SAASX,GACnB,MAAM,IAAIY,MAAM,8DAElB,OAAIrB,OAAOsB,cAAcb,GAChBA,EAAQ,EACX,CAAEhE,YAAY,EAAMI,YAAaC,QAAQ2D,IACzC,CAAEhE,YAAY,EAAOI,YAAaC,OAAO2D,IAExClE,EAgBX,SAAyBkE,GACvB,MAAMjE,EAAMiE,EAAMc,WAClB,OAAQ/E,EAAIgF,SAAS,MAAQhF,EAAIgF,SAAS,KACtCvE,EAAyBT,GACzBA,CACN,CArB8BiF,CAAehB,GAC3C,CAGA,GAAa,WAATU,EACF,OAAO5E,EAqBX,SAA0BkE,GACxB,MAAMiB,EAAUjB,EAAMkB,OACtB,GAAuB,IAAnBD,EAAQhE,QAAgBsC,OAAO4B,MAAM5B,OAAO0B,IAC9C,MAAM,IAAIL,MAAM,2BAA2BZ,MAE7C,OAAQiB,EAAQF,SAAS,MAAQE,EAAQF,SAAS,KAC9CvE,EAAyByE,GACzBA,CACN,CA7B8BG,CAAgBpB,IAG5C,MAAM,IAAIQ,UACR,oEAAoEE,IAExE,CC4QmDW,CAAkBrB,GAC7D/B,EAA8B,aAAnBgC,EAAQqB,OAEzB,IAAI5B,EAAS,GAYb,OAVI1D,IACF0D,EAAS6B,UAGX7B,GAAUL,EAAejD,EAAa6B,GAElC1B,IACFmD,GAAU,UArDd,SAA6BnD,EAAa0B,GACxC,IAAIyB,EAAS,GAGTb,EAAI,EACR,KAAOA,EAAItC,EAAYU,QAA6B,MAAnBV,EAAYsC,IACvCa,IAAQA,GAAU,KACtBA,GAAU5B,EACVe,IAIF,MAAMY,EAAYlD,EAAYN,MAAM4C,GAMpC,OALIY,IACEC,IAAQA,GAAU,KACtBA,GAAUL,EAAehD,OAAOoD,GAAYxB,IAGvCyB,CACT,CAkCwC8B,CAAmBjF,EAAa0B,IAG/DyB,CACT"}
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 * Self-contained module with its own input validation, ready for subpath exports.\n *\n * Spanish-specific rules:\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// Segment Building\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// ============================================================================\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 // Fast path: numbers < 1000\n if (n < 1000n) {\n return buildSegment(Number(n), feminine)\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 = buildSegment(thousands, false)\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 += ' ' + buildSegment(remainder, feminine)\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 // 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 += buildSegment(Number(segment), feminine)\n } else if (i === 1) {\n // Thousands: omit \"uno\" before mil\n if (segment === 1n) {\n result += THOUSAND\n } else {\n result += buildSegment(Number(segment), false) + ' ' + 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 += buildSegment(Number(segment), false) + ' ' + 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 += buildSegment(Number(segment), false) + ' ' + 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","getScaleWord","scaleIndex","segment","arrayIndex","baseWord","pluralWord","integerToWords","Number","thousands","remainder","result","thousandsWord","segmentValues","temp","i","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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"}
@@ -1,3 +1,3 @@
1
- /*! n2words/fa v3.0.0 | MIT License | github.com/forzagreen/n2words */
1
+ /*! n2words/fa v3.1.0 | MIT License | github.com/forzagreen/n2words */
2
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
3
  //# sourceMappingURL=fa.js.map
@@ -1,3 +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||{});
1
+ /*! n2words/fi v3.1.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(" ")}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 f="";return r&&(f="miinus "),f+=function(e){if(0n===e)return a;if(e<1000n)return c(Number(e));if(e<1000000n){const t=Number(e/1000n),n=Number(e%1000n);let i;return i=1===t?u:c(t)+" "+u,n>0&&(i+=" "+c(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=c(t);o.push(0===a?e:1===a?1===t?u:e+" "+u:e+" "+l[a-2])}a--}return o.join(" ")}(e)}(s),o&&(f+=" 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)),f}},"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
3
  //# sourceMappingURL=fi.js.map
@@ -1 +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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"}
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 * Self-contained module with its own input validation, ready for subpath exports.\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// Segment Building\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// ============================================================================\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 buildSegment(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 = buildSegment(thousands) + ' ' + THOUSAND\n }\n\n if (remainder > 0) {\n result += ' ' + buildSegment(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 = buildSegment(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","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","i","segment","segmentWord","buildLargeNumberWords","integerToWords","digit","d","decimalPartToWords"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- /*! n2words/fil v3.0.0 | MIT License | github.com/forzagreen/n2words */
2
- var n,e;n=this,e=function(n){"use strict";function e(n){const e="-"===n[0];e&&(n=n.slice(1));const t=n.indexOf(".");if(-1===t)return{isNegative:e,integerPart:BigInt(n)};const i=n.slice(0,t)||"0",r=n.slice(t+1);return{isNegative:e,integerPart:BigInt(i),decimalPart:r}}function t(n){const[e,t]=n.toLowerCase().split("e"),i=parseInt(t,10),r=e.indexOf("."),a=-1===r?e:e.slice(0,r)+e.slice(r+1),o=(-1===r?e.length:r)+i;return o>=a.length?a+"0".repeat(o-a.length):o<=0?"0."+"0".repeat(-o)+a:a.slice(0,o)+"."+a.slice(o)}const i=["","isa","dalawa","tatlo","apat","lima","anim","pito","walo","siyam"],r=["sampu","labinisa","labindalawa","labintatlo","labinapat","labinlima","labinanum","labimpito","labingwalo","labinsiyam"],a=["","","dalawampu","tatlumpu","apatnapu","limampu","animnapu","pitumpu","walumpu","siyamnapu"],o="zero",s=["","libong","milyong","bilyong","trilyong"],u=["a","e","i","o","u"];function l(n){return u.includes(n[n.length-1])?n+"ng":n+" na"}function c(n){if(0===n)return"";const e=n%10,t=Math.floor(n/10)%10,o=Math.floor(n/100),s=[];if(o>0){const n=l(i[o]);s.push(n+" daang")}const u=n%100;return 0===u||s.push(u<10?i[e]:u<20?r[e]:0===e?a[t]:5===t?a[t]+"ng "+i[e]:a[t]+" "+i[e]),s.join(" ")}const f=new Array(1e3);for(let n=0;n<1e3;n++)f[n]=c(n);function g(n){const e=f[n];if(!e)return"";const t=e.lastIndexOf(" ");if(-1===t)return"g"===e[e.length-1]&&e.endsWith("ng")?e:l(e);const i=e.slice(0,t+1),r=e.slice(t+1);return r.endsWith("ng")?e:i+l(r)}const p=new Array(1e3);for(let n=0;n<1e3;n++)p[n]=g(n);n.fil=function(n){const{isNegative:r,integerPart:a,decimalPart:u}=function(n){const i=typeof n;if("bigint"===i)return n<0n?{isNegative:!0,integerPart:-n}:{isNegative:!1,integerPart:n};if("number"===i){if(!Number.isFinite(n))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(n)?n<0?{isNegative:!0,integerPart:BigInt(-n)}:{isNegative:!1,integerPart:BigInt(n)}:e(function(n){const e=n.toString();return e.includes("e")||e.includes("E")?t(e):e}(n))}if("string"===i)return e(function(n){const e=n.trim();if(0===e.length||Number.isNaN(Number(e)))throw new Error(`Invalid number format: "${n}"`);return e.includes("e")||e.includes("E")?t(e):e}(n));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${i}`)}(n);let l="";return r&&(l="negatibo "),l+=0n===(c=a)?o:c<1000n?f[Number(c)]:function(n){const e=[];let t=n;for(;t>0n;)e.push(Number(t%1000n)),t/=1000n;let i="";for(let n=e.length-1;n>=0;n--){const t=e[n];0!==t&&(i&&(i+=" "),i+=0===n?f[t]:p[t]+" "+(s[n]||""))}return i}(c),u&&(l+=" punto "+function(n){const e=[];for(const t of n){const n=parseInt(t,10);e.push(0===n?o:i[n])}return e.join(" ")}(u)),l;var c}},"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((n="undefined"!=typeof globalThis?globalThis:n||self).n2words=n.n2words||{});
1
+ /*! n2words/fil v3.1.0 | MIT License | github.com/forzagreen/n2words */
2
+ var n,t;n=this,t=function(n){"use strict";function t(n){const t="-"===n[0];t&&(n=n.slice(1));const e=n.indexOf(".");if(-1===e)return{isNegative:t,integerPart:BigInt(n)};const i=n.slice(0,e)||"0",r=n.slice(e+1);return{isNegative:t,integerPart:BigInt(i),decimalPart:r}}function e(n){const[t,e]=n.toLowerCase().split("e"),i=parseInt(e,10),r=t.indexOf("."),a=-1===r?t:t.slice(0,r)+t.slice(r+1),o=(-1===r?t.length:r)+i;return o>=a.length?a+"0".repeat(o-a.length):o<=0?"0."+"0".repeat(-o)+a:a.slice(0,o)+"."+a.slice(o)}const i=["","isa","dalawa","tatlo","apat","lima","anim","pito","walo","siyam"],r=["sampu","labinisa","labindalawa","labintatlo","labinapat","labinlima","labinanum","labimpito","labingwalo","labinsiyam"],a=["","","dalawampu","tatlumpu","apatnapu","limampu","animnapu","pitumpu","walumpu","siyamnapu"],o="zero",s=["","libong","milyong","bilyong","trilyong"],u=["a","e","i","o","u"];function l(n){return u.includes(n[n.length-1])?n+"ng":n+" na"}function c(n){if(0===n)return"";const t=n%10,e=Math.floor(n/10)%10,o=Math.floor(n/100),s=[];if(o>0){const n=l(i[o]);s.push(n+" daang")}const u=n%100;return 0===u||s.push(u<10?i[t]:u<20?r[t]:0===t?a[e]:5===e?a[e]+"ng "+i[t]:a[e]+" "+i[t]),s.join(" ")}function f(n){const t=c(n);if(!t)return"";const e=t.lastIndexOf(" ");if(-1===e)return"g"===t[t.length-1]&&t.endsWith("ng")?t:l(t);const i=t.slice(0,e+1),r=t.slice(e+1);return r.endsWith("ng")?t:i+l(r)}n.fil=function(n){const{isNegative:r,integerPart:a,decimalPart:u}=function(n){const i=typeof n;if("bigint"===i)return n<0n?{isNegative:!0,integerPart:-n}:{isNegative:!1,integerPart:n};if("number"===i){if(!Number.isFinite(n))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(n)?n<0?{isNegative:!0,integerPart:BigInt(-n)}:{isNegative:!1,integerPart:BigInt(n)}:t(function(n){const t=n.toString();return t.includes("e")||t.includes("E")?e(t):t}(n))}if("string"===i)return t(function(n){const t=n.trim();if(0===t.length||Number.isNaN(Number(t)))throw new Error(`Invalid number format: "${n}"`);return t.includes("e")||t.includes("E")?e(t):t}(n));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${i}`)}(n);let l="";return r&&(l="negatibo "),l+=0n===(g=a)?o:g<1000n?c(Number(g)):function(n){const t=[];let e=n;for(;e>0n;)t.push(Number(e%1000n)),e/=1000n;let i="";for(let n=t.length-1;n>=0;n--){const e=t[n];if(0===e)continue;const r=s[n]||"";i&&(i+=" "),i+=0===n?c(e):f(e)+" "+r}return i}(g),u&&(l+=" punto "+function(n){const t=[];for(const e of n){const n=parseInt(e,10);t.push(0===n?o:i[n])}return t.join(" ")}(u)),l;var g}},"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((n="undefined"!=typeof globalThis?globalThis:n||self).n2words=n.n2words||{});
3
3
  //# sourceMappingURL=fil.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fil.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/fil.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 * Filipino language converter - Functional Implementation\n *\n * Self-contained converter with precomputed lookup tables.\n *\n * Key features:\n * - Linker \"ng\" after vowels: \"isang daang\" (100)\n * - Linker \" na \" after consonants: \"siyam na daang\" (900)\n * - Special tens with linker: \"limampung anim\" (56)\n * - Per-digit decimal reading\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary\n// ============================================================================\n\nconst ONES = ['', 'isa', 'dalawa', 'tatlo', 'apat', 'lima', 'anim', 'pito', 'walo', 'siyam']\nconst TEENS = ['sampu', 'labinisa', 'labindalawa', 'labintatlo', 'labinapat', 'labinlima', 'labinanum', 'labimpito', 'labingwalo', 'labinsiyam']\nconst TENS = ['', '', 'dalawampu', 'tatlumpu', 'apatnapu', 'limampu', 'animnapu', 'pitumpu', 'walumpu', 'siyamnapu']\n\n// Scale words include linker (end with \"ng\")\nconst HUNDRED = 'daang'\nconst THOUSAND = 'libong'\n\nconst ZERO = 'zero'\nconst NEGATIVE = 'negatibo'\nconst DECIMAL_SEP = 'punto'\n\n// Short scale with linker\nconst SCALE_WORDS = ['', THOUSAND, 'milyong', 'bilyong', 'trilyong']\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nconst VOWELS = ['a', 'e', 'i', 'o', 'u']\n\nfunction addLinker (word) {\n const lastChar = word[word.length - 1]\n if (VOWELS.includes(lastChar)) {\n return word + 'ng'\n }\n return word + ' na'\n}\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 // Hundreds: \"isang daan\", \"dalawang daan\", \"siyam na daan\"\n if (hundredsDigit > 0) {\n const hundredPrefix = addLinker(ONES[hundredsDigit])\n parts.push(hundredPrefix + ' ' + 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 (10-19)\n parts.push(TEENS[ones])\n } else if (ones === 0) {\n // Even tens (20, 30, 40, etc.)\n parts.push(TENS[tensDigit])\n } else {\n // Tens + ones\n // limampu (50) gets special linker: \"limampung anim\" (56)\n if (tensDigit === 5) {\n parts.push(TENS[tensDigit] + 'ng ' + ONES[ones])\n } else {\n parts.push(TENS[tensDigit] + ' ' + 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 * Builds segment with linker added to last word (for use before scale words).\n */\nfunction buildSegmentWithLinker (n) {\n const segmentWord = SEGMENTS[n]\n if (!segmentWord) return ''\n\n // Find the last space to get the last word\n const lastSpaceIdx = segmentWord.lastIndexOf(' ')\n if (lastSpaceIdx === -1) {\n // Single word\n const lastChar = segmentWord[segmentWord.length - 1]\n if (lastChar === 'g' && segmentWord.endsWith('ng')) {\n return segmentWord // Already has linker\n }\n return addLinker(segmentWord)\n }\n\n // Multi-word: add linker to last word\n const prefix = segmentWord.slice(0, lastSpaceIdx + 1)\n const lastWord = segmentWord.slice(lastSpaceIdx + 1)\n\n if (lastWord.endsWith('ng')) {\n return segmentWord // Already has linker\n }\n return prefix + addLinker(lastWord)\n}\n\n// Precompute segments with linker for scale word usage\nconst SEGMENTS_WITH_LINKER = new Array(1000)\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS_WITH_LINKER[i] = buildSegmentWithLinker(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\n/**\n * Builds words for large numbers using BigInt division.\n * @param {bigint} n - Number >= 1000\n * @returns {string} Filipino words\n */\nfunction buildLargeNumberWords (n) {\n // Extract segments using BigInt division (faster than string slicing)\n const segmentValues = []\n let temp = n\n while (temp > 0n) {\n segmentValues.push(Number(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 === 0) continue\n\n const scaleWord = SCALE_WORDS[i] || ''\n\n if (result) result += ' '\n\n if (i === 0) {\n result += SEGMENTS[segment]\n } else {\n // Add linker to segment before scale word\n const segmentWord = SEGMENTS_WITH_LINKER[segment]\n result += segmentWord + ' ' + scaleWord\n }\n }\n\n return result\n}\n\nfunction decimalPartToWords (decimalPart) {\n // Per-digit decimal reading\n const digits = []\n for (const char of decimalPart) {\n const d = parseInt(char, 10)\n digits.push(d === 0 ? ZERO : ONES[d])\n }\n return digits.join(' ')\n}\n\n/**\n * Converts a numeric value to Filipino words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in Filipino 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","VOWELS","addLinker","word","includes","buildSegment","n","ones","tensDigit","Math","floor","hundredsDigit","parts","hundredPrefix","push","tensOnes","join","SEGMENTS","Array","i","buildSegmentWithLinker","segmentWord","lastSpaceIdx","lastIndexOf","endsWith","prefix","lastWord","SEGMENTS_WITH_LINKER","value","type","Number","isFinite","Error","isSafeInteger","toString","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","result","NEGATIVE","segmentValues","temp","segment","buildLargeNumberWords","char","d","decimalPartToWords"],"mappings":"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"}
1
+ {"version":3,"file":"fil.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/fil.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 * Filipino language converter - Functional Implementation\n *\n * Self-contained module with its own input validation, ready for subpath exports.\n *\n * Key features:\n * - Linker \"ng\" after vowels: \"isang daang\" (100)\n * - Linker \" na \" after consonants: \"siyam na daang\" (900)\n * - Special tens with linker: \"limampung anim\" (56)\n * - Per-digit decimal reading\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary\n// ============================================================================\n\nconst ONES = ['', 'isa', 'dalawa', 'tatlo', 'apat', 'lima', 'anim', 'pito', 'walo', 'siyam']\nconst TEENS = ['sampu', 'labinisa', 'labindalawa', 'labintatlo', 'labinapat', 'labinlima', 'labinanum', 'labimpito', 'labingwalo', 'labinsiyam']\nconst TENS = ['', '', 'dalawampu', 'tatlumpu', 'apatnapu', 'limampu', 'animnapu', 'pitumpu', 'walumpu', 'siyamnapu']\n\n// Scale words include linker (end with \"ng\")\nconst HUNDRED = 'daang'\nconst THOUSAND = 'libong'\n\nconst ZERO = 'zero'\nconst NEGATIVE = 'negatibo'\nconst DECIMAL_SEP = 'punto'\n\n// Short scale with linker\nconst SCALE_WORDS = ['', THOUSAND, 'milyong', 'bilyong', 'trilyong']\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nconst VOWELS = ['a', 'e', 'i', 'o', 'u']\n\nfunction addLinker (word) {\n const lastChar = word[word.length - 1]\n if (VOWELS.includes(lastChar)) {\n return word + 'ng'\n }\n return word + ' na'\n}\n\n// ============================================================================\n// Segment Building\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 // Hundreds: \"isang daan\", \"dalawang daan\", \"siyam na daan\"\n if (hundredsDigit > 0) {\n const hundredPrefix = addLinker(ONES[hundredsDigit])\n parts.push(hundredPrefix + ' ' + 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 (10-19)\n parts.push(TEENS[ones])\n } else if (ones === 0) {\n // Even tens (20, 30, 40, etc.)\n parts.push(TENS[tensDigit])\n } else {\n // Tens + ones\n // limampu (50) gets special linker: \"limampung anim\" (56)\n if (tensDigit === 5) {\n parts.push(TENS[tensDigit] + 'ng ' + ONES[ones])\n } else {\n parts.push(TENS[tensDigit] + ' ' + ONES[ones])\n }\n }\n\n return parts.join(' ')\n}\n\n/**\n * Builds segment with linker added to last word (for use before scale words).\n */\nfunction buildSegmentWithLinker (n) {\n const segmentWord = buildSegment(n)\n if (!segmentWord) return ''\n\n // Find the last space to get the last word\n const lastSpaceIdx = segmentWord.lastIndexOf(' ')\n if (lastSpaceIdx === -1) {\n // Single word\n const lastChar = segmentWord[segmentWord.length - 1]\n if (lastChar === 'g' && segmentWord.endsWith('ng')) {\n return segmentWord // Already has linker\n }\n return addLinker(segmentWord)\n }\n\n // Multi-word: add linker to last word\n const prefix = segmentWord.slice(0, lastSpaceIdx + 1)\n const lastWord = segmentWord.slice(lastSpaceIdx + 1)\n\n if (lastWord.endsWith('ng')) {\n return segmentWord // Already has linker\n }\n return prefix + addLinker(lastWord)\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\nfunction integerToWords (n) {\n if (n === 0n) return ZERO\n\n if (n < 1000n) {\n return buildSegment(Number(n))\n }\n\n return buildLargeNumberWords(n)\n}\n\n/**\n * Builds words for large numbers using BigInt division.\n * @param {bigint} n - Number >= 1000\n * @returns {string} Filipino words\n */\nfunction buildLargeNumberWords (n) {\n // Extract segments using BigInt division (faster than string slicing)\n const segmentValues = []\n let temp = n\n while (temp > 0n) {\n segmentValues.push(Number(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 === 0) continue\n\n const scaleWord = SCALE_WORDS[i] || ''\n\n if (result) result += ' '\n\n if (i === 0) {\n result += buildSegment(segment)\n } else {\n // Add linker to segment before scale word\n const segmentWord = buildSegmentWithLinker(segment)\n result += segmentWord + ' ' + scaleWord\n }\n }\n\n return result\n}\n\nfunction decimalPartToWords (decimalPart) {\n // Per-digit decimal reading\n const digits = []\n for (const char of decimalPart) {\n const d = parseInt(char, 10)\n digits.push(d === 0 ? ZERO : ONES[d])\n }\n return digits.join(' ')\n}\n\n/**\n * Converts a numeric value to Filipino words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in Filipino 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","VOWELS","addLinker","word","includes","buildSegment","n","ones","tensDigit","Math","floor","hundredsDigit","parts","hundredPrefix","push","tensOnes","join","buildSegmentWithLinker","segmentWord","lastSpaceIdx","lastIndexOf","endsWith","prefix","lastWord","value","type","Number","isFinite","Error","isSafeInteger","toString","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","result","NEGATIVE","segmentValues","temp","i","segment","scaleWord","buildLargeNumberWords","char","d","decimalPartToWords"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- /*! n2words/fr-BE 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=["","un","deux","trois","quatre","cinq","six","sept","huit","neuf"],r=["dix","onze","douze","treize","quatorze","quinze","seize","dix-sept","dix-huit","dix-neuf"],s=["","","vingt","trente","quarante","cinquante","soixante","septante","quatre-vingt","nonante"],o=["million","billion","trillion","quadrillion"],u=["milliard","billiard","trilliard","quadrilliard"],l="mille",c="cent",f="zéro";function a(e){if(0===e)return{word:"",endsWithCents:!1,endsWithVingts:!1};const t=e%100,n=Math.floor(e/100),o=[];let u=!1,l=!1;if(n>0&&(1===n?o.push(c):0===t?(o.push(i[n]+" "+c+"s"),u=!0):o.push(i[n]+" "+c)),0===t);else if(t<10)o.push(i[t]);else if(t<17)o.push(r[t-10]);else if(t<20)o.push(r[t-10]);else if(t<70){const e=Math.floor(t/10),n=t%10;o.push(0===n?s[e]:1===n?s[e]+" et "+i[1]:s[e]+"-"+i[n])}else if(t<80){const e=t%10;o.push(0===e?"septante":1===e?"septante et "+i[1]:"septante-"+i[e])}else if(80===t)o.push("quatre-vingts"),l=!0;else if(t<90)o.push("quatre-vingt-"+i[t-80]);else{const e=t%10;o.push(0===e?"nonante":1===e?"nonante et "+i[1]:"nonante-"+i[e])}return{word:o.join(" "),endsWithCents:u,endsWithVingts:l}}const p=new Array(1e3),d=new Array(1e3),g=new Array(1e3);for(let e=0;e<1e3;e++){const t=a(e);p[e]=t.word,d[e]=t.endsWithCents,g[e]=t.endsWithVingts}function h(e,t){if(1===e)return l;if(e%2==0){const n=o[e/2-1];return n?t>1n?n+"s":n:""}{const n=u[(e-1)/2-1];return n?t>1n?n+"s":n:l}}function b(e,t=!1){if(0n===e)return f;if(e<1000n){const n=p[Number(e)];return t?n.replace(/ /g,"-"):n}if(e<1000000n){const n=Number(e/1000n),i=Number(e%1000n);let r;if(1===n)r=l;else{let e=p[n];(d[n]||g[n])&&(e=e.slice(0,-1)),r=e+(t?"-":" ")+l}return i>0&&(r+=(t?"-":" ")+p[i]),t&&(r=r.replace(/ /g,"-")),r}return function(e,t){const n=e.toString(),i=n.length,r=[],s=i%3;let o=0;for(s>0&&(r.push(Number(n.slice(0,s))),o=s);o<i;)r.push(Number(n.slice(o,o+3))),o+=3;const u=[];let c=r.length-1;for(let e=0;e<r.length;e++){const t=r[e];if(0!==t){const e=c>0?h(c,BigInt(t)):"";if(0===c)u.push(p[t]);else if(1===c)if(1===t)u.push(l);else{let n=p[t];(d[t]||g[t])&&(n=n.slice(0,-1)),u.push(n),u.push(e)}else u.push(p[t]),u.push(e)}c--}let f=u.join(t?"-":" ");return t&&(f=f.replace(/ /g,"-")),f}(e,t)}e.frBE=function(e,i){i=function(e){if(void 0===e)return{};if(function(e){if(null===e||"object"!=typeof e)return!1;const t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}(e))return e;throw new TypeError("Invalid options: expected plain object or undefined, got "+typeof e)}(i);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),u=i.withHyphenSeparator||!1;let l="";const c=u?"-":" ";return r&&(l="moins"+c),l+=b(s,u),o&&(l+=c+"virgule"+c+function(e,t){let n="";const i=t?"-":" ";let r=0;for(;r<e.length&&"0"===e[r];)n&&(n+=i),n+=f,r++;const s=e.slice(r);return s&&(n&&(n+=i),n+=b(BigInt(s),t)),n}(o,u)),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||{});
1
+ /*! n2words/fr-BE v3.1.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=["","un","deux","trois","quatre","cinq","six","sept","huit","neuf"],r=["dix","onze","douze","treize","quatorze","quinze","seize","dix-sept","dix-huit","dix-neuf"],s=["","","vingt","trente","quarante","cinquante","soixante","septante","quatre-vingt","nonante"],o=["million","billion","trillion","quadrillion"],u=["milliard","billiard","trilliard","quadrilliard"],l="mille",c="cent",f="zéro";function a(e){if(0===e)return{word:"",endsWithCents:!1,endsWithVingts:!1};const t=e%100,n=Math.floor(e/100),o=[];let u=!1,l=!1;if(n>0&&(1===n?o.push(c):0===t?(o.push(i[n]+" "+c+"s"),u=!0):o.push(i[n]+" "+c)),0===t);else if(t<10)o.push(i[t]);else if(t<17)o.push(r[t-10]);else if(t<20)o.push(r[t-10]);else if(t<70){const e=Math.floor(t/10),n=t%10;o.push(0===n?s[e]:1===n?s[e]+" et "+i[1]:s[e]+"-"+i[n])}else if(t<80){const e=t%10;o.push(0===e?"septante":1===e?"septante et "+i[1]:"septante-"+i[e])}else if(80===t)o.push("quatre-vingts"),l=!0;else if(t<90)o.push("quatre-vingt-"+i[t-80]);else{const e=t%10;o.push(0===e?"nonante":1===e?"nonante et "+i[1]:"nonante-"+i[e])}return{word:o.join(" "),endsWithCents:u,endsWithVingts:l}}function d(e,t){if(1===e)return l;if(e%2==0){const n=o[e/2-1];return n?t>1n?n+"s":n:""}{const n=u[(e-1)/2-1];return n?t>1n?n+"s":n:l}}function p(e,t=!1){if(0n===e)return f;if(e<1000n){const{word:n}=a(Number(e));return t?n.replace(/ /g,"-"):n}if(e<1000000n){const n=Number(e/1000n),i=Number(e%1000n);let r;if(1===n)r=l;else{const{word:e,endsWithCents:i,endsWithVingts:s}=a(n);let o=e;(i||s)&&(o=e.slice(0,-1)),r=o+(t?"-":" ")+l}if(i>0){const{word:e}=a(i);r+=(t?"-":" ")+e}return t&&(r=r.replace(/ /g,"-")),r}return function(e,t){const n=e.toString(),i=n.length,r=[],s=i%3;let o=0;for(s>0&&(r.push(Number(n.slice(0,s))),o=s);o<i;)r.push(Number(n.slice(o,o+3))),o+=3;const u=[];let c=r.length-1;for(let e=0;e<r.length;e++){const t=r[e];if(0!==t){const e=c>0?d(c,BigInt(t)):"",{word:n,endsWithCents:i,endsWithVingts:r}=a(t);if(0===c)u.push(n);else if(1===c)if(1===t)u.push(l);else{let t=n;(i||r)&&(t=n.slice(0,-1)),u.push(t),u.push(e)}else u.push(n),u.push(e)}c--}let f=u.join(t?"-":" ");return t&&(f=f.replace(/ /g,"-")),f}(e,t)}e.frBE=function(e,i){i=function(e){if(void 0===e)return{};if(function(e){if(null===e||"object"!=typeof e)return!1;const t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}(e))return e;throw new TypeError("Invalid options: expected plain object or undefined, got "+typeof e)}(i);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),u=i.withHyphenSeparator||!1;let l="";const c=u?"-":" ";return r&&(l="moins"+c),l+=p(s,u),o&&(l+=c+"virgule"+c+function(e,t){let n="";const i=t?"-":" ";let r=0;for(;r<e.length&&"0"===e[r];)n&&(n+=i),n+=f,r++;const s=e.slice(r);return s&&(n&&(n+=i),n+=p(BigInt(s),t)),n}(o,u)),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
3
  //# sourceMappingURL=fr-BE.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"fr-BE.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/fr-BE.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 * French (Belgium) language converter - Functional Implementation\n *\n * Self-contained converter with precomputed lookup tables.\n *\n * Belgian French differences from standard French:\n * - septante (70) instead of soixante-dix\n * - nonante (90) instead of quatre-vingt-dix\n * - Keeps quatre-vingts (80) like standard French\n * - Uses \"septante et un\" (71), \"nonante et un\" (91)\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\nimport { validateOptions } from '../utils/validate-options.js'\n\n// ============================================================================\n// Vocabulary\n// ============================================================================\n\nconst ONES = ['', 'un', 'deux', 'trois', 'quatre', 'cinq', 'six', 'sept', 'huit', 'neuf']\nconst TEENS = ['dix', 'onze', 'douze', 'treize', 'quatorze', 'quinze', 'seize', 'dix-sept', 'dix-huit', 'dix-neuf']\nconst TENS = ['', '', 'vingt', 'trente', 'quarante', 'cinquante', 'soixante', 'septante', 'quatre-vingt', 'nonante']\n\n// Scale words (long scale with -ard forms)\nconst SCALES = ['million', 'billion', 'trillion', 'quadrillion']\nconst SCALES_ARD = ['milliard', 'billiard', 'trilliard', 'quadrilliard']\n\nconst THOUSAND = 'mille'\nconst HUNDRED = 'cent'\nconst ZERO = 'zéro'\nconst NEGATIVE = 'moins'\nconst DECIMAL_SEP = 'virgule'\n\n// ============================================================================\n// Precomputed Lookup Tables\n// ============================================================================\n\nfunction buildSegment (n) {\n if (n === 0) return { word: '', endsWithCents: false, endsWithVingts: false }\n\n const tensOnes = n % 100\n const hundreds = Math.floor(n / 100)\n\n const parts = []\n let endsWithCents = false\n let endsWithVingts = false\n\n // Hundreds\n if (hundreds > 0) {\n if (hundreds === 1) {\n parts.push(HUNDRED)\n } else {\n if (tensOnes === 0) {\n parts.push(ONES[hundreds] + ' ' + HUNDRED + 's')\n endsWithCents = true\n } else {\n parts.push(ONES[hundreds] + ' ' + HUNDRED)\n }\n }\n }\n\n // Tens and ones - Belgian pattern\n if (tensOnes === 0) {\n // Just hundreds\n } else if (tensOnes < 10) {\n parts.push(ONES[tensOnes])\n } else if (tensOnes < 17) {\n parts.push(TEENS[tensOnes - 10])\n } else if (tensOnes < 20) {\n parts.push(TEENS[tensOnes - 10])\n } else if (tensOnes < 70) {\n // 20-69: standard pattern\n const t = Math.floor(tensOnes / 10)\n const o = tensOnes % 10\n if (o === 0) {\n parts.push(TENS[t])\n } else if (o === 1) {\n parts.push(TENS[t] + ' et ' + ONES[1])\n } else {\n parts.push(TENS[t] + '-' + ONES[o])\n }\n } else if (tensOnes < 80) {\n // 70-79: septante pattern (Belgian)\n const o = tensOnes % 10\n if (o === 0) {\n parts.push('septante')\n } else if (o === 1) {\n parts.push('septante et ' + ONES[1])\n } else {\n parts.push('septante-' + ONES[o])\n }\n } else if (tensOnes === 80) {\n // 80: quatre-vingts (same as standard)\n parts.push('quatre-vingts')\n endsWithVingts = true\n } else if (tensOnes < 90) {\n // 81-89: quatre-vingt-X (same as standard)\n const remainder = tensOnes - 80\n parts.push('quatre-vingt-' + ONES[remainder])\n } else {\n // 90-99: nonante pattern (Belgian)\n const o = tensOnes % 10\n if (o === 0) {\n parts.push('nonante')\n } else if (o === 1) {\n parts.push('nonante et ' + ONES[1])\n } else {\n parts.push('nonante-' + ONES[o])\n }\n }\n\n return { word: parts.join(' '), endsWithCents, endsWithVingts }\n}\n\n// Precompute all 1000 segment words\nconst SEGMENTS = new Array(1000)\nconst SEGMENTS_ENDS_CENTS = new Array(1000)\nconst SEGMENTS_ENDS_VINGTS = new Array(1000)\n\nfor (let i = 0; i < 1000; i++) {\n const result = buildSegment(i)\n SEGMENTS[i] = result.word\n SEGMENTS_ENDS_CENTS[i] = result.endsWithCents\n SEGMENTS_ENDS_VINGTS[i] = result.endsWithVingts\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction getScaleWord (scaleIndex, segment) {\n if (scaleIndex === 1) return THOUSAND\n\n if (scaleIndex % 2 === 0) {\n const arrayIndex = (scaleIndex / 2) - 1\n const baseWord = SCALES[arrayIndex]\n if (!baseWord) return ''\n return segment > 1n ? baseWord + 's' : baseWord\n } else {\n const arrayIndex = ((scaleIndex - 1) / 2) - 1\n const ardWord = SCALES_ARD[arrayIndex]\n if (!ardWord) return THOUSAND\n return segment > 1n ? ardWord + 's' : ardWord\n }\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\nfunction integerToWords (n, withHyphen = false) {\n if (n === 0n) return ZERO\n\n if (n < 1000n) {\n const word = SEGMENTS[Number(n)]\n return withHyphen ? word.replace(/ /g, '-') : word\n }\n\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 result = THOUSAND\n } else {\n let thousandsWord = SEGMENTS[thousands]\n if (SEGMENTS_ENDS_CENTS[thousands] || SEGMENTS_ENDS_VINGTS[thousands]) {\n thousandsWord = thousandsWord.slice(0, -1)\n }\n result = thousandsWord + (withHyphen ? '-' : ' ') + THOUSAND\n }\n\n if (remainder > 0) {\n result += (withHyphen ? '-' : ' ') + SEGMENTS[remainder]\n }\n\n if (withHyphen) {\n result = result.replace(/ /g, '-')\n }\n\n return result\n }\n\n return buildLargeNumberWords(n, withHyphen)\n}\n\nfunction buildLargeNumberWords (n, withHyphen) {\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 = scaleIndex > 0 ? getScaleWord(scaleIndex, BigInt(segment)) : ''\n\n if (scaleIndex === 0) {\n parts.push(SEGMENTS[segment])\n } else if (scaleIndex === 1) {\n if (segment === 1) {\n parts.push(THOUSAND)\n } else {\n let segWords = SEGMENTS[segment]\n if (SEGMENTS_ENDS_CENTS[segment] || SEGMENTS_ENDS_VINGTS[segment]) {\n segWords = segWords.slice(0, -1)\n }\n parts.push(segWords)\n parts.push(scaleWord)\n }\n } else {\n parts.push(SEGMENTS[segment])\n parts.push(scaleWord)\n }\n }\n\n scaleIndex--\n }\n\n const sep = withHyphen ? '-' : ' '\n let result = parts.join(sep)\n\n if (withHyphen) {\n result = result.replace(/ /g, '-')\n }\n\n return result\n}\n\nfunction decimalPartToWords (decimalPart, withHyphen) {\n let result = ''\n const sep = withHyphen ? '-' : ' '\n\n let i = 0\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += sep\n result += ZERO\n i++\n }\n\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += sep\n result += integerToWords(BigInt(remainder), withHyphen)\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Belgian French words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @param {Object} [options] - Optional configuration\n * @param {boolean} [options.withHyphenSeparator=false] - Use hyphens between words\n * @returns {string} The number in Belgian French words\n */\nfunction toWords (value, options) {\n options = validateOptions(options)\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n const withHyphen = options.withHyphenSeparator || false\n\n let result = ''\n const sep = withHyphen ? '-' : ' '\n\n if (isNegative) {\n result = NEGATIVE + sep\n }\n\n result += integerToWords(integerPart, withHyphen)\n\n if (decimalPart) {\n result += sep + DECIMAL_SEP + sep + decimalPartToWords(decimalPart, withHyphen)\n }\n\n return result\n}\n\n// ============================================================================\n// Exports\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","TEENS","TENS","SCALES","SCALES_ARD","THOUSAND","HUNDRED","ZERO","buildSegment","n","word","endsWithCents","endsWithVingts","tensOnes","hundreds","Math","floor","parts","push","t","o","join","SEGMENTS","Array","SEGMENTS_ENDS_CENTS","SEGMENTS_ENDS_VINGTS","i","result","getScaleWord","scaleIndex","segment","baseWord","ardWord","integerToWords","withHyphen","Number","replace","thousands","remainder","thousandsWord","numStr","toString","len","segments","remainderLen","pos","scaleWord","segWords","buildLargeNumberWords","value","options","undefined","proto","Object","getPrototypeOf","prototype","isPlainObject","TypeError","validateOptions","type","isFinite","Error","isSafeInteger","includes","numberToString","trimmed","trim","isNaN","normalizeString","parseNumericValue","withHyphenSeparator","sep","decimalPartToWords"],"mappings":"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"}
1
+ {"version":3,"file":"fr-BE.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/fr-BE.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 * French (Belgium) language converter - Functional Implementation\n *\n * Self-contained module with its own input validation, ready for subpath exports.\n *\n * Belgian French differences from standard French:\n * - septante (70) instead of soixante-dix\n * - nonante (90) instead of quatre-vingt-dix\n * - Keeps quatre-vingts (80) like standard French\n * - Uses \"septante et un\" (71), \"nonante et un\" (91)\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\nimport { validateOptions } from '../utils/validate-options.js'\n\n// ============================================================================\n// Vocabulary\n// ============================================================================\n\nconst ONES = ['', 'un', 'deux', 'trois', 'quatre', 'cinq', 'six', 'sept', 'huit', 'neuf']\nconst TEENS = ['dix', 'onze', 'douze', 'treize', 'quatorze', 'quinze', 'seize', 'dix-sept', 'dix-huit', 'dix-neuf']\nconst TENS = ['', '', 'vingt', 'trente', 'quarante', 'cinquante', 'soixante', 'septante', 'quatre-vingt', 'nonante']\n\n// Scale words (long scale with -ard forms)\nconst SCALES = ['million', 'billion', 'trillion', 'quadrillion']\nconst SCALES_ARD = ['milliard', 'billiard', 'trilliard', 'quadrilliard']\n\nconst THOUSAND = 'mille'\nconst HUNDRED = 'cent'\nconst ZERO = 'zéro'\nconst NEGATIVE = 'moins'\nconst DECIMAL_SEP = 'virgule'\n\n// ============================================================================\n// Segment Building\n// ============================================================================\n\nfunction buildSegment (n) {\n if (n === 0) return { word: '', endsWithCents: false, endsWithVingts: false }\n\n const tensOnes = n % 100\n const hundreds = Math.floor(n / 100)\n\n const parts = []\n let endsWithCents = false\n let endsWithVingts = false\n\n // Hundreds\n if (hundreds > 0) {\n if (hundreds === 1) {\n parts.push(HUNDRED)\n } else {\n if (tensOnes === 0) {\n parts.push(ONES[hundreds] + ' ' + HUNDRED + 's')\n endsWithCents = true\n } else {\n parts.push(ONES[hundreds] + ' ' + HUNDRED)\n }\n }\n }\n\n // Tens and ones - Belgian pattern\n if (tensOnes === 0) {\n // Just hundreds\n } else if (tensOnes < 10) {\n parts.push(ONES[tensOnes])\n } else if (tensOnes < 17) {\n parts.push(TEENS[tensOnes - 10])\n } else if (tensOnes < 20) {\n parts.push(TEENS[tensOnes - 10])\n } else if (tensOnes < 70) {\n // 20-69: standard pattern\n const t = Math.floor(tensOnes / 10)\n const o = tensOnes % 10\n if (o === 0) {\n parts.push(TENS[t])\n } else if (o === 1) {\n parts.push(TENS[t] + ' et ' + ONES[1])\n } else {\n parts.push(TENS[t] + '-' + ONES[o])\n }\n } else if (tensOnes < 80) {\n // 70-79: septante pattern (Belgian)\n const o = tensOnes % 10\n if (o === 0) {\n parts.push('septante')\n } else if (o === 1) {\n parts.push('septante et ' + ONES[1])\n } else {\n parts.push('septante-' + ONES[o])\n }\n } else if (tensOnes === 80) {\n // 80: quatre-vingts (same as standard)\n parts.push('quatre-vingts')\n endsWithVingts = true\n } else if (tensOnes < 90) {\n // 81-89: quatre-vingt-X (same as standard)\n const remainder = tensOnes - 80\n parts.push('quatre-vingt-' + ONES[remainder])\n } else {\n // 90-99: nonante pattern (Belgian)\n const o = tensOnes % 10\n if (o === 0) {\n parts.push('nonante')\n } else if (o === 1) {\n parts.push('nonante et ' + ONES[1])\n } else {\n parts.push('nonante-' + ONES[o])\n }\n }\n\n return { word: parts.join(' '), endsWithCents, endsWithVingts }\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction getScaleWord (scaleIndex, segment) {\n if (scaleIndex === 1) return THOUSAND\n\n if (scaleIndex % 2 === 0) {\n const arrayIndex = (scaleIndex / 2) - 1\n const baseWord = SCALES[arrayIndex]\n if (!baseWord) return ''\n return segment > 1n ? baseWord + 's' : baseWord\n } else {\n const arrayIndex = ((scaleIndex - 1) / 2) - 1\n const ardWord = SCALES_ARD[arrayIndex]\n if (!ardWord) return THOUSAND\n return segment > 1n ? ardWord + 's' : ardWord\n }\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\nfunction integerToWords (n, withHyphen = false) {\n if (n === 0n) return ZERO\n\n if (n < 1000n) {\n const { word } = buildSegment(Number(n))\n return withHyphen ? word.replace(/ /g, '-') : word\n }\n\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 result = THOUSAND\n } else {\n const { word: thousandsWord, endsWithCents, endsWithVingts } = buildSegment(thousands)\n let adjustedWord = thousandsWord\n if (endsWithCents || endsWithVingts) {\n adjustedWord = thousandsWord.slice(0, -1)\n }\n result = adjustedWord + (withHyphen ? '-' : ' ') + THOUSAND\n }\n\n if (remainder > 0) {\n const { word: remainderWord } = buildSegment(remainder)\n result += (withHyphen ? '-' : ' ') + remainderWord\n }\n\n if (withHyphen) {\n result = result.replace(/ /g, '-')\n }\n\n return result\n }\n\n return buildLargeNumberWords(n, withHyphen)\n}\n\nfunction buildLargeNumberWords (n, withHyphen) {\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 = scaleIndex > 0 ? getScaleWord(scaleIndex, BigInt(segment)) : ''\n const { word: segWords, endsWithCents, endsWithVingts } = buildSegment(segment)\n\n if (scaleIndex === 0) {\n parts.push(segWords)\n } else if (scaleIndex === 1) {\n if (segment === 1) {\n parts.push(THOUSAND)\n } else {\n let adjustedWord = segWords\n if (endsWithCents || endsWithVingts) {\n adjustedWord = segWords.slice(0, -1)\n }\n parts.push(adjustedWord)\n parts.push(scaleWord)\n }\n } else {\n parts.push(segWords)\n parts.push(scaleWord)\n }\n }\n\n scaleIndex--\n }\n\n const sep = withHyphen ? '-' : ' '\n let result = parts.join(sep)\n\n if (withHyphen) {\n result = result.replace(/ /g, '-')\n }\n\n return result\n}\n\nfunction decimalPartToWords (decimalPart, withHyphen) {\n let result = ''\n const sep = withHyphen ? '-' : ' '\n\n let i = 0\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += sep\n result += ZERO\n i++\n }\n\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += sep\n result += integerToWords(BigInt(remainder), withHyphen)\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Belgian French words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @param {Object} [options] - Optional configuration\n * @param {boolean} [options.withHyphenSeparator=false] - Use hyphens between words\n * @returns {string} The number in Belgian French words\n */\nfunction toWords (value, options) {\n options = validateOptions(options)\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n const withHyphen = options.withHyphenSeparator || false\n\n let result = ''\n const sep = withHyphen ? '-' : ' '\n\n if (isNegative) {\n result = NEGATIVE + sep\n }\n\n result += integerToWords(integerPart, withHyphen)\n\n if (decimalPart) {\n result += sep + DECIMAL_SEP + sep + decimalPartToWords(decimalPart, withHyphen)\n }\n\n return result\n}\n\n// ============================================================================\n// Exports\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","TEENS","TENS","SCALES","SCALES_ARD","THOUSAND","HUNDRED","ZERO","buildSegment","n","word","endsWithCents","endsWithVingts","tensOnes","hundreds","Math","floor","parts","push","t","o","join","getScaleWord","scaleIndex","segment","baseWord","ardWord","integerToWords","withHyphen","Number","replace","thousands","remainder","result","thousandsWord","adjustedWord","remainderWord","numStr","toString","len","segments","remainderLen","pos","i","scaleWord","segWords","buildLargeNumberWords","value","options","undefined","proto","Object","getPrototypeOf","prototype","isPlainObject","TypeError","validateOptions","type","isFinite","Error","isSafeInteger","includes","numberToString","trimmed","trim","isNaN","normalizeString","parseNumericValue","withHyphenSeparator","sep","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,CCxFA,MAAMG,EAAO,CAAC,GAAI,KAAM,OAAQ,QAAS,SAAU,OAAQ,MAAO,OAAQ,OAAQ,QAC5EC,EAAQ,CAAC,MAAO,OAAQ,QAAS,SAAU,WAAY,SAAU,QAAS,WAAY,WAAY,YAClGC,EAAO,CAAC,GAAI,GAAI,QAAS,SAAU,WAAY,YAAa,WAAY,WAAY,eAAgB,WAGpGC,EAAS,CAAC,UAAW,UAAW,WAAY,eAC5CC,EAAa,CAAC,WAAY,WAAY,YAAa,gBAEnDC,EAAW,QACXC,EAAU,OACVC,EAAO,OAQb,SAASC,EAAcC,GACrB,GAAU,IAANA,EAAS,MAAO,CAAEC,KAAM,GAAIC,eAAe,EAAOC,gBAAgB,GAEtE,MAAMC,EAAWJ,EAAI,IACfK,EAAWC,KAAKC,MAAMP,EAAI,KAE1BQ,EAAQ,GACd,IAAIN,GAAgB,EAChBC,GAAiB,EAiBrB,GAdIE,EAAW,IACI,IAAbA,EACFG,EAAMC,KAAKZ,GAEM,IAAbO,GACFI,EAAMC,KAAKlB,EAAKc,GAAY,IAAMR,EAAU,KAC5CK,GAAgB,GAEhBM,EAAMC,KAAKlB,EAAKc,GAAY,IAAMR,IAMvB,IAAbO,QAEG,GAAIA,EAAW,GACpBI,EAAMC,KAAKlB,EAAKa,SACX,GAAIA,EAAW,GACpBI,EAAMC,KAAKjB,EAAMY,EAAW,UACvB,GAAIA,EAAW,GACpBI,EAAMC,KAAKjB,EAAMY,EAAW,UACvB,GAAIA,EAAW,GAAI,CAExB,MAAMM,EAAIJ,KAAKC,MAAMH,EAAW,IAC1BO,EAAIP,EAAW,GAEnBI,EAAMC,KADE,IAANE,EACSlB,EAAKiB,GACD,IAANC,EACElB,EAAKiB,GAAK,OAASnB,EAAK,GAExBE,EAAKiB,GAAK,IAAMnB,EAAKoB,GAEpC,MAAO,GAAIP,EAAW,GAAI,CAExB,MAAMO,EAAIP,EAAW,GAEnBI,EAAMC,KADE,IAANE,EACS,WACI,IAANA,EACE,eAAiBpB,EAAK,GAEtB,YAAcA,EAAKoB,GAElC,MAAO,GAAiB,KAAbP,EAETI,EAAMC,KAAK,iBACXN,GAAiB,OACZ,GAAIC,EAAW,GAGpBI,EAAMC,KAAK,gBAAkBlB,EADXa,EAAW,SAExB,CAEL,MAAMO,EAAIP,EAAW,GAEnBI,EAAMC,KADE,IAANE,EACS,UACI,IAANA,EACE,cAAgBpB,EAAK,GAErB,WAAaA,EAAKoB,GAEjC,CAEA,MAAO,CAAEV,KAAMO,EAAMI,KAAK,KAAMV,gBAAeC,iBACjD,CAMA,SAASU,EAAcC,EAAYC,GACjC,GAAmB,IAAfD,EAAkB,OAAOlB,EAE7B,GAAIkB,EAAa,GAAM,EAAG,CACxB,MACME,EAAWtB,EADGoB,EAAa,EAAK,GAEtC,OAAKE,EACED,EAAU,GAAKC,EAAW,IAAMA,EADjB,EAExB,CAAO,CACL,MACMC,EAAUtB,GADKmB,EAAa,GAAK,EAAK,GAE5C,OAAKG,EACEF,EAAU,GAAKE,EAAU,IAAMA,EADjBrB,CAEvB,CACF,CAMA,SAASsB,EAAgBlB,EAAGmB,GAAa,GACvC,GAAU,KAANnB,EAAU,OAAOF,EAErB,GAAIE,EAAI,MAAO,CACb,MAAMC,KAAEA,GAASF,EAAaqB,OAAOpB,IACrC,OAAOmB,EAAalB,EAAKoB,QAAQ,KAAM,KAAOpB,CAChD,CAEA,GAAID,EAAI,SAAY,CAClB,MAAMsB,EAAYF,OAAOpB,EAAI,OACvBuB,EAAYH,OAAOpB,EAAI,OAE7B,IAAIwB,EACJ,GAAkB,IAAdF,EACFE,EAAS5B,MACJ,CACL,MAAQK,KAAMwB,EAAavB,cAAEA,EAAaC,eAAEA,GAAmBJ,EAAauB,GAC5E,IAAII,EAAeD,GACfvB,GAAiBC,KACnBuB,EAAeD,EAAcpD,MAAM,GAAG,IAExCmD,EAASE,GAAgBP,EAAa,IAAM,KAAOvB,CACrD,CAEA,GAAI2B,EAAY,EAAG,CACjB,MAAQtB,KAAM0B,GAAkB5B,EAAawB,GAC7CC,IAAWL,EAAa,IAAM,KAAOQ,CACvC,CAMA,OAJIR,IACFK,EAASA,EAAOH,QAAQ,KAAM,MAGzBG,CACT,CAEA,OAGF,SAAgCxB,EAAGmB,GACjC,MAAMS,EAAS5B,EAAE6B,WACXC,EAAMF,EAAOvC,OAEb0C,EAAW,GAGXC,EAAeF,EAFD,EAGpB,IAAIG,EAAM,EAKV,IAJID,EAAe,IACjBD,EAAStB,KAAKW,OAAOQ,EAAOvD,MAAM,EAAG2D,KACrCC,EAAMD,GAEDC,EAAMH,GACXC,EAAStB,KAAKW,OAAOQ,EAAOvD,MAAM4D,EAAKA,EATrB,KAUlBA,GAVkB,EAapB,MAAMzB,EAAQ,GACd,IAAIM,EAAaiB,EAAS1C,OAAS,EAEnC,IAAK,IAAI6C,EAAI,EAAGA,EAAIH,EAAS1C,OAAQ6C,IAAK,CACxC,MAAMnB,EAAUgB,EAASG,GAEzB,GAAgB,IAAZnB,EAAe,CACjB,MAAMoB,EAAYrB,EAAa,EAAID,EAAaC,EAAYrC,OAAOsC,IAAY,IACvEd,KAAMmC,EAAQlC,cAAEA,EAAaC,eAAEA,GAAmBJ,EAAagB,GAEvE,GAAmB,IAAfD,EACFN,EAAMC,KAAK2B,QACN,GAAmB,IAAftB,EACT,GAAgB,IAAZC,EACFP,EAAMC,KAAKb,OACN,CACL,IAAI8B,EAAeU,GACflC,GAAiBC,KACnBuB,EAAeU,EAAS/D,MAAM,GAAG,IAEnCmC,EAAMC,KAAKiB,GACXlB,EAAMC,KAAK0B,EACb,MAEA3B,EAAMC,KAAK2B,GACX5B,EAAMC,KAAK0B,EAEf,CAEArB,GACF,CAGA,IAAIU,EAAShB,EAAMI,KADPO,EAAa,IAAM,KAO/B,OAJIA,IACFK,EAASA,EAAOH,QAAQ,KAAM,MAGzBG,CACT,CA7DSa,CAAsBrC,EAAGmB,EAClC,QA0FA,SAAkBmB,EAAOC,GACvBA,ECjQK,SAA0BA,GAC/B,QAAgBC,IAAZD,EAAuB,MAAO,CAAA,EAClC,GCCK,SAAwBD,GAC7B,GAAc,OAAVA,GAAmC,iBAAVA,EAAoB,OAAO,EACxD,MAAMG,EAAQC,OAAOC,eAAeL,GACpC,OAAiB,OAAVG,GAAkBA,IAAUC,OAAOE,SAC5C,CDLMC,CAAcN,GAAU,OAAOA,EACnC,MAAM,IAAIO,UACR,mEAAmEP,EAEvE,CD2PYQ,CAAgBR,GAC1B,MAAMnE,WAAEA,EAAUI,YAAEA,EAAWG,YAAEA,GD9P5B,SAA4B2D,GACjC,MAAMU,SAAcV,EAGpB,GAAa,WAATU,EACF,OAAOV,EAAQ,GACX,CAAElE,YAAY,EAAMI,aAAc8D,GAClC,CAAElE,YAAY,EAAOI,YAAa8D,GAIxC,GAAa,WAATU,EAAmB,CACrB,IAAK5B,OAAO6B,SAASX,GACnB,MAAM,IAAIY,MAAM,8DAElB,OAAI9B,OAAO+B,cAAcb,GAChBA,EAAQ,EACX,CAAElE,YAAY,EAAMI,YAAaC,QAAQ6D,IACzC,CAAElE,YAAY,EAAOI,YAAaC,OAAO6D,IAExCpE,EAgBX,SAAyBoE,GACvB,MAAMnE,EAAMmE,EAAMT,WAClB,OAAQ1D,EAAIiF,SAAS,MAAQjF,EAAIiF,SAAS,KACtCxE,EAAyBT,GACzBA,CACN,CArB8BkF,CAAef,GAC3C,CAGA,GAAa,WAATU,EACF,OAAO9E,EAqBX,SAA0BoE,GACxB,MAAMgB,EAAUhB,EAAMiB,OACtB,GAAuB,IAAnBD,EAAQjE,QAAgB+B,OAAOoC,MAAMpC,OAAOkC,IAC9C,MAAM,IAAIJ,MAAM,2BAA2BZ,MAE7C,OAAQgB,EAAQF,SAAS,MAAQE,EAAQF,SAAS,KAC9CxE,EAAyB0E,GACzBA,CACN,CA7B8BG,CAAgBnB,IAG5C,MAAM,IAAIQ,UACR,oEAAoEE,IAExE,CC+NmDU,CAAkBpB,GAC7DnB,EAAaoB,EAAQoB,sBAAuB,EAElD,IAAInC,EAAS,GACb,MAAMoC,EAAMzC,EAAa,IAAM,IAY/B,OAVI/C,IACFoD,EApPa,QAoPOoC,GAGtBpC,GAAUN,EAAe1C,EAAa2C,GAElCxC,IACF6C,GAAUoC,EAzPM,UAyPcA,EA3ClC,SAA6BjF,EAAawC,GACxC,IAAIK,EAAS,GACb,MAAMoC,EAAMzC,EAAa,IAAM,IAE/B,IAAIe,EAAI,EACR,KAAOA,EAAIvD,EAAYU,QAA6B,MAAnBV,EAAYuD,IACvCV,IAAQA,GAAUoC,GACtBpC,GAAU1B,EACVoC,IAGF,MAAMX,EAAY5C,EAAYN,MAAM6D,GAMpC,OALIX,IACEC,IAAQA,GAAUoC,GACtBpC,GAAUN,EAAezC,OAAO8C,GAAYJ,IAGvCK,CACT,CAyBwCqC,CAAmBlF,EAAawC,IAG/DK,CACT"}
@@ -1,3 +1,3 @@
1
- /*! n2words/fr 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=["","un","deux","trois","quatre","cinq","six","sept","huit","neuf"],r=["dix","onze","douze","treize","quatorze","quinze","seize","dix-sept","dix-huit","dix-neuf"],s=["","","vingt","trente","quarante","cinquante","soixante"],o=["million","billion","trillion","quadrillion"],u=["milliard","billiard","trilliard","quadrilliard"],l="mille",c="cent",f="zéro";function a(e){if(0===e)return{word:"",endsWithCents:!1,endsWithVingts:!1};const t=e%100,n=Math.floor(e/100),o=[];let u=!1,l=!1;if(n>0&&(1===n?o.push(c):0===t?(o.push(i[n]+" "+c+"s"),u=!0):o.push(i[n]+" "+c)),0===t);else if(t<10)o.push(i[t]);else if(t<17)o.push(r[t-10]);else if(t<20)o.push(r[t-10]);else if(t<70){const e=Math.floor(t/10),n=t%10;o.push(0===n?s[e]:1===n?s[e]+" et "+i[1]:s[e]+"-"+i[n])}else if(t<80){const e=t-60;o.push(11===e?"soixante et onze":"soixante-"+r[e-10])}else if(80===t)o.push("quatre-vingts"),l=!0;else if(t<100){const e=t-80;o.push(e<10?"quatre-vingt-"+i[e]:"quatre-vingt-"+r[e-10])}return{word:o.join(" "),endsWithCents:u,endsWithVingts:l}}const d=new Array(1e3),p=new Array(1e3),g=new Array(1e3);for(let e=0;e<1e3;e++){const t=a(e);d[e]=t.word,p[e]=t.endsWithCents,g[e]=t.endsWithVingts}function h(e,t){if(1===e)return l;if(e%2==0){const n=o[e/2-1];return n?t>1n?n+"s":n:""}{const n=u[(e-1)/2-1];return n?t>1n?n+"s":n:l}}function b(e,t=!1){if(0n===e)return f;if(e<1000n){const n=d[Number(e)];return t?n.replace(/ /g,"-"):n}if(e<1000000n){const n=Number(e/1000n),i=Number(e%1000n);let r;if(1===n)r=l;else{let e=d[n];(p[n]||g[n])&&(e=e.slice(0,-1)),r=e+(t?"-":" ")+l}return i>0&&(r+=(t?"-":" ")+d[i]),t&&(r=r.replace(/ /g,"-")),r}return function(e,t){const n=e.toString(),i=n.length,r=[],s=i%3;let o=0;for(s>0&&(r.push(Number(n.slice(0,s))),o=s);o<i;)r.push(Number(n.slice(o,o+3))),o+=3;const u=[];let c=r.length-1;for(let e=0;e<r.length;e++){const t=r[e];if(0!==t){const e=c>0?h(c,BigInt(t)):"";if(0===c)u.push(d[t]);else if(1===c)if(1===t)u.push(l);else{let n=d[t];(p[t]||g[t])&&(n=n.slice(0,-1)),u.push(n),u.push(e)}else u.push(d[t]),u.push(e)}c--}let f=u.join(t?"-":" ");return t&&(f=f.replace(/ /g,"-")),f}(e,t)}e.fr=function(e,i){i=function(e){if(void 0===e)return{};if(function(e){if(null===e||"object"!=typeof e)return!1;const t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}(e))return e;throw new TypeError("Invalid options: expected plain object or undefined, got "+typeof e)}(i);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),u=i.withHyphenSeparator||!1;let l="";const c=u?"-":" ";return r&&(l="moins"+c),l+=b(s,u),o&&(l+=c+"virgule"+c+function(e,t){let n="";const i=t?"-":" ";let r=0;for(;r<e.length&&"0"===e[r];)n&&(n+=i),n+=f,r++;const s=e.slice(r);return s&&(n&&(n+=i),n+=b(BigInt(s),t)),n}(o,u)),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||{});
1
+ /*! n2words/fr v3.1.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=["","un","deux","trois","quatre","cinq","six","sept","huit","neuf"],r=["dix","onze","douze","treize","quatorze","quinze","seize","dix-sept","dix-huit","dix-neuf"],s=["","","vingt","trente","quarante","cinquante","soixante"],o=["million","billion","trillion","quadrillion"],u=["milliard","billiard","trilliard","quadrilliard"],l="mille",c="cent",f="zéro";function a(e){if(0===e)return{word:"",endsWithCents:!1,endsWithVingts:!1};const t=e%100,n=Math.floor(e/100),o=[];let u=!1,l=!1;if(n>0&&(1===n?o.push(c):0===t?(o.push(i[n]+" "+c+"s"),u=!0):o.push(i[n]+" "+c)),0===t);else if(t<10)o.push(i[t]);else if(t<17)o.push(r[t-10]);else if(t<20)o.push(r[t-10]);else if(t<70){const e=Math.floor(t/10),n=t%10;o.push(0===n?s[e]:1===n?s[e]+" et "+i[1]:s[e]+"-"+i[n])}else if(t<80){const e=t-60;o.push(11===e?"soixante et onze":"soixante-"+r[e-10])}else if(80===t)o.push("quatre-vingts"),l=!0;else if(t<100){const e=t-80;o.push(e<10?"quatre-vingt-"+i[e]:"quatre-vingt-"+r[e-10])}return{word:o.join(" "),endsWithCents:u,endsWithVingts:l}}function d(e,t){if(1===e)return l;if(e%2==0){const n=o[e/2-1];return n?t>1n?n+"s":n:""}{const n=u[(e-1)/2-1];return n?t>1n?n+"s":n:l}}function g(e,t=!1){if(0n===e)return f;if(e<1000n){const{word:n}=a(Number(e));return t?n.replace(/ /g,"-"):n}if(e<1000000n){const n=Number(e/1000n),i=Number(e%1000n);let r;if(1===n)r=l;else{const{word:e,endsWithCents:i,endsWithVingts:s}=a(n);let o=e;(i||s)&&(o=e.slice(0,-1)),r=o+(t?"-":" ")+l}if(i>0){const{word:e}=a(i);r+=(t?"-":" ")+e}return t&&(r=r.replace(/ /g,"-")),r}return function(e,t){const n=e.toString(),i=n.length,r=[],s=i%3;let o=0;for(s>0&&(r.push(Number(n.slice(0,s))),o=s);o<i;)r.push(Number(n.slice(o,o+3))),o+=3;const u=[];let c=r.length-1;for(let e=0;e<r.length;e++){const t=r[e];if(0!==t){const e=c>0?d(c,BigInt(t)):"",{word:n,endsWithCents:i,endsWithVingts:r}=a(t);if(0===c)u.push(n);else if(1===c)if(1===t)u.push(l);else{let t=n;(i||r)&&(t=n.slice(0,-1)),u.push(t),u.push(e)}else u.push(n),u.push(e)}c--}let f=u.join(t?"-":" ");return t&&(f=f.replace(/ /g,"-")),f}(e,t)}e.fr=function(e,i){i=function(e){if(void 0===e)return{};if(function(e){if(null===e||"object"!=typeof e)return!1;const t=Object.getPrototypeOf(e);return null===t||t===Object.prototype}(e))return e;throw new TypeError("Invalid options: expected plain object or undefined, got "+typeof e)}(i);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),u=i.withHyphenSeparator||!1;let l="";const c=u?"-":" ";return r&&(l="moins"+c),l+=g(s,u),o&&(l+=c+"virgule"+c+function(e,t){let n="";const i=t?"-":" ";let r=0;for(;r<e.length&&"0"===e[r];)n&&(n+=i),n+=f,r++;const s=e.slice(r);return s&&(n&&(n+=i),n+=g(BigInt(s),t)),n}(o,u)),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
3
  //# sourceMappingURL=fr.js.map