@streamscloud/kit 0.60.0 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- export { plural } from './plural';
1
+ export { resolvePluralForm } from './resolve-plural-form';
@@ -1 +1 @@
1
- export { plural } from './plural';
1
+ export { resolvePluralForm } from './resolve-plural-form';
@@ -0,0 +1,23 @@
1
+ export type ResolvePluralFormOptions = {
2
+ count: number;
3
+ candidates: [string, ...string[]];
4
+ locale: string;
5
+ };
6
+ /**
7
+ * Picks the plural form matching `count` for `locale`.
8
+ *
9
+ * @param count - The number to pluralize
10
+ * @param candidates - One string per plural category `locale` distinguishes, in canonical CLDR order
11
+ * (zero, one, two, few, many, other) filtered to that locale's categories — e.g. 2 entries for 'en'
12
+ * (one, other), 4 for 'ru' (one, few, many, other). The caller (or its translation catalog) owns
13
+ * supplying the right length and order for the given locale. In dev, a length mismatch against
14
+ * `locale`'s actual category count throws instead of silently resolving the wrong form.
15
+ * @param locale - BCP 47 locale used to resolve the matching category via `Intl.PluralRules`.
16
+ * Invalid locale tags silently fall back to the runtime default — validate the locale upstream.
17
+ * @returns The matching candidate with # replaced by count
18
+ *
19
+ * @example
20
+ * resolvePluralForm({ count: 1, candidates: ['# item', '# items'], locale: 'en' }) // "1 item"
21
+ * resolvePluralForm({ count: 5, candidates: ['# item', '# items'], locale: 'en' }) // "5 items"
22
+ */
23
+ export declare const resolvePluralForm: ({ count, candidates, locale }: ResolvePluralFormOptions) => string;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Picks the plural form matching `count` for `locale`.
3
+ *
4
+ * @param count - The number to pluralize
5
+ * @param candidates - One string per plural category `locale` distinguishes, in canonical CLDR order
6
+ * (zero, one, two, few, many, other) filtered to that locale's categories — e.g. 2 entries for 'en'
7
+ * (one, other), 4 for 'ru' (one, few, many, other). The caller (or its translation catalog) owns
8
+ * supplying the right length and order for the given locale. In dev, a length mismatch against
9
+ * `locale`'s actual category count throws instead of silently resolving the wrong form.
10
+ * @param locale - BCP 47 locale used to resolve the matching category via `Intl.PluralRules`.
11
+ * Invalid locale tags silently fall back to the runtime default — validate the locale upstream.
12
+ * @returns The matching candidate with # replaced by count
13
+ *
14
+ * @example
15
+ * resolvePluralForm({ count: 1, candidates: ['# item', '# items'], locale: 'en' }) // "1 item"
16
+ * resolvePluralForm({ count: 5, candidates: ['# item', '# items'], locale: 'en' }) // "5 items"
17
+ */
18
+ export const resolvePluralForm = ({ count, candidates, locale }) => {
19
+ const { index, categoriesLength } = indexerFor(locale);
20
+ if (import.meta.env?.DEV && candidates.length !== categoriesLength) {
21
+ throw new Error(`resolvePluralForm(): locale "${locale}" needs ${categoriesLength} candidate(s), got ${JSON.stringify(candidates)}.`);
22
+ }
23
+ const template = candidates[index(count)] ?? candidates[candidates.length - 1];
24
+ return template.replace(/#/g, String(count));
25
+ };
26
+ const PLURAL_CATEGORY_ORDER = ['zero', 'one', 'two', 'few', 'many', 'other'];
27
+ const indexers = new Map();
28
+ const indexerFor = (locale) => {
29
+ const cached = indexers.get(locale);
30
+ if (cached) {
31
+ return cached;
32
+ }
33
+ const rules = new Intl.PluralRules(locale);
34
+ const categories = rules
35
+ .resolvedOptions()
36
+ .pluralCategories.slice()
37
+ .sort((a, b) => PLURAL_CATEGORY_ORDER.indexOf(a) - PLURAL_CATEGORY_ORDER.indexOf(b));
38
+ const indexer = { index: (count) => categories.indexOf(rules.select(count)), categoriesLength: categories.length };
39
+ indexers.set(locale, indexer);
40
+ return indexer;
41
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@streamscloud/kit",
3
- "version": "0.60.0",
3
+ "version": "0.61.0",
4
4
  "author": "StreamsCloud",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,14 +0,0 @@
1
- /**
2
- * Plural function for wuchale i18n.
3
- * Wuchale extracts the candidates array and compiles locale-specific plural rules.
4
- *
5
- * @param count - The number to pluralize
6
- * @param candidates - Array of plural forms, e.g., ['One item', '# items']
7
- * @param rule - Optional plural rule function (injected by wuchale at compile time)
8
- * @returns The appropriate plural form with # replaced by count
9
- *
10
- * @example
11
- * plural(1, ['# item', '# items']) // "1 item"
12
- * plural(5, ['# item', '# items']) // "5 items"
13
- */
14
- export declare const plural: (count: number, candidates: string[], rule?: (n: number) => number) => string;
@@ -1,18 +0,0 @@
1
- /**
2
- * Plural function for wuchale i18n.
3
- * Wuchale extracts the candidates array and compiles locale-specific plural rules.
4
- *
5
- * @param count - The number to pluralize
6
- * @param candidates - Array of plural forms, e.g., ['One item', '# items']
7
- * @param rule - Optional plural rule function (injected by wuchale at compile time)
8
- * @returns The appropriate plural form with # replaced by count
9
- *
10
- * @example
11
- * plural(1, ['# item', '# items']) // "1 item"
12
- * plural(5, ['# item', '# items']) // "5 items"
13
- */
14
- export const plural = (count, candidates, rule = (n) => (n === 1 ? 0 : 1)) => {
15
- const index = rule(count);
16
- const template = candidates[index] ?? candidates[candidates.length - 1];
17
- return template.replace(/#/g, String(count));
18
- };