n2words 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +86 -188
- package/dist/languages/am-Latn.js +3 -0
- package/dist/languages/am-Latn.js.map +1 -0
- package/dist/languages/am.js +3 -0
- package/dist/languages/am.js.map +1 -0
- package/dist/languages/ar.js +3 -0
- package/dist/languages/ar.js.map +1 -0
- package/dist/languages/az.js +3 -0
- package/dist/languages/az.js.map +1 -0
- package/dist/languages/bn.js +3 -0
- package/dist/languages/bn.js.map +1 -0
- package/dist/languages/cs.js +3 -0
- package/dist/languages/cs.js.map +1 -0
- package/dist/languages/da.js +3 -0
- package/dist/languages/da.js.map +1 -0
- package/dist/languages/de.js +3 -0
- package/dist/languages/de.js.map +1 -0
- package/dist/languages/el.js +3 -0
- package/dist/languages/el.js.map +1 -0
- package/dist/languages/en.js +3 -0
- package/dist/languages/en.js.map +1 -0
- package/dist/languages/es.js +3 -0
- package/dist/languages/es.js.map +1 -0
- package/dist/languages/fa.js +3 -0
- package/dist/languages/fa.js.map +1 -0
- package/dist/languages/fi.js +3 -0
- package/dist/languages/fi.js.map +1 -0
- package/dist/languages/fil.js +3 -0
- package/dist/languages/fil.js.map +1 -0
- package/dist/languages/fr-BE.js +3 -0
- package/dist/languages/fr-BE.js.map +1 -0
- package/dist/languages/fr.js +3 -0
- package/dist/languages/fr.js.map +1 -0
- package/dist/languages/gu.js +3 -0
- package/dist/languages/gu.js.map +1 -0
- package/dist/languages/ha.js +3 -0
- package/dist/languages/ha.js.map +1 -0
- package/dist/languages/hbo.js +3 -0
- package/dist/languages/hbo.js.map +1 -0
- package/dist/languages/he.js +3 -0
- package/dist/languages/he.js.map +1 -0
- package/dist/languages/hi.js +3 -0
- package/dist/languages/hi.js.map +1 -0
- package/dist/languages/hr.js +3 -0
- package/dist/languages/hr.js.map +1 -0
- package/dist/languages/hu.js +3 -0
- package/dist/languages/hu.js.map +1 -0
- package/dist/languages/id.js +3 -0
- package/dist/languages/id.js.map +1 -0
- package/dist/languages/it.js +3 -0
- package/dist/languages/it.js.map +1 -0
- package/dist/languages/ja.js +3 -0
- package/dist/languages/ja.js.map +1 -0
- package/dist/languages/kn.js +3 -0
- package/dist/languages/kn.js.map +1 -0
- package/dist/languages/ko.js +3 -0
- package/dist/languages/ko.js.map +1 -0
- package/dist/languages/lt.js +3 -0
- package/dist/languages/lt.js.map +1 -0
- package/dist/languages/lv.js +3 -0
- package/dist/languages/lv.js.map +1 -0
- package/dist/languages/mr.js +3 -0
- package/dist/languages/mr.js.map +1 -0
- package/dist/languages/ms.js +3 -0
- package/dist/languages/ms.js.map +1 -0
- package/dist/languages/nb.js +3 -0
- package/dist/languages/nb.js.map +1 -0
- package/dist/languages/nl.js +3 -0
- package/dist/languages/nl.js.map +1 -0
- package/dist/languages/pa.js +3 -0
- package/dist/languages/pa.js.map +1 -0
- package/dist/languages/pl.js +3 -0
- package/dist/languages/pl.js.map +1 -0
- package/dist/languages/pt.js +3 -0
- package/dist/languages/pt.js.map +1 -0
- package/dist/languages/ro.js +3 -0
- package/dist/languages/ro.js.map +1 -0
- package/dist/languages/ru.js +3 -0
- package/dist/languages/ru.js.map +1 -0
- package/dist/languages/sr-Cyrl.js +3 -0
- package/dist/languages/sr-Cyrl.js.map +1 -0
- package/dist/languages/sr-Latn.js +3 -0
- package/dist/languages/sr-Latn.js.map +1 -0
- package/dist/languages/sv.js +3 -0
- package/dist/languages/sv.js.map +1 -0
- package/dist/languages/sw.js +3 -0
- package/dist/languages/sw.js.map +1 -0
- package/dist/languages/ta.js +3 -0
- package/dist/languages/ta.js.map +1 -0
- package/dist/languages/te.js +3 -0
- package/dist/languages/te.js.map +1 -0
- package/dist/languages/th.js +3 -0
- package/dist/languages/th.js.map +1 -0
- package/dist/languages/tr.js +3 -0
- package/dist/languages/tr.js.map +1 -0
- package/dist/languages/uk.js +3 -0
- package/dist/languages/uk.js.map +1 -0
- package/dist/languages/ur.js +3 -0
- package/dist/languages/ur.js.map +1 -0
- package/dist/languages/vi.js +3 -0
- package/dist/languages/vi.js.map +1 -0
- package/dist/languages/zh-Hans.js +3 -0
- package/dist/languages/zh-Hans.js.map +1 -0
- package/dist/languages/zh-Hant.js +3 -0
- package/dist/languages/zh-Hant.js.map +1 -0
- package/dist/n2words.js +2 -2
- package/dist/n2words.js.map +1 -1
- package/lib/languages/am-Latn.d.ts +7 -0
- package/lib/languages/am-Latn.js +164 -0
- package/lib/languages/am.d.ts +7 -0
- package/lib/languages/am.js +164 -0
- package/lib/languages/ar.d.ts +14 -27
- package/lib/languages/ar.js +175 -129
- package/lib/languages/az.d.ts +4 -9
- package/lib/languages/az.js +171 -37
- package/lib/languages/bn.d.ts +4 -8
- package/lib/languages/bn.js +138 -124
- package/lib/languages/cs.d.ts +15 -85
- package/lib/languages/cs.js +310 -114
- package/lib/languages/da.d.ts +11 -12
- package/lib/languages/da.js +276 -101
- package/lib/languages/de.d.ts +14 -11
- package/lib/languages/de.js +317 -86
- package/lib/languages/el.d.ts +11 -11
- package/lib/languages/el.js +231 -78
- package/lib/languages/en.d.ts +14 -13
- package/lib/languages/en.js +242 -72
- package/lib/languages/es.d.ts +18 -12
- package/lib/languages/es.js +317 -103
- package/lib/languages/fa.d.ts +4 -44
- package/lib/languages/fa.js +112 -122
- package/lib/languages/fi.d.ts +14 -0
- package/lib/languages/fi.js +245 -0
- package/lib/languages/fil.d.ts +4 -13
- package/lib/languages/fil.js +207 -106
- package/lib/languages/fr-BE.d.ts +8 -8
- package/lib/languages/fr-BE.js +294 -19
- package/lib/languages/fr.d.ts +18 -12
- package/lib/languages/fr.js +352 -89
- package/lib/languages/gu.d.ts +4 -8
- package/lib/languages/gu.js +130 -125
- package/lib/languages/ha.d.ts +7 -0
- package/lib/languages/ha.js +230 -0
- package/lib/languages/hbo.d.ts +10 -110
- package/lib/languages/hbo.js +263 -214
- package/lib/languages/he.d.ts +10 -77
- package/lib/languages/he.js +242 -172
- package/lib/languages/hi.d.ts +4 -8
- package/lib/languages/hi.js +138 -124
- package/lib/languages/hr.d.ts +8 -77
- package/lib/languages/hr.js +194 -89
- package/lib/languages/hu.d.ts +4 -19
- package/lib/languages/hu.js +198 -119
- package/lib/languages/id.d.ts +4 -34
- package/lib/languages/id.js +171 -129
- package/lib/languages/it.d.ts +16 -34
- package/lib/languages/it.js +339 -94
- package/lib/languages/ja.d.ts +14 -14
- package/lib/languages/ja.js +233 -111
- package/lib/languages/kn.d.ts +4 -8
- package/lib/languages/kn.js +130 -35
- package/lib/languages/ko.d.ts +11 -11
- package/lib/languages/ko.js +257 -49
- package/lib/languages/lt.d.ts +15 -67
- package/lib/languages/lt.js +296 -122
- package/lib/languages/lv.d.ts +15 -67
- package/lib/languages/lv.js +297 -106
- package/lib/languages/mr.d.ts +4 -8
- package/lib/languages/mr.js +130 -125
- package/lib/languages/ms.d.ts +4 -28
- package/lib/languages/ms.js +171 -116
- package/lib/languages/nb.d.ts +11 -9
- package/lib/languages/nb.js +282 -87
- package/lib/languages/nl.d.ts +23 -13
- package/lib/languages/nl.js +317 -133
- package/lib/languages/pa.d.ts +4 -8
- package/lib/languages/pa.js +156 -124
- package/lib/languages/pl.d.ts +19 -77
- package/lib/languages/pl.js +307 -87
- package/lib/languages/pt.d.ts +14 -26
- package/lib/languages/pt.js +286 -92
- package/lib/languages/ro.d.ts +15 -155
- package/lib/languages/ro.js +219 -235
- package/lib/languages/ru.d.ts +8 -82
- package/lib/languages/ru.js +222 -78
- package/lib/languages/sr-Cyrl.d.ts +8 -77
- package/lib/languages/sr-Cyrl.js +191 -89
- package/lib/languages/sr-Latn.d.ts +8 -77
- package/lib/languages/sr-Latn.js +191 -89
- package/lib/languages/sv.d.ts +11 -11
- package/lib/languages/sv.js +288 -74
- package/lib/languages/sw.d.ts +4 -36
- package/lib/languages/sw.js +133 -106
- package/lib/languages/ta.d.ts +4 -17
- package/lib/languages/ta.js +129 -201
- package/lib/languages/te.d.ts +4 -19
- package/lib/languages/te.js +141 -196
- package/lib/languages/th.d.ts +4 -14
- package/lib/languages/th.js +135 -91
- package/lib/languages/tr.d.ts +15 -9
- package/lib/languages/tr.js +256 -49
- package/lib/languages/uk.d.ts +8 -82
- package/lib/languages/uk.js +200 -78
- package/lib/languages/ur.d.ts +4 -8
- package/lib/languages/ur.js +156 -124
- package/lib/languages/vi.d.ts +14 -69
- package/lib/languages/vi.js +294 -125
- package/lib/languages/zh-Hans.d.ts +8 -18
- package/lib/languages/zh-Hans.js +163 -92
- package/lib/languages/zh-Hant.d.ts +8 -18
- package/lib/languages/zh-Hant.js +181 -90
- package/lib/n2words.d.ts +53 -209
- package/lib/n2words.js +111 -530
- package/lib/utils/is-plain-object.d.ts +13 -0
- package/lib/utils/is-plain-object.js +17 -0
- package/lib/utils/parse-numeric.d.ts +17 -0
- package/lib/utils/parse-numeric.js +108 -0
- package/lib/utils/validate-options.d.ts +8 -0
- package/lib/utils/validate-options.js +16 -0
- package/package.json +26 -14
- package/dist/ArabicConverter.js +0 -3
- package/dist/ArabicConverter.js.map +0 -1
- package/dist/AzerbaijaniConverter.js +0 -3
- package/dist/AzerbaijaniConverter.js.map +0 -1
- package/dist/BanglaConverter.js +0 -3
- package/dist/BanglaConverter.js.map +0 -1
- package/dist/BiblicalHebrewConverter.js +0 -3
- package/dist/BiblicalHebrewConverter.js.map +0 -1
- package/dist/CroatianConverter.js +0 -3
- package/dist/CroatianConverter.js.map +0 -1
- package/dist/CzechConverter.js +0 -3
- package/dist/CzechConverter.js.map +0 -1
- package/dist/DanishConverter.js +0 -3
- package/dist/DanishConverter.js.map +0 -1
- package/dist/DutchConverter.js +0 -3
- package/dist/DutchConverter.js.map +0 -1
- package/dist/EnglishConverter.js +0 -3
- package/dist/EnglishConverter.js.map +0 -1
- package/dist/FilipinoConverter.js +0 -3
- package/dist/FilipinoConverter.js.map +0 -1
- package/dist/FrenchBelgiumConverter.js +0 -3
- package/dist/FrenchBelgiumConverter.js.map +0 -1
- package/dist/FrenchConverter.js +0 -3
- package/dist/FrenchConverter.js.map +0 -1
- package/dist/GermanConverter.js +0 -3
- package/dist/GermanConverter.js.map +0 -1
- package/dist/GreekConverter.js +0 -3
- package/dist/GreekConverter.js.map +0 -1
- package/dist/GujaratiConverter.js +0 -3
- package/dist/GujaratiConverter.js.map +0 -1
- package/dist/HebrewConverter.js +0 -3
- package/dist/HebrewConverter.js.map +0 -1
- package/dist/HindiConverter.js +0 -3
- package/dist/HindiConverter.js.map +0 -1
- package/dist/HungarianConverter.js +0 -3
- package/dist/HungarianConverter.js.map +0 -1
- package/dist/IndonesianConverter.js +0 -3
- package/dist/IndonesianConverter.js.map +0 -1
- package/dist/ItalianConverter.js +0 -3
- package/dist/ItalianConverter.js.map +0 -1
- package/dist/JapaneseConverter.js +0 -3
- package/dist/JapaneseConverter.js.map +0 -1
- package/dist/KannadaConverter.js +0 -3
- package/dist/KannadaConverter.js.map +0 -1
- package/dist/KoreanConverter.js +0 -3
- package/dist/KoreanConverter.js.map +0 -1
- package/dist/LatvianConverter.js +0 -3
- package/dist/LatvianConverter.js.map +0 -1
- package/dist/LithuanianConverter.js +0 -3
- package/dist/LithuanianConverter.js.map +0 -1
- package/dist/MalayConverter.js +0 -3
- package/dist/MalayConverter.js.map +0 -1
- package/dist/MarathiConverter.js +0 -3
- package/dist/MarathiConverter.js.map +0 -1
- package/dist/NorwegianBokmalConverter.js +0 -3
- package/dist/NorwegianBokmalConverter.js.map +0 -1
- package/dist/PersianConverter.js +0 -3
- package/dist/PersianConverter.js.map +0 -1
- package/dist/PolishConverter.js +0 -3
- package/dist/PolishConverter.js.map +0 -1
- package/dist/PortugueseConverter.js +0 -3
- package/dist/PortugueseConverter.js.map +0 -1
- package/dist/PunjabiConverter.js +0 -3
- package/dist/PunjabiConverter.js.map +0 -1
- package/dist/RomanianConverter.js +0 -3
- package/dist/RomanianConverter.js.map +0 -1
- package/dist/RussianConverter.js +0 -3
- package/dist/RussianConverter.js.map +0 -1
- package/dist/SerbianCyrillicConverter.js +0 -3
- package/dist/SerbianCyrillicConverter.js.map +0 -1
- package/dist/SerbianLatinConverter.js +0 -3
- package/dist/SerbianLatinConverter.js.map +0 -1
- package/dist/SimplifiedChineseConverter.js +0 -3
- package/dist/SimplifiedChineseConverter.js.map +0 -1
- package/dist/SpanishConverter.js +0 -3
- package/dist/SpanishConverter.js.map +0 -1
- package/dist/SwahiliConverter.js +0 -3
- package/dist/SwahiliConverter.js.map +0 -1
- package/dist/SwedishConverter.js +0 -3
- package/dist/SwedishConverter.js.map +0 -1
- package/dist/TamilConverter.js +0 -3
- package/dist/TamilConverter.js.map +0 -1
- package/dist/TeluguConverter.js +0 -3
- package/dist/TeluguConverter.js.map +0 -1
- package/dist/ThaiConverter.js +0 -3
- package/dist/ThaiConverter.js.map +0 -1
- package/dist/TraditionalChineseConverter.js +0 -3
- package/dist/TraditionalChineseConverter.js.map +0 -1
- package/dist/TurkishConverter.js +0 -3
- package/dist/TurkishConverter.js.map +0 -1
- package/dist/UkrainianConverter.js +0 -3
- package/dist/UkrainianConverter.js.map +0 -1
- package/dist/UrduConverter.js +0 -3
- package/dist/UrduConverter.js.map +0 -1
- package/dist/VietnameseConverter.js +0 -3
- package/dist/VietnameseConverter.js.map +0 -1
- package/lib/classes/abstract-language.d.ts +0 -178
- package/lib/classes/abstract-language.js +0 -268
- package/lib/classes/greedy-scale-language.d.ts +0 -109
- package/lib/classes/greedy-scale-language.js +0 -201
- package/lib/classes/slavic-language.d.ts +0 -148
- package/lib/classes/slavic-language.js +0 -281
- package/lib/classes/south-asian-language.d.ts +0 -70
- package/lib/classes/south-asian-language.js +0 -154
- package/lib/classes/turkic-language.d.ts +0 -26
- package/lib/classes/turkic-language.js +0 -59
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ms.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/ms.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 * Malay (Bahasa Melayu) language converter - Functional Implementation\n *\n * Self-contained converter with precomputed lookup tables.\n *\n * Key features:\n * - \"Se-\" prefix for ALL singular scale units (seratus, seribu, sejuta, sebilion)\n * - Regular patterns (puluh for tens, ratus for hundreds)\n * - Teens with \"belas\" suffix\n * - Note: \"lapan\" (8) differs from Indonesian \"delapan\"\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary\n// ============================================================================\n\nconst ONES = ['', 'satu', 'dua', 'tiga', 'empat', 'lima', 'enam', 'tujuh', 'lapan', 'sembilan']\nconst TEENS = ['sepuluh', 'sebelas', 'dua belas', 'tiga belas', 'empat belas', 'lima belas', 'enam belas', 'tujuh belas', 'lapan belas', 'sembilan belas']\nconst TENS = ['', '', 'dua puluh', 'tiga puluh', 'empat puluh', 'lima puluh', 'enam puluh', 'tujuh puluh', 'lapan puluh', 'sembilan puluh']\n\nconst HUNDRED_WORD = 'ratus'\nconst THOUSAND_WORD = 'ribu'\nconst SCALE_WORDS = ['juta', 'bilion', 'trilion']\n\nconst ZERO = 'sifar'\nconst NEGATIVE = 'minus'\nconst DECIMAL_SEP = 'perpuluhan'\n\n// ============================================================================\n// Precomputed Lookup Tables\n// ============================================================================\n\nfunction buildSegment (n) {\n if (n === 0) return ''\n\n const onesDigit = n % 10\n const tensDigit = Math.floor(n / 10) % 10\n const hundredsDigit = Math.floor(n / 100)\n\n const parts = []\n\n // Hundreds: seratus (100) or N ratus (200-900)\n if (hundredsDigit > 0) {\n if (hundredsDigit === 1) {\n parts.push('se' + HUNDRED_WORD)\n } else {\n parts.push(ONES[hundredsDigit] + ' ' + HUNDRED_WORD)\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[tensOnes])\n } else if (tensOnes < 20) {\n parts.push(TEENS[tensOnes - 10])\n } else if (onesDigit === 0) {\n parts.push(TENS[tensDigit])\n } else {\n parts.push(TENS[tensDigit] + ' ' + ONES[onesDigit])\n }\n\n return parts.join(' ')\n}\n\nconst SEGMENTS = new Array(1000)\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS[i] = buildSegment(i)\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\nfunction integerToWords (n) {\n if (n === 0n) return ZERO\n\n if (n < 1000n) {\n return SEGMENTS[Number(n)]\n }\n\n 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 = 'se' + THOUSAND_WORD\n } else {\n result = SEGMENTS[thousands] + ' ' + THOUSAND_WORD\n }\n\n if (remainder > 0) {\n result += ' ' + SEGMENTS[remainder]\n }\n\n return result\n }\n\n return buildLargeNumberWords(n)\n}\n\nfunction buildLargeNumberWords (n) {\n const numStr = n.toString()\n const len = numStr.length\n\n const segments = []\n const segmentSize = 3\n\n const remainderLen = len % segmentSize\n let pos = 0\n if (remainderLen > 0) {\n segments.push(Number(numStr.slice(0, remainderLen)))\n pos = remainderLen\n }\n while (pos < len) {\n segments.push(Number(numStr.slice(pos, pos + segmentSize)))\n pos += segmentSize\n }\n\n const parts = []\n let scaleIndex = segments.length - 1\n\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]\n\n if (segment !== 0) {\n if (scaleIndex === 0) {\n parts.push(SEGMENTS[segment])\n } else if (scaleIndex === 1) {\n if (segment === 1) {\n parts.push('se' + THOUSAND_WORD)\n } else {\n parts.push(SEGMENTS[segment] + ' ' + THOUSAND_WORD)\n }\n } else {\n // Malay: \"se-\" prefix for ALL scale words when segment is 1\n const scaleWord = SCALE_WORDS[scaleIndex - 2]\n if (segment === 1) {\n parts.push('se' + scaleWord)\n } else {\n parts.push(SEGMENTS[segment] + ' ' + scaleWord)\n }\n }\n }\n\n scaleIndex--\n }\n\n return parts.join(' ')\n}\n\nfunction decimalPartToWords (decimalPart) {\n let result = ''\n\n let i = 0\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder))\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Malay words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in Malay 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","HUNDRED_WORD","THOUSAND_WORD","SCALE_WORDS","ZERO","buildSegment","n","onesDigit","tensDigit","Math","floor","hundredsDigit","parts","push","tensOnes","join","SEGMENTS","Array","i","integerToWords","Number","thousands","remainder","result","numStr","toString","len","segments","remainderLen","pos","scaleIndex","segment","scaleWord","buildLargeNumberWords","value","type","isFinite","Error","isSafeInteger","includes","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","NEGATIVE","decimalPartToWords"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! n2words/nb v3.0.0 | MIT License | github.com/forzagreen/n2words */
|
|
2
|
+
var e,t;e=this,t=function(e){"use strict";function t(e){const t="-"===e[0];t&&(e=e.slice(1));const n=e.indexOf(".");if(-1===n)return{isNegative:t,integerPart:BigInt(e)};const r=e.slice(0,n)||"0",i=e.slice(n+1);return{isNegative:t,integerPart:BigInt(r),decimalPart:i}}function n(e){const[t,n]=e.toLowerCase().split("e"),r=parseInt(n,10),i=t.indexOf("."),o=-1===i?t:t.slice(0,i)+t.slice(i+1),s=(-1===i?t.length:i)+r;return s>=o.length?o+"0".repeat(s-o.length):s<=0?"0."+"0".repeat(-s)+o:o.slice(0,s)+"."+o.slice(s)}const r=["","en","to","tre","fire","fem","seks","syv","åtte","ni"],i=["ti","elleve","tolv","tretten","fjorten","femten","seksten","sytten","atten","nitten"],o=["","","tjue","tretti","førti","femti","seksti","sytti","åtti","nitti"],s="tusen",u="null",l=["million","milliard","billion","billiard","kvintillion","sekstillion","septillion","oktillion"];function d(e){if(0===e)return{word:"",hasHundred:!1};const t=e%10,n=Math.floor(e/10)%10,s=Math.floor(e/100),u=[];let l=!1;s>0&&(l=!0,u.push(r[s]+" hundre"));const d=e%100;return 0===d||u.push(d<10?r[t]:d<20?i[t]:0===t?o[n]:o[n]+"-"+r[t]),2===u.length?{word:u[0]+" og "+u[1],hasHundred:!0}:{word:u[0]||"",hasHundred:l}}const f=new Array(1e3),a=new Array(1e3);for(let e=0;e<1e3;e++){const t=d(e);f[e]=t.word,a[e]=t.hasHundred}function c(e){if(0n===e)return u;if(e<1000n)return f[Number(e)];if(e<1000000n){const t=Number(e/1000n),n=Number(e%1000n);let r=f[t]+" "+s;return n>0&&(r+=a[n]?", "+f[n]:" og "+f[n]),r}return function(e){const t=e.toString(),n=t.length,r=[],i=n%3;let o=0;for(i>0&&(r.push(Number(t.slice(0,i))),o=i);o<n;)r.push(Number(t.slice(o,o+3))),o+=3;const d=[];let c=r.length-1;for(let e=0;e<r.length;e++){const t=r[e];if(0!==t){const e=f[t];d.push(0===c?{word:e,hasHundred:a[t],type:"units"}:1===c?{word:e+" "+s,hasHundred:!1,type:"thousand"}:{word:e+" "+l[c-2],hasHundred:!1,type:"million"})}c--}return function(e){if(0===e.length)return u;if(1===e.length)return e[0].word;const t=[];for(let n=0;n<e.length;n++){const r=e[n],i=e[n+1];t.push(r.word),i&&t.push("thousand"===r.type?i.hasHundred?", ":" og ":"million"===r.type?"units"!==i.type||i.hasHundred?" ":" og ":" ")}return t.join("")}(d)}(e)}e.nb=function(e){const{isNegative:r,integerPart:i,decimalPart:o}=function(e){const r=typeof e;if("bigint"===r)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===r){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:t(function(e){const t=e.toString();return t.includes("e")||t.includes("E")?n(t):t}(e))}if("string"===r)return t(function(e){const t=e.trim();if(0===t.length||Number.isNaN(Number(t)))throw new Error(`Invalid number format: "${e}"`);return t.includes("e")||t.includes("E")?n(t):t}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${r}`)}(e);let s="";return r&&(s="minus "),s+=c(i),o&&(s+=" komma "+function(e){let t="",n=0;for(;n<e.length&&"0"===e[n];)t&&(t+=" "),t+=u,n++;const r=e.slice(n);return r&&(t&&(t+=" "),t+=c(BigInt(r))),t}(o)),s}},"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
|
|
3
|
+
//# sourceMappingURL=nb.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nb.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/nb.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 * Norwegian Bokmål language converter - Functional Implementation\n *\n * A performance-optimized number-to-words converter using precomputed lookup tables.\n *\n * Key features:\n * - Hyphenated tens+ones: \"tjue-en\" (21)\n * - \"og\" conjunction after hundreds\n * - Comma separator after thousands before hundreds\n * - Short scale: million, milliard, billion, etc.\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary (module-level constants)\n// ============================================================================\n\nconst ONES = ['', 'en', 'to', 'tre', 'fire', 'fem', 'seks', 'syv', 'åtte', 'ni']\n\nconst TEENS = ['ti', 'elleve', 'tolv', 'tretten', 'fjorten', 'femten', 'seksten', 'sytten', 'atten', 'nitten']\nconst TENS = ['', '', 'tjue', 'tretti', 'førti', 'femti', 'seksti', 'sytti', 'åtti', 'nitti']\n\nconst HUNDRED = 'hundre'\nconst THOUSAND = 'tusen'\n\nconst ZERO = 'null'\nconst NEGATIVE = 'minus'\nconst DECIMAL_SEP = 'komma'\n\n// Short scale: million, milliard, billion, etc.\nconst SCALES = ['million', 'milliard', 'billion', 'billiard', 'kvintillion', 'sekstillion', 'septillion', 'oktillion']\n\n// ============================================================================\n// Precomputed Lookup Tables (built once at module load)\n// ============================================================================\n\n/**\n * Builds segment word for 0-999.\n * Returns object with word and hasHundred flag.\n */\nfunction buildSegment (n) {\n if (n === 0) return { word: '', hasHundred: false }\n\n const ones = n % 10\n const tens = Math.floor(n / 10) % 10\n const hundreds = Math.floor(n / 100)\n\n const parts = []\n let hasHundred = false\n\n // Hundreds: \"en hundre\", \"to hundre\"\n if (hundreds > 0) {\n hasHundred = true\n parts.push(ONES[hundreds] + ' ' + HUNDRED)\n }\n\n // Tens and ones\n const tensOnes = n % 100\n\n if (tensOnes === 0) {\n // Just hundreds\n } else if (tensOnes < 10) {\n // Single digit\n parts.push(ONES[ones])\n } else if (tensOnes < 20) {\n // Teens\n parts.push(TEENS[ones])\n } else if (ones === 0) {\n // Even tens\n parts.push(TENS[tens])\n } else {\n // Hyphenated: \"tjue-en\"\n parts.push(TENS[tens] + '-' + ONES[ones])\n }\n\n // Combine with \" og \" between hundreds and remainder\n if (parts.length === 2) {\n return { word: parts[0] + ' og ' + parts[1], hasHundred: true }\n }\n return { word: parts[0] || '', hasHundred }\n}\n\n// Precompute all 1000 segment words (0-999)\nconst SEGMENTS = new Array(1000)\nconst SEGMENTS_HAS_HUNDRED = new Array(1000)\n\nfor (let i = 0; i < 1000; i++) {\n const result = buildSegment(i)\n SEGMENTS[i] = result.word\n SEGMENTS_HAS_HUNDRED[i] = result.hasHundred\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\n/**\n * Converts a non-negative integer to Norwegian Bokmål words.\n *\n * @param {bigint} n - Non-negative integer to convert\n * @returns {string} Norwegian words\n */\nfunction integerToWords (n) {\n if (n === 0n) return ZERO\n\n // Fast path: numbers < 1000 (direct lookup)\n if (n < 1000n) {\n return SEGMENTS[Number(n)]\n }\n\n // Fast path: numbers < 1,000,000 (thousands)\n if (n < 1_000_000n) {\n const thousands = Number(n / 1000n)\n const remainder = Number(n % 1000n)\n\n let result = SEGMENTS[thousands] + ' ' + THOUSAND\n\n if (remainder > 0) {\n // Comma before hundreds, \" og \" before small numbers\n if (SEGMENTS_HAS_HUNDRED[remainder]) {\n result += ', ' + SEGMENTS[remainder]\n } else {\n result += ' og ' + SEGMENTS[remainder]\n }\n }\n\n return result\n }\n\n // For numbers >= 1,000,000, use scale decomposition\n return buildLargeNumberWords(n)\n}\n\n/**\n * Builds words for numbers >= 1,000,000.\n *\n * @param {bigint} n - Number >= 1,000,000\n * @returns {string} Norwegian 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 const hasHundred = SEGMENTS_HAS_HUNDRED[segment]\n\n if (scaleIndex === 0) {\n // Units segment\n parts.push({ word: segmentWord, hasHundred, type: 'units' })\n } else if (scaleIndex === 1) {\n // Thousands\n parts.push({ word: segmentWord + ' ' + THOUSAND, hasHundred: false, type: 'thousand' })\n } else {\n // Millions+\n const scaleWord = SCALES[scaleIndex - 2]\n parts.push({ word: segmentWord + ' ' + scaleWord, hasHundred: false, type: 'million' })\n }\n }\n\n scaleIndex--\n }\n\n // Join parts with Norwegian rules\n return joinNorwegianParts(parts)\n}\n\n/**\n * Joins parts with Norwegian spacing and comma rules.\n *\n * @param {Array} parts - Parts with type metadata\n * @returns {string} Joined string\n */\nfunction joinNorwegianParts (parts) {\n if (parts.length === 0) return ZERO\n if (parts.length === 1) return parts[0].word\n\n const result = []\n\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]\n const nextPart = parts[i + 1]\n\n result.push(part.word)\n\n if (nextPart) {\n if (part.type === 'thousand') {\n // After thousands: comma if next has hundred, else \" og \"\n if (nextPart.hasHundred) {\n result.push(', ')\n } else {\n result.push(' og ')\n }\n } else if (part.type === 'million') {\n // After millions: \" og \" for units without hundred, space otherwise\n if (nextPart.type === 'units' && !nextPart.hasHundred) {\n result.push(' og ')\n } else {\n result.push(' ')\n }\n } else {\n result.push(' ')\n }\n }\n }\n\n return result.join('')\n}\n\n/**\n * Converts decimal digits to Norwegian words.\n *\n * @param {string} decimalPart - Decimal digits (without the point)\n * @returns {string} Norwegian words for decimal part\n */\nfunction decimalPartToWords (decimalPart) {\n let result = ''\n\n // Handle leading zeros\n let i = 0\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n // Convert remainder as a single number\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder))\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Norwegian Bokmål words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in Norwegian 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) // 'tjue-en'\n * toWords(101) // 'en hundre og en'\n * toWords(1000000) // 'en million'\n */\nfunction toWords (value) {\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)\n }\n\n return result\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport { toWords }\n"],"names":["parseNumericString","str","isNegative","slice","dotIndex","indexOf","integerPart","BigInt","integerStr","decimalPart","expandScientificNotation","mantissa","expStr","toLowerCase","split","exp","parseInt","digits","newDotPosition","length","repeat","ONES","TEENS","TENS","THOUSAND","ZERO","SCALES","buildSegment","n","word","hasHundred","ones","tens","Math","floor","hundreds","parts","push","tensOnes","SEGMENTS","Array","SEGMENTS_HAS_HUNDRED","i","result","integerToWords","Number","thousands","remainder","numStr","toString","len","segments","remainderLen","pos","scaleIndex","segment","segmentWord","type","part","nextPart","join","joinNorwegianParts","buildLargeNumberWords","value","isFinite","Error","isSafeInteger","includes","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","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,CCzFA,MAAMG,EAAO,CAAC,GAAI,KAAM,KAAM,MAAO,OAAQ,MAAO,OAAQ,MAAO,OAAQ,MAErEC,EAAQ,CAAC,KAAM,SAAU,OAAQ,UAAW,UAAW,SAAU,UAAW,SAAU,QAAS,UAC/FC,EAAO,CAAC,GAAI,GAAI,OAAQ,SAAU,QAAS,QAAS,SAAU,QAAS,OAAQ,SAG/EC,EAAW,QAEXC,EAAO,OAKPC,EAAS,CAAC,UAAW,WAAY,UAAW,WAAY,cAAe,cAAe,aAAc,aAU1G,SAASC,EAAcC,GACrB,GAAU,IAANA,EAAS,MAAO,CAAEC,KAAM,GAAIC,YAAY,GAE5C,MAAMC,EAAOH,EAAI,GACXI,EAAOC,KAAKC,MAAMN,EAAI,IAAM,GAC5BO,EAAWF,KAAKC,MAAMN,EAAI,KAE1BQ,EAAQ,GACd,IAAIN,GAAa,EAGbK,EAAW,IACbL,GAAa,EACbM,EAAMC,KAAKhB,EAAKc,GAALd,YAIb,MAAMiB,EAAWV,EAAI,IAmBrB,OAjBiB,IAAbU,GAIFF,EAAMC,KAFGC,EAAW,GAETjB,EAAKU,GACPO,EAAW,GAEThB,EAAMS,GACC,IAATA,EAEER,EAAKS,GAGLT,EAAKS,GAAQ,IAAMX,EAAKU,IAIhB,IAAjBK,EAAMjB,OACD,CAAEU,KAAMO,EAAM,GAAK,OAASA,EAAM,GAAIN,YAAY,GAEpD,CAAED,KAAMO,EAAM,IAAM,GAAIN,aACjC,CAGA,MAAMS,EAAW,IAAIC,MAAM,KACrBC,EAAuB,IAAID,MAAM,KAEvC,IAAK,IAAIE,EAAI,EAAGA,EAAI,IAAMA,IAAK,CAC7B,MAAMC,EAAShB,EAAae,GAC5BH,EAASG,GAAKC,EAAOd,KACrBY,EAAqBC,GAAKC,EAAOb,UACnC,CAYA,SAASc,EAAgBhB,GACvB,GAAU,KAANA,EAAU,OAAOH,EAGrB,GAAIG,EAAI,MACN,OAAOW,EAASM,OAAOjB,IAIzB,GAAIA,EAAI,SAAY,CAClB,MAAMkB,EAAYD,OAAOjB,EAAI,OACvBmB,EAAYF,OAAOjB,EAAI,OAE7B,IAAIe,EAASJ,EAASO,GAAa,IAAMtB,EAWzC,OATIuB,EAAY,IAGZJ,GADEF,EAAqBM,GACb,KAAOR,EAASQ,GAEhB,OAASR,EAASQ,IAIzBJ,CACT,CAGA,OASF,SAAgCf,GAC9B,MAAMoB,EAASpB,EAAEqB,WACXC,EAAMF,EAAO7B,OAGbgC,EAAW,GAGXC,EAAeF,EAFD,EAGpB,IAAIG,EAAM,EAKV,IAJID,EAAe,IACjBD,EAASd,KAAKQ,OAAOG,EAAO7C,MAAM,EAAGiD,KACrCC,EAAMD,GAEDC,EAAMH,GACXC,EAASd,KAAKQ,OAAOG,EAAO7C,MAAMkD,EAAKA,EATrB,KAUlBA,GAVkB,EAcpB,MAAMjB,EAAQ,GACd,IAAIkB,EAAaH,EAAShC,OAAS,EAEnC,IAAK,IAAIuB,EAAI,EAAGA,EAAIS,EAAShC,OAAQuB,IAAK,CACxC,MAAMa,EAAUJ,EAAST,GAEzB,GAAgB,IAAZa,EAAe,CACjB,MAAMC,EAAcjB,EAASgB,GAK3BnB,EAAMC,KAFW,IAAfiB,EAES,CAAEzB,KAAM2B,EAAa1B,WAJfW,EAAqBc,GAIME,KAAM,SAC1B,IAAfH,EAEE,CAAEzB,KAAM2B,EAAc,IAAMhC,EAAUM,YAAY,EAAO2B,KAAM,YAI/D,CAAE5B,KAAM2B,EAAc,IADf9B,EAAO4B,EAAa,GACYxB,YAAY,EAAO2B,KAAM,WAE/E,CAEAH,GACF,CAGA,OASF,SAA6BlB,GAC3B,GAAqB,IAAjBA,EAAMjB,OAAc,OAAOM,EAC/B,GAAqB,IAAjBW,EAAMjB,OAAc,OAAOiB,EAAM,GAAGP,KAExC,MAAMc,EAAS,GAEf,IAAK,IAAID,EAAI,EAAGA,EAAIN,EAAMjB,OAAQuB,IAAK,CACrC,MAAMgB,EAAOtB,EAAMM,GACbiB,EAAWvB,EAAMM,EAAI,GAE3BC,EAAON,KAAKqB,EAAK7B,MAEb8B,GAIEhB,EAAON,KAHO,aAAdqB,EAAKD,KAEHE,EAAS7B,WACC,KAEA,OAES,YAAd4B,EAAKD,KAEQ,UAAlBE,EAASF,MAAqBE,EAAS7B,WAG7B,IAFA,OAKF,IAGlB,CAEA,OAAOa,EAAOiB,KAAK,GACrB,CA3CSC,CAAmBzB,EAC5B,CAzDS0B,CAAsBlC,EAC/B,MA4IA,SAAkBmC,GAChB,MAAM7D,WAAEA,EAAUI,YAAEA,EAAWG,YAAEA,GDpQ5B,SAA4BsD,GACjC,MAAMN,SAAcM,EAGpB,GAAa,WAATN,EACF,OAAOM,EAAQ,GACX,CAAE7D,YAAY,EAAMI,aAAcyD,GAClC,CAAE7D,YAAY,EAAOI,YAAayD,GAIxC,GAAa,WAATN,EAAmB,CACrB,IAAKZ,OAAOmB,SAASD,GACnB,MAAM,IAAIE,MAAM,8DAElB,OAAIpB,OAAOqB,cAAcH,GAChBA,EAAQ,EACX,CAAE7D,YAAY,EAAMI,YAAaC,QAAQwD,IACzC,CAAE7D,YAAY,EAAOI,YAAaC,OAAOwD,IAExC/D,EAgBX,SAAyB+D,GACvB,MAAM9D,EAAM8D,EAAMd,WAClB,OAAQhD,EAAIkE,SAAS,MAAQlE,EAAIkE,SAAS,KACtCzD,EAAyBT,GACzBA,CACN,CArB8BmE,CAAeL,GAC3C,CAGA,GAAa,WAATN,EACF,OAAOzD,EAqBX,SAA0B+D,GACxB,MAAMM,EAAUN,EAAMO,OACtB,GAAuB,IAAnBD,EAAQlD,QAAgB0B,OAAO0B,MAAM1B,OAAOwB,IAC9C,MAAM,IAAIJ,MAAM,2BAA2BF,MAE7C,OAAQM,EAAQF,SAAS,MAAQE,EAAQF,SAAS,KAC9CzD,EAAyB2D,GACzBA,CACN,CA7B8BG,CAAgBT,IAG5C,MAAM,IAAIU,UACR,oEAAoEhB,IAExE,CCqOmDiB,CAAkBX,GAEnE,IAAIpB,EAAS,GAYb,OAVIzC,IACFyC,EAASgC,UAGXhC,GAAUC,EAAetC,GAErBG,IACFkC,GAAU,UA9Cd,SAA6BlC,GAC3B,IAAIkC,EAAS,GAGTD,EAAI,EACR,KAAOA,EAAIjC,EAAYU,QAA6B,MAAnBV,EAAYiC,IACvCC,IAAQA,GAAU,KACtBA,GAAUlB,EACViB,IAIF,MAAMK,EAAYtC,EAAYN,MAAMuC,GAMpC,OALIK,IACEJ,IAAQA,GAAU,KACtBA,GAAUC,EAAerC,OAAOwC,KAG3BJ,CACT,CA2BwCiC,CAAmBnE,IAGlDkC,CACT"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! n2words/nl v3.0.0 | MIT License | github.com/forzagreen/n2words */
|
|
2
|
+
var e,n;e=this,n=function(e){"use strict";function n(e){const n="-"===e[0];n&&(e=e.slice(1));const t=e.indexOf(".");if(-1===t)return{isNegative:n,integerPart:BigInt(e)};const i=e.slice(0,t)||"0",r=e.slice(t+1);return{isNegative:n,integerPart:BigInt(i),decimalPart:r}}function t(e){const[n,t]=e.toLowerCase().split("e"),i=parseInt(t,10),r=n.indexOf("."),o=-1===r?n:n.slice(0,r)+n.slice(r+1),u=(-1===r?n.length:r)+i;return u>=o.length?o+"0".repeat(u-o.length):u<=0?"0."+"0".repeat(-u)+o:o.slice(0,u)+"."+o.slice(u)}const i=["","een","twee","drie","vier","vijf","zes","zeven","acht","negen"],r=["tien","elf","twaalf","dertien","veertien","vijftien","zestien","zeventien","achttien","negentien"],o=["","","twintig","dertig","veertig","vijftig","zestig","zeventig","tachtig","negentig"],u="honderd",l=["duizend","miljoen","miljard","biljoen","biljard","triljoen","triljard","quadriljoen","quadriljard"];function s(e,n){if(0===e)return"";const t=e%10,l=Math.floor(e/10)%10,s=Math.floor(e/100),c=e%100;let f="";if(s>0&&(f=1===s?u:i[s]+u),0===c);else if(c<10)f+=s>0&&n?"en"+i[c]:i[c];else if(c<20)f+=s>0&&n&&c<13?"en"+r[t]:r[t];else if(0===t)f+=o[l];else{const e=i[t],n=e.endsWith("e")?"ën":"en";f+=e+n+o[l]}return f}const c=new Array(1e3);for(let e=0;e<1e3;e++)c[e]=s(e,!1);const f=new Array(1e3);for(let e=0;e<1e3;e++)f[e]=s(e,!0);function a(e,n){if(0n===e)return"nul";const{accentOne:t,includeOptionalAnd:i,noHundredPairing:r}=n,o=i?f:c,s=e=>t?e.replace(/\been\b/g,"één"):e;if(e<1000n)return s(o[Number(e)]);if(!r&&e>=1100n&&e<10000n){const n=Number(e/100n),t=Number(e%100n);if(n%10!=0){let e=o[n]+u;if(t>0){const n=o[t];e+=i&&t<13?" en "+n:" "+n}return s(e)}}if(e<1000000n){const n=Number(e/1000n),t=Number(e%1000n);let r;if(r=1===n?l[0]:o[n]+l[0],t>0){const e=o[t];r+=i&&t<13?" en "+e:" "+e}return s(r)}return s(function(e,n){const{includeOptionalAnd:t}=n,i=t?f:c,r=[];let o=e;for(;o>0n;)r.push(Number(o%1000n)),o/=1000n;let u="",s=!1;for(let e=r.length-1;e>=0;e--){const n=r[e];if(0!==n)if(0===e){const e=i[n];u?u+=s&&t&&n<13?" en "+e:" "+e:u=e,s=!1}else if(1===e)u&&(u+=" "),u+=1===n?l[0]:i[n]+l[0],s=!0;else{const t=l[e-1];u&&(u+=" "),u+=1===n?"een "+t:i[n]+" "+t,s=!0}}return u}(e,n))}e.nl=function(e,i){i=function(e){if(void 0===e)return{};if(function(e){if(null===e||"object"!=typeof e)return!1;const n=Object.getPrototypeOf(e);return null===n||n===Object.prototype}(e))return e;throw new TypeError("Invalid options: expected plain object or undefined, got "+typeof e)}(i);const{isNegative:r,integerPart:o,decimalPart:u}=function(e){const i=typeof e;if("bigint"===i)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===i){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:n(function(e){const n=e.toString();return n.includes("e")||n.includes("E")?t(n):n}(e))}if("string"===i)return n(function(e){const n=e.trim();if(0===n.length||Number.isNaN(Number(n)))throw new Error(`Invalid number format: "${e}"`);return n.includes("e")||n.includes("E")?t(n):n}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${i}`)}(e),l={accentOne:!1!==i.accentOne,includeOptionalAnd:i.includeOptionalAnd||!1,noHundredPairing:i.noHundredPairing||!1};let s="";return r&&(s="min "),s+=a(o,l),u&&(s+=" komma "+function(e,n){let t="",i=0;for(;i<e.length&&"0"===e[i];)t&&(t+=" "),t+="nul",i++;const r=e.slice(i);return r&&(t&&(t+=" "),t+=a(BigInt(r),{...n,noHundredPairing:!0})),t}(u,l)),s}},"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
|
|
3
|
+
//# sourceMappingURL=nl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nl.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/nl.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 * Dutch 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 * Dutch-specific rules (handled in precomputation):\n * - Inverted tens-ones: eenentwintig (one-and-twenty)\n * - \"ën\" connector when ones ends in 'e' (twee, drie)\n * - Compound words without spaces\n * - Hundred pairing for 1100-9999 (elfhonderd style)\n * - \"één\" vs \"een\" (accentOne option)\n * - Optional \"en\" separator (includeOptionalAnd option)\n * - Long scale with -ard forms\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 = ['', 'een', 'twee', 'drie', 'vier', 'vijf', 'zes', 'zeven', 'acht', 'negen']\nconst TEENS = ['tien', 'elf', 'twaalf', 'dertien', 'veertien', 'vijftien', 'zestien', 'zeventien', 'achttien', 'negentien']\nconst TENS = ['', '', 'twintig', 'dertig', 'veertig', 'vijftig', 'zestig', 'zeventig', 'tachtig', 'negentig']\n\nconst HUNDRED = 'honderd'\n\n// Scale words (long scale with -ard forms)\nconst SCALES = ['duizend', 'miljoen', 'miljard', 'biljoen', 'biljard', 'triljoen', 'triljard', 'quadriljoen', 'quadriljard']\n\nconst ZERO = 'nul'\nconst NEGATIVE = 'min'\nconst DECIMAL_SEP = 'komma'\n\n// ============================================================================\n// Precomputed Lookup Tables (built once at module load)\n// ============================================================================\n\n/**\n * Builds segment word for 0-999.\n * @param {number} n - Segment value\n * @param {boolean} withAnd - Include \"en\" for values < 13 after hundreds\n * @returns {string} Dutch word (compound, no spaces)\n */\nfunction buildSegment (n, withAnd) {\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 const tensOnes = n % 100\n\n let result = ''\n\n // Hundreds\n if (hundreds > 0) {\n if (hundreds === 1) {\n result = HUNDRED\n } else {\n result = ONES[hundreds] + HUNDRED\n }\n }\n\n // Tens and ones\n if (tensOnes === 0) {\n // Just hundreds\n } else if (tensOnes < 10) {\n // Single digit - add \"en\" if withAnd and after hundreds\n if (hundreds > 0 && withAnd) {\n result += 'en' + ONES[tensOnes]\n } else {\n result += ONES[tensOnes]\n }\n } else if (tensOnes < 20) {\n // Teens - add \"en\" if withAnd and after hundreds and < 13\n if (hundreds > 0 && withAnd && tensOnes < 13) {\n result += 'en' + TEENS[ones]\n } else {\n result += TEENS[ones]\n }\n } else {\n // 20-99: Dutch inverts with connector\n if (ones === 0) {\n result += TENS[tens]\n } else {\n // \"ën\" if ones ends in 'e' (twee, drie)\n const onesWord = ONES[ones]\n const connector = onesWord.endsWith('e') ? 'ën' : 'en'\n result += onesWord + connector + TENS[tens]\n }\n }\n\n return result\n}\n\n// Precompute all 1000 segment words (0-999) - standard form\nconst SEGMENTS = new Array(1000)\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS[i] = buildSegment(i, false)\n}\n\n// Precompute all 1000 segment words (0-999) - with optional \"en\"\nconst SEGMENTS_WITH_AND = new Array(1000)\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS_WITH_AND[i] = buildSegment(i, true)\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\n/**\n * Converts a non-negative integer to Dutch words.\n *\n * @param {bigint} n - Non-negative integer to convert\n * @param {Object} options - Conversion options\n * @returns {string} Dutch words\n */\nfunction integerToWords (n, options) {\n if (n === 0n) return ZERO\n\n const { accentOne, includeOptionalAnd, noHundredPairing } = options\n const segments = includeOptionalAnd ? SEGMENTS_WITH_AND : SEGMENTS\n\n // Apply één/een replacement\n const applyAccent = (word) => {\n if (accentOne) {\n return word.replace(/\\been\\b/g, 'één')\n }\n return word\n }\n\n // Fast path: numbers < 1000 (direct lookup)\n if (n < 1000n) {\n return applyAccent(segments[Number(n)])\n }\n\n // Hundred pairing for 1100-9999\n if (!noHundredPairing && n >= 1100n && n < 10000n) {\n const high = Number(n / 100n)\n const low = Number(n % 100n)\n\n // Only use pairing when high is not a multiple of 10\n if (high % 10 !== 0) {\n let result = segments[high] + HUNDRED\n if (low > 0) {\n const lowWord = segments[low]\n if (includeOptionalAnd && low < 13) {\n result += ' en ' + lowWord\n } else {\n result += ' ' + lowWord\n }\n }\n return applyAccent(result)\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 // \"duizend\" not \"eenduizend\"\n result = SCALES[0]\n } else {\n // Compound: \"vijfduizend\"\n result = segments[thousands] + SCALES[0]\n }\n\n if (remainder > 0) {\n const remainderWord = segments[remainder]\n if (includeOptionalAnd && remainder < 13) {\n result += ' en ' + remainderWord\n } else {\n result += ' ' + remainderWord\n }\n }\n\n return applyAccent(result)\n }\n\n // For numbers >= 1,000,000, use scale decomposition\n return applyAccent(buildLargeNumberWords(n, options))\n}\n\n/**\n * Builds words for numbers >= 1,000,000.\n * Uses BigInt division for faster segment extraction (4x faster than string slicing).\n *\n * @param {bigint} n - Number >= 1,000,000\n * @param {Object} options - Conversion options\n * @returns {string} Dutch words\n */\nfunction buildLargeNumberWords (n, options) {\n const { includeOptionalAnd } = options\n const segmentLookup = includeOptionalAnd ? SEGMENTS_WITH_AND : SEGMENTS\n\n // Extract segments using BigInt division (faster than string slicing)\n // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)\n const segmentValues = []\n let temp = n\n while (temp > 0n) {\n segmentValues.push(Number(temp % 1000n))\n temp = temp / 1000n\n }\n\n // Build result string directly (avoids object allocation and join)\n let result = ''\n let prevWasScale = false\n\n for (let i = segmentValues.length - 1; i >= 0; i--) {\n const segment = segmentValues[i]\n if (segment === 0) continue\n\n if (i === 0) {\n // Units segment\n const word = segmentLookup[segment]\n if (result) {\n if (prevWasScale && includeOptionalAnd && segment < 13) {\n result += ' en ' + word\n } else {\n result += ' ' + word\n }\n } else {\n result = word\n }\n prevWasScale = false\n } else if (i === 1) {\n // Thousands - compound\n if (result) result += ' '\n if (segment === 1) {\n result += SCALES[0]\n } else {\n result += segmentLookup[segment] + SCALES[0]\n }\n prevWasScale = true\n } else {\n // Million and above - space around scale\n const scaleWord = SCALES[i - 1]\n if (result) result += ' '\n if (segment === 1) {\n result += 'een ' + scaleWord\n } else {\n result += segmentLookup[segment] + ' ' + scaleWord\n }\n prevWasScale = true\n }\n }\n\n return result\n}\n\n/**\n * Converts decimal digits to Dutch words.\n *\n * @param {string} decimalPart - Decimal digits (without the point)\n * @param {Object} options - Conversion options\n * @returns {string} Dutch words for decimal part\n */\nfunction decimalPartToWords (decimalPart, options) {\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 const word = integerToWords(BigInt(remainder), { ...options, noHundredPairing: true })\n result += word\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Dutch 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 {boolean} [options.accentOne=true] - Use \"één\" instead of \"een\"\n * @param {boolean} [options.includeOptionalAnd=false] - Include \"en\" before small numbers\n * @param {boolean} [options.noHundredPairing=false] - Disable hundred pairing (1104→duizend honderdvier)\n * @returns {string} The number in Dutch 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) // 'eenentwintig'\n * toWords(1) // 'één'\n * toWords(1, {accentOne: false}) // 'een'\n * toWords(1104) // 'elfhonderd vier'\n */\nfunction toWords (value, options) {\n options = validateOptions(options)\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n const opts = {\n accentOne: options.accentOne !== false, // default true\n includeOptionalAnd: options.includeOptionalAnd || false,\n noHundredPairing: options.noHundredPairing || false\n }\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart, opts)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, opts)\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","TEENS","TENS","HUNDRED","SCALES","buildSegment","n","withAnd","ones","tens","Math","floor","hundreds","tensOnes","result","onesWord","connector","endsWith","SEGMENTS","Array","i","SEGMENTS_WITH_AND","integerToWords","options","accentOne","includeOptionalAnd","noHundredPairing","segments","applyAccent","word","replace","Number","high","low","lowWord","thousands","remainder","remainderWord","segmentLookup","segmentValues","temp","push","prevWasScale","segment","scaleWord","buildLargeNumberWords","value","undefined","proto","Object","getPrototypeOf","prototype","isPlainObject","TypeError","validateOptions","type","isFinite","Error","isSafeInteger","toString","includes","numberToString","trimmed","trim","isNaN","normalizeString","parseNumericValue","opts","NEGATIVE","decimalPartToWords"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! n2words/pa 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("."),o=-1===r?t:t.slice(0,r)+t.slice(r+1),s=(-1===r?t.length:r)+i;return s>=o.length?o+"0".repeat(s-o.length):s<=0?"0."+"0".repeat(-s)+o:o.slice(0,s)+"."+o.slice(s)}const i="ਸਿਫ਼ਰ",r=["ਸਿਫ਼ਰ","ਇੱਕ","ਦੋ","ਤਿੰਨ","ਚਾਰ","ਪੰਜ","ਛੇ","ਸੱਤ","ਅੱਠ","ਨੌਂ","ਦੱਸ","ਗਿਆਰਾਂ","ਬਾਰਾਂ","ਤੇਰਾਂ","ਚੌਦਾਂ","ਪੰਦਰਾਂ","ਸੋਲਾਂ","ਸਤਾਰਾਂ","ਅਠਾਰਾਂ","ਉੱਨੀ","ਵੀਹ","ਇੱਕੀ","ਬਾਈ","ਤੇਈ","ਚੌਬੀ","ਪੱਚੀ","ਛੱਬੀ","ਸਤਾਈ","ਅਠਾਈ","ਉਨੱਤੀ","ਤੀਹ","ਇਕੱਤੀ","ਬੱਤੀ","ਤੇਤੀ","ਚੌਂਤੀ","ਪੈਂਤੀ","ਛੱਤੀ","ਸੈਂਤੀ","ਅਠੱਤੀ","ਉਨਤਾਲੀ","ਚਾਲੀ","ਇਕਤਾਲੀ","ਬਿਆਲੀ","ਤਿਰਤਾਲੀ","ਚੁਵਾਲੀ","ਪੰਤਾਲੀ","ਛਿਆਲੀ","ਸੈਂਤਾਲੀ","ਅਠਤਾਲੀ","ਉਨੰਜਾ","ਪੰਜਾਹ","ਇਕਵੰਜਾ","ਬਵੰਜਾ","ਤਰਵੰਜਾ","ਚੁਰਵੰਜਾ","ਪੰਜਵੰਜਾ","ਛਪੰਜਾ","ਸੱਤਵੰਜਾ","ਅਠਵੰਜਾ","ਉਨਾਹਠ","ਸੱਠ","ਇਕਾਹਠ","ਬਾਹਠ","ਤਰਸਠ","ਚੌਂਸਠ","ਪੈਂਸਠ","ਛਿਆਸਠ","ਸੜਸਠ","ਅੜਸਠ","ਉਣਹੱਤਰ","ਸਤੱਰ","ਇਕਹੱਤਰ","ਬਹੱਤਰ","ਤਹੱਤਰ","ਚੌਹੱਤਰ","ਪੰਝਹੱਤਰ","ਛਿਹੱਤਰ","ਸਤੱਤਰ","ਅਠੱਤਰ","ਉਨਾਸੀ","ਅੱਸੀ","ਇਕਿਆਸੀ","ਬਿਆਸੀ","ਤਰਿਆਸੀ","ਚੌਰਿਆਸੀ","ਪਚਾਸੀ","ਛਿਆਸੀ","ਸੱਤਾਸੀ","ਅਠਾਸੀ","ਨਵਾਸੀ","ਨੱਬੇ","ਇਕਾਨਵੇਂ","ਬਾਨਵੇਂ","ਤਰਾਨਵੇਂ","ਚੁਰਾਨਵੇਂ","ਪੰਚਾਨਵੇਂ","ਛਿਆਨਵੇਂ","ਸਤਾਨਵੇਂ","ਅਠਾਨਵੇਂ","ਨਿਨਾਨਵੇਂ"],o=["","ਹਜ਼ਾਰ","ਲੱਖ","ਕਰੋੜ","ਅਰਬ","ਖਰਬ","ਨੀਲ","ਪਦਮ","ਸ਼ੰਖ"];function s(e){if(0===e)return"";if(e<100)return r[e];const t=Math.trunc(e/100),n=e%100;return 0===n?r[t]+" ਸੌ":r[t]+" ਸੌ "+r[n]}function u(e){return 0n===e?i:e<1000n?s(Number(e)):c(e,0)}function c(e,t){if(0n===e)return"";const n=0===t?1000n:100n,i=Number(e%n),u=e/n;let f="";return u>0n&&(f=c(u,t+1)),i>0&&(f&&(f+=" "),f+=0===t?s(i):r[i]+" "+o[t]),f}e.pa=function(e){const{isNegative:r,integerPart:o,decimalPart:s}=function(e){const i=typeof e;if("bigint"===i)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===i){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:t(function(e){const t=e.toString();return t.includes("e")||t.includes("E")?n(t):t}(e))}if("string"===i)return t(function(e){const t=e.trim();if(0===t.length||Number.isNaN(Number(t)))throw new Error(`Invalid number format: "${e}"`);return t.includes("e")||t.includes("E")?n(t):t}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${i}`)}(e);let c="";return r&&(c="ਮਾਇਨਸ "),c+=u(o),s&&(c+=" ਦਸ਼ਮਲਵ "+function(e){let t="",n=0;for(;n<e.length&&"0"===e[n];)t&&(t+=" "),t+=i,n++;const r=e.slice(n);return r&&(t&&(t+=" "),t+=u(BigInt(r))),t}(s)),c}},"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
|
|
3
|
+
//# sourceMappingURL=pa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pa.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/pa.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 * Punjabi language converter - Functional Implementation\n *\n * Self-contained converter for South Asian numbering.\n *\n * Key features:\n * - Indian numbering system (ਹਜ਼ਾਰ, ਲੱਖ, ਕਰੋੜ)\n * - Gurmukhi script\n * - 3-2-2 grouping pattern (last 3 digits, then groups of 2)\n * - Complete word forms for 0-99\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary\n// ============================================================================\n\nconst ZERO = 'ਸਿਫ਼ਰ'\nconst NEGATIVE = 'ਮਾਇਨਸ'\nconst DECIMAL_SEP = 'ਦਸ਼ਮਲਵ'\nconst HUNDRED = 'ਸੌ'\n\nconst BELOW_HUNDRED = [\n 'ਸਿਫ਼ਰ', 'ਇੱਕ', 'ਦੋ', 'ਤਿੰਨ', 'ਚਾਰ', 'ਪੰਜ', 'ਛੇ', 'ਸੱਤ', 'ਅੱਠ', 'ਨੌਂ',\n 'ਦੱਸ', 'ਗਿਆਰਾਂ', 'ਬਾਰਾਂ', 'ਤੇਰਾਂ', 'ਚੌਦਾਂ', 'ਪੰਦਰਾਂ', 'ਸੋਲਾਂ', 'ਸਤਾਰਾਂ', 'ਅਠਾਰਾਂ', 'ਉੱਨੀ',\n 'ਵੀਹ', 'ਇੱਕੀ', 'ਬਾਈ', 'ਤੇਈ', 'ਚੌਬੀ', 'ਪੱਚੀ', 'ਛੱਬੀ', 'ਸਤਾਈ', 'ਅਠਾਈ', 'ਉਨੱਤੀ',\n 'ਤੀਹ', 'ਇਕੱਤੀ', 'ਬੱਤੀ', 'ਤੇਤੀ', 'ਚੌਂਤੀ', 'ਪੈਂਤੀ', 'ਛੱਤੀ', 'ਸੈਂਤੀ', 'ਅਠੱਤੀ', 'ਉਨਤਾਲੀ',\n 'ਚਾਲੀ', 'ਇਕਤਾਲੀ', 'ਬਿਆਲੀ', 'ਤਿਰਤਾਲੀ', 'ਚੁਵਾਲੀ', 'ਪੰਤਾਲੀ', 'ਛਿਆਲੀ', 'ਸੈਂਤਾਲੀ', 'ਅਠਤਾਲੀ', 'ਉਨੰਜਾ',\n 'ਪੰਜਾਹ', 'ਇਕਵੰਜਾ', 'ਬਵੰਜਾ', 'ਤਰਵੰਜਾ', 'ਚੁਰਵੰਜਾ', 'ਪੰਜਵੰਜਾ', 'ਛਪੰਜਾ', 'ਸੱਤਵੰਜਾ', 'ਅਠਵੰਜਾ', 'ਉਨਾਹਠ',\n 'ਸੱਠ', 'ਇਕਾਹਠ', 'ਬਾਹਠ', 'ਤਰਸਠ', 'ਚੌਂਸਠ', 'ਪੈਂਸਠ', 'ਛਿਆਸਠ', 'ਸੜਸਠ', 'ਅੜਸਠ', 'ਉਣਹੱਤਰ',\n 'ਸਤੱਰ', 'ਇਕਹੱਤਰ', 'ਬਹੱਤਰ', 'ਤਹੱਤਰ', 'ਚੌਹੱਤਰ', 'ਪੰਝਹੱਤਰ', 'ਛਿਹੱਤਰ', 'ਸਤੱਤਰ', 'ਅਠੱਤਰ', 'ਉਨਾਸੀ',\n 'ਅੱਸੀ', 'ਇਕਿਆਸੀ', 'ਬਿਆਸੀ', 'ਤਰਿਆਸੀ', 'ਚੌਰਿਆਸੀ', 'ਪਚਾਸੀ', 'ਛਿਆਸੀ', 'ਸੱਤਾਸੀ', 'ਅਠਾਸੀ', 'ਨਵਾਸੀ',\n 'ਨੱਬੇ', 'ਇਕਾਨਵੇਂ', 'ਬਾਨਵੇਂ', 'ਤਰਾਨਵੇਂ', 'ਚੁਰਾਨਵੇਂ', 'ਪੰਚਾਨਵੇਂ', 'ਛਿਆਨਵੇਂ', 'ਸਤਾਨਵੇਂ', 'ਅਠਾਨਵੇਂ', 'ਨਿਨਾਨਵੇਂ'\n]\n\n// Scale words: index 0 = units (empty), 1 = thousand, 2 = lakh, 3 = crore, etc.\nconst SCALE_WORDS = ['', 'ਹਜ਼ਾਰ', 'ਲੱਖ', 'ਕਰੋੜ', 'ਅਰਬ', 'ਖਰਬ', 'ਨੀਲ', 'ਪਦਮ', 'ਸ਼ੰਖ']\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\n/**\n * Converts 0-999 to Punjabi words.\n */\nfunction segmentToWords (n) {\n if (n === 0) return ''\n if (n < 100) return BELOW_HUNDRED[n]\n\n const hundreds = Math.trunc(n / 100)\n const remainder = n % 100\n\n if (remainder === 0) {\n return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED\n }\n return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED + ' ' + BELOW_HUNDRED[remainder]\n}\n\n/**\n * Converts a non-negative integer to Punjabi words.\n * Uses recursive approach for Indian 3-2-2 grouping pattern.\n *\n * @param {bigint} n - Non-negative integer to convert\n * @returns {string} Punjabi 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 segmentToWords(Number(n))\n }\n\n return buildLargeNumberWords(n, 0)\n}\n\n/**\n * Recursively builds words for numbers >= 1000.\n * Indian grouping: first 3 digits, then 2-digit groups.\n *\n * @param {bigint} n - Number to convert\n * @param {number} scale - Current scale index (0=units, 1=thousands, etc.)\n * @returns {string} Punjabi words\n */\nfunction buildLargeNumberWords (n, scale) {\n if (n === 0n) return ''\n\n // Determine divisor: 1000 for first split, 100 for rest\n const divisor = scale === 0 ? 1000n : 100n\n const segment = Number(n % divisor)\n const rest = n / divisor\n\n // Build higher segments first (recursive)\n let result = ''\n if (rest > 0n) {\n result = buildLargeNumberWords(rest, scale + 1)\n }\n\n // Add current segment\n if (segment > 0) {\n if (result) result += ' '\n\n if (scale === 0) {\n // Units segment (0-999)\n result += segmentToWords(segment)\n } else {\n // Scale segments (0-99)\n result += BELOW_HUNDRED[segment] + ' ' + SCALE_WORDS[scale]\n }\n }\n\n return result\n}\n\nfunction decimalPartToWords (decimalPart) {\n let result = ''\n let i = 0\n\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder))\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Punjabi words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in Punjabi words\n */\nfunction toWords (value) {\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)\n }\n\n return result\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport { toWords }\n"],"names":["parseNumericString","str","isNegative","slice","dotIndex","indexOf","integerPart","BigInt","integerStr","decimalPart","expandScientificNotation","mantissa","expStr","toLowerCase","split","exp","parseInt","digits","newDotPosition","length","repeat","ZERO","BELOW_HUNDRED","SCALE_WORDS","segmentToWords","n","hundreds","Math","trunc","remainder","integerToWords","Number","buildLargeNumberWords","scale","divisor","segment","rest","result","value","type","isFinite","Error","isSafeInteger","toString","includes","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","NEGATIVE","i","decimalPartToWords"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! n2words/pl v3.0.0 | MIT License | github.com/forzagreen/n2words */
|
|
2
|
+
var e,i;e=this,i=function(e){"use strict";function i(e){const i="-"===e[0];i&&(e=e.slice(1));const n=e.indexOf(".");if(-1===n)return{isNegative:i,integerPart:BigInt(e)};const t=e.slice(0,n)||"0",r=e.slice(n+1);return{isNegative:i,integerPart:BigInt(t),decimalPart:r}}function n(e){const[i,n]=e.toLowerCase().split("e"),t=parseInt(n,10),r=i.indexOf("."),o=-1===r?i:i.slice(0,r)+i.slice(r+1),s=(-1===r?i.length:r)+t;return s>=o.length?o+"0".repeat(s-o.length):s<=0?"0."+"0".repeat(-s)+o:o.slice(0,s)+"."+o.slice(s)}const t=["","jeden","dwa","trzy","cztery","pięć","sześć","siedem","osiem","dziewięć"],r=["","jedna","dwie","trzy","cztery","pięć","sześć","siedem","osiem","dziewięć"],o=["dziesięć","jedenaście","dwanaście","trzynaście","czternaście","piętnaście","szesnaście","siedemnaście","osiemnaście","dziewiętnaście"],s=["","","dwadzieścia","trzydzieści","czterdzieści","pięćdziesiąt","sześćdziesiąt","siedemdziesiąt","osiemdziesiąt","dziewięćdziesiąt"],c=["","sto","dwieście","trzysta","czterysta","pięćset","sześćset","siedemset","osiemset","dziewięćset"],u={1:["tysiąc","tysiące","tysięcy"],2:["milion","miliony","milionów"],3:["miliard","miliardy","miliardów"],4:["bilion","biliony","bilionów"],5:["biliard","biliardy","biliardów"],6:["trylion","tryliony","trylionów"],7:["tryliard","tryliardy","tryliardów"],8:["kwadrylion","kwadryliony","kwadrylionów"],9:["kwaryliard","kwadryliardy","kwadryliardów"],10:["kwintylion","kwintyliony","kwintylionów"]},a="zero";function l(e){if(0===e)return"";const i=e%10,n=Math.floor(e/10)%10,r=Math.floor(e/100),u=[];return r>0&&u.push(c[r]),1===n?u.push(o[i]):(n>=2&&u.push(s[n]),i>0&&u.push(t[i])),u.join(" ")}function d(e){if(0===e)return"";const i=e%10,n=Math.floor(e/10)%10,t=Math.floor(e/100),u=[];return t>0&&u.push(c[t]),1===n?u.push(o[i]):(n>=2&&u.push(s[n]),i>0&&u.push(r[i])),u.join(" ")}const f=new Array(1e3),y=new Array(1e3);for(let e=0;e<1e3;e++)f[e]=l(e),y[e]=d(e);function p(e,i){if(1n===e)return i[0];const n=e%10n,t=e%100n;return n>=2n&&n<=4n&&(t<10n||t>20n)?i[1]:i[2]}function w(e,i={}){if(0n===e)return a;if(e<1000n)return("feminine"===i.gender?y:f)[Number(e)];if(e<1000000n){const i=Number(e/1000n),n=Number(e%1000n),t=p(BigInt(i),u[1]);let r;return r=1===i?t:f[i]+" "+t,n>0&&(r+=" "+f[n]),r}return function(e){const i=[];let n=e;for(;n>0n;)i.push(n%1000n),n/=1000n;let t="";for(let e=i.length-1;e>=0;e--){const n=i[e];if(0n===n)continue;const r=f[Number(n)];if(t&&(t+=" "),0===e)t+=r;else{const i=u[e];if(i){const e=p(n,i);t+=1n===n?e:r+" "+e}}}return t}(e)}e.pl=function(e,t){t=function(e){if(void 0===e)return{};if(function(e){if(null===e||"object"!=typeof e)return!1;const i=Object.getPrototypeOf(e);return null===i||i===Object.prototype}(e))return e;throw new TypeError("Invalid options: expected plain object or undefined, got "+typeof e)}(t);const{isNegative:r,integerPart:o,decimalPart:s}=function(e){const t=typeof e;if("bigint"===t)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===t){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:i(function(e){const i=e.toString();return i.includes("e")||i.includes("E")?n(i):i}(e))}if("string"===t)return i(function(e){const i=e.trim();if(0===i.length||Number.isNaN(Number(i)))throw new Error(`Invalid number format: "${e}"`);return i.includes("e")||i.includes("E")?n(i):i}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${t}`)}(e);let c="";return r&&(c="minus "),c+=w(o,t),s&&(c+=" przecinek "+function(e,i){let n="",t=0;for(;t<e.length&&"0"===e[t];)n&&(n+=" "),n+=a,t++;const r=e.slice(t);return r&&(n&&(n+=" "),n+=w(BigInt(r),i)),n}(s,t)),c}},"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
|
|
3
|
+
//# sourceMappingURL=pl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pl.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/pl.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 * Polish 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 * Polish-specific rules (handled in precomputation):\n * - Three-form pluralization: 1 = singular, 2-4 = few, 5+ = many\n * - Gender agreement (masculine/feminine for numbers < 1000)\n * - Omit \"jeden\" before scale words (tysiąc, milion, etc.)\n * - Irregular hundreds: dwieście, trzysta, czterysta, pięćset...\n * - Long scale with -ard forms: miliard, biliard, tryliard\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 = ['', 'jeden', 'dwa', 'trzy', 'cztery', 'pięć', 'sześć', 'siedem', 'osiem', 'dziewięć']\nconst ONES_FEM = ['', 'jedna', 'dwie', 'trzy', 'cztery', 'pięć', 'sześć', 'siedem', 'osiem', 'dziewięć']\n\nconst TEENS = ['dziesięć', 'jedenaście', 'dwanaście', 'trzynaście', 'czternaście', 'piętnaście', 'szesnaście', 'siedemnaście', 'osiemnaście', 'dziewiętnaście']\n\nconst TENS = ['', '', 'dwadzieścia', 'trzydzieści', 'czterdzieści', 'pięćdziesiąt', 'sześćdziesiąt', 'siedemdziesiąt', 'osiemdziesiąt', 'dziewięćdziesiąt']\n\n// Irregular hundreds\nconst HUNDREDS = ['', 'sto', 'dwieście', 'trzysta', 'czterysta', 'pięćset', 'sześćset', 'siedemset', 'osiemset', 'dziewięćset']\n\n// Scale words: [singular, few (2-4), many (5+)]\nconst PLURAL_FORMS = {\n 1: ['tysiąc', 'tysiące', 'tysięcy'],\n 2: ['milion', 'miliony', 'milionów'],\n 3: ['miliard', 'miliardy', 'miliardów'],\n 4: ['bilion', 'biliony', 'bilionów'],\n 5: ['biliard', 'biliardy', 'biliardów'],\n 6: ['trylion', 'tryliony', 'trylionów'],\n 7: ['tryliard', 'tryliardy', 'tryliardów'],\n 8: ['kwadrylion', 'kwadryliony', 'kwadrylionów'],\n 9: ['kwaryliard', 'kwadryliardy', 'kwadryliardów'],\n 10: ['kwintylion', 'kwintyliony', 'kwintylionów']\n}\n\nconst ZERO = 'zero'\nconst NEGATIVE = 'minus'\nconst DECIMAL_SEP = 'przecinek'\n\n// ============================================================================\n// Precomputed Lookup Tables (built once at module load)\n// ============================================================================\n\n/**\n * Builds segment word for 0-999 (masculine form).\n * @param {number} n - Segment value\n * @returns {string} Polish word\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\n if (hundreds > 0) {\n parts.push(HUNDREDS[hundreds])\n }\n\n // Tens and ones\n if (tens === 1) {\n // Teens (10-19)\n parts.push(TEENS[ones])\n } else {\n if (tens >= 2) {\n parts.push(TENS[tens])\n }\n if (ones > 0) {\n parts.push(ONES_MASC[ones])\n }\n }\n\n return parts.join(' ')\n}\n\n/**\n * Builds segment word for 0-999 (feminine form - only differs in ones).\n * @param {number} n - Segment value\n * @returns {string} Polish word\n */\nfunction buildSegmentFeminine (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\n if (hundreds > 0) {\n parts.push(HUNDREDS[hundreds])\n }\n\n // Tens and ones - feminine for ones only\n if (tens === 1) {\n parts.push(TEENS[ones])\n } else {\n if (tens >= 2) {\n parts.push(TENS[tens])\n }\n if (ones > 0) {\n parts.push(ONES_FEM[ones])\n }\n }\n\n return parts.join(' ')\n}\n\n// Precompute all 1000 segment words (0-999)\nconst SEGMENTS_MASC = new Array(1000)\nconst SEGMENTS_FEM = new Array(1000)\nfor (let i = 0; i < 1000; i++) {\n SEGMENTS_MASC[i] = buildSegment(i)\n SEGMENTS_FEM[i] = buildSegmentFeminine(i)\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Polish pluralization: 1 = singular, 2-4 = few, else = many.\n * Special case: 11-19 always use many form.\n *\n * @param {bigint} n - Number to pluralize\n * @param {string[]} forms - [singular, few, many]\n * @returns {string} Correct plural form\n */\nfunction pluralize (n, forms) {\n if (n === 1n) {\n return forms[0]\n }\n\n const lastDigit = n % 10n\n const lastTwoDigits = n % 100n\n\n // Teens (11-19) always use many form\n // 2-4 use few form (but not 12-14)\n if (lastDigit >= 2n && lastDigit <= 4n && (lastTwoDigits < 10n || lastTwoDigits > 20n)) {\n return forms[1]\n }\n\n return forms[2]\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\n/**\n * Converts a non-negative integer to Polish words.\n *\n * @param {bigint} n - Non-negative integer to convert\n * @param {Object} options - Conversion options\n * @returns {string} Polish words\n */\nfunction integerToWords (n, options = {}) {\n if (n === 0n) return ZERO\n\n // Fast path: numbers < 1000 (direct lookup)\n if (n < 1000n) {\n const segments = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC\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 const scaleWord = pluralize(BigInt(thousands), PLURAL_FORMS[1])\n\n let result\n if (thousands === 1) {\n // Omit \"jeden\" before tysiąc\n result = scaleWord\n } else {\n result = SEGMENTS_MASC[thousands] + ' ' + scaleWord\n }\n\n if (remainder > 0) {\n result += ' ' + SEGMENTS_MASC[remainder]\n }\n\n return result\n }\n\n // For numbers >= 1,000,000, use scale decomposition\n return buildLargeNumberWords(n, options)\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 {Object} options - Conversion options\n * @returns {string} Polish words\n */\nfunction buildLargeNumberWords (n, options) {\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 segmentWord = SEGMENTS_MASC[Number(segment)]\n\n if (result) result += ' '\n\n if (i === 0) {\n // Units segment\n result += segmentWord\n } else {\n // Scale word needed\n const forms = PLURAL_FORMS[i]\n if (forms) {\n const scaleWord = pluralize(segment, forms)\n\n if (segment === 1n) {\n // Omit \"jeden\" before scale words\n result += scaleWord\n } else {\n result += segmentWord + ' ' + scaleWord\n }\n }\n }\n }\n\n return result\n}\n\n/**\n * Converts decimal digits to Polish words.\n *\n * @param {string} decimalPart - Decimal digits (without the point)\n * @param {Object} options - Conversion options\n * @returns {string} Polish words for decimal part\n */\nfunction decimalPartToWords (decimalPart, options) {\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), options)\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Polish 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] - Conversion options\n * @param {string} [options.gender='masculine'] - Gender for numbers < 1000\n * @returns {string} The number in Polish 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(1) // 'jeden'\n * toWords(1, { gender: 'feminine' }) // 'jedna'\n * toWords(1000) // 'tysiąc'\n * toWords(2000) // 'dwa tysiące'\n */\nfunction toWords (value, options) {\n options = validateOptions(options)\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart, options)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, options)\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","TENS","HUNDREDS","PLURAL_FORMS","ZERO","buildSegment","n","ones","tens","Math","floor","hundreds","parts","push","join","buildSegmentFeminine","SEGMENTS_MASC","Array","SEGMENTS_FEM","i","pluralize","forms","lastDigit","lastTwoDigits","integerToWords","options","gender","Number","thousands","remainder","scaleWord","result","segmentValues","temp","segment","segmentWord","buildLargeNumberWords","value","undefined","proto","Object","getPrototypeOf","prototype","isPlainObject","TypeError","validateOptions","type","isFinite","Error","isSafeInteger","toString","includes","numberToString","trimmed","trim","isNaN","normalizeString","parseNumericValue","NEGATIVE","decimalPartToWords"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! n2words/pt 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("."),o=-1===r?t:t.slice(0,r)+t.slice(r+1),s=(-1===r?t.length:r)+i;return s>=o.length?o+"0".repeat(s-o.length):s<=0?"0."+"0".repeat(-s)+o:o.slice(0,s)+"."+o.slice(s)}const i=["","um","dois","três","quatro","cinco","seis","sete","oito","nove"],r=["dez","onze","doze","treze","catorze","quinze","dezasseis","dezassete","dezoito","dezanove"],o=["","","vinte","trinta","quarenta","cinquenta","sessenta","setenta","oitenta","noventa"],s=["","cento","duzentos","trezentos","quatrocentos","quinhentos","seiscentos","setecentos","oitocentos","novecentos"],u="mil",l="zero";function c(e){if(0===e)return{word:"",isExactHundred:!1};if(100===e)return{word:"cem",isExactHundred:!0};const t=e%10,n=Math.floor(e/10)%10,u=Math.floor(e/100),l=[];return u>0&&l.push(s[u]),1===n?l.push(r[t]):n>=2?l.push(t>0?o[n]+" e "+i[t]:o[n]):t>0&&l.push(i[t]),{word:l.join(" e "),isExactHundred:u>0&&0===n&&0===t}}const a=new Array(1e3),f=new Array(1e3);for(let e=0;e<1e3;e++){const t=c(e);a[e]=t.word,f[e]=e>=100}const d=["",u,"milhão","mil milhões","bilião","mil biliões","trilião","mil triliões","quatrilião"],g=["",u,"milhões","mil milhões","biliões","mil biliões","triliões","mil triliões","quatriliões"];function m(e){if(0n===e)return l;if(e<1000n)return a[Number(e)];if(e<1000000n){const t=Number(e/1000n),n=Number(e%1000n);let i;return i=1===t?u:a[t]+" "+u,n>0&&(i+=f[n]?" "+a[n]:" e "+a[n]),i}return function(e){const t=[];let n=e;for(;n>0n;)t.push(Number(n%1000n)),n/=1000n;let i=0;for(let e=0;e<t.length;e++)if(0!==t[e]){i=e;break}let r="",o=!1;for(let e=t.length-1;e>=0;e--){const n=t[e];if(0===n)continue;const s=a[n];if(r&&e===i&&o&&!f[n]&&(r+=" e"),r&&(r+=" "),0===e)r+=s,o=!1;else if(1===e)r+=1===n?u:s+" "+u,o=!0;else{const t=1===n?d[e]:g[e];r+=1===n?"um "+t:s+" "+t,o=!0}}return r}(e)}e.pt=function(e){const{isNegative:i,integerPart:r,decimalPart:o}=function(e){const i=typeof e;if("bigint"===i)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===i){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}: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 s="";return i&&(s="menos "),s+=m(r),o&&(s+=" vírgula "+function(e){let t="",n=0;for(;n<e.length&&"0"===e[n];)t&&(t+=" "),t+=l,n++;const i=e.slice(n);return i&&(t&&(t+=" "),t+=m(BigInt(i))),t}(o)),s}},"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
|
|
3
|
+
//# sourceMappingURL=pt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pt.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/pt.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 * Portuguese 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 * Portuguese-specific rules (handled in precomputation):\n * - \"e\" conjunction everywhere: vinte e um, cento e um, mil e um\n * - \"cem\" for exact 100, \"cento\" for 100+ remainder\n * - Irregular hundreds: duzentos, trezentos, quatrocentos, etc.\n * - Compound scale: milhão (10^6), mil milhões (10^9), bilião (10^12)\n * - Omit \"um\" before \"mil\"\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\n\n// ============================================================================\n// Vocabulary (module-level constants)\n// ============================================================================\n\nconst ONES = ['', 'um', 'dois', 'três', 'quatro', 'cinco', 'seis', 'sete', 'oito', 'nove']\nconst TEENS = ['dez', 'onze', 'doze', 'treze', 'catorze', 'quinze', 'dezasseis', 'dezassete', 'dezoito', 'dezanove']\nconst TENS = ['', '', 'vinte', 'trinta', 'quarenta', 'cinquenta', 'sessenta', 'setenta', 'oitenta', 'noventa']\n\n// Irregular hundreds\nconst HUNDREDS = ['', 'cento', 'duzentos', 'trezentos', 'quatrocentos', 'quinhentos', 'seiscentos', 'setecentos', 'oitocentos', 'novecentos']\n\nconst THOUSAND = 'mil'\nconst ZERO = 'zero'\nconst NEGATIVE = 'menos'\nconst DECIMAL_SEP = 'vírgula'\n\n// ============================================================================\n// Precomputed Lookup Tables (built once at module load)\n// ============================================================================\n\n/**\n * Builds segment word for 0-999 with Portuguese \"e\" rules.\n * Returns the word and whether it's an exact hundred (for \"cem\" handling).\n */\nfunction buildSegment (n) {\n if (n === 0) return { word: '', isExactHundred: false }\n\n // Special case: exact 100 is \"cem\"\n if (n === 100) return { word: 'cem', isExactHundred: true }\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\n if (hundreds > 0) {\n parts.push(HUNDREDS[hundreds])\n }\n\n // Tens and ones\n if (tens === 1) {\n // Teens (10-19)\n parts.push(TEENS[ones])\n } else if (tens >= 2) {\n if (ones > 0) {\n // Tens + ones with \"e\": \"vinte e um\"\n parts.push(TENS[tens] + ' e ' + ONES[ones])\n } else {\n parts.push(TENS[tens])\n }\n } else if (ones > 0) {\n parts.push(ONES[ones])\n }\n\n // Join hundreds with \"e\": \"cento e um\", \"duzentos e trinta e um\"\n const word = parts.join(' e ')\n\n return { word, isExactHundred: hundreds > 0 && tens === 0 && ones === 0 }\n}\n\n// Precompute all 1000 segment words (0-999)\nconst SEGMENTS = new Array(1000)\nconst SEGMENTS_STARTS_WITH_HUNDREDS = new Array(1000)\n\nfor (let i = 0; i < 1000; i++) {\n const result = buildSegment(i)\n SEGMENTS[i] = result.word\n // Precompute whether segment starts with hundreds (100-999)\n SEGMENTS_STARTS_WITH_HUNDREDS[i] = i >= 100\n}\n\n// ============================================================================\n// Scale Word Lookup (precomputed for common scales)\n// ============================================================================\n\n// Precompute scale words for singular and plural forms\n// Index 1 = thousands, 2 = millions, 3 = billions (mil milhões), etc.\nconst SCALE_WORDS_SINGULAR = [\n '', // 0 unused\n THOUSAND, // 1: mil\n 'milhão', // 2: 10^6\n 'mil milhões', // 3: 10^9 (compound)\n 'bilião', // 4: 10^12\n 'mil biliões', // 5: 10^15 (compound)\n 'trilião', // 6: 10^18\n 'mil triliões', // 7: 10^21 (compound)\n 'quatrilião' // 8: 10^24\n]\n\nconst SCALE_WORDS_PLURAL = [\n '', // 0 unused\n THOUSAND, // 1: mil (same)\n 'milhões', // 2: 10^6\n 'mil milhões', // 3: 10^9 (compound, same)\n 'biliões', // 4: 10^12\n 'mil biliões', // 5: 10^15 (compound, same)\n 'triliões', // 6: 10^18\n 'mil triliões', // 7: 10^21 (compound, same)\n 'quatriliões' // 8: 10^24\n]\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\n/**\n * Converts a non-negative integer to Portuguese words.\n *\n * @param {bigint} n - Non-negative integer to convert\n * @returns {string} Portuguese words\n */\nfunction integerToWords (n) {\n if (n === 0n) return ZERO\n\n // Fast path: numbers < 1000 (direct lookup)\n if (n < 1000n) {\n return SEGMENTS[Number(n)]\n }\n\n // Fast path: numbers < 1,000,000 (thousands)\n if (n < 1_000_000n) {\n const thousands = Number(n / 1000n)\n const remainder = Number(n % 1000n)\n\n let result\n if (thousands === 1) {\n // \"mil\" not \"um mil\"\n result = THOUSAND\n } else {\n result = SEGMENTS[thousands] + ' ' + THOUSAND\n }\n\n if (remainder > 0) {\n // Insert \"e\" before remainder if it doesn't start with hundreds (< 100)\n if (!SEGMENTS_STARTS_WITH_HUNDREDS[remainder]) {\n result += ' e ' + SEGMENTS[remainder]\n } else {\n result += ' ' + SEGMENTS[remainder]\n }\n }\n\n return result\n }\n\n // For numbers >= 1,000,000, use scale decomposition\n return buildLargeNumberWords(n)\n}\n\n/**\n * Builds words for numbers >= 1,000,000.\n * Uses BigInt division for faster segment extraction.\n *\n * @param {bigint} n - Number >= 1,000,000\n * @returns {string} Portuguese words\n */\nfunction buildLargeNumberWords (n) {\n // Extract segments using BigInt division\n // Segments stored least-significant first (index 0 = ones, 1 = thousands, etc.)\n const segments = []\n let temp = n\n while (temp > 0n) {\n segments.push(Number(temp % 1000n))\n temp = temp / 1000n\n }\n\n // Find the first non-zero segment index (lowest scale with value)\n let firstNonZeroIdx = 0\n for (let i = 0; i < segments.length; i++) {\n if (segments[i] !== 0) {\n firstNonZeroIdx = i\n break\n }\n }\n\n // Build result string directly\n let result = ''\n let prevWasScale = false\n\n for (let i = segments.length - 1; i >= 0; i--) {\n const segment = segments[i]\n if (segment === 0) continue\n\n const segmentWord = SEGMENTS[segment]\n const isLastSegment = (i === firstNonZeroIdx)\n\n // Add \"e\" before final segment if previous was scale and this doesn't start with hundreds\n if (result && isLastSegment && prevWasScale && !SEGMENTS_STARTS_WITH_HUNDREDS[segment]) {\n result += ' e'\n }\n\n if (result) result += ' '\n\n if (i === 0) {\n // Units segment\n result += segmentWord\n prevWasScale = false\n } else if (i === 1) {\n // Thousands\n if (segment === 1) {\n result += THOUSAND\n } else {\n result += segmentWord + ' ' + THOUSAND\n }\n prevWasScale = true\n } else {\n // Million and above - use precomputed scale arrays\n const scaleWord = segment === 1 ? SCALE_WORDS_SINGULAR[i] : SCALE_WORDS_PLURAL[i]\n if (segment === 1) {\n result += 'um ' + scaleWord\n } else {\n result += segmentWord + ' ' + scaleWord\n }\n prevWasScale = true\n }\n }\n\n return result\n}\n\n/**\n * Converts decimal digits to Portuguese words.\n *\n * @param {string} decimalPart - Decimal digits (without the point)\n * @returns {string} Portuguese words for decimal part\n */\nfunction decimalPartToWords (decimalPart) {\n let result = ''\n\n // Handle leading zeros\n let i = 0\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n // Convert remainder as a single number\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder))\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Portuguese words.\n *\n * This is the main public API. It accepts any valid numeric input\n * (number, string, or bigint) and handles parsing internally.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @returns {string} The number in Portuguese 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) // 'vinte e um'\n * toWords(100) // 'cem'\n * toWords(1000000) // 'um milhão'\n */\nfunction toWords (value) {\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)\n }\n\n return result\n}\n\n// ============================================================================\n// Public API\n// ============================================================================\n\nexport { toWords }\n"],"names":["parseNumericString","str","isNegative","slice","dotIndex","indexOf","integerPart","BigInt","integerStr","decimalPart","expandScientificNotation","mantissa","expStr","toLowerCase","split","exp","parseInt","digits","newDotPosition","length","repeat","ONES","TEENS","TENS","HUNDREDS","THOUSAND","ZERO","buildSegment","n","word","isExactHundred","ones","tens","Math","floor","hundreds","parts","push","join","SEGMENTS","Array","SEGMENTS_STARTS_WITH_HUNDREDS","i","result","SCALE_WORDS_SINGULAR","SCALE_WORDS_PLURAL","integerToWords","Number","thousands","remainder","segments","temp","firstNonZeroIdx","prevWasScale","segment","segmentWord","scaleWord","buildLargeNumberWords","value","type","isFinite","Error","isSafeInteger","toString","includes","numberToString","trimmed","trim","isNaN","normalizeString","TypeError","parseNumericValue","NEGATIVE","decimalPartToWords"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! n2words/ro v3.0.0 | MIT License | github.com/forzagreen/n2words */
|
|
2
|
+
var e,n;e=this,n=function(e){"use strict";function n(e){const n="-"===e[0];n&&(e=e.slice(1));const i=e.indexOf(".");if(-1===i)return{isNegative:n,integerPart:BigInt(e)};const t=e.slice(0,i)||"0",r=e.slice(i+1);return{isNegative:n,integerPart:BigInt(t),decimalPart:r}}function i(e){const[n,i]=e.toLowerCase().split("e"),t=parseInt(i,10),r=n.indexOf("."),u=-1===r?n:n.slice(0,r)+n.slice(r+1),o=(-1===r?n.length:r)+t;return o>=u.length?u+"0".repeat(o-u.length):o<=0?"0."+"0".repeat(-o)+u:u.slice(0,o)+"."+u.slice(o)}const t=["","unu","doi","trei","patru","cinci","șase","șapte","opt","nouă"],r=["","una","două","trei","patru","cinci","șase","șapte","opt","nouă"],u=["zece","unsprezece","douăsprezece","treisprezece","paisprezece","cincisprezece","șaisprezece","șaptesprezece","optsprezece","nouăsprezece"],o=["zece","unsprezece","doisprezece","treisprezece","paisprezece","cincisprezece","șaisprezece","șaptesprezece","optsprezece","nouăsprezece"],s=["","","douăzeci","treizeci","patruzeci","cincizeci","șaizeci","șaptezeci","optzeci","nouăzeci"],c=["","o sută","două sute","trei sute","patru sute","cinci sute","șase sute","șapte sute","opt sute","nouă sute"],l="zero",a=[{singular:"mie",plural:"mii",article:"o",feminine:!0,needsDe:!0},{singular:"milion",plural:"milioane",article:"un",feminine:!1,needsDe:!0},{singular:"miliard",plural:"miliarde",article:"un",feminine:!1,needsDe:!0},{singular:"trilion",plural:"trilioane",article:"un",feminine:!1,needsDe:!0},{singular:"cvadrilion",plural:"cvadrilioane",article:"un",feminine:!1,needsDe:!0},{singular:"cvintilion",plural:"cvintilioane",article:"un",feminine:!1,needsDe:!0},{singular:"sextilion",plural:"sextilioane",article:"un",feminine:!1,needsDe:!0},{singular:"septilion",plural:"septilioane",article:"un",feminine:!1,needsDe:!0},{singular:"octilion",plural:"octilioane",article:"un",feminine:!1,needsDe:!0}];function p(e,n=!1,i=!1){if(0===e)return"";if(e<10)return n?r[e]:t[e];if(e<20)return i?o[e-10]:u[e-10];const c=Math.floor(e/10),l=e%10;return 0===l?s[c]:s[c]+" și "+(n?r[l]:t[l])}function f(e,n=!1,i=!1){if(0===e)return"";if(e<100)return p(e,n,i);const t=Math.floor(e/100),r=e%100,u=c[t];return 0===r?u:u+" "+p(r,n,i)}function d(e,n){const i=a[n-1];return i?1===e?i.article+" "+i.singular:21===e&&i.needsDe?"douăzeci și una de "+i.plural:f(e,!0)+(i.needsDe&&e>=20?" de ":" ")+i.plural:f(e,!0)}function g(e,n={}){if(0n===e)return l;if(e<1000n){const i="feminine"===n.gender;return f(Number(e),i)}return function(e,n){const i=[];let t=e;for(;t>0n;)i.push(Number(t%1000n)),t/=1000n;let r="";for(let e=i.length-1;e>=0;e--){const t=i[e];if(0===t)continue;let u;u=0===e?f(t,"feminine"===n.gender):d(t,e),r&&u?r+=" "+u:u&&(r=u)}return r}(e,n)}e.ro=function(e,t){t=function(e){if(void 0===e)return{};if(function(e){if(null===e||"object"!=typeof e)return!1;const n=Object.getPrototypeOf(e);return null===n||n===Object.prototype}(e))return e;throw new TypeError("Invalid options: expected plain object or undefined, got "+typeof e)}(t);const{isNegative:r,integerPart:u,decimalPart:o}=function(e){const t=typeof e;if("bigint"===t)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===t){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:n(function(e){const n=e.toString();return n.includes("e")||n.includes("E")?i(n):n}(e))}if("string"===t)return n(function(e){const n=e.trim();if(0===n.length||Number.isNaN(Number(n)))throw new Error(`Invalid number format: "${e}"`);return n.includes("e")||n.includes("E")?i(n):n}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${t}`)}(e);let s="";return r&&(s="minus "),s+=g(u,t),o&&(s+=" virgulă "+function(e){let n="",i=0;for(;i<e.length&&"0"===e[i];)n&&(n+=" "),n+=l,i++;const t=e.slice(i);if(t){n&&(n+=" ");const e=BigInt(t);n+=e<1000n?f(Number(e),!1,!0):g(e,{gender:"masculine"})}return n}(o)),s}},"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
|
|
3
|
+
//# sourceMappingURL=ro.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ro.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/ro.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 * Romanian language converter - Functional Implementation\n *\n * A performance-optimized number-to-words converter using precomputed lookup tables.\n *\n * Key features:\n * - Gender agreement (unu/una, doi/două)\n * - \"De\" preposition insertion for groups >= 20\n * - Complex scale word handling (mie/mii, milion/milioane)\n * - Feminine units for thousands\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 = ['', 'unu', 'doi', 'trei', 'patru', 'cinci', 'șase', 'șapte', 'opt', 'nouă']\nconst ONES_FEM = ['', 'una', 'două', 'trei', 'patru', 'cinci', 'șase', 'șapte', 'opt', 'nouă']\n\nconst TEENS = ['zece', 'unsprezece', 'douăsprezece', 'treisprezece', 'paisprezece', 'cincisprezece', 'șaisprezece', 'șaptesprezece', 'optsprezece', 'nouăsprezece']\nconst TEENS_MASC = ['zece', 'unsprezece', 'doisprezece', 'treisprezece', 'paisprezece', 'cincisprezece', 'șaisprezece', 'șaptesprezece', 'optsprezece', 'nouăsprezece']\n\nconst TWENTIES = ['', '', 'douăzeci', 'treizeci', 'patruzeci', 'cincizeci', 'șaizeci', 'șaptezeci', 'optzeci', 'nouăzeci']\n\nconst HUNDREDS = ['', 'o sută', 'două sute', 'trei sute', 'patru sute', 'cinci sute', 'șase sute', 'șapte sute', 'opt sute', 'nouă sute']\n\nconst ZERO = 'zero'\nconst NEGATIVE = 'minus'\nconst DECIMAL_SEP = 'virgulă'\n\n// Scale metadata: [singular, plural, article, feminine, needsDe]\n// - singular: form for 1\n// - plural: form for 2+\n// - article: 'o' for feminine, 'un' for masculine\n// - feminine: whether units should be feminine\n// - needsDe: whether \"de\" is inserted for segment >= 20\nconst SCALE_META = [\n { singular: 'mie', plural: 'mii', article: 'o', feminine: true, needsDe: true },\n { singular: 'milion', plural: 'milioane', article: 'un', feminine: false, needsDe: true },\n { singular: 'miliard', plural: 'miliarde', article: 'un', feminine: false, needsDe: true },\n { singular: 'trilion', plural: 'trilioane', article: 'un', feminine: false, needsDe: true },\n { singular: 'cvadrilion', plural: 'cvadrilioane', article: 'un', feminine: false, needsDe: true },\n { singular: 'cvintilion', plural: 'cvintilioane', article: 'un', feminine: false, needsDe: true },\n { singular: 'sextilion', plural: 'sextilioane', article: 'un', feminine: false, needsDe: true },\n { singular: 'septilion', plural: 'septilioane', article: 'un', feminine: false, needsDe: true },\n { singular: 'octilion', plural: 'octilioane', article: 'un', feminine: false, needsDe: true }\n]\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Spells number under 100.\n */\nfunction spellUnder100 (n, feminine = false, masculineTeens = false) {\n if (n === 0) return ''\n if (n < 10) {\n return feminine ? ONES_FEM[n] : ONES_MASC[n]\n }\n if (n < 20) {\n return masculineTeens ? TEENS_MASC[n - 10] : TEENS[n - 10]\n }\n const t = Math.floor(n / 10)\n const u = n % 10\n if (u === 0) {\n return TWENTIES[t]\n }\n const onesWord = feminine ? ONES_FEM[u] : ONES_MASC[u]\n return TWENTIES[t] + ' și ' + onesWord\n}\n\n/**\n * Spells number under 1000.\n */\nfunction spellUnder1000 (n, feminine = false, masculineTeens = false) {\n if (n === 0) return ''\n if (n < 100) return spellUnder100(n, feminine, masculineTeens)\n\n const h = Math.floor(n / 100)\n const r = n % 100\n const hundredWord = HUNDREDS[h]\n\n if (r === 0) return hundredWord\n return hundredWord + ' ' + spellUnder100(r, feminine, masculineTeens)\n}\n\n/**\n * Builds scale word with proper pluralization and \"de\" insertion.\n * Romanian always uses feminine forms (două, not doi) when counting scale words.\n */\nfunction buildScalePhrase (segment, scaleIndex) {\n const meta = SCALE_META[scaleIndex - 1]\n if (!meta) return spellUnder1000(segment, true)\n\n if (segment === 1) {\n return meta.article + ' ' + meta.singular\n }\n\n // Special case: 21 with scale words uses feminine \"una\"\n if (segment === 21 && meta.needsDe) {\n return 'douăzeci și una de ' + meta.plural\n }\n\n // Romanian always uses feminine when counting scale words (două milioane, not doi milioane)\n const words = spellUnder1000(segment, true)\n\n // \"de\" after >= 20\n const needsDe = meta.needsDe && segment >= 20\n const separator = needsDe ? ' de ' : ' '\n\n return words + separator + meta.plural\n}\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\n/**\n * Converts a non-negative integer to Romanian words.\n *\n * @param {bigint} n - Non-negative integer to convert\n * @param {Object} options - Conversion options\n * @returns {string} Romanian words\n */\nfunction integerToWords (n, options = {}) {\n if (n === 0n) return ZERO\n\n // Fast path: numbers < 1000\n if (n < 1000n) {\n const feminine = options.gender === 'feminine'\n return spellUnder1000(Number(n), feminine)\n }\n\n return buildLargeNumberWords(n, options)\n}\n\n/**\n * Builds words for numbers >= 1000.\n * Uses BigInt division for faster segment extraction.\n *\n * @param {bigint} n - Number >= 1000\n * @param {Object} options - Conversion options\n * @returns {string} Romanian words\n */\nfunction buildLargeNumberWords (n, options) {\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(Number(temp % 1000n))\n temp = temp / 1000n\n }\n\n // Build result string directly (avoid regex cleanup)\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 let segmentWords\n if (i === 0) {\n // Units segment - use gender from options\n const feminine = options.gender === 'feminine'\n segmentWords = spellUnder1000(segment, feminine)\n } else {\n // Scale segment\n segmentWords = buildScalePhrase(segment, i)\n }\n\n if (result && segmentWords) {\n result += ' ' + segmentWords\n } else if (segmentWords) {\n result = segmentWords\n }\n }\n\n return result\n}\n\n/**\n * Converts decimal digits to Romanian words.\n * Decimals always use masculine forms.\n *\n * @param {string} decimalPart - Decimal digits (without the point)\n * @returns {string} Romanian words for decimal part\n */\nfunction decimalPartToWords (decimalPart) {\n let result = ''\n let i = 0\n\n // Handle leading zeros\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 (masculine, with masculine teens)\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n const n = BigInt(remainder)\n if (n < 1000n) {\n result += spellUnder1000(Number(n), false, true)\n } else {\n result += integerToWords(n, { gender: 'masculine' })\n }\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Romanian words.\n *\n * @param {number | string | bigint} value - The numeric value to convert\n * @param {Object} [options] - Conversion options\n * @param {string} [options.gender='masculine'] - Gender for numbers\n * @returns {string} The number in Romanian 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) // 'douăzeci și unu'\n * toWords(1, { gender: 'feminine' }) // 'una'\n * toWords(1000) // 'o mie'\n */\nfunction toWords (value, options) {\n options = validateOptions(options)\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart, options)\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","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","TEENS_MASC","TWENTIES","HUNDREDS","ZERO","SCALE_META","singular","plural","article","feminine","needsDe","spellUnder100","n","masculineTeens","t","Math","floor","u","spellUnder1000","h","r","hundredWord","buildScalePhrase","segment","scaleIndex","meta","integerToWords","options","gender","Number","segmentValues","temp","push","result","i","segmentWords","buildLargeNumberWords","value","undefined","proto","Object","getPrototypeOf","prototype","isPlainObject","TypeError","validateOptions","type","isFinite","Error","isSafeInteger","toString","includes","numberToString","trimmed","trim","isNaN","normalizeString","parseNumericValue","NEGATIVE","remainder","decimalPartToWords"],"mappings":";0CAwEA,SAASA,EAAoBC,GAC3B,MAAMC,EAAwB,MAAXD,EAAI,GACnBC,IAAYD,EAAMA,EAAIE,MAAM,IAEhC,MAAMC,EAAWH,EAAII,QAAQ,KAC7B,IAAiB,IAAbD,EACF,MAAO,CAAEF,aAAYI,YAAaC,OAAON,IAG3C,MAAMO,EAAaP,EAAIE,MAAM,EAAGC,IAAa,IACvCK,EAAcR,EAAIE,MAAMC,EAAW,GACzC,MAAO,CAAEF,aAAYI,YAAaC,OAAOC,GAAaC,cACxD,CAKA,SAASC,EAA0BT,GACjC,MAAOU,EAAUC,GAAUX,EAAIY,cAAcC,MAAM,KAC7CC,EAAMC,SAASJ,EAAQ,IAEvBR,EAAWO,EAASN,QAAQ,KAC5BY,OAASb,EACXO,EACAA,EAASR,MAAM,EAAGC,GAAYO,EAASR,MAAMC,EAAW,GAEtDc,IAD6B,IAAbd,EAAkBO,EAASQ,OAASf,GACnBW,EAEvC,OAAIG,GAAkBD,EAAOE,OACpBF,EAAS,IAAIG,OAAOF,EAAiBD,EAAOE,QAEjDD,GAAkB,EACb,KAAO,IAAIE,QAAQF,GAAkBD,EAEvCA,EAAOd,MAAM,EAAGe,GAAkB,IAAMD,EAAOd,MAAMe,EAC9D,CCxFA,MAAMG,EAAY,CAAC,GAAI,MAAO,MAAO,OAAQ,QAAS,QAAS,OAAQ,QAAS,MAAO,QACjFC,EAAW,CAAC,GAAI,MAAO,OAAQ,OAAQ,QAAS,QAAS,OAAQ,QAAS,MAAO,QAEjFC,EAAQ,CAAC,OAAQ,aAAc,eAAgB,eAAgB,cAAe,gBAAiB,cAAe,gBAAiB,cAAe,gBAC9IC,EAAa,CAAC,OAAQ,aAAc,cAAe,eAAgB,cAAe,gBAAiB,cAAe,gBAAiB,cAAe,gBAElJC,EAAW,CAAC,GAAI,GAAI,WAAY,WAAY,YAAa,YAAa,UAAW,YAAa,UAAW,YAEzGC,EAAW,CAAC,GAAI,SAAU,YAAa,YAAa,aAAc,aAAc,YAAa,aAAc,WAAY,aAEvHC,EAAO,OAUPC,EAAa,CACjB,CAAEC,SAAU,MAAOC,OAAQ,MAAOC,QAAS,IAAKC,UAAU,EAAMC,SAAS,GACzE,CAAEJ,SAAU,SAAUC,OAAQ,WAAYC,QAAS,KAAMC,UAAU,EAAOC,SAAS,GACnF,CAAEJ,SAAU,UAAWC,OAAQ,WAAYC,QAAS,KAAMC,UAAU,EAAOC,SAAS,GACpF,CAAEJ,SAAU,UAAWC,OAAQ,YAAaC,QAAS,KAAMC,UAAU,EAAOC,SAAS,GACrF,CAAEJ,SAAU,aAAcC,OAAQ,eAAgBC,QAAS,KAAMC,UAAU,EAAOC,SAAS,GAC3F,CAAEJ,SAAU,aAAcC,OAAQ,eAAgBC,QAAS,KAAMC,UAAU,EAAOC,SAAS,GAC3F,CAAEJ,SAAU,YAAaC,OAAQ,cAAeC,QAAS,KAAMC,UAAU,EAAOC,SAAS,GACzF,CAAEJ,SAAU,YAAaC,OAAQ,cAAeC,QAAS,KAAMC,UAAU,EAAOC,SAAS,GACzF,CAAEJ,SAAU,WAAYC,OAAQ,aAAcC,QAAS,KAAMC,UAAU,EAAOC,SAAS,IAUzF,SAASC,EAAeC,EAAGH,GAAW,EAAOI,GAAiB,GAC5D,GAAU,IAAND,EAAS,MAAO,GACpB,GAAIA,EAAI,GACN,OAAOH,EAAWV,EAASa,GAAKd,EAAUc,GAE5C,GAAIA,EAAI,GACN,OAAOC,EAAiBZ,EAAWW,EAAI,IAAMZ,EAAMY,EAAI,IAEzD,MAAME,EAAIC,KAAKC,MAAMJ,EAAI,IACnBK,EAAIL,EAAI,GACd,OAAU,IAANK,EACKf,EAASY,GAGXZ,EAASY,GAAK,QADJL,EAAWV,EAASkB,GAAKnB,EAAUmB,GAEtD,CAKA,SAASC,EAAgBN,EAAGH,GAAW,EAAOI,GAAiB,GAC7D,GAAU,IAAND,EAAS,MAAO,GACpB,GAAIA,EAAI,IAAK,OAAOD,EAAcC,EAAGH,EAAUI,GAE/C,MAAMM,EAAIJ,KAAKC,MAAMJ,EAAI,KACnBQ,EAAIR,EAAI,IACRS,EAAclB,EAASgB,GAE7B,OAAU,IAANC,EAAgBC,EACbA,EAAc,IAAMV,EAAcS,EAAGX,EAAUI,EACxD,CAMA,SAASS,EAAkBC,EAASC,GAClC,MAAMC,EAAOpB,EAAWmB,EAAa,GACrC,OAAKC,EAEW,IAAZF,EACKE,EAAKjB,QAAU,IAAMiB,EAAKnB,SAInB,KAAZiB,GAAkBE,EAAKf,QAClB,sBAAwBe,EAAKlB,OAIxBW,EAAeK,GAAS,IAGtBE,EAAKf,SAAWa,GAAW,GACf,OAAS,KAEVE,EAAKlB,OAlBdW,EAAeK,GAAS,EAmB5C,CAaA,SAASG,EAAgBd,EAAGe,EAAU,IACpC,GAAU,KAANf,EAAU,OAAOR,EAGrB,GAAIQ,EAAI,MAAO,CACb,MAAMH,EAA8B,aAAnBkB,EAAQC,OACzB,OAAOV,EAAeW,OAAOjB,GAAIH,EACnC,CAEA,OAWF,SAAgCG,EAAGe,GAGjC,MAAMG,EAAgB,GACtB,IAAIC,EAAOnB,EACX,KAAOmB,EAAO,IACZD,EAAcE,KAAKH,OAAOE,EAAO,QACjCA,GAAc,MAIhB,IAAIE,EAAS,GAEb,IAAK,IAAIC,EAAIJ,EAAclC,OAAS,EAAGsC,GAAK,EAAGA,IAAK,CAClD,MAAMX,EAAUO,EAAcI,GAC9B,GAAgB,IAAZX,EAAe,SAEnB,IAAIY,EAIFA,EAHQ,IAAND,EAGahB,EAAeK,EADM,aAAnBI,EAAQC,QAIVN,EAAiBC,EAASW,GAGvCD,GAAUE,EACZF,GAAU,IAAME,EACPA,IACTF,EAASE,EAEb,CAEA,OAAOF,CACT,CA9CSG,CAAsBxB,EAAGe,EAClC,MA+FA,SAAkBU,EAAOV,GACvBA,ECjOK,SAA0BA,GAC/B,QAAgBW,IAAZX,EAAuB,MAAO,CAAA,EAClC,GCCK,SAAwBU,GAC7B,GAAc,OAAVA,GAAmC,iBAAVA,EAAoB,OAAO,EACxD,MAAME,EAAQC,OAAOC,eAAeJ,GACpC,OAAiB,OAAVE,GAAkBA,IAAUC,OAAOE,SAC5C,CDLMC,CAAchB,GAAU,OAAOA,EACnC,MAAM,IAAIiB,UACR,mEAAmEjB,EAEvE,CD2NYkB,CAAgBlB,GAC1B,MAAMhD,WAAEA,EAAUI,YAAEA,EAAWG,YAAEA,GD9N5B,SAA4BmD,GACjC,MAAMS,SAAcT,EAGpB,GAAa,WAATS,EACF,OAAOT,EAAQ,GACX,CAAE1D,YAAY,EAAMI,aAAcsD,GAClC,CAAE1D,YAAY,EAAOI,YAAasD,GAIxC,GAAa,WAATS,EAAmB,CACrB,IAAKjB,OAAOkB,SAASV,GACnB,MAAM,IAAIW,MAAM,8DAElB,OAAInB,OAAOoB,cAAcZ,GAChBA,EAAQ,EACX,CAAE1D,YAAY,EAAMI,YAAaC,QAAQqD,IACzC,CAAE1D,YAAY,EAAOI,YAAaC,OAAOqD,IAExC5D,EAgBX,SAAyB4D,GACvB,MAAM3D,EAAM2D,EAAMa,WAClB,OAAQxE,EAAIyE,SAAS,MAAQzE,EAAIyE,SAAS,KACtChE,EAAyBT,GACzBA,CACN,CArB8B0E,CAAef,GAC3C,CAGA,GAAa,WAATS,EACF,OAAOrE,EAqBX,SAA0B4D,GACxB,MAAMgB,EAAUhB,EAAMiB,OACtB,GAAuB,IAAnBD,EAAQzD,QAAgBiC,OAAO0B,MAAM1B,OAAOwB,IAC9C,MAAM,IAAIL,MAAM,2BAA2BX,MAE7C,OAAQgB,EAAQF,SAAS,MAAQE,EAAQF,SAAS,KAC9ChE,EAAyBkE,GACzBA,CACN,CA7B8BG,CAAgBnB,IAG5C,MAAM,IAAIO,UACR,oEAAoEE,IAExE,CC+LmDW,CAAkBpB,GAEnE,IAAIJ,EAAS,GAYb,OAVItD,IACFsD,EAASyB,UAGXzB,GAAUP,EAAe3C,EAAa4C,GAElCzC,IACF+C,GAAU,YAtDd,SAA6B/C,GAC3B,IAAI+C,EAAS,GACTC,EAAI,EAGR,KAAOA,EAAIhD,EAAYU,QAA6B,MAAnBV,EAAYgD,IACvCD,IAAQA,GAAU,KACtBA,GAAU7B,EACV8B,IAIF,MAAMyB,EAAYzE,EAAYN,MAAMsD,GACpC,GAAIyB,EAAW,CACT1B,IAAQA,GAAU,KACtB,MAAMrB,EAAI5B,OAAO2E,GAEf1B,GADErB,EAAI,MACIM,EAAeW,OAAOjB,IAAI,GAAO,GAEjCc,EAAed,EAAG,CAAEgB,OAAQ,aAE1C,CAEA,OAAOK,CACT,CA8BwC2B,CAAmB1E,IAGlD+C,CACT"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! n2words/ru v3.0.0 | MIT License | github.com/forzagreen/n2words */
|
|
2
|
+
var e,t;e=this,t=function(e){"use strict";function t(e){const t="-"===e[0];t&&(e=e.slice(1));const n=e.indexOf(".");if(-1===n)return{isNegative:t,integerPart:BigInt(e)};const r=e.slice(0,n)||"0",i=e.slice(n+1);return{isNegative:t,integerPart:BigInt(r),decimalPart:i}}function n(e){const[t,n]=e.toLowerCase().split("e"),r=parseInt(n,10),i=t.indexOf("."),o=-1===i?t:t.slice(0,i)+t.slice(i+1),u=(-1===i?t.length:i)+r;return u>=o.length?o+"0".repeat(u-o.length):u<=0?"0."+"0".repeat(-u)+o:o.slice(0,u)+"."+o.slice(u)}function r(e,t){const n="bigint"==typeof e?Number(e):e,r=n%10,i=n%100;return i>=11&&i<=19?t[2]:1===r?t[0]:r>=2&&r<=4?t[1]:t[2]}function i(e,t,n,r,i){if(0===e)return"";const o=e%10,u=Math.floor(e/10)%10,s=Math.floor(e/100),c=[];return s>0&&c.push(i[s]),u>1&&c.push(r[u]),1===u?c.push(n[o]):o>0&&c.push(t[o]),c.join(" ")}const o="ноль",u=[["тысяча","тысячи","тысяч"],["миллион","миллиона","миллионов"],["миллиард","миллиарда","миллиардов"],["триллион","триллиона","триллионов"],["квадриллион","квадриллиона","квадриллионов"],["квинтиллион","квинтиллиона","квинтиллионов"],["секстиллион","секстиллиона","секстиллионов"],["септиллион","септиллиона","септиллионов"],["октиллион","октиллиона","октиллионов"],["нониллион","нониллиона","нониллионов"]],{masc:s,fem:c}=function(e,t,n,r,o){const u=new Array(1e3),s=new Array(1e3);for(let c=0;c<1e3;c++)u[c]=i(c,e,n,r,o),s[c]=i(c,t,n,r,o);return{masc:u,fem:s}}(["","один","два","три","четыре","пять","шесть","семь","восемь","девять"],["","одна","две","три","четыре","пять","шесть","семь","восемь","девять"],["десять","одиннадцать","двенадцать","тринадцать","четырнадцать","пятнадцать","шестнадцать","семнадцать","восемнадцать","девятнадцать"],["","","двадцать","тридцать","сорок","пятьдесят","шестьдесят","семьдесят","восемьдесят","девяносто"],["","сто","двести","триста","четыреста","пятьсот","шестьсот","семьсот","восемьсот","девятьсот"]);function f(e,t={}){if(0n===e)return o;const n="feminine"===t.gender;if(e<1000n)return(n?c:s)[Number(e)];if(e<1000000n){const t=Number(e/1000n),i=Number(e%1000n);let o=c[t]+" "+r(t,u[0]);return i>0&&(o+=" "+(n?c:s)[i]),o}return function(e,t){const n="feminine"===t.gender,i=e.toString(),o=i.length,f=[],l=o%3;let a=0;for(l>0&&(f.push(Number(i.slice(0,l))),a=l);a<o;)f.push(Number(i.slice(a,a+3))),a+=3;const g=[];let p=f.length-1;for(let e=0;e<f.length;e++){const t=f[e];if(0!==t)if(0===p)g.push((n?c:s)[t]);else{const e=r(t,u[p-1]);g.push((1===p?c:s)[t]+" "+e)}p--}return g.join(" ")}(e,t)}e.ru=function(e,r){r=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)}(r);const{isNegative:i,integerPart:u,decimalPart:s}=function(e){const r=typeof e;if("bigint"===r)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===r){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:t(function(e){const t=e.toString();return t.includes("e")||t.includes("E")?n(t):t}(e))}if("string"===r)return t(function(e){const t=e.trim();if(0===t.length||Number.isNaN(Number(t)))throw new Error(`Invalid number format: "${e}"`);return t.includes("e")||t.includes("E")?n(t):t}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${r}`)}(e);let c="";return i&&(c="минус "),c+=f(u,r),s&&(c+=" запятая "+function(e,t){let n="",r=0;for(;r<e.length&&"0"===e[r];)n&&(n+=" "),n+=o,r++;const i=e.slice(r);return i&&(n&&(n+=" "),n+=f(BigInt(i),t)),n}(s,r)),c}},"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
|
|
3
|
+
//# sourceMappingURL=ru.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ru.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/ru.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 * Russian language converter - Functional Implementation\n *\n * Self-contained converter using shared Slavic utilities.\n *\n * Key features:\n * - Three-form pluralization (one/few/many)\n * - Gender: thousands are feminine, millions+ are masculine\n * - Irregular hundreds (двести, триста, etc.)\n * - Long scale naming\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\nimport { validateOptions } from '../utils/validate-options.js'\n\n// ============================================================================\n// Slavic Utilities (inlined for performance)\n// ============================================================================\n\nfunction pluralize (n, forms) {\n const num = typeof n === 'bigint' ? Number(n) : n\n const lastDigit = num % 10\n const lastTwoDigits = num % 100\n\n if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {\n return forms[2]\n }\n\n if (lastDigit === 1) return forms[0]\n if (lastDigit >= 2 && lastDigit <= 4) return forms[1]\n return forms[2]\n}\n\nfunction buildAllSegments (onesMasc, onesFem, teens, tens, hundreds) {\n const masc = new Array(1000)\n const fem = new Array(1000)\n\n for (let i = 0; i < 1000; i++) {\n masc[i] = buildSegment(i, onesMasc, teens, tens, hundreds)\n fem[i] = buildSegment(i, onesFem, teens, tens, hundreds)\n }\n\n return { masc, fem }\n}\n\nfunction buildSegment (n, ones, teens, tens, hundreds) {\n if (n === 0) return ''\n\n const onesDigit = n % 10\n const tensDigit = Math.floor(n / 10) % 10\n const hundredsDigit = Math.floor(n / 100)\n\n const parts = []\n\n if (hundredsDigit > 0) {\n parts.push(hundreds[hundredsDigit])\n }\n\n if (tensDigit > 1) {\n parts.push(tens[tensDigit])\n }\n\n if (tensDigit === 1) {\n parts.push(teens[onesDigit])\n } else if (onesDigit > 0) {\n parts.push(ones[onesDigit])\n }\n\n return parts.join(' ')\n}\n\n// ============================================================================\n// Vocabulary\n// ============================================================================\n\nconst ONES_MASC = ['', 'один', 'два', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять']\nconst ONES_FEM = ['', 'одна', 'две', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять']\n\nconst TEENS = ['десять', 'одиннадцать', 'двенадцать', 'тринадцать', 'четырнадцать', 'пятнадцать', 'шестнадцать', 'семнадцать', 'восемнадцать', 'девятнадцать']\nconst TENS = ['', '', 'двадцать', 'тридцать', 'сорок', 'пятьдесят', 'шестьдесят', 'семьдесят', 'восемьдесят', 'девяносто']\n\n// Irregular hundreds\nconst HUNDREDS = ['', 'сто', 'двести', 'триста', 'четыреста', 'пятьсот', 'шестьсот', 'семьсот', 'восемьсот', 'девятьсот']\n\nconst ZERO = 'ноль'\nconst NEGATIVE = 'минус'\nconst DECIMAL_SEP = 'запятая'\n\n// Scale words: [singular, few, many]\n// Thousands (index 0) are feminine, rest are masculine\nconst SCALE_FORMS = [\n ['тысяча', 'тысячи', 'тысяч'],\n ['миллион', 'миллиона', 'миллионов'],\n ['миллиард', 'миллиарда', 'миллиардов'],\n ['триллион', 'триллиона', 'триллионов'],\n ['квадриллион', 'квадриллиона', 'квадриллионов'],\n ['квинтиллион', 'квинтиллиона', 'квинтиллионов'],\n ['секстиллион', 'секстиллиона', 'секстиллионов'],\n ['септиллион', 'септиллиона', 'септиллионов'],\n ['октиллион', 'октиллиона', 'октиллионов'],\n ['нониллион', 'нониллиона', 'нониллионов']\n]\n\n// ============================================================================\n// Precomputed Lookup Tables\n// ============================================================================\n\nconst { masc: SEGMENTS_MASC, fem: SEGMENTS_FEM } = buildAllSegments(ONES_MASC, ONES_FEM, TEENS, TENS, HUNDREDS)\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\nfunction integerToWords (n, options = {}) {\n if (n === 0n) return ZERO\n\n const feminine = options.gender === 'feminine'\n\n if (n < 1000n) {\n const segments = feminine ? SEGMENTS_FEM : SEGMENTS_MASC\n return segments[Number(n)]\n }\n\n if (n < 1_000_000n) {\n const thousands = Number(n / 1000n)\n const remainder = Number(n % 1000n)\n\n // Thousands are always feminine in Russian\n const thousandsWord = SEGMENTS_FEM[thousands]\n const scaleWord = pluralize(thousands, SCALE_FORMS[0])\n\n let result = thousandsWord + ' ' + scaleWord\n\n if (remainder > 0) {\n const segments = feminine ? SEGMENTS_FEM : SEGMENTS_MASC\n result += ' ' + segments[remainder]\n }\n\n return result\n }\n\n return buildLargeNumberWords(n, options)\n}\n\nfunction buildLargeNumberWords (n, options) {\n const feminine = options.gender === 'feminine'\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 if (scaleIndex === 0) {\n const segmentWords = feminine ? SEGMENTS_FEM : SEGMENTS_MASC\n parts.push(segmentWords[segment])\n } else {\n const scaleForms = SCALE_FORMS[scaleIndex - 1]\n const scaleWord = pluralize(segment, scaleForms)\n // Thousands (scaleIndex=1) are feminine, others masculine\n const isFeminine = scaleIndex === 1\n const segmentWords = isFeminine ? SEGMENTS_FEM : SEGMENTS_MASC\n parts.push(segmentWords[segment] + ' ' + scaleWord)\n }\n }\n\n scaleIndex--\n }\n\n return parts.join(' ')\n}\n\nfunction decimalPartToWords (decimalPart, options) {\n let result = ''\n let i = 0\n\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder), options)\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Russian words.\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 Russian words\n */\nfunction toWords (value, options) {\n options = validateOptions(options)\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart, options)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, options)\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","pluralize","n","forms","num","Number","lastDigit","lastTwoDigits","buildSegment","ones","teens","tens","hundreds","onesDigit","tensDigit","Math","floor","hundredsDigit","parts","push","join","ZERO","SCALE_FORMS","masc","SEGMENTS_MASC","fem","SEGMENTS_FEM","onesMasc","onesFem","Array","i","buildAllSegments","integerToWords","options","feminine","gender","thousands","remainder","result","numStr","toString","len","segments","remainderLen","pos","scaleIndex","segment","scaleWord","buildLargeNumberWords","value","undefined","proto","Object","getPrototypeOf","prototype","isPlainObject","TypeError","validateOptions","type","isFinite","Error","isSafeInteger","includes","numberToString","trimmed","trim","isNaN","normalizeString","parseNumericValue","NEGATIVE","decimalPartToWords"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/*! n2words/sr-Cyrl v3.0.0 | MIT License | github.com/forzagreen/n2words */
|
|
2
|
+
var e,t;e=this,t=function(e){"use strict";function t(e){const t="-"===e[0];t&&(e=e.slice(1));const n=e.indexOf(".");if(-1===n)return{isNegative:t,integerPart:BigInt(e)};const r=e.slice(0,n)||"0",i=e.slice(n+1);return{isNegative:t,integerPart:BigInt(r),decimalPart:i}}function n(e){const[t,n]=e.toLowerCase().split("e"),r=parseInt(n,10),i=t.indexOf("."),o=-1===i?t:t.slice(0,i)+t.slice(i+1),s=(-1===i?t.length:i)+r;return s>=o.length?o+"0".repeat(s-o.length):s<=0?"0."+"0".repeat(-s)+o:o.slice(0,s)+"."+o.slice(s)}function r(e,t){const n="bigint"==typeof e?Number(e):e,r=n%10,i=n%100;return i>=11&&i<=19?t[2]:1===r?t[0]:r>=2&&r<=4?t[1]:t[2]}function i(e,t,n,r,i){if(0===e)return"";const o=e%10,s=Math.floor(e/10)%10,u=Math.floor(e/100),c=[];return u>0&&c.push(i[u]),s>1&&c.push(r[s]),1===s?c.push(n[o]):o>0&&c.push(t[o]),c.join(" ")}const o="нула",s=[["хиљада","хиљаде","хиљада"],["милион","милиона","милиона"],["милијарда","милијарде","милијарда"],["билион","билиона","билиона"],["билијарда","билијарде","билијарда"],["трилион","трилиона","трилиона"],["трилијарда","трилијарде","трилијарда"],["квадрилион","квадрилиона","квадрилиона"],["квадрилијарда","квадрилијарде","квадрилијарда"]],{masc:u,fem:c}=function(e,t,n,r,o){const s=new Array(1e3),u=new Array(1e3);for(let c=0;c<1e3;c++)s[c]=i(c,e,n,r,o),u[c]=i(c,t,n,r,o);return{masc:s,fem:u}}(["","један","два","три","четири","пет","шест","седам","осам","девет"],["","једна","две","три","четири","пет","шест","седам","осам","девет"],["десет","једанаест","дванаест","тринаест","четрнаест","петнаест","шеснаест","седамнаест","осамнаест","деветнаест"],["","","двадесет","тридесет","четрдесет","педесет","шездесет","седамдесет","осамдесет","деведесет"],["","сто","двеста","триста","четиристо","петсто","шесто","седамсто","осамсто","девестo"]);function f(e,t={}){return 0n===e?o:e<1000n?("feminine"===t.gender?c:u)[Number(e)]:function(e,t){const n=e.toString(),i=n.length,o=[],f=i%3;let l=0;for(f>0&&(o.push(Number(n.slice(0,f))),l=f);l<i;)o.push(Number(n.slice(l,l+3))),l+=3;const a=[];let g=o.length-1;for(let e=0;e<o.length;e++){const n=o[e];if(0!==n)if(0===g)a.push(("feminine"===t.gender?c:u)[n]);else{const e=r(n,s[g-1]);a.push((1===g?c:u)[n]+" "+e)}g--}return a.join(" ")}(e,t)}e.srCyrl=function(e,r){r=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)}(r);const{isNegative:i,integerPart:s,decimalPart:u}=function(e){const r=typeof e;if("bigint"===r)return e<0n?{isNegative:!0,integerPart:-e}:{isNegative:!1,integerPart:e};if("number"===r){if(!Number.isFinite(e))throw new Error("Number must be finite (NaN and Infinity are not supported)");return Number.isSafeInteger(e)?e<0?{isNegative:!0,integerPart:BigInt(-e)}:{isNegative:!1,integerPart:BigInt(e)}:t(function(e){const t=e.toString();return t.includes("e")||t.includes("E")?n(t):t}(e))}if("string"===r)return t(function(e){const t=e.trim();if(0===t.length||Number.isNaN(Number(t)))throw new Error(`Invalid number format: "${e}"`);return t.includes("e")||t.includes("E")?n(t):t}(e));throw new TypeError(`Invalid value type: expected number, string, or bigint, received ${r}`)}(e);let c="";return i&&(c="минус "),c+=f(s,r),u&&(c+=" запета "+function(e,t){let n="",r=0;for(;r<e.length&&"0"===e[r];)n&&(n+=" "),n+=o,r++;const i=e.slice(r);return i&&(n&&(n+=" "),n+=f(BigInt(i),t)),n}(u,r)),c}},"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).n2words=e.n2words||{});
|
|
3
|
+
//# sourceMappingURL=sr-Cyrl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sr-Cyrl.js","sources":["../../lib/utils/parse-numeric.js","../../lib/languages/sr-Cyrl.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 * Serbian Cyrillic language converter - Functional Implementation\n *\n * Self-contained converter using shared Slavic utilities.\n *\n * Key features:\n * - Three-form pluralization (one/few/many)\n * - Gender: thousands are feminine, millions+ are masculine\n * - Irregular hundreds\n * - Long scale naming with -ard forms\n * - Cyrillic script\n */\n\nimport { parseNumericValue } from '../utils/parse-numeric.js'\nimport { validateOptions } from '../utils/validate-options.js'\n\n// ============================================================================\n// Slavic Utilities (inlined for performance)\n// ============================================================================\n\nfunction pluralize (n, forms) {\n const num = typeof n === 'bigint' ? Number(n) : n\n const lastDigit = num % 10\n const lastTwoDigits = num % 100\n\n if (lastTwoDigits >= 11 && lastTwoDigits <= 19) {\n return forms[2]\n }\n\n if (lastDigit === 1) return forms[0]\n if (lastDigit >= 2 && lastDigit <= 4) return forms[1]\n return forms[2]\n}\n\nfunction buildAllSegments (onesMasc, onesFem, teens, tens, hundreds) {\n const masc = new Array(1000)\n const fem = new Array(1000)\n\n for (let i = 0; i < 1000; i++) {\n masc[i] = buildSegment(i, onesMasc, teens, tens, hundreds)\n fem[i] = buildSegment(i, onesFem, teens, tens, hundreds)\n }\n\n return { masc, fem }\n}\n\nfunction buildSegment (n, ones, teens, tens, hundreds) {\n if (n === 0) return ''\n\n const onesDigit = n % 10\n const tensDigit = Math.floor(n / 10) % 10\n const hundredsDigit = Math.floor(n / 100)\n\n const parts = []\n\n if (hundredsDigit > 0) {\n parts.push(hundreds[hundredsDigit])\n }\n\n if (tensDigit > 1) {\n parts.push(tens[tensDigit])\n }\n\n if (tensDigit === 1) {\n parts.push(teens[onesDigit])\n } else if (onesDigit > 0) {\n parts.push(ones[onesDigit])\n }\n\n return parts.join(' ')\n}\n\n// ============================================================================\n// Vocabulary\n// ============================================================================\n\nconst ONES_MASC = ['', 'један', 'два', 'три', 'четири', 'пет', 'шест', 'седам', 'осам', 'девет']\nconst ONES_FEM = ['', 'једна', 'две', 'три', 'четири', 'пет', 'шест', 'седам', 'осам', 'девет']\nconst TEENS = ['десет', 'једанаест', 'дванаест', 'тринаест', 'четрнаест', 'петнаест', 'шеснаест', 'седамнаест', 'осамнаест', 'деветнаест']\nconst TENS = ['', '', 'двадесет', 'тридесет', 'четрдесет', 'педесет', 'шездесет', 'седамдесет', 'осамдесет', 'деведесет']\nconst HUNDREDS = ['', 'сто', 'двеста', 'триста', 'четиристо', 'петсто', 'шесто', 'седамсто', 'осамсто', 'девестo']\n\nconst ZERO = 'нула'\nconst NEGATIVE = 'минус'\nconst DECIMAL_SEP = 'запета'\n\n// Scale words: [singular, few, many]\nconst SCALE_FORMS = [\n ['хиљада', 'хиљаде', 'хиљада'],\n ['милион', 'милиона', 'милиона'],\n ['милијарда', 'милијарде', 'милијарда'],\n ['билион', 'билиона', 'билиона'],\n ['билијарда', 'билијарде', 'билијарда'],\n ['трилион', 'трилиона', 'трилиона'],\n ['трилијарда', 'трилијарде', 'трилијарда'],\n ['квадрилион', 'квадрилиона', 'квадрилиона'],\n ['квадрилијарда', 'квадрилијарде', 'квадрилијарда']\n]\n\n// ============================================================================\n// Precomputed Lookup Tables\n// ============================================================================\n\nconst { masc: SEGMENTS_MASC, fem: SEGMENTS_FEM } = buildAllSegments(ONES_MASC, ONES_FEM, TEENS, TENS, HUNDREDS)\n\n// ============================================================================\n// Conversion Functions\n// ============================================================================\n\nfunction integerToWords (n, options = {}) {\n if (n === 0n) return ZERO\n\n if (n < 1000n) {\n const segments = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC\n return segments[Number(n)]\n }\n\n return buildLargeNumberWords(n, options)\n}\n\nfunction buildLargeNumberWords (n, options) {\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 if (scaleIndex === 0) {\n const segmentWords = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC\n parts.push(segmentWords[segment])\n } else {\n const scaleForms = SCALE_FORMS[scaleIndex - 1]\n const scaleWord = pluralize(segment, scaleForms)\n // Thousands (scaleIndex=1) are feminine, others masculine\n const isFeminine = scaleIndex === 1\n const segmentWords = isFeminine ? SEGMENTS_FEM : SEGMENTS_MASC\n parts.push(segmentWords[segment] + ' ' + scaleWord)\n }\n }\n\n scaleIndex--\n }\n\n return parts.join(' ')\n}\n\nfunction decimalPartToWords (decimalPart, options) {\n let result = ''\n let i = 0\n\n while (i < decimalPart.length && decimalPart[i] === '0') {\n if (result) result += ' '\n result += ZERO\n i++\n }\n\n const remainder = decimalPart.slice(i)\n if (remainder) {\n if (result) result += ' '\n result += integerToWords(BigInt(remainder), options)\n }\n\n return result\n}\n\n/**\n * Converts a numeric value to Serbian (Cyrillic) words.\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 Serbian Cyrillic words\n */\nfunction toWords (value, options) {\n options = validateOptions(options)\n const { isNegative, integerPart, decimalPart } = parseNumericValue(value)\n\n let result = ''\n\n if (isNegative) {\n result = NEGATIVE + ' '\n }\n\n result += integerToWords(integerPart, options)\n\n if (decimalPart) {\n result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart, options)\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","pluralize","n","forms","num","Number","lastDigit","lastTwoDigits","buildSegment","ones","teens","tens","hundreds","onesDigit","tensDigit","Math","floor","hundredsDigit","parts","push","join","ZERO","SCALE_FORMS","masc","SEGMENTS_MASC","fem","SEGMENTS_FEM","onesMasc","onesFem","Array","i","buildAllSegments","integerToWords","options","gender","numStr","toString","len","segments","remainderLen","pos","scaleIndex","segment","scaleWord","buildLargeNumberWords","value","undefined","proto","Object","getPrototypeOf","prototype","isPlainObject","TypeError","validateOptions","type","isFinite","Error","isSafeInteger","includes","numberToString","trimmed","trim","isNaN","normalizeString","parseNumericValue","result","NEGATIVE","remainder","decimalPartToWords"],"mappings":"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"}
|