@ngx-runtime-i18n/core 2.1.0 → 2.2.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.
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Framework‑agnostic primitives for runtime internationalisation:
|
|
4
4
|
|
|
5
|
-
- Tiny, dependency‑free **ICU‑lite** formatter (interpolation
|
|
5
|
+
- Tiny, dependency‑free **ICU‑lite** formatter (interpolation, `plural`, `select`, `selectordinal`).
|
|
6
6
|
- Shared types used by the Angular wrapper.
|
|
7
7
|
|
|
8
8
|
This package is designed to be used directly or via `@ngx-runtime-i18n/angular`.
|
|
@@ -39,18 +39,33 @@ formatIcu('en', 'cart.items', catalog, { count: 2 }); // "2 items"
|
|
|
39
39
|
|
|
40
40
|
## API
|
|
41
41
|
|
|
42
|
-
### `formatIcu(lang, key, catalog, params?, onMissingKey?)`
|
|
42
|
+
### `formatIcu(lang, key, catalog, params?, onMissingKey?, pluralResolver?)`
|
|
43
43
|
|
|
44
44
|
- **`lang: string`**: current language (for plural rules and future features).
|
|
45
45
|
- **`key: string`**: dotted path into the catalog.
|
|
46
46
|
- **`catalog: Catalog`**: a nested object of strings/objects.
|
|
47
|
-
- **`params?: Record<string, unknown
|
|
47
|
+
- **`params?: Record<string, unknown> | null`**: interpolation values (`null`/`undefined` are both treated as no params).
|
|
48
48
|
- **`onMissingKey?: (key: string) => string`**: transform for missing keys (defaults to returning the key).
|
|
49
|
+
- **`pluralResolver?: PluralResolver`**: resolves the CLDR plural category for `plural`/`selectordinal` blocks. Without one, the built-in fallback treats `Math.abs(n) === 1` as `one` and everything else as `other` — English-shaped, but sign-agnostic (`-1` is also `one`).
|
|
49
50
|
|
|
50
51
|
### Types
|
|
51
52
|
|
|
52
53
|
- **`Catalog`**: `Record<string, unknown>` (nested object).
|
|
53
54
|
- **`RuntimeI18nConfig`**: shape shared with the Angular wrapper for consistency.
|
|
55
|
+
- **`I18nSchema`**: an empty, module-augmentable interface. Augment it in your app (see below) to unlock typed keys and params:
|
|
56
|
+
```ts
|
|
57
|
+
// src/i18n.d.ts
|
|
58
|
+
import type en from '../public/i18n/en.json';
|
|
59
|
+
declare module '@ngx-runtime-i18n/core' {
|
|
60
|
+
interface I18nSchema { translations: typeof en; }
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
- **`TranslationKey`**: union of dot-notation key paths derived from `I18nSchema['translations']`, or `string` when no schema is augmented.
|
|
64
|
+
- **`TranslationParams<K>`**: the interpolation params required for key `K`, derived from parsing the catalog string for `K` at the type level. Falls back to `Record<string, unknown>` when no schema is augmented.
|
|
65
|
+
- **`DeepKeys<T>`** / **`ExtractParams<S>`**: the type-level machinery behind `TranslationKey`/`TranslationParams` — exported for advanced use (e.g. building your own typed helpers). `ExtractParams` balances nested braces inside `plural`/`select`/`selectordinal` clause bodies, so params are extracted correctly even when a clause contains its own `{...}` (e.g. `{count, plural, other {# items}}` still yields `{ count: number }`, not a param named after the whole clause body).
|
|
66
|
+
- **`ActiveCatalogType`**: the augmented catalog's shape (`I18nSchema['translations']`), or `Record<string, unknown>` when unaugmented.
|
|
67
|
+
- **`PluralCategory`**: `'zero' | 'one' | 'two' | 'few' | 'many' | 'other'` (CLDR TR35 categories).
|
|
68
|
+
- **`PluralResolver`**: `(count: number, locale: string) => PluralCategory` — see `pluralResolver` above.
|
|
54
69
|
|
|
55
70
|
---
|
|
56
71
|
|
|
@@ -80,14 +95,15 @@ formatIcu('en', 'cart.items', catalog, { count: 2 }); // "2 items"
|
|
|
80
95
|
### Supported
|
|
81
96
|
|
|
82
97
|
- Basic `{param}` interpolation (tokens may include dots and hyphens for nested data).
|
|
83
|
-
- `plural` blocks with `one`, `other`, and `=n`
|
|
84
|
-
-
|
|
98
|
+
- `plural` blocks with `one`, `other`, `=n` selectors, `#` replacement, and an optional leading `offset:N` (subtracted from the count for category selection and `#`, but not for `=n` exact-match comparisons, per ICU).
|
|
99
|
+
- `select` blocks with arbitrary named options, falling back to `other` when a value doesn't match.
|
|
100
|
+
- `selectordinal` blocks, using the same `=n` exact-match syntax as `plural`. Without a custom plural resolver, both `plural` and `selectordinal` fall back to a plain `one`-if-equal-to-1/`other` rule; pass a `pluralResolver` for full CLDR (cardinal or ordinal) categories.
|
|
101
|
+
- Nesting `plural`, `select`, and `selectordinal` blocks inside each other, including the same keyword nested inside itself, to arbitrary depth.
|
|
85
102
|
|
|
86
103
|
### Not supported
|
|
87
104
|
|
|
88
|
-
- `
|
|
89
|
-
-
|
|
90
|
-
- Plural blocks inside other plural blocks (depth beyond one level is skipped).
|
|
105
|
+
- ICU-style escaping or quoting (`''`, `'{'`) — quotes and braces pass through as written.
|
|
106
|
+
- Argument types beyond `plural`, `select`, and `selectordinal` (ICU's `number`, `date`, `time`, or custom formatters).
|
|
91
107
|
- Escaping braces beyond the literals above; unmatched braces must not resemble valid tokens.
|
|
92
108
|
|
|
93
109
|
---
|
|
@@ -4,12 +4,13 @@ function formatIcu(_lang, key, cat, params = {}, onMissingKey, pluralResolver) {
|
|
|
4
4
|
const raw = lookup(key, cat);
|
|
5
5
|
if (raw == null)
|
|
6
6
|
return onMissingKey ? onMissingKey(key) : key;
|
|
7
|
+
const p = params ?? {};
|
|
7
8
|
let out = String(raw);
|
|
8
9
|
// 1) Resolve {x, plural, ...}, {x, select, ...}, {x, selectordinal, ...} with a brace-balanced scanner.
|
|
9
|
-
out = replaceMessageBlocks(out, _lang,
|
|
10
|
+
out = replaceMessageBlocks(out, _lang, p, pluralResolver);
|
|
10
11
|
// 2) Simple {name} interpolation AFTER message block selection.
|
|
11
12
|
INTERPOLATION_PATTERN.lastIndex = 0;
|
|
12
|
-
out = out.replace(INTERPOLATION_PATTERN, (_m, p1) =>
|
|
13
|
+
out = out.replace(INTERPOLATION_PATTERN, (_m, p1) => p[p1] != null ? String(p[p1]) : `{${p1}}`);
|
|
13
14
|
return out;
|
|
14
15
|
}
|
|
15
16
|
function lookup(path, obj) {
|
|
@@ -62,24 +63,25 @@ function replaceMessageBlocks(s, lang, params, pluralResolver) {
|
|
|
62
63
|
}
|
|
63
64
|
// Body is the contents between prefix end and the final '}'.
|
|
64
65
|
const body = s.slice(prefixMatch.lastIndex, j - 1);
|
|
65
|
-
const options = parsePluralBody(body);
|
|
66
|
+
const { offset, map: options } = parsePluralBody(body);
|
|
66
67
|
if (keyword === 'plural' || keyword === 'selectordinal') {
|
|
67
68
|
const n = Number(params[arg] ?? 0);
|
|
68
69
|
if (Number.isFinite(n)) {
|
|
69
70
|
const exact = options[`=${n}`];
|
|
70
71
|
if (exact != null) {
|
|
71
|
-
out += replaceHash(replaceMessageBlocks(exact, lang, params, pluralResolver), n);
|
|
72
|
+
out += replaceHash(replaceMessageBlocks(exact, lang, params, pluralResolver), n - offset);
|
|
72
73
|
i = j;
|
|
73
74
|
continue;
|
|
74
75
|
}
|
|
76
|
+
const shifted = n - offset;
|
|
75
77
|
const category = pluralResolver
|
|
76
|
-
? pluralResolver(
|
|
77
|
-
:
|
|
78
|
+
? pluralResolver(shifted, lang)
|
|
79
|
+
: Math.abs(shifted) === 1 ? 'one' : 'other';
|
|
78
80
|
const match = options[category] ?? options['other'] ?? '';
|
|
79
|
-
out += replaceHash(replaceMessageBlocks(match, lang, params, pluralResolver),
|
|
81
|
+
out += replaceHash(replaceMessageBlocks(match, lang, params, pluralResolver), shifted);
|
|
80
82
|
}
|
|
81
83
|
else {
|
|
82
|
-
out += replaceHash(replaceMessageBlocks(options['other'] ?? '', lang, params, pluralResolver),
|
|
84
|
+
out += replaceHash(replaceMessageBlocks(options['other'] ?? '', lang, params, pluralResolver), 0);
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
else {
|
|
@@ -96,11 +98,16 @@ function replaceHash(s, n) {
|
|
|
96
98
|
}
|
|
97
99
|
/**
|
|
98
100
|
* Parse a simple ICU plural/select clause body: e.g. `one {A} other {B} =0 {C}`.
|
|
99
|
-
* Supports balanced brace bodies and
|
|
101
|
+
* Supports balanced brace bodies, nested forms, and a leading `offset:N` token.
|
|
102
|
+
* The returned map has no prototype, so a selector value matching an
|
|
103
|
+
* `Object.prototype` member name (e.g. "constructor") can never resolve to an
|
|
104
|
+
* inherited function instead of `undefined`.
|
|
100
105
|
*/
|
|
101
106
|
function parsePluralBody(body) {
|
|
102
|
-
const map =
|
|
103
|
-
|
|
107
|
+
const map = Object.create(null);
|
|
108
|
+
const offsetMatch = /^\s*offset:\s*(\d+)\s*/.exec(body);
|
|
109
|
+
const offset = offsetMatch ? Number(offsetMatch[1]) : 0;
|
|
110
|
+
let i = offsetMatch ? offsetMatch[0].length : 0;
|
|
104
111
|
while (i < body.length) {
|
|
105
112
|
// Skip whitespace between selectors.
|
|
106
113
|
while (i < body.length && /\s/.test(body.charAt(i)))
|
|
@@ -135,7 +142,7 @@ function parsePluralBody(body) {
|
|
|
135
142
|
}
|
|
136
143
|
map[key] = body.slice(valueStart, i - 1);
|
|
137
144
|
}
|
|
138
|
-
return map;
|
|
145
|
+
return { offset, map };
|
|
139
146
|
}
|
|
140
147
|
|
|
141
148
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ngx-runtime-i18n-core.mjs","sources":["../../../../libs/runtime-i18n/src/lib/icu.ts","../../../../libs/runtime-i18n/src/index.ts","../../../../libs/runtime-i18n/src/ngx-runtime-i18n-core.ts"],"sourcesContent":["/**\n * ICU-style formatter with interpolation, plural, select, and selectordinal support.\n * Used by the Angular service and pipe.\n * @experimental\n */\nimport type { Catalog, PluralCategory, PluralResolver } from './types';\n\n// Tokens may include dots or hyphens so nested object keys like \"user.name\" are practical.\nconst INTERPOLATION_PATTERN = /\\{([a-zA-Z_][a-zA-Z0-9_.-]*)\\}/g;\n\nexport function formatIcu(\n _lang: string,\n key: string,\n cat: Catalog,\n params: Record<string, unknown> = {},\n onMissingKey?: (k: string) => string,\n pluralResolver?: PluralResolver\n): string {\n const raw = lookup(key, cat);\n if (raw == null) return onMissingKey ? onMissingKey(key) : key;\n\n let out = String(raw);\n\n // 1) Resolve {x, plural, ...}, {x, select, ...}, {x, selectordinal, ...} with a brace-balanced scanner.\n out = replaceMessageBlocks(out, _lang, params, pluralResolver);\n\n // 2) Simple {name} interpolation AFTER message block selection.\n INTERPOLATION_PATTERN.lastIndex = 0;\n out = out.replace(INTERPOLATION_PATTERN, (_m: string, p1: string) =>\n params[p1] != null ? String(params[p1]) : `{${p1}}`\n );\n\n return out;\n}\n\nfunction lookup(path: string, obj: unknown): unknown {\n return path\n .split('.')\n .reduce((o: unknown, k: string) =>\n o && typeof o === 'object' && Object.prototype.hasOwnProperty.call(o, k)\n ? (o as Record<string, unknown>)[k]\n : undefined,\n obj);\n}\n\n/**\n * Replace all `{arg, plural|select|selectordinal, ...}` blocks in `s` using a brace-balanced scan.\n */\nfunction replaceMessageBlocks(\n s: string,\n lang: string,\n params: Record<string, unknown>,\n pluralResolver?: PluralResolver\n): string {\n let i = 0;\n let out = '';\n\n while (i < s.length) {\n const start = s.indexOf('{', i);\n if (start === -1) { out += s.slice(i); break; }\n out += s.slice(i, start);\n\n // Try to match the prefix \"{arg, plural|select|selectordinal,\"\n const prefixMatch = /\\{(\\w+),\\s*(plural|select|selectordinal),\\s*/y;\n prefixMatch.lastIndex = start;\n const m = prefixMatch.exec(s);\n if (!m) {\n // Not a message block; copy '{' and continue scanning after it.\n out += '{';\n i = start + 1;\n continue;\n }\n\n const arg = m[1];\n const keyword = m[2] as 'plural' | 'select' | 'selectordinal';\n let j = prefixMatch.lastIndex; // position after the matched prefix\n\n // Find the matching closing '}' for the whole block with nesting.\n let depth = 1;\n while (j < s.length && depth > 0) {\n const ch = s.charAt(j++);\n if (ch === '{') depth++;\n else if (ch === '}') depth--;\n }\n\n if (depth !== 0) {\n // Unbalanced; fall back to literal copy of the unmatched segment.\n out += s.slice(start, j);\n i = j;\n continue;\n }\n\n // Body is the contents between prefix end and the final '}'.\n const body = s.slice(prefixMatch.lastIndex, j - 1);\n const options = parsePluralBody(body);\n\n if (keyword === 'plural' || keyword === 'selectordinal') {\n const n = Number(params[arg] ?? 0);\n if (Number.isFinite(n)) {\n const exact = options[`=${n}`];\n if (exact != null) {\n out += replaceHash(replaceMessageBlocks(exact, lang, params, pluralResolver), n);\n i = j;\n continue;\n }\n\n const category: PluralCategory = pluralResolver\n ? pluralResolver(n, lang)\n : n === 1 ? 'one' : 'other';\n\n const match = options[category] ?? options['other'] ?? '';\n out += replaceHash(replaceMessageBlocks(match, lang, params, pluralResolver), n);\n } else {\n out += replaceHash(replaceMessageBlocks(options['other'] ?? '', lang, params, pluralResolver), Number(params[arg]));\n }\n } else {\n // select: look up param value directly, then resolve any nested keyword blocks\n const val = String(params[arg] ?? 'other');\n out += replaceMessageBlocks(options[val] ?? options['other'] ?? '', lang, params, pluralResolver);\n }\n\n i = j;\n }\n\n return out;\n}\n\nfunction replaceHash(s: string, n: number): string {\n return s.replace(/#/g, String(n));\n}\n\n/**\n * Parse a simple ICU plural/select clause body: e.g. `one {A} other {B} =0 {C}`.\n * Supports balanced brace bodies and nested forms.\n */\nfunction parsePluralBody(body: string): Record<string, string> {\n const map: Record<string, string> = {};\n let i = 0;\n\n while (i < body.length) {\n // Skip whitespace between selectors.\n while (i < body.length && /\\s/.test(body.charAt(i))) i++;\n if (i >= body.length) break;\n\n const keyStart = i;\n while (i < body.length && !/[\\s{]/.test(body.charAt(i))) i++;\n if (keyStart === i) break;\n const key = body.slice(keyStart, i);\n\n // Skip whitespace before the opening brace.\n while (i < body.length && /\\s/.test(body.charAt(i))) i++;\n if (body.charAt(i) !== '{') break;\n i++; // Consume '{'\n\n const valueStart = i;\n let depth = 1;\n // Consume until the matching closing brace; supports nested braces for placeholders.\n while (i < body.length && depth > 0) {\n const ch = body.charAt(i++);\n if (ch === '{') depth++;\n else if (ch === '}') depth--;\n }\n if (depth !== 0) {\n // Unbalanced braces (malformed plural); bail out to avoid infinite loops.\n break;\n }\n\n map[key] = body.slice(valueStart, i - 1);\n }\n\n return map;\n}\n","/**\n * @packageDocumentation\n * Core runtime i18n primitives.\n * Keep this surface minimal and stable.\n */\n\nexport type {\n Catalog,\n RuntimeI18nConfig,\n I18nSchema,\n TranslationKey,\n TranslationParams,\n DeepKeys,\n ExtractParams,\n ActiveCatalogType,\n PluralCategory,\n PluralResolver,\n} from './lib/types';\n\n/**\n * Lightweight ICU-style formatter used internally by the Angular service.\n * Exposed as @experimental for advanced users or custom integrations.\n * @experimental\n */\nexport { formatIcu } from './lib/icu';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ngx-runtime-i18n-core.mjs","sources":["../../../../libs/runtime-i18n/src/lib/icu.ts","../../../../libs/runtime-i18n/src/index.ts","../../../../libs/runtime-i18n/src/ngx-runtime-i18n-core.ts"],"sourcesContent":["/**\n * ICU-style formatter with interpolation, plural, select, and selectordinal support.\n * Used by the Angular service and pipe.\n * @experimental\n */\nimport type { Catalog, PluralCategory, PluralResolver } from './types';\n\n// Tokens may include dots or hyphens so nested object keys like \"user.name\" are practical.\nconst INTERPOLATION_PATTERN = /\\{([a-zA-Z_][a-zA-Z0-9_.-]*)\\}/g;\n\nexport function formatIcu(\n _lang: string,\n key: string,\n cat: Catalog,\n params: Record<string, unknown> | null = {},\n onMissingKey?: (k: string) => string,\n pluralResolver?: PluralResolver\n): string {\n const raw = lookup(key, cat);\n if (raw == null) return onMissingKey ? onMissingKey(key) : key;\n\n const p = params ?? {};\n let out = String(raw);\n\n // 1) Resolve {x, plural, ...}, {x, select, ...}, {x, selectordinal, ...} with a brace-balanced scanner.\n out = replaceMessageBlocks(out, _lang, p, pluralResolver);\n\n // 2) Simple {name} interpolation AFTER message block selection.\n INTERPOLATION_PATTERN.lastIndex = 0;\n out = out.replace(INTERPOLATION_PATTERN, (_m: string, p1: string) =>\n p[p1] != null ? String(p[p1]) : `{${p1}}`\n );\n\n return out;\n}\n\nfunction lookup(path: string, obj: unknown): unknown {\n return path\n .split('.')\n .reduce((o: unknown, k: string) =>\n o && typeof o === 'object' && Object.prototype.hasOwnProperty.call(o, k)\n ? (o as Record<string, unknown>)[k]\n : undefined,\n obj);\n}\n\n/**\n * Replace all `{arg, plural|select|selectordinal, ...}` blocks in `s` using a brace-balanced scan.\n */\nfunction replaceMessageBlocks(\n s: string,\n lang: string,\n params: Record<string, unknown>,\n pluralResolver?: PluralResolver\n): string {\n let i = 0;\n let out = '';\n\n while (i < s.length) {\n const start = s.indexOf('{', i);\n if (start === -1) { out += s.slice(i); break; }\n out += s.slice(i, start);\n\n // Try to match the prefix \"{arg, plural|select|selectordinal,\"\n const prefixMatch = /\\{(\\w+),\\s*(plural|select|selectordinal),\\s*/y;\n prefixMatch.lastIndex = start;\n const m = prefixMatch.exec(s);\n if (!m) {\n // Not a message block; copy '{' and continue scanning after it.\n out += '{';\n i = start + 1;\n continue;\n }\n\n const arg = m[1];\n const keyword = m[2] as 'plural' | 'select' | 'selectordinal';\n let j = prefixMatch.lastIndex; // position after the matched prefix\n\n // Find the matching closing '}' for the whole block with nesting.\n let depth = 1;\n while (j < s.length && depth > 0) {\n const ch = s.charAt(j++);\n if (ch === '{') depth++;\n else if (ch === '}') depth--;\n }\n\n if (depth !== 0) {\n // Unbalanced; fall back to literal copy of the unmatched segment.\n out += s.slice(start, j);\n i = j;\n continue;\n }\n\n // Body is the contents between prefix end and the final '}'.\n const body = s.slice(prefixMatch.lastIndex, j - 1);\n const { offset, map: options } = parsePluralBody(body);\n\n if (keyword === 'plural' || keyword === 'selectordinal') {\n const n = Number(params[arg] ?? 0);\n if (Number.isFinite(n)) {\n const exact = options[`=${n}`];\n if (exact != null) {\n out += replaceHash(replaceMessageBlocks(exact, lang, params, pluralResolver), n - offset);\n i = j;\n continue;\n }\n\n const shifted = n - offset;\n const category: PluralCategory = pluralResolver\n ? pluralResolver(shifted, lang)\n : Math.abs(shifted) === 1 ? 'one' : 'other';\n\n const match = options[category] ?? options['other'] ?? '';\n out += replaceHash(replaceMessageBlocks(match, lang, params, pluralResolver), shifted);\n } else {\n out += replaceHash(replaceMessageBlocks(options['other'] ?? '', lang, params, pluralResolver), 0);\n }\n } else {\n // select: look up param value directly, then resolve any nested keyword blocks\n const val = String(params[arg] ?? 'other');\n out += replaceMessageBlocks(options[val] ?? options['other'] ?? '', lang, params, pluralResolver);\n }\n\n i = j;\n }\n\n return out;\n}\n\nfunction replaceHash(s: string, n: number): string {\n return s.replace(/#/g, String(n));\n}\n\n/**\n * Parse a simple ICU plural/select clause body: e.g. `one {A} other {B} =0 {C}`.\n * Supports balanced brace bodies, nested forms, and a leading `offset:N` token.\n * The returned map has no prototype, so a selector value matching an\n * `Object.prototype` member name (e.g. \"constructor\") can never resolve to an\n * inherited function instead of `undefined`.\n */\nfunction parsePluralBody(body: string): { offset: number; map: Record<string, string> } {\n const map: Record<string, string> = Object.create(null);\n const offsetMatch = /^\\s*offset:\\s*(\\d+)\\s*/.exec(body);\n const offset = offsetMatch ? Number(offsetMatch[1]) : 0;\n let i = offsetMatch ? offsetMatch[0].length : 0;\n\n while (i < body.length) {\n // Skip whitespace between selectors.\n while (i < body.length && /\\s/.test(body.charAt(i))) i++;\n if (i >= body.length) break;\n\n const keyStart = i;\n while (i < body.length && !/[\\s{]/.test(body.charAt(i))) i++;\n if (keyStart === i) break;\n const key = body.slice(keyStart, i);\n\n // Skip whitespace before the opening brace.\n while (i < body.length && /\\s/.test(body.charAt(i))) i++;\n if (body.charAt(i) !== '{') break;\n i++; // Consume '{'\n\n const valueStart = i;\n let depth = 1;\n // Consume until the matching closing brace; supports nested braces for placeholders.\n while (i < body.length && depth > 0) {\n const ch = body.charAt(i++);\n if (ch === '{') depth++;\n else if (ch === '}') depth--;\n }\n if (depth !== 0) {\n // Unbalanced braces (malformed plural); bail out to avoid infinite loops.\n break;\n }\n\n map[key] = body.slice(valueStart, i - 1);\n }\n\n return { offset, map };\n}\n","/**\n * @packageDocumentation\n * Core runtime i18n primitives.\n * Keep this surface minimal and stable.\n */\n\nexport type {\n Catalog,\n RuntimeI18nConfig,\n I18nSchema,\n TranslationKey,\n TranslationParams,\n DeepKeys,\n ExtractParams,\n ActiveCatalogType,\n PluralCategory,\n PluralResolver,\n} from './lib/types';\n\n/**\n * Lightweight ICU-style formatter used internally by the Angular service.\n * Exposed as @experimental for advanced users or custom integrations.\n * @experimental\n */\nexport { formatIcu } from './lib/icu';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"AAOA;AACA,MAAM,qBAAqB,GAAG,iCAAiC;AAEzD,SAAU,SAAS,CACvB,KAAa,EACb,GAAW,EACX,GAAY,EACZ,MAAA,GAAyC,EAAE,EAC3C,YAAoC,EACpC,cAA+B,EAAA;IAE/B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC;IAC5B,IAAI,GAAG,IAAI,IAAI;AAAE,QAAA,OAAO,YAAY,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,GAAG;AAE9D,IAAA,MAAM,CAAC,GAAG,MAAM,IAAI,EAAE;AACtB,IAAA,IAAI,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;;IAGrB,GAAG,GAAG,oBAAoB,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,cAAc,CAAC;;AAGzD,IAAA,qBAAqB,CAAC,SAAS,GAAG,CAAC;AACnC,IAAA,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,EAAU,EAAE,EAAU,KAC9D,CAAC,CAAC,EAAE,CAAC,IAAI,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,CAAA,CAAA,CAAG,CAC1C;AAED,IAAA,OAAO,GAAG;AACZ;AAEA,SAAS,MAAM,CAAC,IAAY,EAAE,GAAY,EAAA;AACxC,IAAA,OAAO;SACJ,KAAK,CAAC,GAAG;SACT,MAAM,CAAC,CAAC,CAAU,EAAE,CAAS,KAC5B,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;AACrE,UAAG,CAA6B,CAAC,CAAC;AAClC,UAAE,SAAS,EACb,GAAG,CAAC;AACV;AAEA;;AAEG;AACH,SAAS,oBAAoB,CAC3B,CAAS,EACT,IAAY,EACZ,MAA+B,EAC/B,cAA+B,EAAA;IAE/B,IAAI,CAAC,GAAG,CAAC;IACT,IAAI,GAAG,GAAG,EAAE;AAEZ,IAAA,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE;QACnB,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;AAC/B,QAAA,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE;AAAE,YAAA,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YAAE;QAAO;QAC9C,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC;;QAGxB,MAAM,WAAW,GAAG,+CAA+C;AACnE,QAAA,WAAW,CAAC,SAAS,GAAG,KAAK;QAC7B,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7B,IAAI,CAAC,CAAC,EAAE;;YAEN,GAAG,IAAI,GAAG;AACV,YAAA,CAAC,GAAG,KAAK,GAAG,CAAC;YACb;QACF;AAEA,QAAA,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;AAChB,QAAA,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CAA0C;AAC7D,QAAA,IAAI,CAAC,GAAG,WAAW,CAAC,SAAS,CAAC;;QAG9B,IAAI,KAAK,GAAG,CAAC;QACb,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,IAAI,KAAK,GAAG,CAAC,EAAE;YAChC,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;YACxB,IAAI,EAAE,KAAK,GAAG;AAAE,gBAAA,KAAK,EAAE;iBAClB,IAAI,EAAE,KAAK,GAAG;AAAE,gBAAA,KAAK,EAAE;QAC9B;AAEA,QAAA,IAAI,KAAK,KAAK,CAAC,EAAE;;YAEf,GAAG,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;YACxB,CAAC,GAAG,CAAC;YACL;QACF;;AAGA,QAAA,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC;AAClD,QAAA,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,eAAe,CAAC,IAAI,CAAC;QAEtD,IAAI,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,eAAe,EAAE;YACvD,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAClC,YAAA,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;gBACtB,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA,CAAE,CAAC;AAC9B,gBAAA,IAAI,KAAK,IAAI,IAAI,EAAE;AACjB,oBAAA,GAAG,IAAI,WAAW,CAAC,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC;oBACzF,CAAC,GAAG,CAAC;oBACL;gBACF;AAEA,gBAAA,MAAM,OAAO,GAAG,CAAC,GAAG,MAAM;gBAC1B,MAAM,QAAQ,GAAmB;AAC/B,sBAAE,cAAc,CAAC,OAAO,EAAE,IAAI;AAC9B,sBAAE,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,OAAO;AAE7C,gBAAA,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE;AACzD,gBAAA,GAAG,IAAI,WAAW,CAAC,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC;YACxF;iBAAO;gBACL,GAAG,IAAI,WAAW,CAAC,oBAAoB,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,CAAC,CAAC;YACnG;QACF;aAAO;;YAEL,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC;YAC1C,GAAG,IAAI,oBAAoB,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC;QACnG;QAEA,CAAC,GAAG,CAAC;IACP;AAEA,IAAA,OAAO,GAAG;AACZ;AAEA,SAAS,WAAW,CAAC,CAAS,EAAE,CAAS,EAAA;IACvC,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;AACnC;AAEA;;;;;;AAMG;AACH,SAAS,eAAe,CAAC,IAAY,EAAA;IACnC,MAAM,GAAG,GAA2B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;IACvD,MAAM,WAAW,GAAG,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC;AACvD,IAAA,MAAM,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACvD,IAAA,IAAI,CAAC,GAAG,WAAW,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC;AAE/C,IAAA,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE;;AAEtB,QAAA,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAAE,YAAA,CAAC,EAAE;AACxD,QAAA,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM;YAAE;QAEtB,MAAM,QAAQ,GAAG,CAAC;AAClB,QAAA,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAAE,YAAA,CAAC,EAAE;QAC5D,IAAI,QAAQ,KAAK,CAAC;YAAE;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;;AAGnC,QAAA,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAAE,YAAA,CAAC,EAAE;AACxD,QAAA,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE;QAC5B,CAAC,EAAE,CAAC;QAEJ,MAAM,UAAU,GAAG,CAAC;QACpB,IAAI,KAAK,GAAG,CAAC;;QAEb,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,GAAG,CAAC,EAAE;YACnC,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;YAC3B,IAAI,EAAE,KAAK,GAAG;AAAE,gBAAA,KAAK,EAAE;iBAClB,IAAI,EAAE,KAAK,GAAG;AAAE,gBAAA,KAAK,EAAE;QAC9B;AACA,QAAA,IAAI,KAAK,KAAK,CAAC,EAAE;;YAEf;QACF;AAEA,QAAA,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC;IAC1C;AAEA,IAAA,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE;AACxB;;AClLA;;;;AAIG;AAeH;;;;AAIG;;ACvBH;;AAEG;;;;"}
|
package/package.json
CHANGED
|
@@ -82,18 +82,34 @@ type DeepKeys<T, Depth extends number = 4> = [
|
|
|
82
82
|
type TranslationKey = I18nSchema extends {
|
|
83
83
|
translations: infer T;
|
|
84
84
|
} ? T extends Record<string, unknown> ? DeepKeys<T> : string : string;
|
|
85
|
+
/**
|
|
86
|
+
* Splits `S` at the `}` that balances the block's opening `{` (already consumed
|
|
87
|
+
* by the caller), so nested ICU clause braces don't truncate the match early.
|
|
88
|
+
* Returns `[Token, Rest]` once `depth` closes back to 0, else `never`.
|
|
89
|
+
* @internal
|
|
90
|
+
*/
|
|
91
|
+
type SplitBalanced<S extends string, Depth extends unknown[] = [], Token extends string = ''> = S extends `${infer Ch}${infer Rest}` ? Ch extends '{' ? SplitBalanced<Rest, [...Depth, 0], `${Token}${Ch}`> : Ch extends '}' ? Depth extends [unknown, ...infer DepthRest] ? SplitBalanced<Rest, DepthRest, `${Token}${Ch}`> : [Token, Rest] : SplitBalanced<Rest, Depth, `${Token}${Ch}`> : never;
|
|
92
|
+
/** Strips leading whitespace, mirroring the runtime scanner's `\s*` after each comma. @internal */
|
|
93
|
+
type TrimLeft<S extends string> = S extends ` ${infer Rest}` ? TrimLeft<Rest> : S;
|
|
85
94
|
/**
|
|
86
95
|
* Extracts interpolation param names from an ICU message string literal.
|
|
87
|
-
* Works for simple {name} tokens and ICU keyword blocks {count, plural, ...}
|
|
96
|
+
* Works for simple {name} tokens and ICU keyword blocks {count, plural, ...},
|
|
97
|
+
* balancing nested braces inside plural/select clause bodies (e.g. `{# items}`)
|
|
98
|
+
* instead of stopping at the first `}`, and tolerating arbitrary whitespace
|
|
99
|
+
* after the comma the same way the runtime scanner's `\s*` does.
|
|
88
100
|
* @publicApi
|
|
89
101
|
*/
|
|
90
|
-
type ExtractParams<S extends string> = S extends `${string}{${infer
|
|
102
|
+
type ExtractParams<S extends string> = S extends `${string}{${infer AfterBrace}` ? SplitBalanced<AfterBrace> extends [infer Token extends string, infer Rest extends string] ? Token extends `${infer Arg},${infer AfterComma}` ? TrimLeft<AfterComma> extends `${infer Keyword},${string}` ? Keyword extends 'plural' | 'selectordinal' ? {
|
|
91
103
|
[K in Arg]: number;
|
|
104
|
+
} & ExtractParams<Rest> : Keyword extends 'select' ? {
|
|
105
|
+
[K in Arg]: string | number;
|
|
106
|
+
} & ExtractParams<Rest> : {
|
|
107
|
+
[K in Token]: string | number;
|
|
92
108
|
} & ExtractParams<Rest> : {
|
|
93
109
|
[K in Token]: string | number;
|
|
94
110
|
} & ExtractParams<Rest> : {
|
|
95
111
|
[K in Token]: string | number;
|
|
96
|
-
} & ExtractParams<Rest> : Record<never, never>;
|
|
112
|
+
} & ExtractParams<Rest> : Record<never, never> : Record<never, never>;
|
|
97
113
|
/** @internal */
|
|
98
114
|
type ResolveValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? ResolveValue<T[K], Rest> : never : P extends keyof T ? T[P] : never;
|
|
99
115
|
/**
|
|
@@ -123,7 +139,7 @@ type PluralResolver = (count: number, locale: string) => PluralCategory;
|
|
|
123
139
|
* @experimental
|
|
124
140
|
*/
|
|
125
141
|
|
|
126
|
-
declare function formatIcu(_lang: string, key: string, cat: Catalog, params?: Record<string, unknown
|
|
142
|
+
declare function formatIcu(_lang: string, key: string, cat: Catalog, params?: Record<string, unknown> | null, onMissingKey?: (k: string) => string, pluralResolver?: PluralResolver): string;
|
|
127
143
|
|
|
128
144
|
export { formatIcu };
|
|
129
145
|
export type { ActiveCatalogType, Catalog, DeepKeys, ExtractParams, I18nSchema, PluralCategory, PluralResolver, RuntimeI18nConfig, TranslationKey, TranslationParams };
|