n2words 2.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.
- package/CHANGELOG.md +64 -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/ka.js +3 -0
- package/dist/languages/ka.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/yo.js +3 -0
- package/dist/languages/yo.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 +159 -0
- package/lib/languages/am.d.ts +7 -0
- package/lib/languages/am.js +159 -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 +166 -37
- package/lib/languages/bn.d.ts +4 -8
- package/lib/languages/bn.js +159 -124
- package/lib/languages/cs.d.ts +15 -85
- package/lib/languages/cs.js +293 -114
- package/lib/languages/da.d.ts +11 -12
- package/lib/languages/da.js +269 -101
- package/lib/languages/de.d.ts +14 -11
- package/lib/languages/de.js +305 -86
- package/lib/languages/el.d.ts +11 -11
- package/lib/languages/el.js +224 -78
- package/lib/languages/en.d.ts +14 -13
- package/lib/languages/en.js +228 -72
- package/lib/languages/es.d.ts +18 -12
- package/lib/languages/es.js +297 -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 +238 -0
- package/lib/languages/fil.d.ts +4 -13
- package/lib/languages/fil.js +196 -106
- package/lib/languages/fr-BE.d.ts +8 -8
- package/lib/languages/fr-BE.js +285 -19
- package/lib/languages/fr.d.ts +18 -12
- package/lib/languages/fr.js +339 -89
- package/lib/languages/gu.d.ts +4 -8
- package/lib/languages/gu.js +143 -125
- package/lib/languages/ha.d.ts +7 -0
- package/lib/languages/ha.js +225 -0
- package/lib/languages/hbo.d.ts +10 -110
- package/lib/languages/hbo.js +245 -214
- package/lib/languages/he.d.ts +10 -77
- package/lib/languages/he.js +231 -172
- package/lib/languages/hi.d.ts +4 -8
- package/lib/languages/hi.js +163 -124
- package/lib/languages/hr.d.ts +8 -77
- package/lib/languages/hr.js +200 -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 +166 -129
- package/lib/languages/it.d.ts +16 -34
- package/lib/languages/it.js +307 -97
- package/lib/languages/ja.d.ts +14 -14
- package/lib/languages/ja.js +221 -111
- package/lib/languages/ka.d.ts +17 -0
- package/lib/languages/ka.js +291 -0
- package/lib/languages/kn.d.ts +4 -8
- package/lib/languages/kn.js +143 -35
- package/lib/languages/ko.d.ts +11 -11
- package/lib/languages/ko.js +250 -49
- package/lib/languages/lt.d.ts +15 -67
- package/lib/languages/lt.js +287 -122
- package/lib/languages/lv.d.ts +15 -67
- package/lib/languages/lv.js +288 -106
- package/lib/languages/mr.d.ts +4 -8
- package/lib/languages/mr.js +143 -125
- package/lib/languages/ms.d.ts +4 -28
- package/lib/languages/ms.js +166 -116
- package/lib/languages/nb.d.ts +11 -9
- package/lib/languages/nb.js +272 -87
- package/lib/languages/nl.d.ts +23 -13
- package/lib/languages/nl.js +299 -133
- package/lib/languages/pa.d.ts +4 -8
- package/lib/languages/pa.js +151 -124
- package/lib/languages/pl.d.ts +19 -77
- package/lib/languages/pl.js +294 -87
- package/lib/languages/pt.d.ts +14 -26
- package/lib/languages/pt.js +272 -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 +239 -90
- package/lib/languages/sr-Cyrl.d.ts +8 -77
- package/lib/languages/sr-Cyrl.js +197 -89
- package/lib/languages/sr-Latn.d.ts +8 -77
- package/lib/languages/sr-Latn.js +197 -89
- package/lib/languages/sv.d.ts +11 -11
- package/lib/languages/sv.js +278 -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 +143 -202
- package/lib/languages/te.d.ts +4 -19
- package/lib/languages/te.js +133 -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 +245 -49
- package/lib/languages/uk.d.ts +8 -82
- package/lib/languages/uk.js +206 -78
- package/lib/languages/ur.d.ts +4 -8
- package/lib/languages/ur.js +151 -124
- package/lib/languages/vi.d.ts +14 -69
- package/lib/languages/vi.js +278 -129
- package/lib/languages/yo.d.ts +7 -0
- package/lib/languages/yo.js +303 -0
- 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 +55 -209
- package/lib/n2words.js +115 -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
package/lib/languages/sv.js
CHANGED
|
@@ -1,90 +1,294 @@
|
|
|
1
|
-
import { GreedyScaleLanguage } from '../classes/greedy-scale-language.js'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* Swedish language converter
|
|
2
|
+
* Swedish language converter - Functional Implementation
|
|
3
|
+
*
|
|
4
|
+
* Self-contained module with its own input validation, ready for subpath exports.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* - Hyphenation for
|
|
8
|
-
* - "och"
|
|
9
|
-
* -
|
|
6
|
+
* Key features:
|
|
7
|
+
* - Hyphenation for tens-ones (tjugo-ett)
|
|
8
|
+
* - "och" after hundreds before small numbers
|
|
9
|
+
* - Omit "ett" before hundra and tusen
|
|
10
|
+
* - Use "en" (not "ett") before million+ scales
|
|
11
|
+
* - Long scale naming with -ard forms
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Vocabulary (module-level constants)
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
const ONES = ['', 'ett', 'två', 'tre', 'fyra', 'fem', 'sex', 'sju', 'åtta', 'nio']
|
|
21
|
+
|
|
22
|
+
const TEENS = ['tio', 'elva', 'tolv', 'tretton', 'fjorton', 'femton', 'sexton', 'sjutton', 'arton', 'nitton']
|
|
23
|
+
const TENS = ['', '', 'tjugo', 'trettio', 'fyrtio', 'femtio', 'sextio', 'sjuttio', 'åttio', 'nittio']
|
|
24
|
+
|
|
25
|
+
const HUNDRED = 'hundra'
|
|
26
|
+
|
|
27
|
+
const ZERO = 'noll'
|
|
28
|
+
const NEGATIVE = 'minus'
|
|
29
|
+
const DECIMAL_SEP = 'komma'
|
|
30
|
+
|
|
31
|
+
// Scale words (long scale with -ard forms)
|
|
32
|
+
const SCALES = ['tusen', 'miljon', 'miljard', 'biljon', 'biljard', 'triljon', 'triljard', 'kvadriljon']
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Segment Building
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Builds segment word for 0-999.
|
|
40
|
+
* Returns object with word and metadata for "och" logic.
|
|
10
41
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
[80n, 'åttio'],
|
|
29
|
-
[70n, 'sjuttio'],
|
|
30
|
-
[60n, 'sextio'],
|
|
31
|
-
[50n, 'femtio'],
|
|
32
|
-
[40n, 'fyrtio'],
|
|
33
|
-
[30n, 'trettio'],
|
|
34
|
-
[20n, 'tjugo'],
|
|
35
|
-
[19n, 'nitton'],
|
|
36
|
-
[18n, 'arton'],
|
|
37
|
-
[17n, 'sjutton'],
|
|
38
|
-
[16n, 'sexton'],
|
|
39
|
-
[15n, 'femton'],
|
|
40
|
-
[14n, 'fjorton'],
|
|
41
|
-
[13n, 'tretton'],
|
|
42
|
-
[12n, 'tolv'],
|
|
43
|
-
[11n, 'elva'],
|
|
44
|
-
[10n, 'tio'],
|
|
45
|
-
[9n, 'nio'],
|
|
46
|
-
[8n, 'åtta'],
|
|
47
|
-
[7n, 'sju'],
|
|
48
|
-
[6n, 'sex'],
|
|
49
|
-
[5n, 'fem'],
|
|
50
|
-
[4n, 'fyra'],
|
|
51
|
-
[3n, 'tre'],
|
|
52
|
-
[2n, 'två'],
|
|
53
|
-
[1n, 'ett'],
|
|
54
|
-
[0n, 'noll']
|
|
55
|
-
]
|
|
56
|
-
|
|
57
|
-
/** Combines two word-sets according to Swedish grammar rules. */
|
|
58
|
-
combineWordSets (preceding, following) {
|
|
59
|
-
const precedingWord = Object.keys(preceding)[0]
|
|
60
|
-
const followingWord = Object.keys(following)[0]
|
|
61
|
-
const precedingValue = Object.values(preceding)[0]
|
|
62
|
-
const followingValue = Object.values(following)[0]
|
|
63
|
-
|
|
64
|
-
if (precedingValue === 1n && followingValue < 100n) {
|
|
65
|
-
return following
|
|
42
|
+
function buildSegment (n) {
|
|
43
|
+
if (n === 0) return { word: '', hasHundred: false, lessThan100: false }
|
|
44
|
+
|
|
45
|
+
const ones = n % 10
|
|
46
|
+
const tens = Math.floor(n / 10) % 10
|
|
47
|
+
const hundreds = Math.floor(n / 100)
|
|
48
|
+
|
|
49
|
+
const parts = []
|
|
50
|
+
let hasHundred = false
|
|
51
|
+
|
|
52
|
+
// Hundreds - omit "ett" before hundra
|
|
53
|
+
if (hundreds > 0) {
|
|
54
|
+
hasHundred = true
|
|
55
|
+
if (hundreds === 1) {
|
|
56
|
+
parts.push(HUNDRED)
|
|
57
|
+
} else {
|
|
58
|
+
parts.push(ONES[hundreds] + ' ' + HUNDRED)
|
|
66
59
|
}
|
|
60
|
+
}
|
|
67
61
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
62
|
+
// Tens and ones with hyphenation
|
|
63
|
+
let tensOnesWord = ''
|
|
64
|
+
if (tens === 1) {
|
|
65
|
+
tensOnesWord = TEENS[ones]
|
|
66
|
+
} else if (tens >= 2) {
|
|
67
|
+
if (ones > 0) {
|
|
68
|
+
tensOnesWord = TENS[tens] + '-' + ONES[ones]
|
|
69
|
+
} else {
|
|
70
|
+
tensOnesWord = TENS[tens]
|
|
71
71
|
}
|
|
72
|
+
} else if (ones > 0) {
|
|
73
|
+
tensOnesWord = ONES[ones]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Combine with "och" after hundreds if there's a remainder
|
|
77
|
+
if (hasHundred && tensOnesWord) {
|
|
78
|
+
return { word: parts[0] + ' och ' + tensOnesWord, hasHundred: true, lessThan100: false }
|
|
79
|
+
} else if (hasHundred) {
|
|
80
|
+
return { word: parts[0], hasHundred: true, lessThan100: false }
|
|
81
|
+
} else {
|
|
82
|
+
return { word: tensOnesWord, hasHundred: false, lessThan100: true }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// Conversion Functions
|
|
88
|
+
// ============================================================================
|
|
72
89
|
|
|
73
|
-
|
|
74
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Converts a non-negative integer to Swedish words.
|
|
92
|
+
*
|
|
93
|
+
* @param {bigint} n - Non-negative integer to convert
|
|
94
|
+
* @returns {string} Swedish words
|
|
95
|
+
*/
|
|
96
|
+
function integerToWords (n) {
|
|
97
|
+
if (n === 0n) return ZERO
|
|
98
|
+
|
|
99
|
+
// Fast path: numbers < 1000
|
|
100
|
+
if (n < 1000n) {
|
|
101
|
+
return buildSegment(Number(n)).word
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Fast path: numbers < 1,000,000 (thousands)
|
|
105
|
+
if (n < 1_000_000n) {
|
|
106
|
+
const thousands = Number(n / 1000n)
|
|
107
|
+
const remainder = Number(n % 1000n)
|
|
108
|
+
|
|
109
|
+
// Omit "ett" before tusen
|
|
110
|
+
let result = thousands === 1 ? SCALES[0] : buildSegment(thousands).word + ' ' + SCALES[0]
|
|
111
|
+
|
|
112
|
+
if (remainder > 0) {
|
|
113
|
+
const remainderResult = buildSegment(remainder)
|
|
114
|
+
// Insert "och" if remainder < 100 (doesn't have hundred)
|
|
115
|
+
if (remainderResult.lessThan100) {
|
|
116
|
+
result += ' och ' + remainderResult.word
|
|
117
|
+
} else {
|
|
118
|
+
result += ' ' + remainderResult.word
|
|
119
|
+
}
|
|
75
120
|
}
|
|
76
121
|
|
|
77
|
-
|
|
78
|
-
|
|
122
|
+
return result
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// For numbers >= 1,000,000, use scale decomposition
|
|
126
|
+
return buildLargeNumberWords(n)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Builds words for numbers >= 1,000,000.
|
|
131
|
+
*
|
|
132
|
+
* @param {bigint} n - Number >= 1,000,000
|
|
133
|
+
* @returns {string} Swedish words
|
|
134
|
+
*/
|
|
135
|
+
function buildLargeNumberWords (n) {
|
|
136
|
+
const numStr = n.toString()
|
|
137
|
+
const len = numStr.length
|
|
138
|
+
|
|
139
|
+
// Build segments of 3 digits from right to left
|
|
140
|
+
const segments = []
|
|
141
|
+
const segmentSize = 3
|
|
142
|
+
|
|
143
|
+
const remainderLen = len % segmentSize
|
|
144
|
+
let pos = 0
|
|
145
|
+
if (remainderLen > 0) {
|
|
146
|
+
segments.push(Number(numStr.slice(0, remainderLen)))
|
|
147
|
+
pos = remainderLen
|
|
148
|
+
}
|
|
149
|
+
while (pos < len) {
|
|
150
|
+
segments.push(Number(numStr.slice(pos, pos + segmentSize)))
|
|
151
|
+
pos += segmentSize
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Convert segments to words
|
|
155
|
+
const parts = []
|
|
156
|
+
let scaleIndex = segments.length - 1
|
|
157
|
+
|
|
158
|
+
for (let i = 0; i < segments.length; i++) {
|
|
159
|
+
const segment = segments[i]
|
|
160
|
+
|
|
161
|
+
if (segment !== 0) {
|
|
162
|
+
const segmentResult = buildSegment(segment)
|
|
163
|
+
|
|
164
|
+
if (scaleIndex === 0) {
|
|
165
|
+
// Units segment
|
|
166
|
+
parts.push({
|
|
167
|
+
word: segmentResult.word,
|
|
168
|
+
hasHundred: segmentResult.hasHundred,
|
|
169
|
+
isScale: false
|
|
170
|
+
})
|
|
171
|
+
} else {
|
|
172
|
+
// Segment with scale word
|
|
173
|
+
const scaleWord = SCALES[scaleIndex - 1]
|
|
174
|
+
|
|
175
|
+
let segmentWord
|
|
176
|
+
if (segment === 1) {
|
|
177
|
+
// Omit "ett" before tusen, use "en" before million+
|
|
178
|
+
if (scaleIndex === 1) {
|
|
179
|
+
segmentWord = '' // Just "tusen"
|
|
180
|
+
} else {
|
|
181
|
+
segmentWord = 'en' // "en miljon"
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
segmentWord = segmentResult.word
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (segmentWord) {
|
|
188
|
+
parts.push({ word: segmentWord, hasHundred: false, isScale: false })
|
|
189
|
+
}
|
|
190
|
+
parts.push({ word: scaleWord, hasHundred: false, isScale: true })
|
|
191
|
+
}
|
|
79
192
|
}
|
|
80
193
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
194
|
+
scaleIndex--
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Join with Swedish "och" rules
|
|
198
|
+
return joinSwedishParts(parts)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Joins parts with Swedish "och" rules.
|
|
203
|
+
* Insert "och" before final segment if it follows a scale word and doesn't have "hundra".
|
|
204
|
+
*
|
|
205
|
+
* @param {Array} parts - Parts with metadata
|
|
206
|
+
* @returns {string} Joined string
|
|
207
|
+
*/
|
|
208
|
+
function joinSwedishParts (parts) {
|
|
209
|
+
if (parts.length === 0) return ZERO
|
|
210
|
+
if (parts.length === 1) return parts[0].word
|
|
211
|
+
|
|
212
|
+
const result = []
|
|
213
|
+
|
|
214
|
+
for (let i = 0; i < parts.length; i++) {
|
|
215
|
+
const part = parts[i]
|
|
216
|
+
const isLast = i === parts.length - 1
|
|
217
|
+
|
|
218
|
+
if (isLast && parts.length > 1) {
|
|
219
|
+
const prevPart = parts[i - 1]
|
|
220
|
+
// Insert "och" if previous was scale and this doesn't have hundred
|
|
221
|
+
if (prevPart.isScale && !part.hasHundred) {
|
|
222
|
+
result.push('och')
|
|
84
223
|
}
|
|
85
|
-
return { [`${precedingWord} ${followingWord}`]: precedingValue * followingValue }
|
|
86
224
|
}
|
|
87
225
|
|
|
88
|
-
|
|
226
|
+
result.push(part.word)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return result.join(' ')
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Converts decimal digits to Swedish words.
|
|
234
|
+
*
|
|
235
|
+
* @param {string} decimalPart - Decimal digits (without the point)
|
|
236
|
+
* @returns {string} Swedish words for decimal part
|
|
237
|
+
*/
|
|
238
|
+
function decimalPartToWords (decimalPart) {
|
|
239
|
+
let result = ''
|
|
240
|
+
|
|
241
|
+
// Handle leading zeros
|
|
242
|
+
let i = 0
|
|
243
|
+
while (i < decimalPart.length && decimalPart[i] === '0') {
|
|
244
|
+
if (result) result += ' '
|
|
245
|
+
result += ZERO
|
|
246
|
+
i++
|
|
89
247
|
}
|
|
248
|
+
|
|
249
|
+
// Convert remainder as a single number
|
|
250
|
+
const remainder = decimalPart.slice(i)
|
|
251
|
+
if (remainder) {
|
|
252
|
+
if (result) result += ' '
|
|
253
|
+
result += integerToWords(BigInt(remainder))
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return result
|
|
90
257
|
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Converts a numeric value to Swedish words.
|
|
261
|
+
*
|
|
262
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
263
|
+
* @returns {string} The number in Swedish words
|
|
264
|
+
* @throws {TypeError} If value is not a valid numeric type
|
|
265
|
+
* @throws {Error} If value is not a valid number format
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* toWords(42) // 'fyrtio-två'
|
|
269
|
+
* toWords(101) // 'hundra och ett'
|
|
270
|
+
* toWords(1000000) // 'en miljon'
|
|
271
|
+
*/
|
|
272
|
+
function toWords (value) {
|
|
273
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
274
|
+
|
|
275
|
+
let result = ''
|
|
276
|
+
|
|
277
|
+
if (isNegative) {
|
|
278
|
+
result = NEGATIVE + ' '
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
result += integerToWords(integerPart)
|
|
282
|
+
|
|
283
|
+
if (decimalPart) {
|
|
284
|
+
result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return result
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ============================================================================
|
|
291
|
+
// Public API
|
|
292
|
+
// ============================================================================
|
|
293
|
+
|
|
294
|
+
export { toWords }
|
package/lib/languages/sw.d.ts
CHANGED
|
@@ -1,39 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Swahili
|
|
2
|
+
* Converts a numeric value to Swahili words.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - Space-separated components
|
|
7
|
-
* - Simple decimal point notation
|
|
4
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
5
|
+
* @returns {string} The number in Swahili words
|
|
8
6
|
*/
|
|
9
|
-
export
|
|
10
|
-
onesWords: {
|
|
11
|
-
0: string;
|
|
12
|
-
1: string;
|
|
13
|
-
2: string;
|
|
14
|
-
3: string;
|
|
15
|
-
4: string;
|
|
16
|
-
5: string;
|
|
17
|
-
6: string;
|
|
18
|
-
7: string;
|
|
19
|
-
8: string;
|
|
20
|
-
9: string;
|
|
21
|
-
};
|
|
22
|
-
tensWords: {
|
|
23
|
-
10: string;
|
|
24
|
-
20: string;
|
|
25
|
-
30: string;
|
|
26
|
-
40: string;
|
|
27
|
-
50: string;
|
|
28
|
-
60: string;
|
|
29
|
-
70: string;
|
|
30
|
-
80: string;
|
|
31
|
-
90: string;
|
|
32
|
-
};
|
|
33
|
-
scaleWords: string[];
|
|
34
|
-
wordsUnder100(n: any): any;
|
|
35
|
-
wordsUnder1000(n: any): any;
|
|
36
|
-
splitBy3(number: any): number[];
|
|
37
|
-
integerToWords(integerPart: any): string;
|
|
38
|
-
}
|
|
39
|
-
import { AbstractLanguage } from '../classes/abstract-language.js';
|
|
7
|
+
export function toWords(value: number | string | bigint): string;
|
package/lib/languages/sw.js
CHANGED
|
@@ -1,126 +1,153 @@
|
|
|
1
|
-
import { AbstractLanguage } from '../classes/abstract-language.js'
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
* Swahili language converter
|
|
2
|
+
* Swahili language converter - Functional Implementation
|
|
3
|
+
*
|
|
4
|
+
* Self-contained converter with precomputed lookup tables.
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
6
|
+
* Key features:
|
|
7
|
+
* - "na" connector for compound numbers
|
|
8
|
+
* - Reversed hundreds: "mia moja" (one hundred)
|
|
9
|
+
* - Scale words: elfu, milioni, bilioni
|
|
10
10
|
*/
|
|
11
|
-
export class Swahili extends AbstractLanguage {
|
|
12
|
-
negativeWord = 'minus'
|
|
13
|
-
decimalSeparatorWord = 'nukta'
|
|
14
|
-
zeroWord = 'sifuri'
|
|
15
|
-
|
|
16
|
-
onesWords = {
|
|
17
|
-
0: 'sifuri',
|
|
18
|
-
1: 'moja',
|
|
19
|
-
2: 'mbili',
|
|
20
|
-
3: 'tatu',
|
|
21
|
-
4: 'nne',
|
|
22
|
-
5: 'tano',
|
|
23
|
-
6: 'sita',
|
|
24
|
-
7: 'saba',
|
|
25
|
-
8: 'nane',
|
|
26
|
-
9: 'tisa'
|
|
27
|
-
}
|
|
28
11
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
12
|
+
import { parseNumericValue } from '../utils/parse-numeric.js'
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Vocabulary
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
const ONES = ['sifuri', 'moja', 'mbili', 'tatu', 'nne', 'tano', 'sita', 'saba', 'nane', 'tisa']
|
|
19
|
+
const TENS = { 10: 'kumi', 20: 'ishirini', 30: 'thelathini', 40: 'arobaini', 50: 'hamsini', 60: 'sitini', 70: 'sabini', 80: 'themanini', 90: 'tisini' }
|
|
20
|
+
|
|
21
|
+
const SCALE_WORDS = ['', 'elfu', 'milioni', 'bilioni', 'trilioni', 'kwadrilioni', 'kwintilioni']
|
|
22
|
+
|
|
23
|
+
const ZERO = 'sifuri'
|
|
24
|
+
const NEGATIVE = 'minus'
|
|
25
|
+
const DECIMAL_SEP = 'nukta'
|
|
26
|
+
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// Conversion Functions
|
|
29
|
+
// ============================================================================
|
|
30
|
+
|
|
31
|
+
function wordsUnder100 (n) {
|
|
32
|
+
if (n < 10) return ONES[n]
|
|
33
|
+
if (n === 10) return TENS[10]
|
|
34
|
+
if (n < 20) {
|
|
35
|
+
// 11-19: 'kumi na <digit>'
|
|
36
|
+
return TENS[10] + ' na ' + ONES[n - 10]
|
|
39
37
|
}
|
|
38
|
+
const tens = Math.trunc(n / 10) * 10
|
|
39
|
+
const ones = n % 10
|
|
40
|
+
if (ones === 0) return TENS[tens]
|
|
41
|
+
return TENS[tens] + ' na ' + ONES[ones]
|
|
42
|
+
}
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
function wordsUnder1000 (n) {
|
|
45
|
+
if (n < 100) return wordsUnder100(n)
|
|
46
|
+
if (n === 100) return 'mia moja'
|
|
47
|
+
const hundreds = Math.trunc(n / 100)
|
|
48
|
+
const rest = n % 100
|
|
49
|
+
const parts = []
|
|
50
|
+
|
|
51
|
+
// Hundreds: 'mia <digit>'
|
|
52
|
+
parts.push('mia ' + ONES[hundreds])
|
|
53
|
+
if (rest > 0) {
|
|
54
|
+
if (rest < 10) {
|
|
55
|
+
parts.push('na ' + ONES[rest])
|
|
56
|
+
} else {
|
|
57
|
+
parts.push(wordsUnder100(rest))
|
|
55
58
|
}
|
|
56
|
-
const tens = Math.trunc(n / 10) * 10
|
|
57
|
-
const ones = n % 10
|
|
58
|
-
if (ones === 0) return this.tensWords[tens]
|
|
59
|
-
return this.tensWords[tens] + ' na ' + this.onesWords[ones]
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
61
|
+
return parts.join(' ')
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function extractSegments (n) {
|
|
65
|
+
const segments = []
|
|
66
|
+
let temp = n
|
|
67
|
+
while (temp > 0n) {
|
|
68
|
+
segments.push(Number(temp % 1000n))
|
|
69
|
+
temp = temp / 1000n
|
|
70
|
+
}
|
|
71
|
+
return segments
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function integerToWords (n) {
|
|
75
|
+
if (n === 0n) return ZERO
|
|
76
|
+
|
|
77
|
+
// segments stored least-significant first: [ones, thousands, millions, ...]
|
|
78
|
+
const segments = extractSegments(n)
|
|
79
|
+
const parts = []
|
|
80
|
+
|
|
81
|
+
// Iterate from highest scale to lowest
|
|
82
|
+
for (let scaleIndex = segments.length - 1; scaleIndex >= 0; scaleIndex--) {
|
|
83
|
+
const val = segments[scaleIndex]
|
|
84
|
+
if (val === 0) continue
|
|
85
|
+
|
|
86
|
+
if (scaleIndex === 0) {
|
|
87
|
+
// Units segment
|
|
88
|
+
if (val < 10 && parts.length > 0) {
|
|
89
|
+
parts.push('na ' + ONES[val])
|
|
90
|
+
} else if (val === 100 && parts.length > 0) {
|
|
91
|
+
// In compound numbers (e.g., 1100 -> 'elfu moja mia'), use 'mia' not 'mia moja'
|
|
92
|
+
parts.push('mia')
|
|
74
93
|
} else {
|
|
75
|
-
parts.push(
|
|
94
|
+
parts.push(wordsUnder1000(val))
|
|
76
95
|
}
|
|
96
|
+
} else {
|
|
97
|
+
// Scale segments: 'elfu moja', 'milioni mbili'
|
|
98
|
+
const unit = (val === 1) ? 'moja' : wordsUnder1000(val)
|
|
99
|
+
parts.push(SCALE_WORDS[scaleIndex] + ' ' + unit)
|
|
77
100
|
}
|
|
101
|
+
}
|
|
78
102
|
|
|
79
|
-
|
|
103
|
+
return parts.join(' ').trim()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function decimalPartToWords (decimalPart) {
|
|
107
|
+
let result = ''
|
|
108
|
+
let i = 0
|
|
109
|
+
|
|
110
|
+
while (i < decimalPart.length && decimalPart[i] === '0') {
|
|
111
|
+
if (result) result += ' '
|
|
112
|
+
result += ZERO
|
|
113
|
+
i++
|
|
80
114
|
}
|
|
81
115
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
const last3 = s.slice(-3)
|
|
87
|
-
groups.unshift(Number(last3))
|
|
88
|
-
let remaining = s.slice(0, -3)
|
|
89
|
-
while (remaining.length > 0) {
|
|
90
|
-
const group = remaining.slice(-3)
|
|
91
|
-
groups.unshift(Number(group))
|
|
92
|
-
remaining = remaining.slice(0, -3)
|
|
93
|
-
}
|
|
94
|
-
return groups
|
|
116
|
+
const remainder = decimalPart.slice(i)
|
|
117
|
+
if (remainder) {
|
|
118
|
+
if (result) result += ' '
|
|
119
|
+
result += integerToWords(BigInt(remainder))
|
|
95
120
|
}
|
|
96
121
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const groups = this.splitBy3(integerPart)
|
|
101
|
-
const parts = []
|
|
102
|
-
|
|
103
|
-
for (let i = 0; i < groups.length; i++) {
|
|
104
|
-
const val = groups[i]
|
|
105
|
-
if (val === 0) continue
|
|
106
|
-
const scaleIndex = groups.length - i - 1
|
|
107
|
-
// scale word
|
|
108
|
-
if (scaleIndex === 0) {
|
|
109
|
-
if (val < 10 && parts.length > 0) {
|
|
110
|
-
parts.push('na ' + this.onesWords[val])
|
|
111
|
-
} else if (val === 100 && parts.length > 0) {
|
|
112
|
-
// In compound numbers (e.g., 1100 -> 'elfu moja mia'), use 'mia' not 'mia moja'
|
|
113
|
-
parts.push('mia')
|
|
114
|
-
} else {
|
|
115
|
-
parts.push(this.wordsUnder1000(val))
|
|
116
|
-
}
|
|
117
|
-
} else {
|
|
118
|
-
// e.g., 'elfu moja', 'milioni mbili'
|
|
119
|
-
const unit = (val === 1) ? 'moja' : this.wordsUnder1000(val)
|
|
120
|
-
parts.push(this.scaleWords[scaleIndex] + ' ' + unit)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
122
|
+
return result
|
|
123
|
+
}
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Converts a numeric value to Swahili words.
|
|
127
|
+
*
|
|
128
|
+
* @param {number | string | bigint} value - The numeric value to convert
|
|
129
|
+
* @returns {string} The number in Swahili words
|
|
130
|
+
*/
|
|
131
|
+
function toWords (value) {
|
|
132
|
+
const { isNegative, integerPart, decimalPart } = parseNumericValue(value)
|
|
133
|
+
|
|
134
|
+
let result = ''
|
|
135
|
+
|
|
136
|
+
if (isNegative) {
|
|
137
|
+
result = NEGATIVE + ' '
|
|
125
138
|
}
|
|
139
|
+
|
|
140
|
+
result += integerToWords(integerPart)
|
|
141
|
+
|
|
142
|
+
if (decimalPart) {
|
|
143
|
+
result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return result
|
|
126
147
|
}
|
|
148
|
+
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// Exports
|
|
151
|
+
// ============================================================================
|
|
152
|
+
|
|
153
|
+
export { toWords }
|