n2words 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/languages/am-Latn.js +2 -2
  3. package/dist/languages/am-Latn.js.map +1 -1
  4. package/dist/languages/am.js +2 -2
  5. package/dist/languages/am.js.map +1 -1
  6. package/dist/languages/ar.js +1 -1
  7. package/dist/languages/az.js +2 -2
  8. package/dist/languages/az.js.map +1 -1
  9. package/dist/languages/bn.js +2 -2
  10. package/dist/languages/bn.js.map +1 -1
  11. package/dist/languages/cs.js +2 -2
  12. package/dist/languages/cs.js.map +1 -1
  13. package/dist/languages/da.js +2 -2
  14. package/dist/languages/da.js.map +1 -1
  15. package/dist/languages/de.js +2 -2
  16. package/dist/languages/de.js.map +1 -1
  17. package/dist/languages/el.js +2 -2
  18. package/dist/languages/el.js.map +1 -1
  19. package/dist/languages/en.js +2 -2
  20. package/dist/languages/en.js.map +1 -1
  21. package/dist/languages/es.js +2 -2
  22. package/dist/languages/es.js.map +1 -1
  23. package/dist/languages/fa.js +1 -1
  24. package/dist/languages/fi.js +2 -2
  25. package/dist/languages/fi.js.map +1 -1
  26. package/dist/languages/fil.js +2 -2
  27. package/dist/languages/fil.js.map +1 -1
  28. package/dist/languages/fr-BE.js +2 -2
  29. package/dist/languages/fr-BE.js.map +1 -1
  30. package/dist/languages/fr.js +2 -2
  31. package/dist/languages/fr.js.map +1 -1
  32. package/dist/languages/gu.js +2 -2
  33. package/dist/languages/gu.js.map +1 -1
  34. package/dist/languages/ha.js +2 -2
  35. package/dist/languages/ha.js.map +1 -1
  36. package/dist/languages/hbo.js +2 -2
  37. package/dist/languages/hbo.js.map +1 -1
  38. package/dist/languages/he.js +2 -2
  39. package/dist/languages/he.js.map +1 -1
  40. package/dist/languages/hi.js +2 -2
  41. package/dist/languages/hi.js.map +1 -1
  42. package/dist/languages/hr.js +2 -2
  43. package/dist/languages/hr.js.map +1 -1
  44. package/dist/languages/hu.js +1 -1
  45. package/dist/languages/id.js +2 -2
  46. package/dist/languages/id.js.map +1 -1
  47. package/dist/languages/it.js +2 -2
  48. package/dist/languages/it.js.map +1 -1
  49. package/dist/languages/ja.js +2 -2
  50. package/dist/languages/ja.js.map +1 -1
  51. package/dist/languages/ka.js +3 -0
  52. package/dist/languages/ka.js.map +1 -0
  53. package/dist/languages/kn.js +2 -2
  54. package/dist/languages/kn.js.map +1 -1
  55. package/dist/languages/ko.js +2 -2
  56. package/dist/languages/ko.js.map +1 -1
  57. package/dist/languages/lt.js +2 -2
  58. package/dist/languages/lt.js.map +1 -1
  59. package/dist/languages/lv.js +2 -2
  60. package/dist/languages/lv.js.map +1 -1
  61. package/dist/languages/mr.js +2 -2
  62. package/dist/languages/mr.js.map +1 -1
  63. package/dist/languages/ms.js +2 -2
  64. package/dist/languages/ms.js.map +1 -1
  65. package/dist/languages/nb.js +2 -2
  66. package/dist/languages/nb.js.map +1 -1
  67. package/dist/languages/nl.js +2 -2
  68. package/dist/languages/nl.js.map +1 -1
  69. package/dist/languages/pa.js +2 -2
  70. package/dist/languages/pa.js.map +1 -1
  71. package/dist/languages/pl.js +2 -2
  72. package/dist/languages/pl.js.map +1 -1
  73. package/dist/languages/pt.js +2 -2
  74. package/dist/languages/pt.js.map +1 -1
  75. package/dist/languages/ro.js +1 -1
  76. package/dist/languages/ru.js +2 -2
  77. package/dist/languages/ru.js.map +1 -1
  78. package/dist/languages/sr-Cyrl.js +2 -2
  79. package/dist/languages/sr-Cyrl.js.map +1 -1
  80. package/dist/languages/sr-Latn.js +2 -2
  81. package/dist/languages/sr-Latn.js.map +1 -1
  82. package/dist/languages/sv.js +2 -2
  83. package/dist/languages/sv.js.map +1 -1
  84. package/dist/languages/sw.js +1 -1
  85. package/dist/languages/ta.js +2 -2
  86. package/dist/languages/ta.js.map +1 -1
  87. package/dist/languages/te.js +2 -2
  88. package/dist/languages/te.js.map +1 -1
  89. package/dist/languages/th.js +1 -1
  90. package/dist/languages/tr.js +2 -2
  91. package/dist/languages/tr.js.map +1 -1
  92. package/dist/languages/uk.js +2 -2
  93. package/dist/languages/uk.js.map +1 -1
  94. package/dist/languages/ur.js +2 -2
  95. package/dist/languages/ur.js.map +1 -1
  96. package/dist/languages/vi.js +2 -2
  97. package/dist/languages/vi.js.map +1 -1
  98. package/dist/languages/yo.js +3 -0
  99. package/dist/languages/yo.js.map +1 -0
  100. package/dist/languages/zh-Hans.js +1 -1
  101. package/dist/languages/zh-Hant.js +1 -1
  102. package/dist/n2words.js +2 -2
  103. package/dist/n2words.js.map +1 -1
  104. package/lib/languages/am-Latn.js +4 -9
  105. package/lib/languages/am.js +4 -9
  106. package/lib/languages/az.js +4 -9
  107. package/lib/languages/bn.js +51 -30
  108. package/lib/languages/cs.js +9 -26
  109. package/lib/languages/da.js +6 -13
  110. package/lib/languages/de.js +21 -33
  111. package/lib/languages/el.js +6 -13
  112. package/lib/languages/en.js +44 -58
  113. package/lib/languages/es.js +10 -30
  114. package/lib/languages/fi.js +6 -13
  115. package/lib/languages/fil.js +6 -17
  116. package/lib/languages/fr-BE.js +17 -26
  117. package/lib/languages/fr.js +18 -31
  118. package/lib/languages/gu.js +43 -30
  119. package/lib/languages/ha.js +4 -9
  120. package/lib/languages/hbo.js +7 -25
  121. package/lib/languages/he.js +7 -18
  122. package/lib/languages/hi.js +55 -30
  123. package/lib/languages/hr.js +61 -55
  124. package/lib/languages/id.js +8 -13
  125. package/lib/languages/it.js +64 -99
  126. package/lib/languages/ja.js +8 -20
  127. package/lib/languages/ka.d.ts +17 -0
  128. package/lib/languages/ka.js +291 -0
  129. package/lib/languages/kn.js +43 -30
  130. package/lib/languages/ko.js +6 -13
  131. package/lib/languages/lt.js +6 -15
  132. package/lib/languages/lv.js +6 -15
  133. package/lib/languages/mr.js +43 -30
  134. package/lib/languages/ms.js +8 -13
  135. package/lib/languages/nb.js +13 -23
  136. package/lib/languages/nl.js +11 -29
  137. package/lib/languages/pa.js +32 -37
  138. package/lib/languages/pl.js +7 -20
  139. package/lib/languages/pt.js +17 -31
  140. package/lib/languages/ru.js +64 -59
  141. package/lib/languages/sr-Cyrl.js +58 -52
  142. package/lib/languages/sr-Latn.js +58 -52
  143. package/lib/languages/sv.js +14 -24
  144. package/lib/languages/ta.js +55 -42
  145. package/lib/languages/te.js +33 -41
  146. package/lib/languages/tr.js +7 -18
  147. package/lib/languages/uk.js +61 -55
  148. package/lib/languages/ur.js +32 -37
  149. package/lib/languages/vi.js +23 -43
  150. package/lib/languages/yo.d.ts +7 -0
  151. package/lib/languages/yo.js +303 -0
  152. package/lib/n2words.d.ts +3 -1
  153. package/lib/n2words.js +4 -0
  154. package/package.json +1 -1
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * Tamil language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter for South Asian numbering.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
6
  * Key features:
7
7
  * - Indian numbering system (ஆயிரம், லட்சம், கோடி)
8
8
  * - Tamil script
9
9
  * - 3-2-2 grouping pattern
10
10
  * - Complete word forms for 0-99
11
- * - Special hundred word transformations
11
+ * - Special hundred word transformations (connected vs standalone)
12
12
  * - Per-digit decimal reading
13
+ * - BigInt modulo for efficient segment extraction
13
14
  */
14
15
 
15
16
  import { parseNumericValue } from '../utils/parse-numeric.js'
@@ -48,35 +49,16 @@ const ONES = ['ஒன்று', 'இரண்டு', 'மூன்று', '
48
49
  const SCALE_WORDS = ['', 'ஆயிரம்', 'லட்சம்', 'கோடி', 'அரபு', 'கராபு', 'நீல்', 'பத்ம', 'சங்கு']
49
50
 
50
51
  // ============================================================================
51
- // Segment Splitting (inlined for performance)
52
+ // Segment Building
52
53
  // ============================================================================
53
54
 
54
- function groupByThreeThenTwos (n) {
55
- const numStr = n.toString()
56
-
57
- if (numStr.length <= 3) {
58
- return [Number(numStr)]
59
- }
60
-
61
- const segments = []
62
- const last3 = numStr.slice(-3)
63
- segments.unshift(Number(last3))
64
-
65
- let remaining = numStr.slice(0, -3)
66
- while (remaining.length > 0) {
67
- const segment = remaining.slice(-2)
68
- segments.unshift(Number(segment))
69
- remaining = remaining.slice(0, -2)
70
- }
71
-
72
- return segments
73
- }
74
-
75
- // ============================================================================
76
- // Conversion Functions
77
- // ============================================================================
78
-
79
- function convertBelowThousand (n) {
55
+ /**
56
+ * Builds words for a 0-999 segment.
57
+ *
58
+ * @param {number} n - Number 0-999
59
+ * @returns {string} Tamil words for the segment
60
+ */
61
+ function buildSegment (n) {
80
62
  if (n === 0) return ''
81
63
  if (n < 100) return BELOW_HUNDRED[n]
82
64
 
@@ -87,30 +69,61 @@ function convertBelowThousand (n) {
87
69
  return HUNDREDS[hundreds]
88
70
  }
89
71
 
90
- // Use precomputed connected form
72
+ // Use connected form when followed by remainder
91
73
  return HUNDREDS_CONNECTED[hundreds] + ' ' + BELOW_HUNDRED[remainder]
92
74
  }
93
75
 
76
+ // ============================================================================
77
+ // Conversion Functions
78
+ // ============================================================================
79
+
80
+ /**
81
+ * Converts a non-negative integer to Tamil words.
82
+ *
83
+ * Uses BigInt modulo for segment extraction (faster than string slicing).
84
+ * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
85
+ *
86
+ * @param {bigint} n - Non-negative integer to convert
87
+ * @returns {string} Tamil words
88
+ */
94
89
  function integerToWords (n) {
95
90
  if (n === 0n) return ZERO
96
91
 
97
- const groups = groupByThreeThenTwos(n)
98
- const groupCount = groups.length
99
- const words = []
92
+ // Fast path: numbers < 1000
93
+ if (n < 1000n) {
94
+ return buildSegment(Number(n))
95
+ }
96
+
97
+ // Extract segments using BigInt modulo
98
+ const segments = []
99
+ segments.push(Number(n % 1000n))
100
+ let temp = n / 1000n
100
101
 
101
- for (let i = 0; i < groupCount; i++) {
102
- const groupValue = groups[i]
103
- if (groupValue === 0) continue
102
+ while (temp > 0n) {
103
+ segments.push(Number(temp % 100n))
104
+ temp = temp / 100n
105
+ }
106
+
107
+ // Build result string (process from most-significant to least)
108
+ const words = []
109
+ for (let i = segments.length - 1; i >= 0; i--) {
110
+ const segment = segments[i]
111
+ if (segment === 0) continue
112
+
113
+ if (i === 0) {
114
+ words.push(buildSegment(segment))
115
+ } else {
116
+ // Use 'ஒரு' for 1 at scale positions
117
+ const groupWords = (segment === 1) ? 'ஒரு' : BELOW_HUNDRED[segment]
118
+ words.push(groupWords)
119
+ }
104
120
 
105
- const scaleIndex = groupCount - i - 1
106
- const groupWords = (groupValue === 1 && scaleIndex > 0) ? 'ஒரு' : convertBelowThousand(groupValue)
107
- words.push(groupWords)
108
- if (scaleIndex > 0 && SCALE_WORDS[scaleIndex]) {
109
- words.push(SCALE_WORDS[scaleIndex])
121
+ if (i > 0 && SCALE_WORDS[i]) {
122
+ words.push(SCALE_WORDS[i])
110
123
  }
111
124
  }
112
125
 
113
- return words.join(' ').trim()
126
+ return words.join(' ')
114
127
  }
115
128
 
116
129
  function decimalPartToWords (decimalPart) {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Telugu language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter for South Asian numbering.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
6
  * Key features:
7
7
  * - Indian numbering system (వెయ్యి, లక్ష, కోటి)
@@ -34,10 +34,6 @@ const BELOW_HUNDRED = [
34
34
  'తొంభై', 'తొంభై ఒకటి', 'తొంభై రెండు', 'తొంభై మూడు', 'తొంభై నాలుగు', 'తొంభై ఐదు', 'తొంభై ఆరు', 'తొంభై ఏడు', 'తొంభై ఎనిమిది', 'తొంభై తొమ్మిది'
35
35
  ]
36
36
 
37
- // ============================================================================
38
- // Vocabulary (continued)
39
- // ============================================================================
40
-
41
37
  const HUNDREDS = ['', 'వంద', 'రెండు వందలు', 'మూడు వందలు', 'నాలుగు వందలు', 'ఐదు వందలు', 'ఆరు వందలు', 'ఏడు వందలు', 'ఎనిమిది వందలు', 'తొమ్మిది వందలు']
42
38
 
43
39
  // Ones for decimal reading
@@ -47,13 +43,13 @@ const ONES = ['ఒకటి', 'రెండు', 'మూడు', 'నాలు
47
43
  const SCALE_WORDS = ['', 'వెయ్యి', 'లక్ష', 'కోటి', 'అరబ్', 'ఖరబ్', 'నిల్', 'పడ్మ', 'శంకు']
48
44
 
49
45
  // ============================================================================
50
- // Conversion Functions
46
+ // Segment Building
51
47
  // ============================================================================
52
48
 
53
49
  /**
54
- * Converts 0-999 to Telugu words.
50
+ * Builds words for a 0-999 segment.
55
51
  */
56
- function convertBelowThousand (n) {
52
+ function buildSegment (n) {
57
53
  if (n === 0) return ''
58
54
  if (n < 100) return BELOW_HUNDRED[n]
59
55
 
@@ -66,9 +62,15 @@ function convertBelowThousand (n) {
66
62
  return HUNDREDS[hundreds] + ' ' + BELOW_HUNDRED[remainder]
67
63
  }
68
64
 
65
+ // ============================================================================
66
+ // Conversion Functions
67
+ // ============================================================================
68
+
69
69
  /**
70
70
  * Converts a non-negative integer to Telugu words.
71
- * Uses recursive approach for Indian 3-2-2 grouping pattern.
71
+ *
72
+ * Uses BigInt modulo for segment extraction (faster than string slicing).
73
+ * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
72
74
  *
73
75
  * @param {bigint} n - Non-negative integer to convert
74
76
  * @returns {string} Telugu words
@@ -78,49 +80,39 @@ function integerToWords (n) {
78
80
 
79
81
  // Fast path: numbers < 1000 (direct lookup)
80
82
  if (n < 1000n) {
81
- return convertBelowThousand(Number(n))
83
+ return buildSegment(Number(n))
82
84
  }
83
85
 
84
- return buildLargeNumberWords(n, 0)
85
- }
86
-
87
- /**
88
- * Recursively builds words for numbers >= 1000.
89
- * Indian grouping: first 3 digits, then 2-digit groups.
90
- *
91
- * @param {bigint} n - Number to convert
92
- * @param {number} scale - Current scale index (0=units, 1=thousands, etc.)
93
- * @returns {string} Telugu words
94
- */
95
- function buildLargeNumberWords (n, scale) {
96
- if (n === 0n) return ''
97
-
98
- // Determine divisor: 1000 for first split, 100 for rest
99
- const divisor = scale === 0 ? 1000n : 100n
100
- const segment = Number(n % divisor)
101
- const rest = n / divisor
86
+ // Extract segments using BigInt modulo
87
+ const segments = []
88
+ segments.push(Number(n % 1000n))
89
+ let temp = n / 1000n
102
90
 
103
- // Build higher segments first (recursive)
104
- let result = ''
105
- if (rest > 0n) {
106
- result = buildLargeNumberWords(rest, scale + 1)
91
+ while (temp > 0n) {
92
+ segments.push(Number(temp % 100n))
93
+ temp = temp / 100n
107
94
  }
108
95
 
109
- // Add current segment
110
- if (segment > 0) {
111
- if (result) result += ' '
96
+ // Build result string (process from most-significant to least)
97
+ const words = []
98
+ for (let i = segments.length - 1; i >= 0; i--) {
99
+ const segment = segments[i]
100
+ if (segment === 0) continue
112
101
 
113
- if (scale === 0) {
114
- // Units segment (0-999)
115
- result += convertBelowThousand(segment)
102
+ if (i === 0) {
103
+ words.push(buildSegment(segment))
116
104
  } else {
117
- // Scale segments (0-99)
105
+ // Use 'ఒక' for 1 at scale positions
118
106
  const groupWords = (segment === 1) ? 'ఒక' : BELOW_HUNDRED[segment]
119
- result += groupWords + ' ' + SCALE_WORDS[scale]
107
+ words.push(groupWords)
108
+ }
109
+
110
+ if (i > 0 && SCALE_WORDS[i]) {
111
+ words.push(SCALE_WORDS[i])
120
112
  }
121
113
  }
122
114
 
123
- return result
115
+ return words.join(' ')
124
116
  }
125
117
 
126
118
  function decimalPartToWords (decimalPart) {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Turkish language converter - Functional Implementation
3
3
  *
4
- * A performance-optimized number-to-words converter using precomputed lookup tables.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
6
  * Key features:
7
7
  * - Omits 'bir' (one) before hundreds and thousands
@@ -32,7 +32,7 @@ const DECIMAL_SEP = 'virgül'
32
32
  const SCALES = ['milyon', 'milyar', 'trilyon', 'katrilyon', 'kentilyon']
33
33
 
34
34
  // ============================================================================
35
- // Precomputed Lookup Tables (built once at module load)
35
+ // Segment Building
36
36
  // ============================================================================
37
37
 
38
38
  /**
@@ -75,15 +75,6 @@ function buildSegment (n, separator = ' ') {
75
75
  return parts.join(separator)
76
76
  }
77
77
 
78
- // Precompute all 1000 segment words (0-999) with space separator
79
- const SEGMENTS = new Array(1000)
80
- const SEGMENTS_NO_SPACE = new Array(1000)
81
-
82
- for (let i = 0; i < 1000; i++) {
83
- SEGMENTS[i] = buildSegment(i, ' ')
84
- SEGMENTS_NO_SPACE[i] = buildSegment(i, '')
85
- }
86
-
87
78
  // ============================================================================
88
79
  // Conversion Functions
89
80
  // ============================================================================
@@ -99,11 +90,10 @@ function integerToWords (n, options = {}) {
99
90
  if (n === 0n) return ZERO
100
91
 
101
92
  const sep = options.dropSpaces ? '' : ' '
102
- const segments = options.dropSpaces ? SEGMENTS_NO_SPACE : SEGMENTS
103
93
 
104
- // Fast path: numbers < 1000 (direct lookup)
94
+ // Fast path: numbers < 1000
105
95
  if (n < 1000n) {
106
- return segments[Number(n)]
96
+ return buildSegment(Number(n), sep)
107
97
  }
108
98
 
109
99
  // Fast path: numbers < 1,000,000 (thousands)
@@ -116,11 +106,11 @@ function integerToWords (n, options = {}) {
116
106
  if (thousands === 1) {
117
107
  result = THOUSAND
118
108
  } else {
119
- result = segments[thousands] + sep + THOUSAND
109
+ result = buildSegment(thousands, sep) + sep + THOUSAND
120
110
  }
121
111
 
122
112
  if (remainder > 0) {
123
- result += sep + segments[remainder]
113
+ result += sep + buildSegment(remainder, sep)
124
114
  }
125
115
 
126
116
  return result
@@ -139,7 +129,6 @@ function integerToWords (n, options = {}) {
139
129
  */
140
130
  function buildLargeNumberWords (n, options) {
141
131
  const sep = options.dropSpaces ? '' : ' '
142
- const segmentsArr = options.dropSpaces ? SEGMENTS_NO_SPACE : SEGMENTS
143
132
 
144
133
  const numStr = n.toString()
145
134
  const len = numStr.length
@@ -167,7 +156,7 @@ function buildLargeNumberWords (n, options) {
167
156
  const segment = segments[i]
168
157
 
169
158
  if (segment !== 0) {
170
- const segmentWord = segmentsArr[segment]
159
+ const segmentWord = buildSegment(segment, sep)
171
160
 
172
161
  if (scaleIndex === 0) {
173
162
  // Units segment
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Ukrainian language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter using shared Slavic utilities.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
6
  * Key features:
7
7
  * - Three-form pluralization (one/few/many)
@@ -14,7 +14,38 @@ import { parseNumericValue } from '../utils/parse-numeric.js'
14
14
  import { validateOptions } from '../utils/validate-options.js'
15
15
 
16
16
  // ============================================================================
17
- // Slavic Utilities (inlined for performance)
17
+ // Vocabulary
18
+ // ============================================================================
19
+
20
+ const ONES_MASC = ['', 'один', 'два', 'три', 'чотири', 'п\'ять', 'шiсть', 'сiм', 'вiсiм', 'дев\'ять']
21
+ const ONES_FEM = ['', 'одна', 'двi', 'три', 'чотири', 'п\'ять', 'шiсть', 'сiм', 'вiсiм', 'дев\'ять']
22
+
23
+ const TEENS = ['десять', 'одинадцять', 'дванадцять', 'тринадцять', 'чотирнадцять', 'п\'ятнадцять', 'шiстнадцять', 'сiмнадцять', 'вiсiмнадцять', 'дев\'ятнадцять']
24
+ const TENS = ['', '', 'двадцять', 'тридцять', 'сорок', 'п\'ятдесят', 'шiстдесят', 'сiмдесят', 'вiсiмдесят', 'дев\'яносто']
25
+
26
+ // Irregular hundreds
27
+ const HUNDREDS = ['', 'сто', 'двiстi', 'триста', 'чотириста', 'п\'ятсот', 'шiстсот', 'сiмсот', 'вiсiмсот', 'дев\'ятсот']
28
+
29
+ const ZERO = 'нуль'
30
+ const NEGATIVE = 'мiнус'
31
+ const DECIMAL_SEP = 'кома'
32
+
33
+ // Scale words: [singular, few, many]
34
+ // Thousands (index 0) are feminine, rest are masculine
35
+ const SCALE_FORMS = [
36
+ ['тисяча', 'тисячi', 'тисяч'],
37
+ ['мiльйон', 'мiльйони', 'мiльйонiв'],
38
+ ['мiльярд', 'мiльярди', 'мiльярдiв'],
39
+ ['трильйон', 'трильйони', 'трильйонiв'],
40
+ ['квадрильйон', 'квадрильйони', 'квадрильйонiв'],
41
+ ['квiнтильйон', 'квiнтильйони', 'квiнтильйонiв'],
42
+ ['секстильйон', 'секстильйони', 'секстильйонiв'],
43
+ ['септильйон', 'септильйони', 'септильйонiв'],
44
+ ['октильйон', 'октильйони', 'октильйонiв']
45
+ ]
46
+
47
+ // ============================================================================
48
+ // Segment Building
18
49
  // ============================================================================
19
50
 
20
51
  function pluralize (n, forms) {
@@ -31,19 +62,7 @@ function pluralize (n, forms) {
31
62
  return forms[2]
32
63
  }
33
64
 
34
- function buildAllSegments (onesMasc, onesFem, teens, tens, hundreds) {
35
- const masc = new Array(1000)
36
- const fem = new Array(1000)
37
-
38
- for (let i = 0; i < 1000; i++) {
39
- masc[i] = buildSegment(i, onesMasc, teens, tens, hundreds)
40
- fem[i] = buildSegment(i, onesFem, teens, tens, hundreds)
41
- }
42
-
43
- return { masc, fem }
44
- }
45
-
46
- function buildSegment (n, ones, teens, tens, hundreds) {
65
+ function buildSegmentMasc (n) {
47
66
  if (n === 0) return ''
48
67
 
49
68
  const onesDigit = n % 10
@@ -53,58 +72,47 @@ function buildSegment (n, ones, teens, tens, hundreds) {
53
72
  const parts = []
54
73
 
55
74
  if (hundredsDigit > 0) {
56
- parts.push(hundreds[hundredsDigit])
75
+ parts.push(HUNDREDS[hundredsDigit])
57
76
  }
58
77
 
59
78
  if (tensDigit > 1) {
60
- parts.push(tens[tensDigit])
79
+ parts.push(TENS[tensDigit])
61
80
  }
62
81
 
63
82
  if (tensDigit === 1) {
64
- parts.push(teens[onesDigit])
83
+ parts.push(TEENS[onesDigit])
65
84
  } else if (onesDigit > 0) {
66
- parts.push(ones[onesDigit])
85
+ parts.push(ONES_MASC[onesDigit])
67
86
  }
68
87
 
69
88
  return parts.join(' ')
70
89
  }
71
90
 
72
- // ============================================================================
73
- // Vocabulary
74
- // ============================================================================
75
-
76
- const ONES_MASC = ['', 'один', 'два', 'три', 'чотири', 'п\'ять', 'шiсть', 'сiм', 'вiсiм', 'дев\'ять']
77
- const ONES_FEM = ['', 'одна', 'двi', 'три', 'чотири', 'п\'ять', 'шiсть', 'сiм', 'вiсiм', 'дев\'ять']
91
+ function buildSegmentFem (n) {
92
+ if (n === 0) return ''
78
93
 
79
- const TEENS = ['десять', 'одинадцять', 'дванадцять', 'тринадцять', 'чотирнадцять', 'п\'ятнадцять', 'шiстнадцять', 'сiмнадцять', 'вiсiмнадцять', 'дев\'ятнадцять']
80
- const TENS = ['', '', 'двадцять', 'тридцять', 'сорок', 'п\'ятдесят', 'шiстдесят', 'сiмдесят', 'вiсiмдесят', 'дев\'яносто']
94
+ const onesDigit = n % 10
95
+ const tensDigit = Math.floor(n / 10) % 10
96
+ const hundredsDigit = Math.floor(n / 100)
81
97
 
82
- // Irregular hundreds
83
- const HUNDREDS = ['', 'сто', 'двiстi', 'триста', 'чотириста', 'п\'ятсот', 'шiстсот', 'сiмсот', 'вiсiмсот', 'дев\'ятсот']
98
+ const parts = []
84
99
 
85
- const ZERO = 'нуль'
86
- const NEGATIVE = 'мiнус'
87
- const DECIMAL_SEP = 'кома'
100
+ if (hundredsDigit > 0) {
101
+ parts.push(HUNDREDS[hundredsDigit])
102
+ }
88
103
 
89
- // Scale words: [singular, few, many]
90
- // Thousands (index 0) are feminine, rest are masculine
91
- const SCALE_FORMS = [
92
- ['тисяча', 'тисячi', 'тисяч'],
93
- ['мiльйон', 'мiльйони', 'мiльйонiв'],
94
- ['мiльярд', 'мiльярди', 'мiльярдiв'],
95
- ['трильйон', 'трильйони', 'трильйонiв'],
96
- ['квадрильйон', 'квадрильйони', 'квадрильйонiв'],
97
- ['квiнтильйон', 'квiнтильйони', 'квiнтильйонiв'],
98
- ['секстильйон', 'секстильйони', 'секстильйонiв'],
99
- ['септильйон', 'септильйони', 'септильйонiв'],
100
- ['октильйон', 'октильйони', 'октильйонiв']
101
- ]
104
+ if (tensDigit > 1) {
105
+ parts.push(TENS[tensDigit])
106
+ }
102
107
 
103
- // ============================================================================
104
- // Precomputed Lookup Tables
105
- // ============================================================================
108
+ if (tensDigit === 1) {
109
+ parts.push(TEENS[onesDigit])
110
+ } else if (onesDigit > 0) {
111
+ parts.push(ONES_FEM[onesDigit])
112
+ }
106
113
 
107
- const { masc: SEGMENTS_MASC, fem: SEGMENTS_FEM } = buildAllSegments(ONES_MASC, ONES_FEM, TEENS, TENS, HUNDREDS)
114
+ return parts.join(' ')
115
+ }
108
116
 
109
117
  // ============================================================================
110
118
  // Conversion Functions
@@ -114,8 +122,7 @@ function integerToWords (n, options = {}) {
114
122
  if (n === 0n) return ZERO
115
123
 
116
124
  if (n < 1000n) {
117
- const segments = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
118
- return segments[Number(n)]
125
+ return options.gender === 'feminine' ? buildSegmentFem(Number(n)) : buildSegmentMasc(Number(n))
119
126
  }
120
127
 
121
128
  return buildLargeNumberWords(n, options)
@@ -147,15 +154,14 @@ function buildLargeNumberWords (n, options) {
147
154
 
148
155
  if (segment !== 0) {
149
156
  if (scaleIndex === 0) {
150
- const segmentWords = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
151
- parts.push(segmentWords[segment])
157
+ parts.push(options.gender === 'feminine' ? buildSegmentFem(segment) : buildSegmentMasc(segment))
152
158
  } else {
153
159
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
154
160
  const scaleWord = pluralize(segment, scaleForms)
155
161
  // Thousands (scaleIndex=1) are feminine, others masculine
156
162
  const isFeminine = scaleIndex === 1
157
- const segmentWords = isFeminine ? SEGMENTS_FEM : SEGMENTS_MASC
158
- parts.push(segmentWords[segment] + ' ' + scaleWord)
163
+ const segmentWord = isFeminine ? buildSegmentFem(segment) : buildSegmentMasc(segment)
164
+ parts.push(segmentWord + ' ' + scaleWord)
159
165
  }
160
166
  }
161
167
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Urdu language converter - Functional Implementation
3
3
  *
4
- * Self-contained converter for South Asian numbering.
4
+ * Self-contained module with its own input validation, ready for subpath exports.
5
5
  *
6
6
  * Key features:
7
7
  * - Indian numbering system (ہزار, لاکھ, کروڑ)
@@ -38,13 +38,13 @@ const BELOW_HUNDRED = [
38
38
  const SCALE_WORDS = ['', 'ہزار', 'لاکھ', 'کروڑ', 'ارب', 'کھرب', 'نیل', 'پدم', 'شنکھ']
39
39
 
40
40
  // ============================================================================
41
- // Conversion Functions
41
+ // Segment Building
42
42
  // ============================================================================
43
43
 
44
44
  /**
45
- * Converts 0-999 to Urdu words.
45
+ * Builds words for a 0-999 segment.
46
46
  */
47
- function segmentToWords (n) {
47
+ function buildSegment (n) {
48
48
  if (n === 0) return ''
49
49
  if (n < 100) return BELOW_HUNDRED[n]
50
50
 
@@ -57,9 +57,15 @@ function segmentToWords (n) {
57
57
  return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED + ' ' + BELOW_HUNDRED[remainder]
58
58
  }
59
59
 
60
+ // ============================================================================
61
+ // Conversion Functions
62
+ // ============================================================================
63
+
60
64
  /**
61
65
  * Converts a non-negative integer to Urdu words.
62
- * Uses recursive approach for Indian 3-2-2 grouping pattern.
66
+ *
67
+ * Uses BigInt modulo for segment extraction (faster than string slicing).
68
+ * South Asian 3-2-2 grouping: first 3 digits, then groups of 2.
63
69
  *
64
70
  * @param {bigint} n - Non-negative integer to convert
65
71
  * @returns {string} Urdu words
@@ -69,48 +75,37 @@ function integerToWords (n) {
69
75
 
70
76
  // Fast path: numbers < 1000 (direct lookup)
71
77
  if (n < 1000n) {
72
- return segmentToWords(Number(n))
78
+ return buildSegment(Number(n))
73
79
  }
74
80
 
75
- return buildLargeNumberWords(n, 0)
76
- }
77
-
78
- /**
79
- * Recursively builds words for numbers >= 1000.
80
- * Indian grouping: first 3 digits, then 2-digit groups.
81
- *
82
- * @param {bigint} n - Number to convert
83
- * @param {number} scale - Current scale index (0=units, 1=thousands, etc.)
84
- * @returns {string} Urdu words
85
- */
86
- function buildLargeNumberWords (n, scale) {
87
- if (n === 0n) return ''
88
-
89
- // Determine divisor: 1000 for first split, 100 for rest
90
- const divisor = scale === 0 ? 1000n : 100n
91
- const segment = Number(n % divisor)
92
- const rest = n / divisor
81
+ // Extract segments using BigInt modulo
82
+ const segments = []
83
+ segments.push(Number(n % 1000n))
84
+ let temp = n / 1000n
93
85
 
94
- // Build higher segments first (recursive)
95
- let result = ''
96
- if (rest > 0n) {
97
- result = buildLargeNumberWords(rest, scale + 1)
86
+ while (temp > 0n) {
87
+ segments.push(Number(temp % 100n))
88
+ temp = temp / 100n
98
89
  }
99
90
 
100
- // Add current segment
101
- if (segment > 0) {
102
- if (result) result += ' '
91
+ // Build result string (process from most-significant to least)
92
+ const words = []
93
+ for (let i = segments.length - 1; i >= 0; i--) {
94
+ const segment = segments[i]
95
+ if (segment === 0) continue
103
96
 
104
- if (scale === 0) {
105
- // Units segment (0-999)
106
- result += segmentToWords(segment)
97
+ if (i === 0) {
98
+ words.push(buildSegment(segment))
107
99
  } else {
108
- // Scale segments (0-99)
109
- result += BELOW_HUNDRED[segment] + ' ' + SCALE_WORDS[scale]
100
+ words.push(BELOW_HUNDRED[segment])
101
+ }
102
+
103
+ if (i > 0 && SCALE_WORDS[i]) {
104
+ words.push(SCALE_WORDS[i])
110
105
  }
111
106
  }
112
107
 
113
- return result
108
+ return words.join(' ')
114
109
  }
115
110
 
116
111
  function decimalPartToWords (decimalPart) {