@konce-pt/i18n 0.15.1 → 0.16.1

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.
package/dist/messages.js CHANGED
@@ -40,7 +40,7 @@ export const KPT_MESSAGES_EN = {
40
40
  grid: 'Grid',
41
41
  columns: 'Columns',
42
42
  empty: 'No items',
43
- count: '{count} items',
43
+ count: '{count, plural, one {# item} other {# items}}',
44
44
  },
45
45
  roadmap: {
46
46
  empty: 'No items on the roadmap',
@@ -526,7 +526,7 @@ export const KPT_MESSAGES_PL = {
526
526
  grid: 'Siatka',
527
527
  columns: 'Kolumny',
528
528
  empty: 'Brak elementów',
529
- count: '{count} elementów',
529
+ count: '{count, plural, one {# element} few {# elementy} many {# elementów} other {# elementu}}',
530
530
  },
531
531
  roadmap: {
532
532
  empty: 'Brak elementów na roadmapie',
@@ -7,5 +7,8 @@ export declare function lookup(dict: KptMessages, key: string): string | undefin
7
7
  /**
8
8
  * Tłumaczy klucz w notacji kropkowej (np. `'paginator.range'`), z opcjonalną
9
9
  * interpolacją `{name}`. Brakujący klucz spada na EN, a w ostateczności zwraca sam klucz.
10
+ *
11
+ * Liczbę mnogą zapisuje się blokiem ICU `{count, plural, one {# item} other {# items}}`;
12
+ * `locale` wybiera reguły odmiany (domyślnie `'en'`). Porty przekazują tu bieżący język.
10
13
  */
11
- export declare function translate(dict: KptMessages, key: string, params?: Record<string, string | number>): string;
14
+ export declare function translate(dict: KptMessages, key: string, params?: Record<string, string | number>, locale?: string): string;
package/dist/translate.js CHANGED
@@ -33,13 +33,85 @@ export function lookup(dict, key) {
33
33
  }
34
34
  return typeof node === 'string' ? node : undefined;
35
35
  }
36
+ /** Pozycja zamykającego `}` dla `{` stojącego na `open`, z liczeniem zagnieżdżeń; -1 bez pary. */
37
+ function closingBrace(text, open) {
38
+ let depth = 0;
39
+ for (let i = open; i < text.length; i++) {
40
+ if (text[i] === '{')
41
+ depth++;
42
+ else if (text[i] === '}' && --depth === 0)
43
+ return i;
44
+ }
45
+ return -1;
46
+ }
47
+ /** Gałęzie bloku liczby mnogiej: `one {...} few {...} =0 {...}` -> mapa selektor -> tekst. */
48
+ function pluralBranches(body) {
49
+ const branches = new Map();
50
+ let i = 0;
51
+ while (i < body.length) {
52
+ const match = /^\s*(=\d+|zero|one|two|few|many|other)\s*\{/.exec(body.slice(i));
53
+ if (!match)
54
+ return body.slice(i).trim() === '' ? branches : null;
55
+ const open = i + match[0].length - 1;
56
+ const close = closingBrace(body, open);
57
+ if (close < 0)
58
+ return null;
59
+ branches.set(match[1], body.slice(open + 1, close));
60
+ i = close + 1;
61
+ }
62
+ return branches;
63
+ }
64
+ /** Reguły liczby mnogiej per język - `Intl.PluralRules` jest drogi w tworzeniu, więc raz na język. */
65
+ const pluralRules = new Map();
66
+ function pluralCategory(locale, value) {
67
+ let rules = pluralRules.get(locale);
68
+ if (!rules) {
69
+ try {
70
+ rules = new Intl.PluralRules(locale);
71
+ }
72
+ catch {
73
+ // Nieznany tag języka (własny słownik pod umowną nazwą) - reguły angielskie zamiast wyjątku.
74
+ rules = new Intl.PluralRules('en');
75
+ }
76
+ pluralRules.set(locale, rules);
77
+ }
78
+ return rules.select(value);
79
+ }
80
+ /**
81
+ * Rozwija bloki `{name, plural, one {...} few {...} many {...} other {...}}` (podzbiór składni ICU).
82
+ *
83
+ * Formę wybiera `Intl.PluralRules` dla `locale`, więc słownik podaje tylko formy, a reguły
84
+ * (`2 elementy`, ale `5 elementów` i `22 elementy`) zna przeglądarka. Dokładne dopasowanie `=N`
85
+ * wygrywa z kategorią, `other` jest obowiązkowym zapasem. `#` w gałęzi to sama liczba.
86
+ * Blok z nieznanym parametrem albo bez pary nawiasów zostaje w tekście bez zmian.
87
+ */
88
+ function expandPlurals(text, params, locale) {
89
+ const head = /\{(\w+),\s*plural,/g;
90
+ let out = '';
91
+ let from = 0;
92
+ for (let match = head.exec(text); match; match = head.exec(text)) {
93
+ const close = closingBrace(text, match.index);
94
+ const value = Number(params[match[1]]);
95
+ const branches = close < 0 ? null : pluralBranches(text.slice(match.index + match[0].length, close));
96
+ if (close < 0 || !branches || params[match[1]] === undefined || Number.isNaN(value))
97
+ continue;
98
+ const chosen = branches.get(`=${value}`) ?? branches.get(pluralCategory(locale, value)) ?? branches.get('other') ?? '';
99
+ out += text.slice(from, match.index) + chosen.replace(/#/g, String(params[match[1]]));
100
+ from = close + 1;
101
+ head.lastIndex = from;
102
+ }
103
+ return out + text.slice(from);
104
+ }
36
105
  /**
37
106
  * Tłumaczy klucz w notacji kropkowej (np. `'paginator.range'`), z opcjonalną
38
107
  * interpolacją `{name}`. Brakujący klucz spada na EN, a w ostateczności zwraca sam klucz.
108
+ *
109
+ * Liczbę mnogą zapisuje się blokiem ICU `{count, plural, one {# item} other {# items}}`;
110
+ * `locale` wybiera reguły odmiany (domyślnie `'en'`). Porty przekazują tu bieżący język.
39
111
  */
40
- export function translate(dict, key, params) {
112
+ export function translate(dict, key, params, locale = 'en') {
41
113
  const raw = lookup(dict, key) ?? lookup(KPT_MESSAGES_EN, key) ?? key;
42
114
  if (!params)
43
115
  return raw;
44
- return raw.replace(/\{(\w+)\}/g, (_, name) => String(params[name] ?? `{${name}}`));
116
+ return expandPlurals(raw, params, locale).replace(/\{(\w+)\}/g, (_, name) => String(params[name] ?? `{${name}}`));
45
117
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@konce-pt/i18n",
3
- "version": "0.15.1",
3
+ "version": "0.16.1",
4
4
  "description": "Translation contract and base dictionaries (EN, PL) for Koncept UI - shared by the Angular and React ports. Pure TypeScript, zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "konce.pt",