Skip to content

Commit

Permalink
feat: API for checking supported locales and plural forms
Browse files Browse the repository at this point in the history
Add functions `getSupportedLocales` and `getPluralFormsForLocale`.
  • Loading branch information
prantlf committed Aug 18, 2022
1 parent 8beef35 commit 0b8fadc
Show file tree
Hide file tree
Showing 13 changed files with 157 additions and 40 deletions.
38 changes: 38 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ This library contains pure functions to get [plural forms](./design.md#plural-fo
- [getPluralRulesForCardinals](#getpluralrulesforcardinals)
- [populatePluralData](#populatepluraldata)
- [setPluralFormsForCardinals](#setpluralformsforcardinals)
- [getSupportedLocales](#getsupportedlocales)
- [getPluralFormsForLocale](#getpluralformsforlocale)
- [Data Generator](#data-generator)

## Loading
Expand Down Expand Up @@ -190,6 +192,42 @@ setPluralFormsForCardinals('cs', {

The [locale](./design.md#locales) will be normalized for the plural rules lookup by converting it to lower-case and using a hyphen as a separator, if the country is present and separated by an underscore.

### getSupportedLocales

```
getSupportedLocales(): string[]
```

Returns an array of [locales supported by this library]](./languages.md#supported-languages). You can use it to check programmatically if your locale is suppored. The `getPluralFormsForLocale` might be more efficient, though.

```js
import { getSupportedLocales } from 'plural-rules'

const supportedLocales = getSupportedLocales()

console.log(supportedLocales.length, supportedLocales.includes['cs'))
// Prints "211 true".
```

The [locale](./design.md#locales) will be normalized for the plural rules lookup by converting it to lower-case and using a hyphen as a separator, if the country is present and separated by an underscore.

### getPluralFormsForLocale

```
getPluralFormsForLocale(locale: string): string[]
```

Returns the plural forms needed to cover the specified `locale`. [Plural form names](./languages.md#supported-languages) returned by `getPluralFormForCardinal` and other methods will be always included in the returned returned array.

```js
import { getPluralFormsForLocale } from 'plural-rules'

getPluralFormsForLocale('cs')
// Returns ["one", "few", "other"].
```

The [locale](./design.md#locales) will be normalized for the plural rules lookup by converting it to lower-case and using a hyphen as a separator, if the country is present and separated by an underscore.

## Data Generator

If you want to [limit the supported languages](./usage.md#limit-supported-languages) to improve performance of your application by reducing the size of the JavaScript code, you can use the command-line tool included in this package:
Expand Down
4 changes: 3 additions & 1 deletion docs/design.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Plural forms are identified by their names standardized by [CLDR plural rules]:
| many | Optional | For multiple items, usually many. |
| other | Mandatory | For numbers not covered by other rules. |

The count and names of plural forms depend on the [language](./languages.md#supported-languages). All plural forms of an expression are usually stored with a lookup key [*language packs*](#language-packs).
The count and names of plural forms depend on the [language](./languages.md#supported-languages). All plural forms of an expression are usually stored with a lookup key [*language packs*](#language-packs). The list of plural form names covering a particular `locale` can be obtained by [`getPluralFormsForLocale`](./API.md#getpluralformsforlocale).

## Plural Rules

Expand Down Expand Up @@ -95,6 +95,8 @@ Tha language is specified using a two-letter code from [ISO 369](https://en.wiki

The locale is handled case-insensitively. Before it is used for the plural rule lookup, it is "normalized". It is converted to lower-case and if it consists of both language and country paths separated by an underscore, the separator is replaced by a hyphen. For example: `pt_BR ==> pt-br`.

The list of supported locales can be obtained by [`getSupportedLocales`](./API.md#getsupportedlocales).

## Language Packs

The result of [plural rules](#plural-rules) evaluation is an identifier of the [plural form](#plural-forms) - one of the six strings, depending on the specified [locale](#locales) and on the item count. The plural form identifier can be used to identify the right localizable message in the language pack. The localizable message is supposed to be entered as an object with one item for every plural form defined for the particular [language locale](#locales). The cardinal is usually placed to a placeholder like "{0}" or "%d". For example:
Expand Down
3 changes: 2 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ Translators need to know, how many and what plural forms the target language nee
}
```

Have a look at the [list of plural rules and number of plural forms for locales supported by this library](./languages.md#supported-languages).
Have a look at the [list of plural rules and number of plural forms for locales supported by this library](./languages.md#supported-languages). The proper plural form names covering a particular `locale` can be checked pro
grammatically by [`getPluralFormsForLocale`](./API.md#getpluralformsforlocale).

## Get a localized message in the right plural form

Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"module": "dist/index.mjs",
"main": "dist/index.cjs",
"browser": "dist/index.umd.min.js",
"types": "lib/index.d.ts",
"types": "src/index.d.ts",
"exports": {
"require": "./dist/index.cjs",
"import": "./dist/index.mjs"
Expand All @@ -40,7 +40,8 @@
"files": [
"bin",
"dist",
"lib/index.d.ts",
"src/code.d.ts",
"src/index.d.ts",
"util/data-creator.js",
"util/data-packer.js",
"util/data-unpacker.js"
Expand All @@ -49,7 +50,7 @@
"prepare": "node util/wrap-data && rollup -c",
"build": "rollup -c",
"generate": "node util/wrap-data",
"lint": "denolint && tsc --noEmit test/types.test.ts",
"lint": "denolint && tsc --noEmit test/types.test.ts test/types-all.test.ts",
"check": "node test/cjs.cjs && node --experimental-vm-modules node_modules/jest/bin/jest --testPathIgnorePatterns \"browser.test.js\" --collectCoverage",
"check:browser": "node --experimental-vm-modules node_modules/jest/bin/jest --testPathPattern browser.test.js",
"pretest": "npm run lint && node util/generate-browser-tests",
Expand Down
14 changes: 6 additions & 8 deletions src/code.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,12 @@ interface PluralData {
cardinals: PluralLocales
}

declare function getPluralRulesForCardinals (locale: string): PluralRules
declare function getPluralFormForCardinal (localeOrRules: string | PluralRules, count: number): string
declare function setPluralFormsForCardinals (locale: string, forms: PluralForms): void
declare function populatePluralData (data: PluralData): void
export function getPluralRulesForCardinals (locale: string): PluralRules
export function getPluralFormForCardinal (localeOrRules: string | PluralRules, count: number): string
export function setPluralFormsForCardinals (locale: string, forms: PluralForms): void
export function populatePluralData (data: PluralData): void

export {
getPluralRulesForCardinals, getPluralFormForCardinal,
setPluralFormsForCardinals, populatePluralData
}
export function getSupportedLocales(): string[]
export function getPluralFormsForLocale(locale: string): string[]

export as namespace pluralRules
18 changes: 17 additions & 1 deletion src/code.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,23 @@ function populatePluralData (data) {
cardinals = data.cardinals
}

let supportedLocales

function getSupportedLocales() {
if (!supportedLocales) supportedLocales = Object.keys(cardinals)
return supportedLocales
}

function getPluralFormsForLocale(locale) {
const cardinal = cardinals[locale]
if (cardinal === undefined) return
return cardinal
.split(',')
.map(form => form.substring(0, form.indexOf(':')))
}

export {
getPluralRulesForCardinals, getPluralFormForCardinal,
setPluralFormsForCardinals, populatePluralData
setPluralFormsForCardinals, populatePluralData,
getSupportedLocales, getPluralFormsForLocale
}
11 changes: 5 additions & 6 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ interface PluralForms {
[ key: string ]: string
}

declare function getPluralRulesForCardinals (locale: string): PluralRules
declare function getPluralFormForCardinal (localeOrRules: string | PluralRules, count: number): string
declare function setPluralFormsForCardinals (locale: string, forms: PluralForms): void
export function getPluralRulesForCardinals (locale: string): PluralRules
export function getPluralFormForCardinal (localeOrRules: string | PluralRules, count: number): string
export function setPluralFormsForCardinals (locale: string, forms: PluralForms): void

export {
getPluralRulesForCardinals, getPluralFormForCardinal, setPluralFormsForCardinals
}
export function getSupportedLocales(): string[]
export function getPluralFormsForLocale(locale: string): string[]

export as namespace pluralRules
9 changes: 7 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import {
getPluralRulesForCardinals, getPluralFormForCardinal,
setPluralFormsForCardinals, populatePluralData
setPluralFormsForCardinals, populatePluralData,
getSupportedLocales, getPluralFormsForLocale
} from './code.js'
import pluralData from './data.js'

populatePluralData(pluralData)

export { getPluralRulesForCardinals, getPluralFormForCardinal, setPluralFormsForCardinals }
export {
getPluralRulesForCardinals, getPluralFormForCardinal,
setPluralFormsForCardinals,
getSupportedLocales, getPluralFormsForLocale
}
15 changes: 15 additions & 0 deletions test/getPluralFormsForLocale.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* global it, expect */

import { getPluralFormsForLocale } from '../src/index.js'

it('is exported as a function', () => {
expect(typeof getPluralFormsForLocale === 'function').toBeTruthy()
})

it('returns plural form names for a supported locale', () => {
expect(getPluralFormsForLocale('cs')).toEqual(['one', 'few', 'many', 'other'])
})

it('returns an undefined for an unsupported locale', () => {
expect(getPluralFormsForLocale('dummy')).toEqual(undefined)
})
15 changes: 15 additions & 0 deletions test/getSupportedLocales.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* global it, expect */

import { getSupportedLocales } from '../src/index.js'

it('is exported as a function', () => {
expect(typeof getSupportedLocales === 'function').toBeTruthy()
})

it('returns some supported locales', () => {
expect(getSupportedLocales().includes('cs')).toBeTruthy()
})

it('returns some supported locales when called once more', () => {
expect(getSupportedLocales().includes('cs')).toBeTruthy()
})
34 changes: 34 additions & 0 deletions test/types-all.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
getPluralRulesForCardinals, getPluralFormForCardinal,
setPluralFormsForCardinals, populatePluralData,
getSupportedLocales, getPluralFormsForLocale,
PluralRules
} from '../src/code'

declare type testCallback = () => void
declare function test (label: string, callback: testCallback)

test('Type declarations for TypeScript', () => {
populatePluralData({
rules: [
'i = 1 and v = 0 @integer 1',
'@integer 0, 2~16, 100, … @decimal 0.0~1.5, 10.0, 100.0, …'
],
cardinals: {
test: {
one: 0,
other: 1
}
}
})
let _pluralForm: string
_pluralForm = getPluralFormForCardinal('test', 1)
const rules: PluralRules = getPluralRulesForCardinals('test')
_pluralForm = getPluralFormForCardinal(rules, 1)
setPluralFormsForCardinals('test', {
one: 'i = 1 and v = 0 @integer 1',
'pluralRule-count-other': ' @integer 0, 2~16, 100, … @decimal 0.0~1.5, 10.0, 100.0, …'
})
const _locales: string[] = getSupportedLocales()
const _pluralForms: string[] | undefined = getPluralFormsForLocale('cs')
})
27 changes: 10 additions & 17 deletions test/types.test.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,22 @@
import {
getPluralRulesForCardinals, getPluralFormForCardinal,
setPluralFormsForCardinals, populatePluralData
} from '../src/code'
setPluralFormsForCardinals,
getSupportedLocales, getPluralFormsForLocale,
PluralRules
} from 'plural-rules'

declare type testCallback = () => void
declare function test (label: string, callback: testCallback)

test('Type declarations for TypeScript', () => {
populatePluralData({
rules: [
'i = 1 and v = 0 @integer 1',
'@integer 0, 2~16, 100, … @decimal 0.0~1.5, 10.0, 100.0, …'
],
cardinals: {
test: {
one: 0,
other: 1
}
}
})
getPluralFormForCardinal('test', 1)
const rules = getPluralRulesForCardinals('test')
getPluralFormForCardinal(rules, 1)
let _pluralForm: string
_pluralForm = getPluralFormForCardinal('test', 1)
const rules: PluralRules = getPluralRulesForCardinals('test')
_pluralForm = getPluralFormForCardinal(rules, 1)
setPluralFormsForCardinals('test', {
one: 'i = 1 and v = 0 @integer 1',
'pluralRule-count-other': ' @integer 0, 2~16, 100, … @decimal 0.0~1.5, 10.0, 100.0, …'
})
const _locales: string[] = getSupportedLocales()
const _pluralForms: string[] | undefined = getPluralFormsForLocale('cs')
})
2 changes: 1 addition & 1 deletion util/generate-browser-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const nonBrowserTests = [
'browser.test.js', 'cardinals.test.js', 'rules.test.js', 'typings.test.js',
'version.test.js'
]
const importModuleExpression = /import ({[^}]+}) from '..\/src\/([^']+)'/
const importModuleExpression = /import ({[^}]+}) from '..\/src\/([^']+)\.js'/

function readTemplate () {
console.log('Reading browser test template...')
Expand Down

0 comments on commit 0b8fadc

Please sign in to comment.