diff --git a/README-english.md b/README-english.md index 1e674b0..6706b5c 100644 --- a/README-english.md +++ b/README-english.md @@ -159,22 +159,23 @@ Currency of number. | `'eur'` | Euro | 124 **евро** 42 **цента** | | `'number'` | Number without currency | 124 **целых** 42 **сотых** | -
+**Note**: For all common currencies except `number` set `fractionalPartMinLength: 2`. Also these currencies will be rounded to `2`. - Own currency: ```js { - currencyNameCases: ['рубль', 'рубля', 'рублей'], // Integer currency names - fractionalPartNameCases: ['копейка', 'копейки', 'копеек'], // Fractional number currency names + currencyNameCases: ['рубль', 'рубля', 'рублей'], // [1 рубль, 2-4 рубля, 5-9 рублей] + fractionalPartNameCases: ['копейка', 'копейки', 'копеек'], currencyNounGender: { integer: 0, // 0 => ('один', 'два'...) fractionalPart: 1 // 1 => ('одна', 'две'...) } + fractionalPartMinLength: 2 } // or { - currencyNameCases: ['сообщение', 'сообщения', 'сообщений'], // [1 сообщение, 2-4 сообщения, 5-9 сообщений] + currencyNameCases: ['сообщение', 'сообщения', 'сообщений'], fractionalPartNameCases: ['', '', ''], currencyNounGender: { integer: 2, // 2 => ('одно', 'два'...) @@ -185,6 +186,22 @@ Currency of number. **Note**: If currency object will not be filled completely then missing data will be taken from default currency (`'rub'`). +#### Parameters of object `currency` + +`currencyNameCases: (Array)`: Currency form name of integer part. 3 elements in array. + +`fractionalPartNameCases: (Array)`: Currency form name of fractional part. 3 elements in array. + +`currencyNounGender: (Object)`: Form of number: 0 - ("один"), 1 - ("одна"), 2 - ("одно"). + +- `integer` - For integer part. + +- `fractionalPart` - For fractional part. + +`fractionalPartMinLength: (number)`: Minimal length of fractional part. For example if set `3` then in fractional part mey be number `002`. + +**Note**: In arrays `currencyNameCases` and `fractionalPartNameCases`: first element for digit 1 (1 `рубль`), second elemet for digits 2-4 (2 `рубля`), third element for digits 5-9 and 0 (5 `рублей`). +
``` @@ -203,25 +220,25 @@ Round number to specified precision. - `-1` - Disable rounding. -**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"). +**Note**: If option `currency` is a common currency (`rub` / `usd` / `eur`) then after rounding it will be rounded again to 2 digits. #### Example ```js numberToWordsRu.convert('129.6789', { - currency: 'eur', + currency: 'rub', roundNumber: 5, }); // Сто двадцать девять рублей 68 копеек numberToWordsRu.convert('129.6789', { - currency: 'eur', + currency: 'rub', roundNumber: 1, }); // Сто двадцать девять рублей 70 копеек numberToWordsRu.convert('129.6789', { - currency: 'eur', + currency: 'rub', roundNumber: 0, }); // Сто тридцать рублей 00 копеек diff --git a/README.md b/README.md index e7d4d89..26e4540 100644 --- a/README.md +++ b/README.md @@ -159,22 +159,23 @@ currency: (string|Object) | `'eur'` | Евро | 124 **евро** 42 **цента** | | `'number'` | Число без реальной валюты | 124 **целых** 42 **сотых** | -
+**Примечание**: Для всех стандартных валют, кроме `number` установлено `fractionalPartMinLength: 2`. Также эти валюты автоматически округляются до `2` знаков после запятой. - Своя валюта: ```js { - currencyNameCases: ['рубль', 'рубля', 'рублей'], // Падежи названия целой части числа - fractionalPartNameCases: ['копейка', 'копейки', 'копеек'], // Падежи названия дробной части числа + currencyNameCases: ['рубль', 'рубля', 'рублей'], // [1 рубль, 2-4 рубля, 5-9 рублей] + fractionalPartNameCases: ['копейка', 'копейки', 'копеек'], currencyNounGender: { integer: 0, // 0 => Мужской род ('один', 'два'...) fractionalPart: 1 // 1 => Женский род ('одна', 'две'...) - } + }, + fractionalPartMinLength: 2 } // или { - currencyNameCases: ['сообщение', 'сообщения', 'сообщений'], // [1 сообщение, 2-4 сообщения, 5-9 сообщений] + currencyNameCases: ['сообщение', 'сообщения', 'сообщений'], fractionalPartNameCases: ['', '', ''], currencyNounGender: { integer: 2, // 2 => Средний род ('одно', 'два'...) @@ -183,7 +184,23 @@ currency: (string|Object) } ``` -**Примечание**: Если объект валюты заполнить не полностью, то недостающие данные будут взяты из объекта валюты по умолчанию (`'rub'`). +**Примечание**: Если объект валюты заполнить не полностью, то недостающие параметры будут взяты из объекта валюты по умолчанию (`'rub'`). + +#### Параметры объекта `currency` + +`currencyNameCases: (Array)`: Формы названия валюты целой части числа. 3 элемента в массиве. + +`fractionalPartNameCases: (Array)`: Формы названия валюты дробной части числа. 3 элемента в массиве. + +`currencyNounGender: (Object)`: Род числа: 0 - мужской род (один), 1 - женский род (одна), 2 - средний род (одно). + +- `integer` - Для целой части числа. + +- `fractionalPart` - Для дробной части числа. + +`fractionalPartMinLength: (number)`: Минимальное количество знаков, котрое может остаться в дробной части. Например, при значении `3` в дробной части возможно число `002`. + +**Примечание**: В массивах `currencyNameCases` и `fractionalPartNameCases`: первый элемент для цифры 1 (1 `рубль`), второй элемент для цифр 2-4 (2 `рубля`), третий элемент для цифр 5-9 и 0 (5 `рублей`).
@@ -203,25 +220,25 @@ roundNumber: (number) - `-1` - Отключить округление. -**Примечание**: Если опция `currency` является стандартной валютой (`'rub'` / `'usd'` / `'eur'`), то даже после округления число будет еще раз округлено до 2 знаков после запятой. Также в этом случае у результата в дробной части всегда будет 2 знака (например "00", "05"). +**Примечание**: Если опция `currency` является стандартной валютой (`'rub'` / `'usd'` / `'eur'`), то даже после округления число будет еще раз округлено до 2 знаков после запятой. #### Пример ```js numberToWordsRu.convert('129.6789', { - currency: 'eur', + currency: 'rub', roundNumber: 5, }); // Сто двадцать девять рублей 68 копеек numberToWordsRu.convert('129.6789', { - currency: 'eur', + currency: 'rub', roundNumber: 1, }); // Сто двадцать девять рублей 70 копеек numberToWordsRu.convert('129.6789', { - currency: 'eur', + currency: 'rub', roundNumber: 0, }); // Сто тридцать рублей 00 копеек diff --git a/package.json b/package.json index 1db92bb..e60573e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "number-to-words-ru", - "version": "2.1.2", + "version": "2.2.0", "description": "Convert a number to words on russian language.", "license": "MIT", "repository": "Ant1mas/number-to-words-ru", diff --git a/src/combineResultData.js b/src/combineResultData.js index 22041d1..b36ba66 100644 --- a/src/combineResultData.js +++ b/src/combineResultData.js @@ -1,6 +1,7 @@ import textValues from 'textValues'; import getOptions from 'getOptions'; import roundNumber from 'roundNumber'; +import fractionalPartToMinLength from 'fractionalPartToMinLength'; import getCurrencyObject from 'getCurrencyObject'; import numberToScales from 'numberToScales'; import convertsEachScaleToWords from 'convertsEachScaleToWords'; @@ -30,28 +31,18 @@ const combineResultData = (numberArray, options) => { convertedNumberArr[0] = '-'; } } - // Если разделитель - не дробная черта - if (numberArray[2] !== '/') { - // Округлить число до заданной точности - modifiedNumberArray = roundNumber(numberArray, useOptions.roundNumber); - } + // Округлить число до заданной точности + modifiedNumberArray = roundNumber(numberArray, useOptions.roundNumber); // Если указана валюта if ( typeof useOptions.currency === 'string' && useOptions.currency !== 'number' ) { - // Если разделитель - не дробная черта - if (numberArray[2] !== '/') { - // Округлить число до 2 знаков после запятой - modifiedNumberArray = roundNumber(modifiedNumberArray, 2); - // Если в дробной части < 2 цифр - if (modifiedNumberArray[3].length < 2) { - // Заполнить нулями - modifiedNumberArray[3] = modifiedNumberArray[3] + '0' - .repeat(2 - modifiedNumberArray[3].length); - } - } + // Округлить число до 2 знаков после запятой + modifiedNumberArray = roundNumber(modifiedNumberArray, 2); } + // Обеспечить минимальную длину дробной части числа + modifiedNumberArray = fractionalPartToMinLength(modifiedNumberArray, currencyObject); // Если нужно отображать целую часть числа if (useOptions.showNumberParts.integer === true) { convertedNumberArr[1] = modifiedNumberArray[1]; @@ -105,7 +96,7 @@ const combineResultData = (numberArray, options) => { ).result; } } else { - // Если не нужно конвертировать в слова + // Если не нужно конвертировать число в слова // Если валюта "number" if (useOptions.currency === 'number') { // Если в дробной части есть цифры diff --git a/src/fractionalPartToMinLength.js b/src/fractionalPartToMinLength.js new file mode 100644 index 0000000..405c0c3 --- /dev/null +++ b/src/fractionalPartToMinLength.js @@ -0,0 +1,23 @@ +/** + * Сделать так, чтобы у дробной части числа + * минимальная длина соответствовала currencyObject.fractionalPartMinLength + * @param {Array} numberArray - Число в виде массива ['-', '150', '/', '25']. + * @param {Object} currencyObject - Объект с параметрами валюты. + * @return {Array} Обновленный массив числа. + */ +const fractionalPartToMinLength = (numberArray, currencyObject) => { + // Если разделитель - дробная черта + if (numberArray[2] === '/') { + return numberArray; + } + const updatedNumberArray = [...numberArray]; + // Если в дробной части цифр меньше, чем fractionalPartMinLength + if (updatedNumberArray[3].length < currencyObject.fractionalPartMinLength) { + // Заполнить нулями + updatedNumberArray[3] = updatedNumberArray[3] + '0' + .repeat(currencyObject.fractionalPartMinLength - updatedNumberArray[3].length); + } + return updatedNumberArray; +}; + +export default fractionalPartToMinLength; diff --git a/src/getCurrencyObject.js b/src/getCurrencyObject.js index 1075dcd..c048dd2 100644 --- a/src/getCurrencyObject.js +++ b/src/getCurrencyObject.js @@ -1,6 +1,5 @@ import textValues from 'textValues'; import defaultOptions from 'defaultOptions'; -import stringCurrencies from 'stringCurrencies'; import updateObjectDeep from 'updateObjectDeep'; /** @@ -10,22 +9,17 @@ import updateObjectDeep from 'updateObjectDeep'; */ const getCurrencyObject = (convertOptions) => { let currencyObject; - // Если отображение без валюты - if (convertOptions.currency === 'number') { - currencyObject = { - currencyNameCases: ['целая', 'целых', 'целых'], - getFractionalPartNameCases: textValues.getFractionalUnits, - currencyNounGender: { - integer: 1, - fractionalPart: 1, - }, - }; // Если валюта указана словами - } else if (typeof convertOptions.currency === 'string') { + if (typeof convertOptions.currency === 'string') { // Если такая валюта есть в списке if (textValues.currency[convertOptions.currency] !== undefined) { // Получить данные найденной валюты currencyObject = textValues.currency[convertOptions.currency]; + // Если валюта указана как "number" + if (convertOptions.currency === 'number') { + // Добавить функцию для заполнения fractionalPartNameCases + currencyObject.getFractionalPartNameCases = textValues.getFractionalUnits; + } } else { throw new Error( 'Wrong currency name [' + convertOptions.currency + ']. ' @@ -35,13 +29,13 @@ const getCurrencyObject = (convertOptions) => { // Если валюта описана объектом } else if (typeof convertOptions.currency === 'object') { // Объект валюты по умолчанию - const defaultCurrencyObject = stringCurrencies[defaultOptions['currency']]; + const defaultCurrencyObject = textValues.currency[defaultOptions['currency']]; // Обновить объект валюты новым объектом валюты const updatedCurrencyObject = updateObjectDeep(defaultCurrencyObject, convertOptions.currency); // Если объект оформлен правильно if ( typeof updatedCurrencyObject === 'object' && - Object.keys(updatedCurrencyObject).length === 3 && + Object.keys(updatedCurrencyObject).length === 4 && updatedCurrencyObject.currencyNameCases.length === 3 && updatedCurrencyObject.fractionalPartNameCases.length === 3 && typeof updatedCurrencyObject.currencyNounGender === 'object' && diff --git a/src/methods/convert.js b/src/methods/convert.js index 91c0cbd..fb49119 100644 --- a/src/methods/convert.js +++ b/src/methods/convert.js @@ -9,7 +9,7 @@ import combineResultData from 'combineResultData'; */ const convert = (number, options = {}) => { // Обработать введенное число - const numberArray = splitNumberToArray(number); + const numberArray = splitNumberToArray(number, options); // Собрать конечный словестный результат const convertedNumberString = combineResultData(numberArray, options); return convertedNumberString; diff --git a/src/roundNumber.js b/src/roundNumber.js index fc58837..620744f 100644 --- a/src/roundNumber.js +++ b/src/roundNumber.js @@ -11,6 +11,10 @@ const roundNumber = (numberArray, precision = 2) => { if (precision < 0) { return numberArray; } + // Если разделитель - дробная черта то не округлять + if (numberArray[2] === '/') { + return numberArray; + } // Если количество знаков после запятой <= precision, то не округлять if (numberArray[3].length <= precision) { return numberArray; diff --git a/src/splitNumberToArray.js b/src/splitNumberToArray.js index 63725e2..a705589 100644 --- a/src/splitNumberToArray.js +++ b/src/splitNumberToArray.js @@ -1,9 +1,10 @@ /** * Разделить число на части. * @param {(number|string)} number - Число, которое нужно обработать. + * @param {Object} options - Параметры конвертирования. * @return {Array} Обработанное число в виде ['-', '150', '/', '25']. */ -const splitNumberToArray = (number) => { +const splitNumberToArray = (number, options) => { // Максимальная длинна целой части числа const maxIntegerPartLength = 306; // Конвертировать в String @@ -33,8 +34,11 @@ const splitNumberToArray = (number) => { numberArray[3] = numberArray[3] === undefined ? '' : numberArray[3]; // Убрать лишние нули из целой части numberArray[1] = numberArray[1].replace(/^0+/, ''); - // Если разделитель не дробная черта - if (numberArray[2] !== '/') { + // Если разделитель не дробная черта и валюта не 'number' + if ( + numberArray[2] !== '/' + && options.currency !== 'number' + ) { // Убрать лишние нули из дробной части numberArray[3] = numberArray[3] .split('') diff --git a/src/stringCurrencies.js b/src/stringCurrencies.js index d9a1142..1dda9d6 100644 --- a/src/stringCurrencies.js +++ b/src/stringCurrencies.js @@ -1,5 +1,17 @@ +const defaultParams = { + fractionalPartMinLength: 2, +}; + const stringCurrencies = { + number: { + currencyNameCases: ['целая', 'целых', 'целых'], + currencyNounGender: { + integer: 1, + fractionalPart: 1, + }, + }, rub: { + ...defaultParams, currencyNameCases: ['рубль', 'рубля', 'рублей'], fractionalPartNameCases: ['копейка', 'копейки', 'копеек'], currencyNounGender: { @@ -8,6 +20,7 @@ const stringCurrencies = { }, }, usd: { + ...defaultParams, currencyNameCases: ['доллар', 'доллара', 'долларов'], fractionalPartNameCases: ['цент', 'цента', 'центов'], currencyNounGender: { @@ -16,6 +29,7 @@ const stringCurrencies = { }, }, eur: { + ...defaultParams, currencyNameCases: ['евро', 'евро', 'евро'], fractionalPartNameCases: ['цент', 'цента', 'центов'], currencyNounGender: { diff --git a/test/index.test.js b/test/index.test.js index 9c2a86d..5805d5b 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -374,6 +374,9 @@ describe('Options', () => { expect(numberToWordsRu.convert('1234567.12345', { currency: 'number', })).toBe('Один миллион двести тридцать четыре тысячи пятьсот шестьдесят семь целых 12345 стотысячных'); + expect(numberToWordsRu.convert('1.8000', { + currency: 'number', + })).toBe('Одна целая 8000 десятитысячных'); }); test("'number' words", () => { expect(numberToWordsRu.convert('1234567.12345', { @@ -382,6 +385,12 @@ describe('Options', () => { fractional: true, }, })).toBe('Один миллион двести тридцать четыре тысячи пятьсот шестьдесят семь целых двенадцать тысяч триста сорок пять стотысячных'); + expect(numberToWordsRu.convert('1.8000', { + currency: 'number', + convertNumbertToWords: { + fractional: true, + }, + })).toBe('Одна целая восемь тысяч десятитысячных'); }); }); describe('object values', () => { @@ -402,12 +411,44 @@ describe('Options', () => { currencyNameCases: ['доллар', 'доллара', 'долларов'], fractionalPartNameCases: ['цент', 'цента', 'центов'], currencyNounGender: { - integer: 0, // Мужской род - fractionalPart: 1, // Женский род + integer: 0, + fractionalPart: 1, }, }, roundNumber: 2, })).toBe('Один миллион двести тридцать четыре тысячи пятьсот шестьдесят семь долларов 12 центов'); + expect(numberToWordsRu.convert('1.6789', { + currency: { + currencyNameCases: ['доллар', 'доллара', 'долларов'], + fractionalPartNameCases: ['цент', 'цента', 'центов'], + fractionalPartMinLength: 2, + }, + roundNumber: 1, + })).toBe('Один доллар 70 центов'); + expect(numberToWordsRu.convert('1.6789', { + currency: { + currencyNameCases: ['доллар', 'доллара', 'долларов'], + fractionalPartNameCases: ['цент', 'цента', 'центов'], + fractionalPartMinLength: 2, + }, + roundNumber: 0, + })).toBe('Два доллара 00 центов'); + expect(numberToWordsRu.convert('1.6789', { + currency: { + currencyNameCases: ['доллар', 'доллара', 'долларов'], + fractionalPartNameCases: ['цент', 'цента', 'центов'], + fractionalPartMinLength: 1, + }, + roundNumber: 0, + })).toBe('Два доллара 0 центов'); + expect(numberToWordsRu.convert('1.6789', { + currency: { + currencyNameCases: ['доллар', 'доллара', 'долларов'], + fractionalPartNameCases: ['цент', 'цента', 'центов'], + fractionalPartMinLength: 0, + }, + roundNumber: 0, + })).toBe('Два доллара'); }); test('not full objects', () => { expect(numberToWordsRu.convert('1234561.12345', { @@ -589,7 +630,7 @@ describe('Options', () => { }, }, roundNumber: 0, - })).toBe('Одна тысяча двести тридцать пять рублей'); + })).toBe('Одна тысяча двести тридцать пять рублей 00 копеек'); expect(numberToWordsRu.convert('1234.6789', { currency: { currencyNameCases: ['рубль', 'рубля', 'рублей'],