-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
119 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# ru-plurals | ||
|
||
[![npm version](http://img.shields.io/npm/v/ru-plurals.svg?style=flat)](https://npmjs.org/package/ru-plurals "View this project on npm") | ||
[![flow coverage](https://img.shields.io/badge/flow%20coverage-99%25-brightgreen.svg)](https://flow.org) | ||
[![jest coverage](https://img.shields.io/badge/jest%20coverage-92%25-brightgreen.svg)](https://jestjs.io) | ||
|
||
Simple functional pluralization of Russian, Belarusian, and Ukrainian words. | ||
|
||
## Install | ||
```bash | ||
npm install --save ru-plurals | ||
# or | ||
yarn add ru-plurals | ||
``` | ||
|
||
## Usage | ||
#### plural | ||
```ts | ||
import { plural } from 'ru-plurals'; | ||
|
||
const ruble = plural('рубль', 'рубля', 'рублей'); | ||
const work = plural('работает', 'работают'); // same as plural('работает', 'работают', 'работают'); | ||
const coffee = plural('кофе'); // same as plural('кофе', 'кофе', 'кофе') | ||
|
||
ruble(101) // 'рубль' | ||
ruble(500) // 'рублей' | ||
work(21) // 'работает' | ||
coffee(2) // 'кофе' | ||
``` | ||
#### format | ||
```ts | ||
import { format } from 'ru-plurals'; | ||
|
||
const meters = format((count, word) => `${count} {word}`, 'метр', 'метра', 'метров'); | ||
|
||
meters(1) // '1 метр' | ||
meters(200) // '200 метров' | ||
``` | ||
with jsx: | ||
```ts | ||
const distance = format( | ||
(count, word) => ( | ||
<div> | ||
<div>{count}</div> | ||
<span>{word}</span> | ||
</div> | ||
), | ||
'метр', | ||
'метра', | ||
'метров', | ||
); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,18 @@ | ||
// @flow strict | ||
|
||
type Plural = (number: ?number) => string; | ||
import plural from './plural'; | ||
|
||
// N - number parameter (e.g. or ?number) | ||
// R - retyrn type | ||
export type Formatter = <N, R>(number: N, word: string) => R; | ||
|
||
function format(formatter: Formatter) { | ||
return (_plural: Plural) => (number: ?number) => | ||
formatter(number, _plural(number)); | ||
function format( | ||
formatter: Formatter, | ||
one: string, | ||
two?: string, | ||
five?: string, | ||
) { | ||
return (number: ?number) => formatter(number, plural(one, two, five)(number)); | ||
} | ||
|
||
export default format; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,17 @@ | ||
/* eslint-disable sonarjs/no-duplicate-string */ | ||
import plural from './plural'; | ||
import format from './format'; | ||
|
||
// nouns | ||
const ruble = plural('рубль', 'рубля', 'рублей'); | ||
const meter = plural('метр', 'метра', 'метров'); | ||
const seat = plural('место', 'места', 'мест'); | ||
const coffee = plural('кофе'); | ||
|
||
// formatting | ||
const joinDash = (number, word) => `${number}-${word}`; | ||
const joinSpace = (number, word) => `${number} ${word}`; | ||
const skipZero = (number, word) => (number > 0 ? `${number} ${word}` : '-'); | ||
|
||
describe.each([ | ||
[joinDash, ruble, 0, '0-рублей'], | ||
[joinSpace, meter, 1, '1 метр'], | ||
[skipZero, coffee, 0, '-'], | ||
[skipZero, seat, 5, '5 мест'], | ||
])('formatPlural', (formatter, wordPlural, number, result) => { | ||
[joinDash, ['рубль', 'рубля', 'рублей'], 0, '0-рублей'], | ||
[joinSpace, ['метр', 'метра', 'метров'], 1, '1 метр'], | ||
[skipZero, ['кофе'], 0, '-'], | ||
[skipZero, ['место', 'места', 'мест'], 55, '55 мест'], | ||
])('format', (formatter, words, number, result) => { | ||
test('works', () => { | ||
expect(format(formatter)(wordPlural)(number)).toBe(result); | ||
expect(format(formatter, ...words)(number)).toBe(result); | ||
}); | ||
}); | ||
/* eslint-enable sonarjs/no-duplicate-string */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,36 @@ | ||
// @flow strict | ||
|
||
opaque type PositiveInt = number; | ||
opaque type AbsInt = number; | ||
|
||
function toPositiveInt(number: ?number): PositiveInt { | ||
return Math.abs(parseInt(number, 10)); | ||
} | ||
export type Numberlike = number | string | null | void; | ||
export type Declensions = [string, string, string]; | ||
export type Plural = (number: ?number) => string; | ||
|
||
type Declensions = [string, string, string]; | ||
function absInt(number: Numberlike): AbsInt { | ||
const int = Math.abs(parseInt(number, 10) || 0); | ||
if (Number.isNaN(int)) { | ||
throw new TypeError('Invalid number'); | ||
} | ||
return int; | ||
} | ||
|
||
function declensions(one: string, _two?: string, _five?: string): Declensions { | ||
const two = typeof _two === 'undefined' ? one : _two; | ||
const five = typeof _five === 'undefined' ? two : _five; | ||
return [one, two, five]; | ||
} | ||
|
||
function pluralize(int: PositiveInt, _declensions: Declensions) { | ||
function pluralize(int: AbsInt, _declensions: Declensions) { | ||
return _declensions[ | ||
int % 100 > 4 && int % 100 < 20 | ||
? 2 | ||
: [2, 0, 1, 1, 1, 2][int % 10 < 5 ? int % 10 : 5] | ||
]; | ||
} | ||
|
||
export type Plural = (number: ?number) => string; | ||
|
||
function plural(one: string, two?: string, five?: string): Plural { | ||
return (number: ?number) => | ||
pluralize(toPositiveInt(number), declensions(one, two, five)); | ||
return (number: Numberlike) => | ||
pluralize(absInt(number), declensions(one, two, five)); | ||
} | ||
|
||
export default plural; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters