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,7 +1,7 @@
1
1
  /**
2
2
  * Russian 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,39 @@ 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 = ['', 'один', 'два', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять']
21
+ const ONES_FEM = ['', 'одна', 'две', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять']
22
+
23
+ const TEENS = ['десять', 'одиннадцать', 'двенадцать', 'тринадцать', 'четырнадцать', 'пятнадцать', 'шестнадцать', 'семнадцать', 'восемнадцать', 'девятнадцать']
24
+ const TENS = ['', '', 'двадцать', 'тридцать', 'сорок', 'пятьдесят', 'шестьдесят', 'семьдесят', 'восемьдесят', 'девяносто']
25
+
26
+ // Irregular hundreds
27
+ const HUNDREDS = ['', 'сто', 'двести', 'триста', 'четыреста', 'пятьсот', 'шестьсот', 'семьсот', 'восемьсот', 'девятьсот']
28
+
29
+ const ZERO = 'ноль'
30
+ const NEGATIVE = 'минус'
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
+ ['тысяча', 'тысячи', 'тысяч'],
37
+ ['миллион', 'миллиона', 'миллионов'],
38
+ ['миллиард', 'миллиарда', 'миллиардов'],
39
+ ['триллион', 'триллиона', 'триллионов'],
40
+ ['квадриллион', 'квадриллиона', 'квадриллионов'],
41
+ ['квинтиллион', 'квинтиллиона', 'квинтиллионов'],
42
+ ['секстиллион', 'секстиллиона', 'секстиллионов'],
43
+ ['септиллион', 'септиллиона', 'септиллионов'],
44
+ ['октиллион', 'октиллиона', 'октиллионов'],
45
+ ['нониллион', 'нониллиона', 'нониллионов']
46
+ ]
47
+
48
+ // ============================================================================
49
+ // Segment Building
18
50
  // ============================================================================
19
51
 
20
52
  function pluralize (n, forms) {
@@ -31,19 +63,7 @@ function pluralize (n, forms) {
31
63
  return forms[2]
32
64
  }
33
65
 
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) {
66
+ function buildSegmentMasc (n) {
47
67
  if (n === 0) return ''
48
68
 
49
69
  const onesDigit = n % 10
@@ -53,59 +73,47 @@ function buildSegment (n, ones, teens, tens, hundreds) {
53
73
  const parts = []
54
74
 
55
75
  if (hundredsDigit > 0) {
56
- parts.push(hundreds[hundredsDigit])
76
+ parts.push(HUNDREDS[hundredsDigit])
57
77
  }
58
78
 
59
79
  if (tensDigit > 1) {
60
- parts.push(tens[tensDigit])
80
+ parts.push(TENS[tensDigit])
61
81
  }
62
82
 
63
83
  if (tensDigit === 1) {
64
- parts.push(teens[onesDigit])
84
+ parts.push(TEENS[onesDigit])
65
85
  } else if (onesDigit > 0) {
66
- parts.push(ones[onesDigit])
86
+ parts.push(ONES_MASC[onesDigit])
67
87
  }
68
88
 
69
89
  return parts.join(' ')
70
90
  }
71
91
 
72
- // ============================================================================
73
- // Vocabulary
74
- // ============================================================================
75
-
76
- const ONES_MASC = ['', 'один', 'два', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять']
77
- const ONES_FEM = ['', 'одна', 'две', 'три', 'четыре', 'пять', 'шесть', 'семь', 'восемь', 'девять']
92
+ function buildSegmentFem (n) {
93
+ if (n === 0) return ''
78
94
 
79
- const TEENS = ['десять', 'одиннадцать', 'двенадцать', 'тринадцать', 'четырнадцать', 'пятнадцать', 'шестнадцать', 'семнадцать', 'восемнадцать', 'девятнадцать']
80
- const TENS = ['', '', 'двадцать', 'тридцать', 'сорок', 'пятьдесят', 'шестьдесят', 'семьдесят', 'восемьдесят', 'девяносто']
95
+ const onesDigit = n % 10
96
+ const tensDigit = Math.floor(n / 10) % 10
97
+ const hundredsDigit = Math.floor(n / 100)
81
98
 
82
- // Irregular hundreds
83
- const HUNDREDS = ['', 'сто', 'двести', 'триста', 'четыреста', 'пятьсот', 'шестьсот', 'семьсот', 'восемьсот', 'девятьсот']
99
+ const parts = []
84
100
 
85
- const ZERO = 'ноль'
86
- const NEGATIVE = 'минус'
87
- const DECIMAL_SEP = 'запятая'
101
+ if (hundredsDigit > 0) {
102
+ parts.push(HUNDREDS[hundredsDigit])
103
+ }
88
104
 
89
- // Scale words: [singular, few, many]
90
- // Thousands (index 0) are feminine, rest are masculine
91
- const SCALE_FORMS = [
92
- ['тысяча', 'тысячи', 'тысяч'],
93
- ['миллион', 'миллиона', 'миллионов'],
94
- ['миллиард', 'миллиарда', 'миллиардов'],
95
- ['триллион', 'триллиона', 'триллионов'],
96
- ['квадриллион', 'квадриллиона', 'квадриллионов'],
97
- ['квинтиллион', 'квинтиллиона', 'квинтиллионов'],
98
- ['секстиллион', 'секстиллиона', 'секстиллионов'],
99
- ['септиллион', 'септиллиона', 'септиллионов'],
100
- ['октиллион', 'октиллиона', 'октиллионов'],
101
- ['нониллион', 'нониллиона', 'нониллионов']
102
- ]
105
+ if (tensDigit > 1) {
106
+ parts.push(TENS[tensDigit])
107
+ }
103
108
 
104
- // ============================================================================
105
- // Precomputed Lookup Tables
106
- // ============================================================================
109
+ if (tensDigit === 1) {
110
+ parts.push(TEENS[onesDigit])
111
+ } else if (onesDigit > 0) {
112
+ parts.push(ONES_FEM[onesDigit])
113
+ }
107
114
 
108
- const { masc: SEGMENTS_MASC, fem: SEGMENTS_FEM } = buildAllSegments(ONES_MASC, ONES_FEM, TEENS, TENS, HUNDREDS)
115
+ return parts.join(' ')
116
+ }
109
117
 
110
118
  // ============================================================================
111
119
  // Conversion Functions
@@ -117,8 +125,7 @@ function integerToWords (n, options = {}) {
117
125
  const feminine = options.gender === 'feminine'
118
126
 
119
127
  if (n < 1000n) {
120
- const segments = feminine ? SEGMENTS_FEM : SEGMENTS_MASC
121
- return segments[Number(n)]
128
+ return feminine ? buildSegmentFem(Number(n)) : buildSegmentMasc(Number(n))
122
129
  }
123
130
 
124
131
  if (n < 1_000_000n) {
@@ -126,14 +133,13 @@ function integerToWords (n, options = {}) {
126
133
  const remainder = Number(n % 1000n)
127
134
 
128
135
  // Thousands are always feminine in Russian
129
- const thousandsWord = SEGMENTS_FEM[thousands]
136
+ const thousandsWord = buildSegmentFem(thousands)
130
137
  const scaleWord = pluralize(thousands, SCALE_FORMS[0])
131
138
 
132
139
  let result = thousandsWord + ' ' + scaleWord
133
140
 
134
141
  if (remainder > 0) {
135
- const segments = feminine ? SEGMENTS_FEM : SEGMENTS_MASC
136
- result += ' ' + segments[remainder]
142
+ result += ' ' + (feminine ? buildSegmentFem(remainder) : buildSegmentMasc(remainder))
137
143
  }
138
144
 
139
145
  return result
@@ -169,15 +175,14 @@ function buildLargeNumberWords (n, options) {
169
175
 
170
176
  if (segment !== 0) {
171
177
  if (scaleIndex === 0) {
172
- const segmentWords = feminine ? SEGMENTS_FEM : SEGMENTS_MASC
173
- parts.push(segmentWords[segment])
178
+ parts.push(feminine ? buildSegmentFem(segment) : buildSegmentMasc(segment))
174
179
  } else {
175
180
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
176
181
  const scaleWord = pluralize(segment, scaleForms)
177
182
  // Thousands (scaleIndex=1) are feminine, others masculine
178
183
  const isFeminine = scaleIndex === 1
179
- const segmentWords = isFeminine ? SEGMENTS_FEM : SEGMENTS_MASC
180
- parts.push(segmentWords[segment] + ' ' + scaleWord)
184
+ const segmentWord = isFeminine ? buildSegmentFem(segment) : buildSegmentMasc(segment)
185
+ parts.push(segmentWord + ' ' + scaleWord)
181
186
  }
182
187
  }
183
188
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Serbian Cyrillic 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)
@@ -15,7 +15,34 @@ import { parseNumericValue } from '../utils/parse-numeric.js'
15
15
  import { validateOptions } from '../utils/validate-options.js'
16
16
 
17
17
  // ============================================================================
18
- // Slavic Utilities (inlined for performance)
18
+ // Vocabulary
19
+ // ============================================================================
20
+
21
+ const ONES_MASC = ['', 'један', 'два', 'три', 'четири', 'пет', 'шест', 'седам', 'осам', 'девет']
22
+ const ONES_FEM = ['', 'једна', 'две', 'три', 'четири', 'пет', 'шест', 'седам', 'осам', 'девет']
23
+ const TEENS = ['десет', 'једанаест', 'дванаест', 'тринаест', 'четрнаест', 'петнаест', 'шеснаест', 'седамнаест', 'осамнаест', 'деветнаест']
24
+ const TENS = ['', '', 'двадесет', 'тридесет', 'четрдесет', 'педесет', 'шездесет', 'седамдесет', 'осамдесет', 'деведесет']
25
+ const HUNDREDS = ['', 'сто', 'двеста', 'триста', 'четиристо', 'петсто', 'шесто', 'седамсто', 'осамсто', 'девестo']
26
+
27
+ const ZERO = 'нула'
28
+ const NEGATIVE = 'минус'
29
+ const DECIMAL_SEP = 'запета'
30
+
31
+ // Scale words: [singular, few, many]
32
+ const SCALE_FORMS = [
33
+ ['хиљада', 'хиљаде', 'хиљада'],
34
+ ['милион', 'милиона', 'милиона'],
35
+ ['милијарда', 'милијарде', 'милијарда'],
36
+ ['билион', 'билиона', 'билиона'],
37
+ ['билијарда', 'билијарде', 'билијарда'],
38
+ ['трилион', 'трилиона', 'трилиона'],
39
+ ['трилијарда', 'трилијарде', 'трилијарда'],
40
+ ['квадрилион', 'квадрилиона', 'квадрилиона'],
41
+ ['квадрилијарда', 'квадрилијарде', 'квадрилијарда']
42
+ ]
43
+
44
+ // ============================================================================
45
+ // Segment Building
19
46
  // ============================================================================
20
47
 
21
48
  function pluralize (n, forms) {
@@ -32,19 +59,7 @@ function pluralize (n, forms) {
32
59
  return forms[2]
33
60
  }
34
61
 
35
- function buildAllSegments (onesMasc, onesFem, teens, tens, hundreds) {
36
- const masc = new Array(1000)
37
- const fem = new Array(1000)
38
-
39
- for (let i = 0; i < 1000; i++) {
40
- masc[i] = buildSegment(i, onesMasc, teens, tens, hundreds)
41
- fem[i] = buildSegment(i, onesFem, teens, tens, hundreds)
42
- }
43
-
44
- return { masc, fem }
45
- }
46
-
47
- function buildSegment (n, ones, teens, tens, hundreds) {
62
+ function buildSegmentMasc (n) {
48
63
  if (n === 0) return ''
49
64
 
50
65
  const onesDigit = n % 10
@@ -54,54 +69,47 @@ function buildSegment (n, ones, teens, tens, hundreds) {
54
69
  const parts = []
55
70
 
56
71
  if (hundredsDigit > 0) {
57
- parts.push(hundreds[hundredsDigit])
72
+ parts.push(HUNDREDS[hundredsDigit])
58
73
  }
59
74
 
60
75
  if (tensDigit > 1) {
61
- parts.push(tens[tensDigit])
76
+ parts.push(TENS[tensDigit])
62
77
  }
63
78
 
64
79
  if (tensDigit === 1) {
65
- parts.push(teens[onesDigit])
80
+ parts.push(TEENS[onesDigit])
66
81
  } else if (onesDigit > 0) {
67
- parts.push(ones[onesDigit])
82
+ parts.push(ONES_MASC[onesDigit])
68
83
  }
69
84
 
70
85
  return parts.join(' ')
71
86
  }
72
87
 
73
- // ============================================================================
74
- // Vocabulary
75
- // ============================================================================
88
+ function buildSegmentFem (n) {
89
+ if (n === 0) return ''
76
90
 
77
- const ONES_MASC = ['', 'један', 'два', 'три', 'четири', 'пет', 'шест', 'седам', 'осам', 'девет']
78
- const ONES_FEM = ['', 'једна', 'две', 'три', 'четири', 'пет', 'шест', 'седам', 'осам', 'девет']
79
- const TEENS = ['десет', 'једанаест', 'дванаест', 'тринаест', 'четрнаест', 'петнаест', 'шеснаест', 'седамнаест', 'осамнаест', 'деветнаест']
80
- const TENS = ['', '', 'двадесет', 'тридесет', 'четрдесет', 'педесет', 'шездесет', 'седамдесет', 'осамдесет', 'деведесет']
81
- const HUNDREDS = ['', 'сто', 'двеста', 'триста', 'четиристо', 'петсто', 'шесто', 'седамсто', 'осамсто', 'девестo']
91
+ const onesDigit = n % 10
92
+ const tensDigit = Math.floor(n / 10) % 10
93
+ const hundredsDigit = Math.floor(n / 100)
82
94
 
83
- const ZERO = 'нула'
84
- const NEGATIVE = 'минус'
85
- const DECIMAL_SEP = 'запета'
95
+ const parts = []
86
96
 
87
- // Scale words: [singular, few, many]
88
- const SCALE_FORMS = [
89
- ['хиљада', 'хиљаде', 'хиљада'],
90
- ['милион', 'милиона', 'милиона'],
91
- ['милијарда', 'милијарде', 'милијарда'],
92
- ['билион', 'билиона', 'билиона'],
93
- ['билијарда', 'билијарде', 'билијарда'],
94
- ['трилион', 'трилиона', 'трилиона'],
95
- ['трилијарда', 'трилијарде', 'трилијарда'],
96
- ['квадрилион', 'квадрилиона', 'квадрилиона'],
97
- ['квадрилијарда', 'квадрилијарде', 'квадрилијарда']
98
- ]
97
+ if (hundredsDigit > 0) {
98
+ parts.push(HUNDREDS[hundredsDigit])
99
+ }
99
100
 
100
- // ============================================================================
101
- // Precomputed Lookup Tables
102
- // ============================================================================
101
+ if (tensDigit > 1) {
102
+ parts.push(TENS[tensDigit])
103
+ }
103
104
 
104
- const { masc: SEGMENTS_MASC, fem: SEGMENTS_FEM } = buildAllSegments(ONES_MASC, ONES_FEM, TEENS, TENS, HUNDREDS)
105
+ if (tensDigit === 1) {
106
+ parts.push(TEENS[onesDigit])
107
+ } else if (onesDigit > 0) {
108
+ parts.push(ONES_FEM[onesDigit])
109
+ }
110
+
111
+ return parts.join(' ')
112
+ }
105
113
 
106
114
  // ============================================================================
107
115
  // Conversion Functions
@@ -111,8 +119,7 @@ function integerToWords (n, options = {}) {
111
119
  if (n === 0n) return ZERO
112
120
 
113
121
  if (n < 1000n) {
114
- const segments = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
115
- return segments[Number(n)]
122
+ return options.gender === 'feminine' ? buildSegmentFem(Number(n)) : buildSegmentMasc(Number(n))
116
123
  }
117
124
 
118
125
  return buildLargeNumberWords(n, options)
@@ -144,15 +151,14 @@ function buildLargeNumberWords (n, options) {
144
151
 
145
152
  if (segment !== 0) {
146
153
  if (scaleIndex === 0) {
147
- const segmentWords = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
148
- parts.push(segmentWords[segment])
154
+ parts.push(options.gender === 'feminine' ? buildSegmentFem(segment) : buildSegmentMasc(segment))
149
155
  } else {
150
156
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
151
157
  const scaleWord = pluralize(segment, scaleForms)
152
158
  // Thousands (scaleIndex=1) are feminine, others masculine
153
159
  const isFeminine = scaleIndex === 1
154
- const segmentWords = isFeminine ? SEGMENTS_FEM : SEGMENTS_MASC
155
- parts.push(segmentWords[segment] + ' ' + scaleWord)
160
+ const segmentWord = isFeminine ? buildSegmentFem(segment) : buildSegmentMasc(segment)
161
+ parts.push(segmentWord + ' ' + scaleWord)
156
162
  }
157
163
  }
158
164
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Serbian Latin 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)
@@ -15,7 +15,34 @@ import { parseNumericValue } from '../utils/parse-numeric.js'
15
15
  import { validateOptions } from '../utils/validate-options.js'
16
16
 
17
17
  // ============================================================================
18
- // Slavic Utilities (inlined for performance)
18
+ // Vocabulary
19
+ // ============================================================================
20
+
21
+ const ONES_MASC = ['', 'jedan', 'dva', 'tri', 'četiri', 'pet', 'šest', 'sedam', 'osam', 'devet']
22
+ const ONES_FEM = ['', 'jedna', 'dve', 'tri', 'četiri', 'pet', 'šest', 'sedam', 'osam', 'devet']
23
+ const TEENS = ['deset', 'jedanaest', 'dvanaest', 'trinaest', 'četrnaest', 'petnaest', 'šesnaest', 'sedamnaest', 'osamnaest', 'devetnaest']
24
+ const TENS = ['', '', 'dvadeset', 'trideset', 'četrdeset', 'pedeset', 'šezdeset', 'sedamdeset', 'osamdeset', 'devedeset']
25
+ const HUNDREDS = ['', 'sto', 'dvesta', 'trista', 'četiristo', 'petsto', 'šesto', 'sedamsto', 'osamsto', 'devetsto']
26
+
27
+ const ZERO = 'nula'
28
+ const NEGATIVE = 'minus'
29
+ const DECIMAL_SEP = 'zapeta'
30
+
31
+ // Scale words: [singular, few, many]
32
+ const SCALE_FORMS = [
33
+ ['hiljada', 'hiljade', 'hiljada'],
34
+ ['milion', 'miliona', 'miliona'],
35
+ ['milijarda', 'milijarde', 'milijarda'],
36
+ ['bilion', 'biliona', 'biliona'],
37
+ ['bilijarda', 'bilijarde', 'bilijarda'],
38
+ ['trilion', 'triliona', 'triliona'],
39
+ ['trilijarda', 'trilijarde', 'trilijarda'],
40
+ ['kvadrilion', 'kvadriliona', 'kvadriliona'],
41
+ ['kvadrilijarda', 'kvadrilijarde', 'kvadrilijarda']
42
+ ]
43
+
44
+ // ============================================================================
45
+ // Segment Building
19
46
  // ============================================================================
20
47
 
21
48
  function pluralize (n, forms) {
@@ -32,19 +59,7 @@ function pluralize (n, forms) {
32
59
  return forms[2]
33
60
  }
34
61
 
35
- function buildAllSegments (onesMasc, onesFem, teens, tens, hundreds) {
36
- const masc = new Array(1000)
37
- const fem = new Array(1000)
38
-
39
- for (let i = 0; i < 1000; i++) {
40
- masc[i] = buildSegment(i, onesMasc, teens, tens, hundreds)
41
- fem[i] = buildSegment(i, onesFem, teens, tens, hundreds)
42
- }
43
-
44
- return { masc, fem }
45
- }
46
-
47
- function buildSegment (n, ones, teens, tens, hundreds) {
62
+ function buildSegmentMasc (n) {
48
63
  if (n === 0) return ''
49
64
 
50
65
  const onesDigit = n % 10
@@ -54,54 +69,47 @@ function buildSegment (n, ones, teens, tens, hundreds) {
54
69
  const parts = []
55
70
 
56
71
  if (hundredsDigit > 0) {
57
- parts.push(hundreds[hundredsDigit])
72
+ parts.push(HUNDREDS[hundredsDigit])
58
73
  }
59
74
 
60
75
  if (tensDigit > 1) {
61
- parts.push(tens[tensDigit])
76
+ parts.push(TENS[tensDigit])
62
77
  }
63
78
 
64
79
  if (tensDigit === 1) {
65
- parts.push(teens[onesDigit])
80
+ parts.push(TEENS[onesDigit])
66
81
  } else if (onesDigit > 0) {
67
- parts.push(ones[onesDigit])
82
+ parts.push(ONES_MASC[onesDigit])
68
83
  }
69
84
 
70
85
  return parts.join(' ')
71
86
  }
72
87
 
73
- // ============================================================================
74
- // Vocabulary
75
- // ============================================================================
88
+ function buildSegmentFem (n) {
89
+ if (n === 0) return ''
76
90
 
77
- const ONES_MASC = ['', 'jedan', 'dva', 'tri', 'četiri', 'pet', 'šest', 'sedam', 'osam', 'devet']
78
- const ONES_FEM = ['', 'jedna', 'dve', 'tri', 'četiri', 'pet', 'šest', 'sedam', 'osam', 'devet']
79
- const TEENS = ['deset', 'jedanaest', 'dvanaest', 'trinaest', 'četrnaest', 'petnaest', 'šesnaest', 'sedamnaest', 'osamnaest', 'devetnaest']
80
- const TENS = ['', '', 'dvadeset', 'trideset', 'četrdeset', 'pedeset', 'šezdeset', 'sedamdeset', 'osamdeset', 'devedeset']
81
- const HUNDREDS = ['', 'sto', 'dvesta', 'trista', 'četiristo', 'petsto', 'šesto', 'sedamsto', 'osamsto', 'devetsto']
91
+ const onesDigit = n % 10
92
+ const tensDigit = Math.floor(n / 10) % 10
93
+ const hundredsDigit = Math.floor(n / 100)
82
94
 
83
- const ZERO = 'nula'
84
- const NEGATIVE = 'minus'
85
- const DECIMAL_SEP = 'zapeta'
95
+ const parts = []
86
96
 
87
- // Scale words: [singular, few, many]
88
- const SCALE_FORMS = [
89
- ['hiljada', 'hiljade', 'hiljada'],
90
- ['milion', 'miliona', 'miliona'],
91
- ['milijarda', 'milijarde', 'milijarda'],
92
- ['bilion', 'biliona', 'biliona'],
93
- ['bilijarda', 'bilijarde', 'bilijarda'],
94
- ['trilion', 'triliona', 'triliona'],
95
- ['trilijarda', 'trilijarde', 'trilijarda'],
96
- ['kvadrilion', 'kvadriliona', 'kvadriliona'],
97
- ['kvadrilijarda', 'kvadrilijarde', 'kvadrilijarda']
98
- ]
97
+ if (hundredsDigit > 0) {
98
+ parts.push(HUNDREDS[hundredsDigit])
99
+ }
99
100
 
100
- // ============================================================================
101
- // Precomputed Lookup Tables
102
- // ============================================================================
101
+ if (tensDigit > 1) {
102
+ parts.push(TENS[tensDigit])
103
+ }
103
104
 
104
- const { masc: SEGMENTS_MASC, fem: SEGMENTS_FEM } = buildAllSegments(ONES_MASC, ONES_FEM, TEENS, TENS, HUNDREDS)
105
+ if (tensDigit === 1) {
106
+ parts.push(TEENS[onesDigit])
107
+ } else if (onesDigit > 0) {
108
+ parts.push(ONES_FEM[onesDigit])
109
+ }
110
+
111
+ return parts.join(' ')
112
+ }
105
113
 
106
114
  // ============================================================================
107
115
  // Conversion Functions
@@ -111,8 +119,7 @@ function integerToWords (n, options = {}) {
111
119
  if (n === 0n) return ZERO
112
120
 
113
121
  if (n < 1000n) {
114
- const segments = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
115
- return segments[Number(n)]
122
+ return options.gender === 'feminine' ? buildSegmentFem(Number(n)) : buildSegmentMasc(Number(n))
116
123
  }
117
124
 
118
125
  return buildLargeNumberWords(n, options)
@@ -144,15 +151,14 @@ function buildLargeNumberWords (n, options) {
144
151
 
145
152
  if (segment !== 0) {
146
153
  if (scaleIndex === 0) {
147
- const segmentWords = options.gender === 'feminine' ? SEGMENTS_FEM : SEGMENTS_MASC
148
- parts.push(segmentWords[segment])
154
+ parts.push(options.gender === 'feminine' ? buildSegmentFem(segment) : buildSegmentMasc(segment))
149
155
  } else {
150
156
  const scaleForms = SCALE_FORMS[scaleIndex - 1]
151
157
  const scaleWord = pluralize(segment, scaleForms)
152
158
  // Thousands (scaleIndex=1) are feminine, others masculine
153
159
  const isFeminine = scaleIndex === 1
154
- const segmentWords = isFeminine ? SEGMENTS_FEM : SEGMENTS_MASC
155
- parts.push(segmentWords[segment] + ' ' + scaleWord)
160
+ const segmentWord = isFeminine ? buildSegmentFem(segment) : buildSegmentMasc(segment)
161
+ parts.push(segmentWord + ' ' + scaleWord)
156
162
  }
157
163
  }
158
164
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Swedish 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
  * - Hyphenation for tens-ones (tjugo-ett)
@@ -32,7 +32,7 @@ const DECIMAL_SEP = 'komma'
32
32
  const SCALES = ['tusen', 'miljon', 'miljard', 'biljon', 'biljard', 'triljon', 'triljard', 'kvadriljon']
33
33
 
34
34
  // ============================================================================
35
- // Precomputed Lookup Tables (built once at module load)
35
+ // Segment Building
36
36
  // ============================================================================
37
37
 
38
38
  /**
@@ -83,18 +83,6 @@ function buildSegment (n) {
83
83
  }
84
84
  }
85
85
 
86
- // Precompute all 1000 segment words (0-999)
87
- const SEGMENTS = new Array(1000)
88
- const SEGMENTS_HAS_HUNDRED = new Array(1000)
89
- const SEGMENTS_LESS_THAN_100 = new Array(1000)
90
-
91
- for (let i = 0; i < 1000; i++) {
92
- const result = buildSegment(i)
93
- SEGMENTS[i] = result.word
94
- SEGMENTS_HAS_HUNDRED[i] = result.hasHundred
95
- SEGMENTS_LESS_THAN_100[i] = result.lessThan100
96
- }
97
-
98
86
  // ============================================================================
99
87
  // Conversion Functions
100
88
  // ============================================================================
@@ -108,9 +96,9 @@ for (let i = 0; i < 1000; i++) {
108
96
  function integerToWords (n) {
109
97
  if (n === 0n) return ZERO
110
98
 
111
- // Fast path: numbers < 1000 (direct lookup)
99
+ // Fast path: numbers < 1000
112
100
  if (n < 1000n) {
113
- return SEGMENTS[Number(n)]
101
+ return buildSegment(Number(n)).word
114
102
  }
115
103
 
116
104
  // Fast path: numbers < 1,000,000 (thousands)
@@ -119,15 +107,15 @@ function integerToWords (n) {
119
107
  const remainder = Number(n % 1000n)
120
108
 
121
109
  // Omit "ett" before tusen
122
- let result = thousands === 1 ? SCALES[0] : SEGMENTS[thousands] + ' ' + SCALES[0]
110
+ let result = thousands === 1 ? SCALES[0] : buildSegment(thousands).word + ' ' + SCALES[0]
123
111
 
124
112
  if (remainder > 0) {
125
- const remainderWord = SEGMENTS[remainder]
113
+ const remainderResult = buildSegment(remainder)
126
114
  // Insert "och" if remainder < 100 (doesn't have hundred)
127
- if (SEGMENTS_LESS_THAN_100[remainder]) {
128
- result += ' och ' + remainderWord
115
+ if (remainderResult.lessThan100) {
116
+ result += ' och ' + remainderResult.word
129
117
  } else {
130
- result += ' ' + remainderWord
118
+ result += ' ' + remainderResult.word
131
119
  }
132
120
  }
133
121
 
@@ -171,11 +159,13 @@ function buildLargeNumberWords (n) {
171
159
  const segment = segments[i]
172
160
 
173
161
  if (segment !== 0) {
162
+ const segmentResult = buildSegment(segment)
163
+
174
164
  if (scaleIndex === 0) {
175
165
  // Units segment
176
166
  parts.push({
177
- word: SEGMENTS[segment],
178
- hasHundred: SEGMENTS_HAS_HUNDRED[segment],
167
+ word: segmentResult.word,
168
+ hasHundred: segmentResult.hasHundred,
179
169
  isScale: false
180
170
  })
181
171
  } else {
@@ -191,7 +181,7 @@ function buildLargeNumberWords (n) {
191
181
  segmentWord = 'en' // "en miljon"
192
182
  }
193
183
  } else {
194
- segmentWord = SEGMENTS[segment]
184
+ segmentWord = segmentResult.word
195
185
  }
196
186
 
197
187
  if (segmentWord) {