Skip to content

Commit b614d06

Browse files
authored
feat: add support for zero fractional in currency (#2182)
* feat: add support for zero fractional in currency * test: improve test coverage for ToWordsCore
1 parent 76f084c commit b614d06

123 files changed

Lines changed: 2517 additions & 15 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ All notable changes to this project will be documented in this file. See [commit
44

55
## [5.2.1](https://github.com/mastermunj/to-words/compare/v5.2.0...v5.2.1) (2026-03-04)
66

7-
87
### Bug Fixes
98

10-
* add codeowners ([ea5e648](https://github.com/mastermunj/to-words/commit/ea5e6484edf0127647692089d0a48d2cfe291ff3))
9+
- add codeowners ([ea5e648](https://github.com/mastermunj/to-words/commit/ea5e6484edf0127647692089d0a48d2cfe291ff3))
1110

1211
## [5.2.0](https://github.com/mastermunj/to-words/compare/v5.1.0...v5.2.0) (2026-03-04)
1312

README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,10 @@ tw.convert(452.36, { currency: true, ignoreDecimal: true });
244244
// Ignore zero currency
245245
tw.convert(0.36, { currency: true, ignoreZeroCurrency: true });
246246
// "Thirty Six Paise Only"
247+
248+
// Show fractional unit even when zero (string input preserves .00)
249+
tw.convert('452.00', { currency: true, includeZeroFractional: true });
250+
// "Four Hundred Fifty Two Rupees And Zero Paise Only"
247251
```
248252

249253
**Functional — `toCurrency()` shorthand:**
@@ -689,6 +693,7 @@ interface ToWordsOptions {
689693
ignoreDecimal?: boolean; // Default: false
690694
ignoreZeroCurrency?: boolean; // Default: false
691695
doNotAddOnly?: boolean; // Default: false
696+
includeZeroFractional?: boolean; // Default: false
692697
currencyOptions?: {
693698
name: string;
694699
plural: string;
@@ -800,13 +805,14 @@ detectLocale('en-GB'); // custom fallback if detection fails
800805
801806
### Converter Options
802807

803-
| Option | Type | Default | Description |
804-
| -------------------- | ------- | --------- | ------------------------------------------------------------ |
805-
| `currency` | boolean | false | Convert as currency with locale-specific formatting |
806-
| `ignoreDecimal` | boolean | false | Ignore fractional part when converting |
807-
| `ignoreZeroCurrency` | boolean | false | Skip zero main currency (e.g., show only "Thirty Six Paise") |
808-
| `doNotAddOnly` | boolean | false | Omit "Only" suffix in currency mode |
809-
| `currencyOptions` | object | undefined | Override locale's default currency settings |
808+
| Option | Type | Default | Description |
809+
| ----------------------- | ------- | --------- | ------------------------------------------------------------------------------------------------ |
810+
| `currency` | boolean | false | Convert as currency with locale-specific formatting |
811+
| `ignoreDecimal` | boolean | false | Ignore fractional part when converting |
812+
| `ignoreZeroCurrency` | boolean | false | Skip zero main currency (e.g., show only "Thirty Six Paise") |
813+
| `doNotAddOnly` | boolean | false | Omit "Only" suffix in currency mode |
814+
| `includeZeroFractional` | boolean | false | When input is a string like `"123.00"`, include "And Zero Paise" even though the decimal is zero |
815+
| `currencyOptions` | object | undefined | Override locale's default currency settings |
810816

811817
### Common Options Example
812818

__tests__/ToWordsCore.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ describe('ToWordsCore - DefaultConverterOptions', () => {
88
ignoreDecimal: false,
99
ignoreZeroCurrency: false,
1010
doNotAddOnly: false,
11+
includeZeroFractional: false,
1112
});
1213
});
1314
});
@@ -655,4 +656,32 @@ describe('ToWordsCore - internal coverage via subclass', () => {
655656
const result = core.convert(100, { currency: false });
656657
expect(result).toBe('One Hundred');
657658
});
659+
660+
test('includeZeroFractional uses fractionalUnit.numberSpecificForms[0] when defined (line 527)', async () => {
661+
const { default: EnInLocale } = await import('../src/locales/en-IN');
662+
const core = new ToWordsCore();
663+
core.setLocale(EnInLocale);
664+
665+
const result = core.convert('5.00', {
666+
currency: true,
667+
includeZeroFractional: true,
668+
currencyOptions: {
669+
name: 'Rupee',
670+
plural: 'Rupees',
671+
singular: 'Rupee',
672+
symbol: '₹',
673+
fractionalUnit: {
674+
name: 'Paisa',
675+
plural: 'Paise',
676+
singular: 'Paisa',
677+
symbol: '',
678+
numberSpecificForms: {
679+
0: 'Zero Paise Special',
680+
},
681+
},
682+
},
683+
});
684+
685+
expect(result).toBe('Five Rupees And Zero Paise Special Only');
686+
});
658687
});

__tests__/af-ZA.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,27 @@ const testFloats: [number, string][] = [
158158
[37.683, 'Sewe En Dertig Punt Ses Honderd Drie En Tagtig'],
159159
];
160160

161+
describe('Test with options = { currency: true, includeZeroFractional: true }', () => {
162+
const testIncludeZeroFractional: [number | string, string][] = [
163+
[123, `Een Honderd Drie En Twintig Rand`],
164+
['123', `Een Honderd Drie En Twintig Rand`],
165+
['123.0', `Een Honderd Drie En Twintig Rand En Nul Sente`],
166+
['123.00', `Een Honderd Drie En Twintig Rand En Nul Sente`],
167+
['0.00', `Nul Rand En Nul Sente`],
168+
['-123.00', `Negatief Een Honderd Drie En Twintig Rand En Nul Sente`],
169+
['37.68', `Sewe En Dertig Rand En Agt En Sestig Sente`],
170+
];
171+
172+
test.concurrent.each(testIncludeZeroFractional)('convert %s => %s', (input, expected) => {
173+
expect(
174+
toWords.convert(input, {
175+
currency: true,
176+
includeZeroFractional: true,
177+
}),
178+
).toBe(expected);
179+
});
180+
});
181+
161182
describe('Test Floats with options = {}', () => {
162183
test.concurrent.each(testFloats)('convert %d => %s', (input, expected) => {
163184
expect(toWords.convert(input as number)).toBe(expected);

__tests__/am-ET.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,27 @@ const testFloats: [number, string][] = [
144144
[37.683, 'ሰላሳ ሰባት ነጥብ ስድስት መቶ ሰማንያ ሦስት'],
145145
];
146146

147+
describe('Test with options = { currency: true, includeZeroFractional: true }', () => {
148+
const testIncludeZeroFractional: [number | string, string][] = [
149+
[123, `አንድ መቶ ሃያ ሦስት ብር ብቻ`],
150+
['123', `አንድ መቶ ሃያ ሦስት ብር ብቻ`],
151+
['123.0', `አንድ መቶ ሃያ ሦስት ብር እና ዜሮ ሳንቲም ብቻ`],
152+
['123.00', `አንድ መቶ ሃያ ሦስት ብር እና ዜሮ ሳንቲም ብቻ`],
153+
['0.00', `ዜሮ ብር እና ዜሮ ሳንቲም ብቻ`],
154+
['-123.00', `አሉታዊ አንድ መቶ ሃያ ሦስት ብር እና ዜሮ ሳንቲም ብቻ`],
155+
['37.68', `ሰላሳ ሰባት ብር እና ስድሳ ስምንት ሳንቲም ብቻ`],
156+
];
157+
158+
test.concurrent.each(testIncludeZeroFractional)('convert %s => %s', (input, expected) => {
159+
expect(
160+
toWords.convert(input, {
161+
currency: true,
162+
includeZeroFractional: true,
163+
}),
164+
).toBe(expected);
165+
});
166+
});
167+
147168
describe('Test Floats with options = {}', () => {
148169
test.concurrent.each(testFloats)('convert %d => %s', (input, expected) => {
149170
expect(toWords.convert(input as number)).toBe(expected);

__tests__/ar-AE.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,27 @@ const testFloats: [number, string][] = [
133133
[37.683, 'سبعة و ثلاثون فاصلة ستمائة و ثلاثة و ثمانون'],
134134
];
135135

136+
describe('Test with options = { currency: true, includeZeroFractional: true }', () => {
137+
const testIncludeZeroFractional: [number | string, string][] = [
138+
[123, `مائة و ثلاثة و عشرون درهم فقط لا غير`],
139+
['123', `مائة و ثلاثة و عشرون درهم فقط لا غير`],
140+
['123.0', `مائة و ثلاثة و عشرون درهم و صفر فلس فقط لا غير`],
141+
['123.00', `مائة و ثلاثة و عشرون درهم و صفر فلس فقط لا غير`],
142+
['0.00', `صفر درهم و صفر فلس فقط لا غير`],
143+
['-123.00', `سالب مائة و ثلاثة و عشرون درهم و صفر فلس فقط لا غير`],
144+
['37.68', `سبعة و ثلاثون درهم و ثمانية و ستون فلس فقط لا غير`],
145+
];
146+
147+
test.concurrent.each(testIncludeZeroFractional)('convert %s => %s', (input, expected) => {
148+
expect(
149+
toWords.convert(input, {
150+
currency: true,
151+
includeZeroFractional: true,
152+
}),
153+
).toBe(expected);
154+
});
155+
});
156+
136157
describe('Test Floats with options = {}', () => {
137158
test.each(testFloats)('convert %d => %s', (input, expected) => {
138159
expect(toWords.convert(input as number)).toBe(expected);

__tests__/ar-LB.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,27 @@ const testFloats: [number, string][] = [
133133
[37.683, 'سبعة و ثلاثون فاصلة ستمائة و ثلاثة و ثمانون'],
134134
];
135135

136+
describe('Test with options = { currency: true, includeZeroFractional: true }', () => {
137+
const testIncludeZeroFractional: [number | string, string][] = [
138+
[123, `مائة و ثلاثة و عشرون ليرات فقط لا غير`],
139+
['123', `مائة و ثلاثة و عشرون ليرات فقط لا غير`],
140+
['123.0', `مائة و ثلاثة و عشرون ليرات و صفر قروش فقط لا غير`],
141+
['123.00', `مائة و ثلاثة و عشرون ليرات و صفر قروش فقط لا غير`],
142+
['0.00', `صفر ليرات و صفر قروش فقط لا غير`],
143+
['-123.00', `سالب مائة و ثلاثة و عشرون ليرات و صفر قروش فقط لا غير`],
144+
['37.68', `سبعة و ثلاثون ليرات و ثمانية و ستون قروش فقط لا غير`],
145+
];
146+
147+
test.concurrent.each(testIncludeZeroFractional)('convert %s => %s', (input, expected) => {
148+
expect(
149+
toWords.convert(input, {
150+
currency: true,
151+
includeZeroFractional: true,
152+
}),
153+
).toBe(expected);
154+
});
155+
});
156+
136157
describe('Test Floats with options = {}', () => {
137158
test.each(testFloats)('convert %d => %s', (input, expected) => {
138159
expect(toWords.convert(input as number)).toBe(expected);

__tests__/ar-MA.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,27 @@ const testFloats: [number, string][] = [
133133
[37.683, 'سبعة و ثلاثون فاصلة ستمائة و ثلاثة و ثمانون'],
134134
];
135135

136+
describe('Test with options = { currency: true, includeZeroFractional: true }', () => {
137+
const testIncludeZeroFractional: [number | string, string][] = [
138+
[123, `مائة و ثلاثة و عشرون درهم فقط لا غير`],
139+
['123', `مائة و ثلاثة و عشرون درهم فقط لا غير`],
140+
['123.0', `مائة و ثلاثة و عشرون درهم و صفر سنتيم فقط لا غير`],
141+
['123.00', `مائة و ثلاثة و عشرون درهم و صفر سنتيم فقط لا غير`],
142+
['0.00', `صفر درهم و صفر سنتيم فقط لا غير`],
143+
['-123.00', `سالب مائة و ثلاثة و عشرون درهم و صفر سنتيم فقط لا غير`],
144+
['37.68', `سبعة و ثلاثون درهم و ثمانية و ستون سنتيم فقط لا غير`],
145+
];
146+
147+
test.concurrent.each(testIncludeZeroFractional)('convert %s => %s', (input, expected) => {
148+
expect(
149+
toWords.convert(input, {
150+
currency: true,
151+
includeZeroFractional: true,
152+
}),
153+
).toBe(expected);
154+
});
155+
});
156+
136157
describe('Test Floats with options = {}', () => {
137158
test.each(testFloats)('convert %d => %s', (input, expected) => {
138159
expect(toWords.convert(input as number)).toBe(expected);

__tests__/ar-SA.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,27 @@ const testFloats: [number, string][] = [
133133
[37.683, 'سبعة و ثلاثون فاصلة ستمائة و ثلاثة و ثمانون'],
134134
];
135135

136+
describe('Test with options = { currency: true, includeZeroFractional: true }', () => {
137+
const testIncludeZeroFractional: [number | string, string][] = [
138+
[123, `مائة و ثلاثة و عشرون ريال فقط لا غير`],
139+
['123', `مائة و ثلاثة و عشرون ريال فقط لا غير`],
140+
['123.0', `مائة و ثلاثة و عشرون ريال و صفر هللة فقط لا غير`],
141+
['123.00', `مائة و ثلاثة و عشرون ريال و صفر هللة فقط لا غير`],
142+
['0.00', `صفر ريال و صفر هللة فقط لا غير`],
143+
['-123.00', `سالب مائة و ثلاثة و عشرون ريال و صفر هللة فقط لا غير`],
144+
['37.68', `سبعة و ثلاثون ريال و ثمانية و ستون هللة فقط لا غير`],
145+
];
146+
147+
test.concurrent.each(testIncludeZeroFractional)('convert %s => %s', (input, expected) => {
148+
expect(
149+
toWords.convert(input, {
150+
currency: true,
151+
includeZeroFractional: true,
152+
}),
153+
).toBe(expected);
154+
});
155+
});
156+
136157
describe('Test Floats with options = {}', () => {
137158
test.each(testFloats)('convert %d => %s', (input, expected) => {
138159
expect(toWords.convert(input as number)).toBe(expected);

__tests__/az-AZ.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,27 @@ const testFloats: [number, string][] = [
144144
[37.683, 'Otuz Yeddi Nöqtə Altı Yüz Səksən Üç'],
145145
];
146146

147+
describe('Test with options = { currency: true, includeZeroFractional: true }', () => {
148+
const testIncludeZeroFractional: [number | string, string][] = [
149+
[123, `Bir Yüz İyirmi Üç Manat`],
150+
['123', `Bir Yüz İyirmi Üç Manat`],
151+
['123.0', `Bir Yüz İyirmi Üç Manat Və Sıfır Qəpik`],
152+
['123.00', `Bir Yüz İyirmi Üç Manat Və Sıfır Qəpik`],
153+
['0.00', `Sıfır Manat Və Sıfır Qəpik`],
154+
['-123.00', `Mənfi Bir Yüz İyirmi Üç Manat Və Sıfır Qəpik`],
155+
['37.68', `Otuz Yeddi Manat Və Altmış Səkkiz Qəpik`],
156+
];
157+
158+
test.concurrent.each(testIncludeZeroFractional)('convert %s => %s', (input, expected) => {
159+
expect(
160+
toWords.convert(input, {
161+
currency: true,
162+
includeZeroFractional: true,
163+
}),
164+
).toBe(expected);
165+
});
166+
});
167+
147168
describe('Test Floats with options = {}', () => {
148169
test.concurrent.each(testFloats)('convert %d => %s', (input, expected) => {
149170
expect(toWords.convert(input as number)).toBe(expected);

0 commit comments

Comments
 (0)