diff --git a/README-english.md b/README-english.md index c181491..1e674b0 100644 --- a/README-english.md +++ b/README-english.md @@ -89,33 +89,31 @@ numberToWordsRu.convert('-4201512.21', { ## Methods -- [convert(number[, options])](#methods-convert) +- convert(number, [options]) ------------------------ -**

`convert(number[, options])`

** +``` +convert(number, [options]) +``` -*Convert number to words.* +Convert number to words. -Return value is *String*. +#### **Arguments** -
+`number (string|number)`: Number to convert to words. -`number` {String | Number} +>If typed as *number* max value is **9'007'199'254'740'991** (limit of Javascript). -*Number to convert to words.* +>If typed as *string* max value is 10305 (**306 digits**) before point and 10304 (**305 digits**) after point. -If typed as *Number* max value is **9'007'199'254'740'991** (limit of Javascript). +`[options] (Object)`: Convert options. -If typed as *String* max value is **10305** (306 digits) before point and **10304** (305 digits) after point. +#### **Returns** -
+`(string)`: Returns converted to text number. -`options` {Object} - -*Convert options.* - -**Default options**: +**Default *options* object**: ```js { @@ -138,15 +136,21 @@ If typed as *String* max value is **10305** (306 digits) before point ``` ------------------------ -### **Options object** +### **Argument `options`** + +``` +currency: (string|Object) +``` -`currency`: {String | Object} +Currency of number. -*Select currency.* +#### **Default value** -**Default**: `'rub'` +'rub' -String values: +#### **Possible values** + +- String values: | String value | Description | Example | | ------------- | ------------- | ------------- | @@ -157,7 +161,7 @@ String values:
-Own currency **object** example: +- Own currency: ```js { @@ -179,21 +183,29 @@ Own currency **object** example: } ``` +**Note**: If currency object will not be filled completely then missing data will be taken from default currency (`'rub'`). +
-`roundNumber`: {Number} +``` +roundNumber: (number) +``` + +Round number to specified precision. -*Round number to specified precision.* +#### **Default value** -**Default**: `-1` +-1 -`{Number}` - Precision. +#### **Possible values** -`-1` - Disable rounding. +- `(number)` - Precision. Integer. -If option `currency` is a common currency (`rub` / `usd` / `eur`) then after rounding it will be rounded again to 2 digits. Also in this case the result always will have 2 digits in fractional part (for example "00", "05"). +- `-1` - Disable rounding. -Example: +**Note**: If option `currency` is a common currency (`rub` / `usd` / `eur`) then after rounding it will be rounded again to 2 digits. Also in this case the result always will have 2 digits in fractional part (for example "00", "05"). + +#### Example ```js numberToWordsRu.convert('129.6789', { @@ -215,32 +227,38 @@ numberToWordsRu.convert('129.6789', { // Сто тридцать рублей 00 копеек ``` -If delimiter is slash ("`/`") then number will NOT be rounded in any case. +**Note**: If delimiter is slash ("`/`") then number will NOT be rounded in any case.
-`convertMinusSignToWord`: {Boolean} +``` +convertMinusSignToWord: (Boolean) +``` + +Convert minus sign to word ( '-' --> 'минус' ). -*Convert minus sign to word ('-' => 'минус').* +#### **Default value** -**Default**: `true` +true
-`showNumberParts`: {Object} +``` +showNumberParts: (Object) +``` -*Show parts of number.* +Show parts of number. -**Default object**: +#### **Default value** ```js -showNumberParts: { +{ integer: true, fractional: true } ``` -Example: +#### Example ```js numberToWordsRu.convert('123.45', { @@ -262,20 +280,22 @@ numberToWordsRu.convert('123.45', {
-`convertNumbertToWords`: {Object} +``` +convertNumbertToWords: (Object) +``` -*Convert to words parts of number.* +Convert to words parts of number. -**Default object**: +#### **Default value** ```js -convertNumbertToWords: { +{ integer: true, fractional: false } ``` -Example: +#### Example ```js numberToWordsRu.convert('123.45', { @@ -297,20 +317,22 @@ numberToWordsRu.convert('123.45', {
-`showCurrency`: {Object} +``` +showCurrency: (Object) +``` -*Show currency in parts of number.* +Show currency in parts of number. -**Default object**: +#### **Default value** ```js -showCurrency: { +{ integer: true, fractional: true } ``` -Example: +#### Example ```js numberToWordsRu.convert('123.45', { diff --git a/README.md b/README.md index 9249363..e7d4d89 100644 --- a/README.md +++ b/README.md @@ -89,33 +89,31 @@ numberToWordsRu.convert('-4201512.21', { ## Методы -- [convert(number[, options])](#methods-convert) +- convert(number, [options]) ------------------------ -**

`convert(number[, options])`

** +``` +convert(number, [options]) +``` -*Конвертировать число в слова.* +Конвертировать число в слова. -Тип возвращаемых данных: *String*. +#### **Аргументы метода** -
+`number (string|number)`: Число, которое нужно конвертировать. -`number`: {String | Number} +>Если введенное число типа *number*, то максимальное значение **9'007'199'254'740'991** (ограничение Javascript). -*Число, которое нужно конвертировать.* +>Если введенное число типа *string*, то максимальное значение 10305 (**306 цифр**) до запятой и 10304 (**305 цифр**) после запятой. -Если введенное число типа *Number*, то максимальное значение **9'007'199'254'740'991** (ограничение Javascript). +`[options] (Object)`: Опции конвертирования числа. -Если введенное число типа *String*, то максимальное значение **10305** (306 цифр) до запятой и **10304** (305 цифр) после запятой. +#### **Возвращаемое значение** -
+`(string)`: Возвращает конвертированное в текст число. -`options`: {Object} - -*Опции конвертирования числа.* - -**Опций по умолчанию**: +**Объект *options* по умолчанию**: ```js { @@ -138,15 +136,21 @@ numberToWordsRu.convert('-4201512.21', { ``` ------------------------ -### **Объект опций** +### **Аргумент `options`** + +``` +currency: (string|Object) +``` -`currency`: {String | Object} +Валюта числа. -*Валюта числа.* +#### **Значение по умолчанию** -**По умолчанию**: `'rub'` +'rub' -Строковые значения: +#### **Возможные значения** + +- Строковые значения: | Строковое значение | Описание | Пример | | ------------- | ------------- | ------------- | @@ -157,7 +161,7 @@ numberToWordsRu.convert('-4201512.21', {
-Можно задать свою валюту объектом: +- Своя валюта: ```js { @@ -178,21 +182,30 @@ numberToWordsRu.convert('-4201512.21', { } } ``` + +**Примечание**: Если объект валюты заполнить не полностью, то недостающие данные будут взяты из объекта валюты по умолчанию (`'rub'`). +
-`roundNumber`: {Number} +``` +roundNumber: (number) +``` + +Округлить число до заданной точности. + +#### **Значение по умолчанию** -*Округлить число до заданной точности.* +-1 -**По умолчанию**: `-1` +#### **Возможные значения** -`{Number}` - Количество знаков после запятой, до которой нужно округлить число. +- `(number)` - Целое число. Количество знаков после запятой, до которой нужно округлить число. -`-1` - Отключить округление. +- `-1` - Отключить округление. -Если опция `currency` является стандартной валютой (`rub` / `usd` / `eur`), то даже после округления число будет еще раз округлено до 2 знаков после запятой. Также в этом случае у результата в дробной части всегда будет 2 знака (например "00", "05"). +**Примечание**: Если опция `currency` является стандартной валютой (`'rub'` / `'usd'` / `'eur'`), то даже после округления число будет еще раз округлено до 2 знаков после запятой. Также в этом случае у результата в дробной части всегда будет 2 знака (например "00", "05"). -Например: +#### Пример ```js numberToWordsRu.convert('129.6789', { @@ -214,32 +227,38 @@ numberToWordsRu.convert('129.6789', { // Сто тридцать рублей 00 копеек ``` -Если разделитель числа является дробной чертой ("`/`"), то число НЕ будет округлено в любом случае. +**Примечание**: Если разделитель числа является дробной чертой ("`/`"), то число НЕ будет округлено в любом случае.
-`convertMinusSignToWord`: {Boolean} +``` +convertMinusSignToWord: (Boolean) +``` -*Конвертировать знак минус в слово ('-' => 'минус').* +Конвертировать знак минус в слово ( '-' --> 'минус' ). -**По умолчанию**: `true` +#### **Значение по умолчанию** + +true
-`showNumberParts`: {Object} +``` +showNumberParts: (Object) +``` -*Отображать часть числа.* +Отображать указанные части числа. -**Объект по умолчанию**: +#### **Значение по умолчанию** ```js -showNumberParts: { +{ integer: true, // Целая часть числа fractional: true // Дробная часть числа } ``` -Например: +#### Пример ```js numberToWordsRu.convert('123.45', { @@ -261,20 +280,22 @@ numberToWordsRu.convert('123.45', {
-`convertNumbertToWords`: {Object} +``` +convertNumbertToWords: (Object) +``` -*Конвертировать в слова указанные части числа .* +Конвертировать в слова указанные части числа . -**Объект по умолчанию**: +#### **Значение по умолчанию** ```js -convertNumbertToWords: { +{ integer: true, // Целая часть числа fractional: false // Дробная часть числа } ``` -Например: +#### Пример ```js numberToWordsRu.convert('123.45', { @@ -296,20 +317,22 @@ numberToWordsRu.convert('123.45', {
-`showCurrency`: {Object} +``` +showCurrency: (Object) +``` -*Отображать валюту в указанных частях числа.* +Отображать валюту в указанных частях числа. -**Объект по умолчанию**: +#### **Значение по умолчанию** ```js -showCurrency: { +{ integer: true, // Целая часть числа fractional: true // Дробная часть числа } ``` -Например: +#### Пример ```js numberToWordsRu.convert('123.45', { diff --git a/package.json b/package.json index e1dd5de..1db92bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "number-to-words-ru", - "version": "2.1.1", + "version": "2.1.2", "description": "Convert a number to words on russian language.", "license": "MIT", "repository": "Ant1mas/number-to-words-ru", @@ -20,6 +20,7 @@ "eslint-config-google": "^0.14.0", "html-webpack-plugin": "^4.3.0", "jest": "^26.0.1", + "lodash": "^4.17.15", "webpack": "^4.41.2", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.9.0", diff --git a/src/combineResultData.js b/src/combineResultData.js index 0819ee3..22041d1 100644 --- a/src/combineResultData.js +++ b/src/combineResultData.js @@ -104,6 +104,20 @@ const combineResultData = (numberArray, options) => { currencyObject.currencyNounGender.fractionalPart, ).result; } + } else { + // Если не нужно конвертировать в слова + // Если валюта "number" + if (useOptions.currency === 'number') { + // Если в дробной части есть цифры + if (convertedNumberArr[3].length > 0) { + // Удалить лишние нули перед числом + convertedNumberArr[3] = convertedNumberArr[3].replace(/^0+/, ''); + // Если после удаления лишних нулей не осталось цифр, то добавить один "0" + if (convertedNumberArr[3] === '') { + convertedNumberArr[3] = '0'; + } + } + } } // Если нужно отображать валюту числа if (useOptions.showCurrency.fractional === true) { diff --git a/src/defaultOptions.js b/src/defaultOptions.js new file mode 100644 index 0000000..9ed473c --- /dev/null +++ b/src/defaultOptions.js @@ -0,0 +1,22 @@ +const defaultOptions = { + /* currency - Название валюты ('rub', 'usd', 'eur') + или 'number' + или объект со своей валютой */ + currency: 'rub', + roundNumber: -1, + convertMinusSignToWord: true, + showNumberParts: { + integer: true, + fractional: true, + }, + convertNumbertToWords: { + integer: true, + fractional: false, + }, + showCurrency: { + integer: true, + fractional: true, + }, +}; + +export default defaultOptions; diff --git a/src/getCurrencyObject.js b/src/getCurrencyObject.js index e3a112a..1075dcd 100644 --- a/src/getCurrencyObject.js +++ b/src/getCurrencyObject.js @@ -1,4 +1,7 @@ import textValues from 'textValues'; +import defaultOptions from 'defaultOptions'; +import stringCurrencies from 'stringCurrencies'; +import updateObjectDeep from 'updateObjectDeep'; /** * Получить объект с данными валюты. @@ -31,19 +34,23 @@ const getCurrencyObject = (convertOptions) => { } // Если валюта описана объектом } else if (typeof convertOptions.currency === 'object') { + // Объект валюты по умолчанию + const defaultCurrencyObject = stringCurrencies[defaultOptions['currency']]; + // Обновить объект валюты новым объектом валюты + const updatedCurrencyObject = updateObjectDeep(defaultCurrencyObject, convertOptions.currency); // Если объект оформлен правильно if ( - typeof convertOptions.currency === 'object' && - Object.keys(convertOptions.currency).length === 3 && - convertOptions.currency.currencyNameCases.length === 3 && - convertOptions.currency.fractionalPartNameCases.length === 3 && - typeof convertOptions.currency.currencyNounGender === 'object' && - Object.keys(convertOptions.currency.currencyNounGender).length === 2 && - typeof convertOptions.currency.currencyNounGender.integer === 'number' && - typeof convertOptions.currency.currencyNounGender.fractionalPart === 'number' + typeof updatedCurrencyObject === 'object' && + Object.keys(updatedCurrencyObject).length === 3 && + updatedCurrencyObject.currencyNameCases.length === 3 && + updatedCurrencyObject.fractionalPartNameCases.length === 3 && + typeof updatedCurrencyObject.currencyNounGender === 'object' && + Object.keys(updatedCurrencyObject.currencyNounGender).length === 2 && + typeof updatedCurrencyObject.currencyNounGender.integer === 'number' && + typeof updatedCurrencyObject.currencyNounGender.fractionalPart === 'number' ) { // Принять валюту - currencyObject = convertOptions.currency; + currencyObject = updatedCurrencyObject; } else { // Если объект оформлен неправильно throw new Error(`Wrong currency object.`); diff --git a/src/getOptions.js b/src/getOptions.js index 4b4d26c..8b457e0 100644 --- a/src/getOptions.js +++ b/src/getOptions.js @@ -1,3 +1,6 @@ +import _ from 'lodash'; +import defaultOptions from 'defaultOptions'; + /** * Получить опции конверирования. * @param {Object} options - Опции, выбранные пользователем. @@ -5,26 +8,7 @@ */ const getOptions = (options) => { // Опции по умолчанию - const resultOptions = { - /* currency - Название валюты ('rub', 'usd', 'eur') - или 'number' - или объект со своей валютой */ - currency: 'rub', - roundNumber: -1, - convertMinusSignToWord: true, - showNumberParts: { - integer: true, - fractional: true, - }, - convertNumbertToWords: { - integer: true, - fractional: false, - }, - showCurrency: { - integer: true, - fractional: true, - }, - }; + const resultOptions = _.cloneDeep(defaultOptions); // Заменить опции по умолчанию выбранными опциями, если они правильно указаны const updateOptions = (currentOptions, newOptions) => { Object.keys(currentOptions).forEach((key) => { diff --git a/src/index.js b/src/index.js index 5b3a090..a40f0b1 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,4 @@ +import 'lodashMixins/index'; import methodConvert from 'methods/convert'; const numberToWordsRu = { diff --git a/src/lodashMixins/deepMapValues.js b/src/lodashMixins/deepMapValues.js new file mode 100644 index 0000000..220df25 --- /dev/null +++ b/src/lodashMixins/deepMapValues.js @@ -0,0 +1,18 @@ +const deepMapValues = { + 'deepMapValues': (object, iteratee) => { + const result = _.cloneDeep(object); + const iterateObject = (object, path = []) => { + _.forOwn(object, (value, key) => { + if (_.isPlainObject(value)) { + value = iterateObject(value, [...path, key]); + } else { + _.set(result, [...path, key], iteratee(path, key, value)); + } + }); + }; + iterateObject(object); + return result; + }, +}; + +export default deepMapValues; diff --git a/src/lodashMixins/index.js b/src/lodashMixins/index.js new file mode 100644 index 0000000..4cc80cf --- /dev/null +++ b/src/lodashMixins/index.js @@ -0,0 +1,6 @@ +import _ from 'lodash'; +import deepMapValues from 'lodashMixins/deepMapValues'; + +_.mixin({ + ...deepMapValues, +}); diff --git a/src/roundNumber.js b/src/roundNumber.js index a8e9a3c..fc58837 100644 --- a/src/roundNumber.js +++ b/src/roundNumber.js @@ -61,6 +61,22 @@ const roundNumber = (numberArray, precision = 2) => { const resultNumberArray = [...numberArray]; resultNumberArray[1] = numberPartToRound.slice(0, -1).split('.')[0]; resultNumberArray[3] = numberPartToRound.slice(0, -1).split('.')[1]; + // Убрать лишние нули из дробной части справа + resultNumberArray[3] = resultNumberArray[3] + .split('') + .reverse() + .join('') + .replace(/^0+/, '') + .split('') + .reverse() + .join(''); + // Если дробная часть пустая, то сделать равной 0 + if ( + resultNumberArray[3] === '' && + precision > 0 + ) { + resultNumberArray[3] = '0'; + } return resultNumberArray; }; diff --git a/src/updateObjectDeep.js b/src/updateObjectDeep.js new file mode 100644 index 0000000..0183817 --- /dev/null +++ b/src/updateObjectDeep.js @@ -0,0 +1,23 @@ +import _ from 'lodash'; + +/** + * Рекурсивное обновление значений в объекте. + * Новые поля не добавляются. Значение обновляется, только если тип полей одинаковый. + * @param {Object} object - Исходный объект. + * @param {Object} newObject - Новый объект. + * @return {Object} Обновленный объект. + */ +const updateObjectDeep = (object, newObject) => { + return _.deepMapValues(object, (path, key, value) => { + // Если тип данных одинаковый + if (Object.prototype.toString.call(_.get(newObject, [...path, key])) === Object.prototype.toString.call(value)) { + // Заменить новым значением + return _.get(newObject, [...path, key]); + } else { + // Оставить старое значение + return value; + } + }); +}; + +export default updateObjectDeep; diff --git a/test/index.test.js b/test/index.test.js index 4904fdf..9c2a86d 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -385,28 +385,43 @@ describe('Options', () => { }); }); describe('object values', () => { - test('common value', () => { - expect(numberToWordsRu.convert('1234567.12345', { - currency: { - currencyNameCases: ['доллар', 'доллара', 'долларов'], - fractionalPartNameCases: ['цент', 'цента', 'центов'], - currencyNounGender: { - integer: 0, // Мужской род - fractionalPart: 1, // Женский род + describe('common values', () => { + test('full objects', () => { + expect(numberToWordsRu.convert('1234567.12345', { + currency: { + currencyNameCases: ['доллар', 'доллара', 'долларов'], + fractionalPartNameCases: ['цент', 'цента', 'центов'], + currencyNounGender: { + integer: 0, // Мужской род + fractionalPart: 1, // Женский род + }, }, - }, - })).toBe('Один миллион двести тридцать четыре тысячи пятьсот шестьдесят семь долларов 12345 центов'); - expect(numberToWordsRu.convert('1234567.12345', { - currency: { - currencyNameCases: ['доллар', 'доллара', 'долларов'], - fractionalPartNameCases: ['цент', 'цента', 'центов'], - currencyNounGender: { - integer: 0, // Мужской род - fractionalPart: 1, // Женский род + })).toBe('Один миллион двести тридцать четыре тысячи пятьсот шестьдесят семь долларов 12345 центов'); + expect(numberToWordsRu.convert('1234567.12345', { + currency: { + currencyNameCases: ['доллар', 'доллара', 'долларов'], + fractionalPartNameCases: ['цент', 'цента', 'центов'], + currencyNounGender: { + integer: 0, // Мужской род + fractionalPart: 1, // Женский род + }, }, - }, - roundNumber: 2, - })).toBe('Один миллион двести тридцать четыре тысячи пятьсот шестьдесят семь долларов 12 центов'); + roundNumber: 2, + })).toBe('Один миллион двести тридцать четыре тысячи пятьсот шестьдесят семь долларов 12 центов'); + }); + test('not full objects', () => { + expect(numberToWordsRu.convert('1234561.12345', { + currency: { + currencyNameCases: ['сообщение', 'сообщения', 'сообщений'], + currencyNounGender: { + integer: 2, // Средний род + }, + }, + })).toBe('Один миллион двести тридцать четыре тысячи пятьсот шестьдесят одно сообщение 12345 копеек'); + expect(numberToWordsRu.convert('1234567.12345', { + currency: {}, + })).toBe('Один миллион двести тридцать четыре тысячи пятьсот шестьдесят семь рублей 12345 копеек'); + }); }); test('currencyNounGender == 2', () => { expect(numberToWordsRu.convert('1231.52', { @@ -528,6 +543,18 @@ describe('Options', () => { currency: 'number', roundNumber: -1, })).toBe('Одна тысяча двести тридцать четыре целых 6789 десятитысячных'); + expect(numberToWordsRu.convert('1.9999', { + currency: 'number', + roundNumber: 3, + })).toBe('Две целых 0 десятых'); + expect(numberToWordsRu.convert('1.9999', { + currency: 'number', + roundNumber: 0, + })).toBe('Две целых'); + expect(numberToWordsRu.convert('1.00089', { + currency: 'number', + roundNumber: 5, + })).toBe('Одна целая 89 стотысячных'); }); test('custom currency', () => { expect(numberToWordsRu.convert('1234.6789', {