Skip to content

Commit

Permalink
Merge pull request #2 from jlozovei/feature/currency
Browse files Browse the repository at this point in the history
Add currency mode support
  • Loading branch information
jlozovei authored May 2, 2020
2 parents 7835c2a + 66daf91 commit 38719e9
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 55 deletions.
81 changes: 75 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# full-numbers
A nice JS package to convert numbers to words. :zero: :arrow_right: :abc:
Multilanguage support! :tada:
A nice JS package to convert numbers to words. :zero: :arrow_right: :abc:

:tada: Multilanguage and currency support :tada:


## :closed_book: Usage
Expand Down Expand Up @@ -37,18 +38,86 @@ const words = fullNumbers({
}); // "um mil, duzentos e trinta e quatro"
```

To use with `currency` support:

```js
// "simple" values
const words = fullNumbers({
value: 1234,
lang: 'pt-BR',
currency: {
name: {
singular: 'real',
plural: 'reais'
},
decimals: {
singular: 'centavo',
plural: 'centavos'
}
}
}); // "um mil, duzentos e trinta e quatro reais"

// with decimals
const words = fullNumbers({
value: 1234.5,
lang: 'pt-BR',
currency: {
name: {
singular: 'real',
plural: 'reais'
},
decimals: {
singular: 'centavo',
plural: 'centavos'
}
}
}); // "um mil, duzentos e trinta e quatro reais e cinquenta centavos"
```


### Avaliable Options
| Name | Type | Description | Example |
| -------------- | -------------- | ------------------- | ------- |
| `value` | number | The value | `123` |
| `lang` | string | The output language | `pt-BR` |
| Name | Type | Description | Example |
| ------------------ | -------------- | ------------------- | ------------ |
| `value` | number | The value | `123` |
| `lang` | string | The output language | `pt-BR` |
| `currency` | object | The output currency | See below |

The currency object should look like this:

```js
{
name: {
singular: 'real',
plural: 'reais'
},
decimals: {
singular: 'centavo',
plural: 'centavos'
}
}
```

The `singular`/`plural` keys are important to avoid mismatch of grammar rules.


## :computer: Developing
First, fork the project. After it, install the dependencies (preferably using [npm](https://npmjs.com/) - since the project is using it) and do the work.

Also, take a look at the [contributing guide](https://github.com/jlozovei/full-numbers/blob/master/.github/CONTRIBUTING.md)!

### :books: Adding a new language
To add a new language, follow the steps below:

- Create a new `.json` file within `src/languages` directory. The name of this file should be a valid language code (i.e. `en`, `pt-BR`...)
- The file must have the following keys:
- `PUNCTUATION`: an object of punctuations used between `dozens`, `hundreds` and `decimals` - if the language doesn't use them, leave the values blank;
- `LESS_THAN_TWENTY`: an array of numbers' names between 0 and 19.
- `DOZENS`: an array of dozens' names bewteen 0 and 90;
- `HUNDREDS`: an object with the singular/plural names of hundreds between 100 and 900 - if the names are equal, leave the two keys with the same value;
- `SHORT_SCALE_NAME`: an object with the [short scale name](https://en.wikipedia.org/wiki/Long_and_short_scales#Short_scale) from 100 (hundred) to 1000000000000000 (quadrillion).

You can follow the [`en.json`](https://github.com/jlozovei/full-numbers/blob/master/src/languages/en.json) file as an example to follow, and see the [supported languages here](https://github.com/jlozovei/full-numbers/tree/master/src/languages).


## :closed_lock_with_key: License
Licensed under the [MIT](https://github.com/jlozovei/full-numbers/blob/master/LICENSE).
15 changes: 7 additions & 8 deletions src/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,16 @@ import isSafeNumber from './helpers/isSafeNumber';

import generateWords from './generateWords';

const convert = (params) => {
const { value, lang } = params;
const num = parseInt(value, 10);
const convert = (options) => {
const { value, lang } = options;

if (!isFinite(num)) {
/* if (!isFinite(num)) {
throw new TypeError('Not a finite number: ' + value + ' (' + typeof value + ')');
}
} */

if (!isSafeNumber(num)) {
/* if (!isSafeNumber(num)) {
throw new RangeError('Input is not a safe number, it’s either too large or too small.');
}
} */

try {
const locale = require(`./languages/${lang}.json`);
Expand All @@ -27,7 +26,7 @@ const convert = (params) => {
`);
}

return generateWords(num, lang);
return generateWords(options);
};

export default convert;
101 changes: 74 additions & 27 deletions src/generateWords.js
Original file line number Diff line number Diff line change
@@ -1,109 +1,156 @@
import isFloat from './helpers/isFloat';
import getDecimals from './helpers/getDecimals';
import padNumber from './helpers/padNumber';

import { scale } from './constants/index';

const state = {};

function floor(expression) {
return Math.floor(expression);
}

function generateWords(number, lang, lastWords) {
function wordify(number, lastWords) {
let word = '';
let words = lastWords;
let remainder = 0;

const locale = require(`./languages/${lang}.json`);

if (number === 0) return !words ? locale.LESS_THAN_TWENTY[0] : words.join(' ').replace(/,$/, '');
if (number === 0)
return !words ? state.locale.LESS_THAN_TWENTY[0] : words.join(' ').replace(/,$/, '');

if (!words) words = [];

if (number < scale.TWENTY) {
word = locale.LESS_THAN_TWENTY[number];
word = state.locale.LESS_THAN_TWENTY[number];
} else {
if (number < scale.ONE_HUNDRED) {
remainder = number % scale.TEN;
word = locale.DOZENS[floor(number / scale.TEN)];
word = state.locale.DOZENS[floor(number / scale.TEN)];

if (remainder) {
word += `${locale.PUNCTUATION.dozens}${locale.LESS_THAN_TWENTY[remainder]}`;
word += `${state.locale.PUNCTUATION.dozens}${state.locale.LESS_THAN_TWENTY[remainder]}`;
remainder = 0;
}
} else if (number < scale.ONE_THOUSAND) {
const factor = floor(number / scale.ONE_HUNDRED);
remainder = number % scale.ONE_HUNDRED;

if (remainder) {
word = `${locale.HUNDREDS[factor].plural} ${locale.PUNCTUATION.hundreds}`;
word = `${state.locale.HUNDREDS[factor].plural} ${state.locale.PUNCTUATION.hundreds}`;
} else {
word = locale.HUNDREDS[factor].singular;
word = state.locale.HUNDREDS[factor].singular;
}
} else if (number < scale.ONE_MILLION) {
const factor = floor(number / scale.ONE_THOUSAND);
remainder = number % scale.ONE_THOUSAND;
word = `${generateWords(floor(number / scale.ONE_THOUSAND), lang)} `;
word = `${wordify(floor(number / scale.ONE_THOUSAND))} `;

if (remainder) {
word += locale.SHORT_SCALE_NAME.thousand.plural;
word += state.locale.SHORT_SCALE_NAME.thousand.plural;
} else {
word += locale.SHORT_SCALE_NAME.thousand.singular;
word += state.locale.SHORT_SCALE_NAME.thousand.singular;
}

if ((remainder / scale.TEN) % scale.TEN === 0) {
word += ` ${locale.PUNCTUATION.hundreds}`;
word += ` ${state.locale.PUNCTUATION.hundreds}`;
} else {
word += ',';
}
} else if (number < scale.ONE_BILLION) {
const factor = floor(number / scale.ONE_MILLION);
remainder = number % scale.ONE_MILLION;
word = `${generateWords(floor(number / scale.ONE_MILLION), lang)} `;
word = `${wordify(floor(number / scale.ONE_MILLION))} `;

if (remainder) {
word += locale.SHORT_SCALE_NAME.million.plural;
word += state.locale.SHORT_SCALE_NAME.million.plural;
} else {
word += locale.SHORT_SCALE_NAME.million.singular;
word += state.locale.SHORT_SCALE_NAME.million.singular;
}

word += ',';
} else if (number < scale.ONE_TRILLION) {
const factor = floor(number / scale.ONE_BILLION);
remainder = number % scale.ONE_BILLION;
word = `${generateWords(floor(number / scale.ONE_BILLION), lang)} `;
word = `${wordify(floor(number / scale.ONE_BILLION))} `;

if (remainder) {
word += locale.SHORT_SCALE_NAME.billion.plural;
word += state.locale.SHORT_SCALE_NAME.billion.plural;
} else {
word += locale.SHORT_SCALE_NAME.billion.singular;
word += state.locale.SHORT_SCALE_NAME.billion.singular;
}

word += ',';
} else if (number < scale.ONE_QUADRILLION) {
const factor = floor(number / scale.ONE_TRILLION);
remainder = number % scale.ONE_TRILLION;
word = `${generateWords(floor(number / scale.ONE_TRILLION), lang)} `;
word = `${wordify(floor(number / scale.ONE_TRILLION))} `;

if (remainder) {
word += locale.SHORT_SCALE_NAME.trillion.plural;
word += state.locale.SHORT_SCALE_NAME.trillion.plural;
} else {
word += locale.SHORT_SCALE_NAME.trillion.singular;
word += state.locale.SHORT_SCALE_NAME.trillion.singular;
}

word += ',';
} else if (number <= scale.MAX_NUMBER) {
const factor = floor(number / scale.ONE_QUADRILLION);
remainder = number % scale.ONE_QUADRILLION;
word = `${generateWords(floor(number / scale.ONE_QUADRILLION), lang)} `;
word = `${wordify(floor(number / scale.ONE_QUADRILLION))} `;

if (remainder) {
word += locale.SHORT_SCALE_NAME.quadrillion.plural;
word += state.locale.SHORT_SCALE_NAME.quadrillion.plural;
} else {
word += locale.SHORT_SCALE_NAME.quadrillion.singular;
word += state.locale.SHORT_SCALE_NAME.quadrillion.singular;
}

word += ',';
}
}

words.push(word);
return generateWords(remainder, lang, words);
return wordify(remainder, words);
}

export default generateWords;
export default function generateWords(options) {
const { value, lang, currency } = options;
const hasDecimalValues = isFloat(value);

const locale = require(`./languages/${lang}.json`);
state.locale = locale;

if (currency) {
state.currency = currency;
}

if (hasDecimalValues) {
const decimals = getDecimals(value, 10);
const padded = padNumber(decimals);

state.decimals = {
value: padded,
words: wordify(parseInt(padded))
};
} else {
delete state.decimals;
}

const number = parseInt(value, 10);
let words = wordify(number);

if (state.currency) {
if (number > 1) words += ` ${state.currency.name.plural}`;
else words += ` ${state.currency.name.singular}`;

if (state.decimals) {
words += ` ${state.locale.PUNCTUATION.decimals} ${state.decimals.words}`;

if (state.decimals.value > 1) {
words += ` ${state.currency.decimals.plural}`;
} else {
words += ` ${state.currency.decimals.singular}`;
}
}
}

return words;
}
3 changes: 3 additions & 0 deletions src/helpers/getDecimals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function getDecimals(number) {
return (number + '').split('.')[1];
}
3 changes: 3 additions & 0 deletions src/helpers/isFloat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function isFloat(number) {
return Number(number) === number && number % 1 !== 0;
}
12 changes: 12 additions & 0 deletions src/helpers/padNumber.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default function padNumber(number) {
let pad;
const numberAsString = number + '';

if (numberAsString.length >= 2) {
pad = numberAsString.substring(0, 2);
} else {
pad = `${numberAsString}0`;
}

return pad;
}
Loading

0 comments on commit 38719e9

Please sign in to comment.