Skip to content

Commit

Permalink
⚡ improvement(index): Allow pluralization customization via construct…
Browse files Browse the repository at this point in the history
…or options (closes #464) (#482) by @Raiondesu

* improvement(getChoiceIndex): make getChoiceIndex overridable

This provides better pluralization customization. :D

* update(docs): fit the new functionality

* build(dist): generate dist files

* revert(dist): unbuild files to correspond with guidelines

* docs(pluralization): fix typo

* improvement(test/unit): add test case for custom pluralization

* docs(pluralization): remove unnecessary code from new pluralization example

* update(types): add types for the new pluralization feature

* ⚡improvement(types): remove duplicate type definitions

Replace Number and Date format options with standard TS `Intl` types.

* ⚡improvement(tests): Let types allow to pass getChoiceIndex into options

* ⚡improvement(index): Set getChoiceIndex for current instance from opts

Fixes #464

* 🐛fix(types): fix type aliases for format options

* ⚡improvement(index): allow to pass a pluralization rules map instead

Fixes #464

* 📃docs(api): fix md typo

* ⚡improvement(types/flow): add `pluralizationRules` to the instance types

* 📃docs(pluralization): add the documentation for #464 functionality

* 📃docs(pluralization): fix typo

* ⭐️new(test): add a test case for #464

* 📃docs(pluralization): improve custom pluralization definitions

* ⭐new(test): add a test for backward compatibility with #451 and 8.4.0

* improvement(index): apply the pluralization rule to the instance

For better extensibility.

* 📃docs(api): improve `pluralizationRules` property definition

* docs(pluralization): fix jsdoc comment misplacement

* Revert "⚡improvement(types): remove duplicate type definitions"

This reverts commit 286bc2e.

* ⚡revert(types): Bring back original VueI18n aliases for format options
  • Loading branch information
Raiondesu authored and kazupon committed Dec 16, 2018
1 parent ee526b8 commit ef4b1a6
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 91 deletions.
10 changes: 8 additions & 2 deletions decls/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ declare type I18nOptions = {
root?: I18n, // for internal
fallbackRoot?: boolean,
sync?: boolean,
silentTranslationWarn?: boolean
silentTranslationWarn?: boolean,
pluralizationRules?: {
[lang: string]: (choice: number, choicesLength: number) => number,
},
};

declare type IntlAvailability = {
Expand Down Expand Up @@ -100,7 +103,10 @@ declare interface I18n {
getNumberFormat (locale: Locale): NumberFormat,
setNumberFormat (locale: Locale, format: NumberFormat): void,
mergeNumberFormat (locale: Locale, format: NumberFormat): void,
n (value: number, ...args: any): NumberFormatResult
n (value: number, ...args: any): NumberFormatResult,
pluralizationRules: {
[lang: string]: (choice: number, choicesLength: number) => number
}
};

declare interface Formatter {
Expand Down
34 changes: 33 additions & 1 deletion gitbook/en/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@

Localize the locale message of `key` with pluralization. Localize in preferentially component locale messages than global locale messages. If not specified component locale messages, localize with global locale messages. If you specified `locale`, localize the locale messages of `locale`. If you will specify string value to `values`, localize the locale messages of value. If you will specify Array or Object value to `values`, you must specify with `values` of [$t](#t).

If default pluralization does not suit your needs, see [pluralization rules in constructor options](#pluralizationrules) and [custom pluralization](pluralization.md).

#### getChoiceIndex

- **Arguments:**
Expand Down Expand Up @@ -237,7 +239,7 @@ You can specify the below some options of `I18nOptions` constructor options of [

If `false`, regardless of the root level locale, localize for each component locale.

### silentTranslationWarn
#### silentTranslationWarn

> 6.1+
Expand All @@ -249,6 +251,26 @@ You can specify the below some options of `I18nOptions` constructor options of [

If `true`, supress localization fail warnings.

#### pluralizationRules

> 8.5+
- **Type:** `Object`

- **Default:** `{}`

A set of rules for word pluralization in a following format:
```js
{
// Key - locale for the rule to be applied to.
// Value - mapping function that maps a choice index from `$tc` to the actual choice of the plural word.

'ru': function (choice, choiceIndex) => Number/* index of the plural word */;
'en': function (choice, choiceIndex) => Number/* index of the plural word */;
'jp': function (choice, choiceIndex) => Number/* index of the plural word */;
}
```
### Properties
#### locale
Expand Down Expand Up @@ -321,6 +343,16 @@ You can specify the below some options of `I18nOptions` constructor options of [
Whether suppress warnings outputted when localization fails.
#### pluralizationRules
> 8.5+
- **Type:** `Object`
- **Default:** `{}`
A set of rules for word pluralization. Key is a locale, value is the rule function for that locale.
### Methods
#### getLocaleMessage( locale )
Expand Down
65 changes: 36 additions & 29 deletions gitbook/en/pluralization.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,43 +43,46 @@ This will output the following HTML:

Such pluralization, however, does not apply to all languages (Slavic languages, for example, have different pluralization rules).

In order to implement these rules you can override the `VueI18n.prototype.getChoiceIndex` function.
In order to implement these rules you can pass an optional `pluralizationRules` object into `VueI18n` constructor options.

Very simplified example using rules for Slavic languages (Russian, Ukrainian, etc.):
```js
/**
* @param choice {number} a choice index given by the input to $tc: `$tc('path.to.rule', choiceIndex)`
* @param choicesLength {number} an overall amount of available choices
* @returns a final choice index to select plural word by
**/
VueI18n.prototype.getChoiceIndex = function (choice, choicesLength) {
// this === VueI18n instance, so the locale property also exists here
if (this.locale !== 'ru') {
// proceed to the default implementation
new VueI18n({
pluralizationRules: {
/** Key - language to use the rule for, 'ru', in this case */
/** Value - function
* @param choice {number} a choice index given by the input to $tc: `$tc('path.to.rule', choiceIndex)`
* @param choicesLength {number} an overall amount of available choices
* @returns a final choice index to select plural word by
**/
'ru': function (choice, choicesLength) {
// this === VueI18n instance, so the locale property also exists here

if (choice === 0) {
return 0;
}

const teen = choice > 10 && choice < 20;
const endsWithOne = choice % 10 === 1;

if (choicesLength < 4) {
return (!teen && endsWithOne) ? 1 : 2;
}
if (!teen && endsWithOne) {
return 1;
}
if (!teen && choice % 10 >= 2 && choice % 10 <= 4) {
return 2;
}

return (choicesLength < 4) ? 2 : 3;
}
}

if (choice === 0) {
return 0;
}

const teen = choice > 10 && choice < 20;
const endsWithOne = choice % 10 === 1;

if (!teen && endsWithOne) {
return 1;
}

if (!teen && choice % 10 >= 2 && choice % 10 <= 4) {
return 2;
}

return (choicesLength < 4) ? 2 : 3;
}
});
```

This would effectively give this:


```javascript
const messages = {
ru: {
Expand Down Expand Up @@ -119,3 +122,7 @@ Which results in:
<p>11 бананов</p>
<p>31 банан</p>
```

### Default pluralization

If your current locale is not found in a pluralization map, the [default](#pluralization) rule of the english langugage will be used.
32 changes: 23 additions & 9 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ export default class VueI18n {
_numberFormatters: Object
_path: I18nPath
_dataListeners: Array<any>
pluralizationRules: {
[lang: string]: (choice: number, choicesLength: number) => number
}

constructor (options: I18nOptions = {}) {
// Auto install if it is not done yet and `window` has `Vue`.
Expand Down Expand Up @@ -88,6 +91,8 @@ export default class VueI18n {
this._path = new I18nPath()
this._dataListeners = []

this.pluralizationRules = options.pluralizationRules || {}

this._exist = (message: Object, key: Path): boolean => {
if (!message || !key) { return false }
return !isNull(this._path.getPathValue(message, key))
Expand Down Expand Up @@ -435,17 +440,26 @@ export default class VueI18n {
* @returns a final choice index
*/
getChoiceIndex (choice: number, choicesLength: number): number {
choice = Math.abs(choice)

if (choicesLength === 2) {
return choice
? choice > 1
? 1
: 0
: 1
// Default (old) getChoiceIndex implementation - english-compatible
const defaultImpl = (_choice: number, _choicesLength: number) => {
_choice = Math.abs(_choice)

if (_choicesLength === 2) {
return _choice
? _choice > 1
? 1
: 0
: 1
}

return _choice ? Math.min(_choice, 2) : 0
}

return choice ? Math.min(choice, 2) : 0
if (this.locale in this.pluralizationRules) {
return this.pluralizationRules[this.locale].apply(this, [choice, choicesLength])
} else {
return defaultImpl(choice, choicesLength)
}
}

tc (key: Path, choice?: number, ...values: any): TranslateResult {
Expand Down
Loading

0 comments on commit ef4b1a6

Please sign in to comment.