tanisa 1.0.2 → 1.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/README.md CHANGED
@@ -11,6 +11,7 @@
11
11
  - ✅ **Decimal support** : Gracefully converts those pesky fractions into spoken form.
12
12
  - ✅ **Large number linguistics**: Tackles big numbers with the appropriate Malagasy terminology.
13
13
  - ✅ **User-friendly API** : So intuitive, you'll feel like you've been speaking number-words your whole life.
14
+ - ✅ **Configurable options** : Customize the conversion behavior, like controlling how decimal places are handled and more...
14
15
  - 🛡️ **Error Handling** : Throws helpful errors when you try to feed it something it can't digest.
15
16
 
16
17
  ## Installation
@@ -40,3 +41,26 @@ tanisa.toWords(233) ==> Telo amby telopolo sy roanjato
40
41
  tanisa.toWords(18.3) ==> Valo amby folo faingo telo
41
42
  tanisa.toWords(0.008) ==> Aotra faingo aotra aotra valo
42
43
  ```
44
+
45
+ ## Options
46
+
47
+ | Option Name | Type | Default | Description |
48
+ | :-------------- | :-------- | :------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------- |
49
+ | `ignoreDecimal` | `boolean` | `false` | If set to `true`, the converter completely disregards any digits after the decimal point. Only the integer part of the number is considered. |
50
+ | `decimalPlaces` | `number` | `undefined` (converts all) | Specifies the maximum number of digits to convert in the decimal part. Extra digits are truncated (not rounded). Set to `0` to ignore decimals. |
51
+
52
+ **Note:** If both `ignoreDecimal` is `true` and `decimalPlaces` is set, `ignoreDecimal: true` takes precedence, and the decimal part will be entirely ignored.
53
+
54
+ Examples:
55
+
56
+ ```
57
+ converter.toWords("456.789", { ignoreDecimal: true }) => Enina amby dimampolo sy efajato
58
+
59
+ converter.toWords("3.14567", { decimalPlaces: 2 }); ==> Telo faingo efatra amby folo
60
+
61
+ converter.toWords("3.14567", { decimalPlaces: 2, ignoreDecimal: true }); ==> Telo
62
+ ```
63
+
64
+ ## License
65
+
66
+ This project is proudly released under the [MIT License](https://github.com/nifaliana/tanisa/blob/main/LICENSE)
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "tanisa",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "main": "src/index.ts",
5
5
  "description": "An utility to convert Malagasy 🇲🇬 numbers, including decimals, into their word representations.",
6
6
  "license": "MIT",
7
7
  "author": {
8
8
  "name": "Rija Nifaliana",
9
- "email": "rija.nifaliana@gmail.com"
9
+ "email": "rija.nifaliana@gmail.com",
10
+ "url": "https://github.com/nifaliana"
10
11
  },
11
12
  "scripts": {
12
13
  "test": "vitest run",
@@ -46,5 +47,13 @@
46
47
  "fanisana",
47
48
  "typescript",
48
49
  "javascript"
49
- ]
50
+ ],
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/nifaliana/tanisa.git"
54
+ },
55
+ "bugs": {
56
+ "url": "https://github.com/nifaliana/tanisa/issues"
57
+ },
58
+ "homepage": "https://github.com/nifaliana/tanisa#readme"
50
59
  }
package/src/converter.ts CHANGED
@@ -1,14 +1,20 @@
1
1
  import { LargeNumberUnit, MalagasyNumerals } from './dictionary'
2
+ import { TanisaOptions } from './interface'
2
3
 
3
4
  export class Tanisa {
4
- public toWords(number: number | string): string {
5
- const numStr = String(number)
5
+ public toWords(number: number | string, options?: TanisaOptions): string {
6
+ const ignoreDecimal = options?.ignoreDecimal ?? false
7
+ const decimalPlaces = options?.decimalPlaces ?? -1
8
+
9
+ const numStr = String(number).trim()
6
10
  const [integerPartStr, decimalPartStr] = numStr.split('.')
7
11
  const integerPartNum = parseInt(integerPartStr || '0', 10)
8
12
 
9
13
  if (
10
14
  isNaN(integerPartNum) ||
11
- (decimalPartStr && isNaN(parseInt(decimalPartStr, 10)))
15
+ (decimalPartStr &&
16
+ decimalPartStr.length > 0 &&
17
+ isNaN(parseInt(decimalPartStr, 10)))
12
18
  ) {
13
19
  throw new TypeError(`Invalid number input: "${number}"`)
14
20
  }
@@ -29,21 +35,40 @@ export class Tanisa {
29
35
  const integerWords = this.convertInteger(integerPartNum)
30
36
 
31
37
  let decimalWords = ''
32
- if (decimalPartStr && decimalPartStr.length > 0) {
33
- for (let i = 0; i < decimalPartStr.length; i++) {
34
- const digit = decimalPartStr[i]
35
- if (digit === '0') {
36
- decimalWords += MalagasyNumerals.GLUE_DECIMAL_ZERO
37
- } else {
38
- const remainingDecimal = decimalPartStr.substring(i)
39
- decimalWords += this.convertInteger(parseInt(remainingDecimal, 10))
40
- break
38
+ const processDecimals =
39
+ decimalPartStr &&
40
+ decimalPartStr.length > 0 &&
41
+ !ignoreDecimal &&
42
+ decimalPlaces !== 0
43
+
44
+ if (processDecimals) {
45
+ let effectiveDecimalPartStr = decimalPartStr
46
+
47
+ if (decimalPlaces > 0 && decimalPartStr.length > decimalPlaces) {
48
+ effectiveDecimalPartStr = decimalPartStr.substring(0, decimalPlaces)
49
+ }
50
+
51
+ if (parseInt(effectiveDecimalPartStr || '0', 10) > 0) {
52
+ let tempDecimalWords = ''
53
+ for (let i = 0; i < effectiveDecimalPartStr.length; i++) {
54
+ const digit = effectiveDecimalPartStr[i]
55
+ if (digit === '0') {
56
+ tempDecimalWords += MalagasyNumerals.GLUE_DECIMAL_ZERO
57
+ } else {
58
+ const remainingDecimal = effectiveDecimalPartStr.substring(i)
59
+ tempDecimalWords += this.convertInteger(
60
+ parseInt(remainingDecimal, 10)
61
+ )
62
+ break
63
+ }
64
+ }
65
+ if (tempDecimalWords) {
66
+ decimalWords = MalagasyNumerals.GLUE_FAINGO + tempDecimalWords
41
67
  }
42
68
  }
43
- return integerWords + MalagasyNumerals.GLUE_FAINGO + decimalWords
44
- } else {
45
- return integerWords
46
69
  }
70
+
71
+ return integerWords + decimalWords
47
72
  }
48
73
 
49
74
  private convertInteger(num: number): string {
@@ -0,0 +1,15 @@
1
+ export interface TanisaOptions {
2
+ /**
3
+ * If true, ignores the decimal part of the number.
4
+ * @default false
5
+ */
6
+ ignoreDecimal?: boolean
7
+
8
+ /**
9
+ * Specifies the maximum number of decimal places to convert.
10
+ * Extra decimal places will be truncated (not rounded).
11
+ * Set to 0 to ignore decimals.
12
+ * @default undefined (converts all decimal places)
13
+ */
14
+ decimalPlaces?: number
15
+ }
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest'
2
2
  import { Tanisa, MalagasyNumerals } from '../src'
3
+ import { TanisaOptions } from '../src/interface'
3
4
 
4
5
  describe('MalagasyNumberToWords', () => {
5
6
  let converter: Tanisa
@@ -124,4 +125,77 @@ describe('MalagasyNumberToWords', () => {
124
125
  expect(() => converter.toWords(safeInt)).not.toThrow()
125
126
  })
126
127
  })
128
+
129
+ describe('ignoreDecimal option', () => {
130
+ it('should ignore decimal part when ignoreDecimal is true', () => {
131
+ const options: TanisaOptions = { ignoreDecimal: true }
132
+ expect(converter.toWords(123.456, options)).toBe(
133
+ 'telo amby roapolo amby zato'
134
+ )
135
+ expect(converter.toWords(0.99, options)).toBe('aotra')
136
+ expect(converter.toWords(1000.001, options)).toBe('arivo')
137
+ })
138
+
139
+ it('should NOT ignore decimal part when ignoreDecimal is false or absent', () => {
140
+ expect(converter.toWords(10.5, { ignoreDecimal: false })).toBe(
141
+ 'folo faingo dimy'
142
+ )
143
+ expect(converter.toWords(10.5)).toBe('folo faingo dimy')
144
+ })
145
+ })
146
+
147
+ describe('decimalPlaces option', () => {
148
+ it('should ignore decimal part when decimalPlaces is 0', () => {
149
+ const options: TanisaOptions = { decimalPlaces: 0 }
150
+ expect(converter.toWords(123.456, options)).toBe(
151
+ 'telo amby roapolo amby zato'
152
+ )
153
+ expect(converter.toWords(0.99, options)).toBe('aotra')
154
+ })
155
+
156
+ it('should truncate decimal part to specified places', () => {
157
+ expect(converter.toWords(1.2345, { decimalPlaces: 1 })).toBe(
158
+ 'iray faingo roa'
159
+ )
160
+ expect(converter.toWords(1.2345, { decimalPlaces: 2 })).toBe(
161
+ 'iray faingo telo amby roapolo'
162
+ )
163
+ expect(converter.toWords(1.2345, { decimalPlaces: 3 })).toBe(
164
+ 'iray faingo efatra amby telopolo sy roanjato'
165
+ )
166
+ })
167
+
168
+ it('should handle leading zeros correctly with truncation', () => {
169
+ expect(converter.toWords(10.056, { decimalPlaces: 1 })).toBe('folo')
170
+ expect(converter.toWords(10.056, { decimalPlaces: 2 })).toBe(
171
+ 'folo faingo aotra dimy'
172
+ )
173
+ expect(converter.toWords(10.056, { decimalPlaces: 3 })).toBe(
174
+ 'folo faingo aotra enina amby dimampolo'
175
+ )
176
+ expect(converter.toWords(10.009, { decimalPlaces: 2 })).toBe('folo')
177
+ expect(converter.toWords(10.009, { decimalPlaces: 3 })).toBe(
178
+ 'folo faingo aotra aotra sivy'
179
+ )
180
+ })
181
+
182
+ it('should convert all decimals if decimalPlaces is equal to or greater than actual places', () => {
183
+ expect(converter.toWords(5.67, { decimalPlaces: 2 })).toBe(
184
+ 'dimy faingo fito amby enimpolo'
185
+ )
186
+ expect(converter.toWords(5.67, { decimalPlaces: 3 })).toBe(
187
+ 'dimy faingo fito amby enimpolo'
188
+ )
189
+ expect(converter.toWords(5.67, { decimalPlaces: 10 })).toBe(
190
+ 'dimy faingo fito amby enimpolo'
191
+ )
192
+ })
193
+ })
194
+
195
+ describe('decimalPlaces and ignoreDecimal options', () => {
196
+ it('should ignore decimal part if both params is set', () => {
197
+ const options: TanisaOptions = { decimalPlaces: 2, ignoreDecimal: true }
198
+ expect(converter.toWords(3.134343, options)).toBe('telo')
199
+ })
200
+ })
127
201
  })