1 | import { en } from "make-plural";
|
2 |
|
3 | import { Dict, Pluralizer, MakePlural } from "./typing";
|
4 | import { I18n } from "./I18n";
|
5 |
|
6 | /**
|
7 | * Creates a new pluralizer function based on [make-plural](https://github.com/eemeli/make-plural/tree/master/packages/plurals).
|
8 | *
|
9 | * @param {boolean} options.includeZero When `true`, will return `zero` as the
|
10 | * first key for `0` pluralization.
|
11 | * @param {boolean} options.ordinal When `true`, will return the scope based on
|
12 | * make-plural's ordinal category.
|
13 | * @param {MakePlural} options.pluralizer The make-plural function that will be
|
14 | * wrapped.
|
15 | * @return {Pluralizer} Returns a pluralizer that can be used by I18n.
|
16 | */
|
17 | export function useMakePlural({
|
18 | pluralizer,
|
19 | includeZero = true,
|
20 | ordinal = false,
|
21 | }: {
|
22 | pluralizer: MakePlural;
|
23 | includeZero?: boolean;
|
24 | ordinal?: boolean;
|
25 | }): Pluralizer {
|
26 | return function (_i18n: I18n, count: number) {
|
27 | return [
|
28 | includeZero && count === 0 ? "zero" : "",
|
29 | pluralizer(count, ordinal),
|
30 | ].filter(Boolean);
|
31 | };
|
32 | }
|
33 |
|
34 | /**
|
35 | * The default pluralizer.
|
36 | *
|
37 | * It's pretty straightforward:
|
38 | *
|
39 | * - when count is `0`, then the possible keys are
|
40 | * either `zero` for `other`; this allows having translations like
|
41 | * "You have no messages", defaulting to "You have 0 messages".
|
42 | * - when count is `1`, then the key is `one`.
|
43 | * - when greater than `1`, then the key is `other`.
|
44 | *
|
45 | * @type {Pluralizer}
|
46 | *
|
47 | * @param {I18n} _i18n The I18n's instance.
|
48 | *
|
49 | * @param {number} count The number that's being analyzed.
|
50 | *
|
51 | * @returns {string[]} The list of possible translation scopes.
|
52 | */
|
53 | export const defaultPluralizer: Pluralizer = useMakePlural({
|
54 | pluralizer: en,
|
55 | includeZero: true,
|
56 | });
|
57 |
|
58 | /**
|
59 | * This class enables registering different strategies for pluralization, as
|
60 | * well as getting a pluralized translation.
|
61 | *
|
62 | * The default pluralization strategy is based on three counters:
|
63 | *
|
64 | * - `one`: returned when count is equal to absolute `1`.
|
65 | * - `zero`: returned when count is equal to `0`. If this translation is not
|
66 | * set, then it defaults to `other`.
|
67 | * - `other`: returned when count is different than absolute `1`.
|
68 | *
|
69 | * When calling `i18n.translate` (or its alias), pluralization rules will be
|
70 | * triggered whenever the translation options contain `count`.
|
71 | *
|
72 | * @example
|
73 | * A JSON describing the pluralization rules.
|
74 | *
|
75 | * ```json
|
76 | * {
|
77 | * "en": {
|
78 | * "inbox": {
|
79 | * "zero": "You have no messages",
|
80 | * "one": "You have one message",
|
81 | * "other": "You have %{count} messages"
|
82 | * }
|
83 | * }
|
84 | * }
|
85 | * ```
|
86 | *
|
87 | * @example
|
88 | * How to get pluralized translations.
|
89 | *
|
90 | * ```js
|
91 | * i18n.t("inbox", {count: 0}); // returns "You have no messages"
|
92 | * i18n.t("inbox", {count: 1}); // returns "You have on message"
|
93 | * i18n.t("inbox", {count: 2}); // returns "You have 2 messages"
|
94 | * ```
|
95 | */
|
96 | export class Pluralization {
|
97 | private i18n: I18n;
|
98 | private registry: Dict;
|
99 |
|
100 | constructor(i18n: I18n) {
|
101 | this.i18n = i18n;
|
102 | this.registry = {};
|
103 |
|
104 | this.register("default", defaultPluralizer);
|
105 | }
|
106 |
|
107 | /**
|
108 | * Register a new pluralizer strategy.
|
109 | *
|
110 | * You may want to support different pluralization rules based on the locales
|
111 | * you have to support. If you do, make sure you submit a pull request at
|
112 | * <https://github.com/fnando/i18n> so we can distribute it. For now only
|
113 | * Russian is distributed.
|
114 | *
|
115 | * The pluralizer will receive the `I18n` instance, together with `count`.
|
116 | * Is up to the pluralizer to return a list of possible keys given that count.
|
117 | * The Russian pluralizer, for instance, returns `one`, `few`, `many`, `other`
|
118 | * as possible pluralization keys.
|
119 | *
|
120 | * You can view a complete list of pluralization rules at
|
121 | * [unicode.org](http://www.unicode.org/cldr/charts/latest/supplemental/language_plural_rules.html).
|
122 | *
|
123 | * You can also leverage
|
124 | * [make-plural](https://github.com/eemeli/make-plural/), rather than writing
|
125 | * all your pluralization functions. For this, you must wrap make-plural's
|
126 | * function by using `useMakePlural({ pluralizer, includeZero, ordinal })`:
|
127 | *
|
128 | * @example
|
129 | * ```js
|
130 | * import { ru } from "make-plural";
|
131 | * import { useMakePlural } from "i18n-js";
|
132 | *
|
133 | * i18n.pluralization.register("ru", useMakePlural({ pluralizer: ru }));
|
134 | * ```
|
135 | *
|
136 | * @param {string} locale The locale.
|
137 | *
|
138 | * @param {Pluralizer} pluralizer The pluralizer function.
|
139 | *
|
140 | * @returns {void}
|
141 | */
|
142 | public register(locale: string, pluralizer: Pluralizer): void {
|
143 | this.registry[locale] = pluralizer;
|
144 | }
|
145 |
|
146 | /**
|
147 | * Returns a list of possible pluralization keys.
|
148 | * This is defined by a chain of pluralizers, going from locale set
|
149 | * explicitly, then the locale set through `i18n.locale`, defaulting to
|
150 | * `defaultPluralizer`.
|
151 | *
|
152 | * @param {string} locale The locale.
|
153 | *
|
154 | * @returns {Pluralizer} The pluralizer function.
|
155 | */
|
156 | public get(locale: string): Pluralizer {
|
157 | return (
|
158 | this.registry[locale] ||
|
159 | this.registry[this.i18n.locale] ||
|
160 | this.registry["default"]
|
161 | );
|
162 | }
|
163 | }
|