@ngx-runtime-i18n/core 1.2.0 → 2.0.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
|
@@ -75,6 +75,23 @@ formatIcu('en', 'cart.items', catalog, { count: 2 }); // "2 items"
|
|
|
75
75
|
|
|
76
76
|
---
|
|
77
77
|
|
|
78
|
+
## ICU-lite support
|
|
79
|
+
|
|
80
|
+
### Supported
|
|
81
|
+
|
|
82
|
+
- Basic `{param}` interpolation (tokens may include dots and hyphens for nested data).
|
|
83
|
+
- `plural` blocks with `one`, `other`, and `=n` selectors plus `#` replacement.
|
|
84
|
+
- Nested placeholders inside plural option bodies (balanced braces are retained).
|
|
85
|
+
|
|
86
|
+
### Not supported
|
|
87
|
+
|
|
88
|
+
- `select` or other ICU argument types beyond `plural`.
|
|
89
|
+
- Full ICU-style escaping, quoting, or nested plural/select grammar.
|
|
90
|
+
- Plural blocks inside other plural blocks (depth beyond one level is skipped).
|
|
91
|
+
- Escaping braces beyond the literals above; unmatched braces must not resemble valid tokens.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
78
95
|
## License
|
|
79
96
|
|
|
80
97
|
MIT
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Tokens may include dots or hyphens so nested object keys like "user.name" are practical.
|
|
2
|
+
const INTERPOLATION_PATTERN = /\{([a-zA-Z_][a-zA-Z0-9_.-]*)\}/g;
|
|
1
3
|
function formatIcu(_lang, key, cat, params = {}, onMissingKey) {
|
|
2
4
|
const raw = lookup(key, cat);
|
|
3
5
|
if (raw == null)
|
|
@@ -20,7 +22,8 @@ function formatIcu(_lang, key, cat, params = {}, onMissingKey) {
|
|
|
20
22
|
return options['other'] ?? '';
|
|
21
23
|
});
|
|
22
24
|
// 2) Simple {name} interpolation AFTER plural branch selection.
|
|
23
|
-
|
|
25
|
+
INTERPOLATION_PATTERN.lastIndex = 0;
|
|
26
|
+
out = out.replace(INTERPOLATION_PATTERN, (_m, p1) => params[p1] != null ? String(params[p1]) : `{${p1}}`);
|
|
24
27
|
return out;
|
|
25
28
|
}
|
|
26
29
|
function lookup(path, obj) {
|
|
@@ -76,17 +79,53 @@ function replacePluralBlocks(s, render) {
|
|
|
76
79
|
}
|
|
77
80
|
return out;
|
|
78
81
|
}
|
|
79
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Parse a simple ICU plural clause body: e.g. `one {A} other {B} =0 {C}`.
|
|
84
|
+
* Only literal selectors and balanced brace bodies are supported; nested plural/select forms are intentionally skipped.
|
|
85
|
+
*/
|
|
80
86
|
function parsePluralBody(body) {
|
|
81
87
|
const map = {};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
let i = 0;
|
|
89
|
+
while (i < body.length) {
|
|
90
|
+
// Skip whitespace between selectors.
|
|
91
|
+
while (i < body.length && /\s/.test(body.charAt(i))) {
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
if (i >= body.length) {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
const keyStart = i;
|
|
98
|
+
while (i < body.length && !/\s|\{/.test(body.charAt(i))) {
|
|
99
|
+
i++;
|
|
100
|
+
}
|
|
101
|
+
if (keyStart === i) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
const key = body.slice(keyStart, i);
|
|
105
|
+
// Skip whitespace before the opening brace.
|
|
106
|
+
while (i < body.length && /\s/.test(body.charAt(i))) {
|
|
107
|
+
i++;
|
|
108
|
+
}
|
|
109
|
+
if (body.charAt(i) !== '{') {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
i++; // Consume '{'
|
|
113
|
+
const valueStart = i;
|
|
114
|
+
let depth = 1;
|
|
115
|
+
// Consume until the matching closing brace; supports nested braces for placeholders.
|
|
116
|
+
while (i < body.length && depth > 0) {
|
|
117
|
+
const ch = body.charAt(i++);
|
|
118
|
+
if (ch === '{')
|
|
119
|
+
depth++;
|
|
120
|
+
else if (ch === '}')
|
|
121
|
+
depth--;
|
|
122
|
+
}
|
|
123
|
+
if (depth !== 0) {
|
|
124
|
+
// Unbalanced braces (malformed plural); bail out to avoid infinite loops.
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
const valueEnd = i - 1;
|
|
128
|
+
map[key] = body.slice(valueStart, valueEnd);
|
|
90
129
|
}
|
|
91
130
|
return map;
|
|
92
131
|
}
|
|
@@ -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 * Very small ICU-like formatter with interpolation and plural basics.\n * Used by the Angular service and pipe. Replace with full ICU in a later minor.\n * @experimental\n */\nimport type { Catalog } from './types';\n\nexport function formatIcu(\n _lang: string,\n key: string,\n cat: Catalog,\n params: Record<string, unknown> = {},\n onMissingKey?: (k: string) => string\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, ...} with a brace-balanced scanner.\n out = replacePluralBlocks(out, (arg, body) => {\n const n = Number(params[arg] ?? 0);\n const options = parsePluralBody(body);\n if (Number.isFinite(n)) {\n const exact = options[`=${n}`];\n if (exact) return exact;\n const one = options['one'];\n if (n === 1 && one) return one;\n const other = options['other'] ?? '';\n return other.replace(/#/g, String(n));\n }\n return options['other'] ?? '';\n });\n\n // 2) Simple {name} interpolation AFTER plural branch selection.\n out = out.replace(
|
|
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 * Very small ICU-like formatter with interpolation and plural basics.\n * Used by the Angular service and pipe. Replace with full ICU in a later minor.\n * @experimental\n */\nimport type { Catalog } 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): 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, ...} with a brace-balanced scanner.\n out = replacePluralBlocks(out, (arg, body) => {\n const n = Number(params[arg] ?? 0);\n const options = parsePluralBody(body);\n if (Number.isFinite(n)) {\n const exact = options[`=${n}`];\n if (exact) return exact;\n const one = options['one'];\n if (n === 1 && one) return one;\n const other = options['other'] ?? '';\n return other.replace(/#/g, String(n));\n }\n return options['other'] ?? '';\n });\n\n // 2) Simple {name} interpolation AFTER plural branch 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: any): any {\n return path\n .split('.')\n .reduce((o: any, k: string) => (o && k in o ? o[k] : undefined), obj);\n}\n\n/**\n * Replace all `{arg, plural, ...}` blocks in `s` using a brace-balanced scan.\n */\nfunction replacePluralBlocks(\n s: string,\n render: (arg: string, body: string) => string\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) {\n out += s.slice(i);\n break;\n }\n out += s.slice(i, start);\n\n // Try to match the prefix \"{arg, plural,\"\n const prefixMatch = /\\{(\\w+),\\s*plural,\\s*/y;\n prefixMatch.lastIndex = start;\n const m = prefixMatch.exec(s);\n if (!m) {\n // Not a plural block; copy '{' and continue scanning after it.\n out += '{';\n i = start + 1;\n continue;\n }\n\n const arg = m[1];\n let j = prefixMatch.lastIndex; // position after the matched prefix\n\n // Find the matching closing '}' for the whole plural 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 rendered = render(arg, body);\n out += rendered;\n i = j; // continue after the closing brace\n }\n\n return out;\n}\n\n/**\n * Parse a simple ICU plural clause body: e.g. `one {A} other {B} =0 {C}`.\n * Only literal selectors and balanced brace bodies are supported; nested plural/select forms are intentionally skipped.\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))) {\n i++;\n }\n if (i >= body.length) {\n break;\n }\n\n const keyStart = i;\n while (i < body.length && !/\\s|\\{/.test(body.charAt(i))) {\n i++;\n }\n if (keyStart === i) {\n break;\n }\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))) {\n i++;\n }\n if (body.charAt(i) !== '{') {\n break;\n }\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 const valueEnd = i - 1;\n map[key] = body.slice(valueStart, valueEnd);\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 { Catalog, RuntimeI18nConfig } 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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ngx-runtime-i18n/core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Runtime i18n core with ICU-lite formatting",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -40,4 +40,4 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"tslib": "^2.3.0"
|
|
42
42
|
}
|
|
43
|
-
}
|
|
43
|
+
}
|