ngx-kit 8.5.0 → 9.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/fesm2022/ngx-kit-lazy-i18n.mjs +524 -0
- package/fesm2022/ngx-kit-lazy-i18n.mjs.map +1 -0
- package/lazy-i18n/README.md +455 -0
- package/package.json +45 -2
- package/types/ngx-kit-lazy-i18n.d.ts +213 -0
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, signal, computed, isDevMode, Injectable, Pipe, TemplateRef, ViewContainerRef, Input, Directive, ElementRef, Renderer2, effect, makeEnvironmentProviders, provideAppInitializer, provideEnvironmentInitializer, assertInInjectionContext } from '@angular/core';
|
|
3
|
+
import { DOCUMENT } from '@angular/common';
|
|
4
|
+
import { firstValueFrom, defer, from, retry } from 'rxjs';
|
|
5
|
+
import { HttpClient } from '@angular/common/http';
|
|
6
|
+
|
|
7
|
+
class I18nConfig {
|
|
8
|
+
/** All supported languages. */
|
|
9
|
+
langs;
|
|
10
|
+
defaultLang;
|
|
11
|
+
/** Looked up (in order) when a key is empty/missing in the active language. */
|
|
12
|
+
fallbackLangs;
|
|
13
|
+
/** Default `'key'` (returns the key itself). */
|
|
14
|
+
missing;
|
|
15
|
+
/** Log each missing key once. Default: `isDevMode()`. */
|
|
16
|
+
warnMissing;
|
|
17
|
+
/** Replace the default `{{ name }}` interpolation (ICU, plurals, ...). */
|
|
18
|
+
transpile;
|
|
19
|
+
/** Custom loader class (must be `@Injectable({providedIn:'root'})` or constructible without args). Default: HTTP. */
|
|
20
|
+
loader = HttpI18nLoader;
|
|
21
|
+
/** Default HTTP loader: base URL. Default `'/i18n'` -> `/i18n/fa`, `/i18n/setting/fa`. */
|
|
22
|
+
baseUrl;
|
|
23
|
+
/** Default HTTP loader: file extension appended to the URL, e.g. `'.json'`. Default `'.json'`. */
|
|
24
|
+
fileExtension = '.json';
|
|
25
|
+
/** Default HTTP loader: full control over the URL. */
|
|
26
|
+
url;
|
|
27
|
+
/** Retries per failed request. Default `1`. */
|
|
28
|
+
retries;
|
|
29
|
+
/** localStorage key used to remember the language. `null` disables. Default `'lazy-i18n.lang'`. */
|
|
30
|
+
storageKey;
|
|
31
|
+
/** Pick the initial language from `navigator.languages` when nothing is stored. Default `false`. */
|
|
32
|
+
detectBrowserLang;
|
|
33
|
+
/** Keep `<html lang dir>` in sync. Default `true`. */
|
|
34
|
+
manageDocument;
|
|
35
|
+
}
|
|
36
|
+
const I18N_CONFIG = new InjectionToken('I18N_CONFIG');
|
|
37
|
+
const I18N_LOADER = new InjectionToken('I18N_LOADER');
|
|
38
|
+
|
|
39
|
+
/** Flatten a nested dictionary into `target` (dot-separated keys). Non-string leaves are stringified. */
|
|
40
|
+
function flatten(dict, target = new Map(), onConflict, prefix = '') {
|
|
41
|
+
for (const k of Object.keys(dict)) {
|
|
42
|
+
const v = dict[k];
|
|
43
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
44
|
+
if (v !== null && typeof v === 'object') {
|
|
45
|
+
flatten(v, target, onConflict, key);
|
|
46
|
+
}
|
|
47
|
+
else if (v !== null && v !== undefined) {
|
|
48
|
+
const s = String(v);
|
|
49
|
+
if (onConflict) {
|
|
50
|
+
const prev = target.get(key);
|
|
51
|
+
if (prev !== undefined && prev !== '' && s !== '' && prev !== s)
|
|
52
|
+
onConflict(key);
|
|
53
|
+
}
|
|
54
|
+
target.set(key, s);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return target;
|
|
58
|
+
}
|
|
59
|
+
const RE = /\{\{\s*([\w$.-]+)\s*\}\}/g;
|
|
60
|
+
function getPath(obj, path) {
|
|
61
|
+
if (path in obj)
|
|
62
|
+
return obj[path];
|
|
63
|
+
let cur = obj;
|
|
64
|
+
for (const part of path.split('.')) {
|
|
65
|
+
if (cur == null)
|
|
66
|
+
return undefined;
|
|
67
|
+
cur = cur[part];
|
|
68
|
+
}
|
|
69
|
+
return cur;
|
|
70
|
+
}
|
|
71
|
+
/** `Hello {{ name }}` + `{name:'Ali'}` -> `Hello Ali`. Unknown placeholders are left untouched. */
|
|
72
|
+
function interpolate(text, params) {
|
|
73
|
+
if (!params || text.indexOf('{{') === -1)
|
|
74
|
+
return text; // fast path: no regex
|
|
75
|
+
return text.replace(RE, (m, k) => {
|
|
76
|
+
const v = getPath(params, k);
|
|
77
|
+
return v == null ? m : String(v);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function shallowEqual(a, b) {
|
|
81
|
+
if (a === b)
|
|
82
|
+
return true;
|
|
83
|
+
if (!a || !b)
|
|
84
|
+
return false;
|
|
85
|
+
const ka = Object.keys(a);
|
|
86
|
+
if (ka.length !== Object.keys(b).length)
|
|
87
|
+
return false;
|
|
88
|
+
for (const k of ka)
|
|
89
|
+
if (a[k] !== b[k])
|
|
90
|
+
return false;
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
const RTL_LANGS = new Set(['ar', 'fa', 'he', 'ur', 'ps', 'sd', 'ug', 'yi', 'ckb', 'dv']);
|
|
94
|
+
/** Identity function the extractor recognises: `const k = marker('orders.title')`. */
|
|
95
|
+
function marker(key) {
|
|
96
|
+
return key;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const TAG = '[lazy-i18n]';
|
|
100
|
+
class I18nService {
|
|
101
|
+
cfg = inject(I18N_CONFIG);
|
|
102
|
+
loader = inject(I18N_LOADER);
|
|
103
|
+
doc = inject(DOCUMENT);
|
|
104
|
+
_lang = signal(this.cfg.defaultLang, /* @ts-ignore */
|
|
105
|
+
...(ngDevMode ? [{ debugName: "_lang" }] : /* istanbul ignore next */ []));
|
|
106
|
+
_rev = signal(0, /* @ts-ignore */
|
|
107
|
+
...(ngDevMode ? [{ debugName: "_rev" }] : /* istanbul ignore next */ []));
|
|
108
|
+
/** Active language (reactive). */
|
|
109
|
+
lang = this._lang.asReadonly();
|
|
110
|
+
/** Bumps whenever the active language or any dictionary changes. */
|
|
111
|
+
revision = this._rev.asReadonly();
|
|
112
|
+
dir = computed(() => RTL_LANGS.has(this._lang().split('-')[0]) ? 'rtl' : 'ltr', /* @ts-ignore */
|
|
113
|
+
...(ngDevMode ? [{ debugName: "dir" }] : /* istanbul ignore next */ []));
|
|
114
|
+
langs = this.cfg.langs;
|
|
115
|
+
fallbacks = this.cfg.fallbackLangs ?? [];
|
|
116
|
+
warn = this.cfg.warnMissing ?? isDevMode();
|
|
117
|
+
dicts = new Map(); // lang -> flat map
|
|
118
|
+
loaded = new Set(); // `${lang}|${scope}`
|
|
119
|
+
inflight = new Map();
|
|
120
|
+
scopes = new Set();
|
|
121
|
+
pending = new Set();
|
|
122
|
+
warned = new Set();
|
|
123
|
+
useToken = 0;
|
|
124
|
+
// ───────────────────────── translate ─────────────────────────
|
|
125
|
+
/** Translate. Reads signals, so it is reactive inside `computed`, `effect` and templates. */
|
|
126
|
+
t(key, params) {
|
|
127
|
+
this._rev();
|
|
128
|
+
return this.resolve(key, params, this._lang());
|
|
129
|
+
}
|
|
130
|
+
/** Same as `t`, but never creates a reactive dependency. */
|
|
131
|
+
instant(key, params) {
|
|
132
|
+
return this.resolve(key, params, this._lang());
|
|
133
|
+
}
|
|
134
|
+
/** `computed` signal for one key. `params` may be a function reading other signals. */
|
|
135
|
+
select(key, params) {
|
|
136
|
+
return computed(() => this.t(key, typeof params === 'function' ? params() : params));
|
|
137
|
+
}
|
|
138
|
+
/** True when the key has a non-empty value in the active language or a fallback. */
|
|
139
|
+
has(key) {
|
|
140
|
+
this._rev();
|
|
141
|
+
return this.lookup(key, this._lang()) !== undefined;
|
|
142
|
+
}
|
|
143
|
+
/** Returns every key below `prefix` as a nested object (values are raw, not interpolated). */
|
|
144
|
+
getObject(prefix) {
|
|
145
|
+
this._rev();
|
|
146
|
+
const out = {};
|
|
147
|
+
const p = prefix + '.';
|
|
148
|
+
for (const lang of [this._lang(), ...this.fallbacks]) {
|
|
149
|
+
for (const [k, v] of this.dicts.get(lang) ?? []) {
|
|
150
|
+
if (!k.startsWith(p) || !v)
|
|
151
|
+
continue;
|
|
152
|
+
const parts = k.slice(p.length).split('.');
|
|
153
|
+
let cur = out;
|
|
154
|
+
for (let i = 0; i < parts.length - 1; i++)
|
|
155
|
+
cur = typeof cur[parts[i]] === 'object' ? cur[parts[i]] : (cur[parts[i]] = {});
|
|
156
|
+
if (!(parts[parts.length - 1] in cur))
|
|
157
|
+
cur[parts[parts.length - 1]] = v;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
lookup(key, lang) {
|
|
163
|
+
let raw = this.dicts.get(lang)?.get(key);
|
|
164
|
+
if (raw)
|
|
165
|
+
return raw;
|
|
166
|
+
for (const fb of this.fallbacks) {
|
|
167
|
+
if (fb === lang)
|
|
168
|
+
continue;
|
|
169
|
+
raw = this.dicts.get(fb)?.get(key);
|
|
170
|
+
if (raw)
|
|
171
|
+
return raw;
|
|
172
|
+
}
|
|
173
|
+
return undefined; // '' (extractor placeholder) counts as missing -> falls back
|
|
174
|
+
}
|
|
175
|
+
resolve(key, params, lang) {
|
|
176
|
+
const raw = this.lookup(key, lang);
|
|
177
|
+
if (raw === undefined)
|
|
178
|
+
return this.onMissing(key, lang);
|
|
179
|
+
return this.cfg.transpile ? this.cfg.transpile(raw, params, lang) : interpolate(raw, params);
|
|
180
|
+
}
|
|
181
|
+
onMissing(key, lang) {
|
|
182
|
+
if (this.warn && this.dicts.has(lang) && !this.warned.has(key)) {
|
|
183
|
+
this.warned.add(key);
|
|
184
|
+
console.warn(`${TAG} Missing translation "${key}" (lang: ${lang})`);
|
|
185
|
+
}
|
|
186
|
+
const h = this.cfg.missing ?? 'key';
|
|
187
|
+
return typeof h === 'function' ? h(key, lang) : h === 'empty' ? '' : key;
|
|
188
|
+
}
|
|
189
|
+
// ───────────────────────── language ─────────────────────────
|
|
190
|
+
/**
|
|
191
|
+
* Load global + all registered scopes (and fallback languages) for `lang`, THEN activate it.
|
|
192
|
+
* Nothing renders half-translated and no "missing" flash occurs.
|
|
193
|
+
*/
|
|
194
|
+
async use(lang) {
|
|
195
|
+
if (!this.cfg.langs.includes(lang))
|
|
196
|
+
throw new Error(`${TAG} Unsupported language "${lang}"`);
|
|
197
|
+
const token = ++this.useToken;
|
|
198
|
+
do {
|
|
199
|
+
await this.loadLang(lang);
|
|
200
|
+
} while (!this.isComplete(lang)); // a scope may have been registered meanwhile
|
|
201
|
+
if (token !== this.useToken)
|
|
202
|
+
return; // a newer use() won
|
|
203
|
+
this._lang.set(lang);
|
|
204
|
+
this._rev.update((v) => v + 1);
|
|
205
|
+
this.persist(lang);
|
|
206
|
+
this.applyDocument(lang);
|
|
207
|
+
}
|
|
208
|
+
/** Download a language without activating it. */
|
|
209
|
+
preload(lang) {
|
|
210
|
+
return this.loadLang(lang);
|
|
211
|
+
}
|
|
212
|
+
/** Language to start with: stored > browser (opt-in) > default. */
|
|
213
|
+
detectInitialLang() {
|
|
214
|
+
const key = this.storageKey();
|
|
215
|
+
try {
|
|
216
|
+
const s = key ? localStorage.getItem(key) : null;
|
|
217
|
+
if (s && this.cfg.langs.includes(s))
|
|
218
|
+
return s;
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
/* storage unavailable (SSR / privacy mode) */
|
|
222
|
+
}
|
|
223
|
+
if (this.cfg.detectBrowserLang && typeof navigator !== 'undefined') {
|
|
224
|
+
for (const n of navigator.languages ?? [navigator.language]) {
|
|
225
|
+
if (this.cfg.langs.includes(n))
|
|
226
|
+
return n;
|
|
227
|
+
const base = n.split('-')[0];
|
|
228
|
+
if (this.cfg.langs.includes(base))
|
|
229
|
+
return base;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return this.cfg.defaultLang;
|
|
233
|
+
}
|
|
234
|
+
// ───────────────────────── scopes ─────────────────────────
|
|
235
|
+
/** Register a scope and start loading it for the active language. Idempotent. Used by `provideI18nScope`. */
|
|
236
|
+
registerScope(scope) {
|
|
237
|
+
this.scopes.add(scope);
|
|
238
|
+
const lang = this._lang();
|
|
239
|
+
const p = Promise.all([lang, ...this.fallbacks.filter((f) => f !== lang)].map((l) => this.fetch(l, scope).catch((e) => {
|
|
240
|
+
console.error(`${TAG} Failed to load scope "${scope}" (${l})`, e);
|
|
241
|
+
}))).then(() => {
|
|
242
|
+
this.pending.delete(p);
|
|
243
|
+
});
|
|
244
|
+
this.pending.add(p);
|
|
245
|
+
}
|
|
246
|
+
/** Resolves once every scope registered so far is available. Never rejects. */
|
|
247
|
+
async whenReady() {
|
|
248
|
+
while (this.pending.size)
|
|
249
|
+
await Promise.all([...this.pending]);
|
|
250
|
+
}
|
|
251
|
+
// ───────────────────────── data ─────────────────────────
|
|
252
|
+
/** Manually set translations (tests, SSR transfer state, ...). */
|
|
253
|
+
setTranslation(lang, dict, merge = true) {
|
|
254
|
+
if (!merge)
|
|
255
|
+
this.dicts.delete(lang);
|
|
256
|
+
this.merge(lang, dict);
|
|
257
|
+
}
|
|
258
|
+
merge(lang, dict) {
|
|
259
|
+
let m = this.dicts.get(lang);
|
|
260
|
+
if (!m)
|
|
261
|
+
this.dicts.set(lang, (m = new Map()));
|
|
262
|
+
flatten(dict, m, this.warn
|
|
263
|
+
? (k) => console.warn(`${TAG} Key "${k}" is defined twice with different values (${lang})`)
|
|
264
|
+
: undefined);
|
|
265
|
+
this._rev.update((v) => v + 1);
|
|
266
|
+
}
|
|
267
|
+
fetch(lang, scope) {
|
|
268
|
+
const id = `${lang}|${scope ?? ''}`;
|
|
269
|
+
if (this.loaded.has(id))
|
|
270
|
+
return Promise.resolve();
|
|
271
|
+
let p = this.inflight.get(id);
|
|
272
|
+
if (!p) {
|
|
273
|
+
p = firstValueFrom(defer(() => from(this.loader.load(lang, scope))).pipe(retry({ count: this.cfg.retries ?? 1, delay: 250 })))
|
|
274
|
+
.then((d) => {
|
|
275
|
+
this.merge(lang, d);
|
|
276
|
+
this.loaded.add(id);
|
|
277
|
+
})
|
|
278
|
+
.finally(() => this.inflight.delete(id));
|
|
279
|
+
this.inflight.set(id, p);
|
|
280
|
+
}
|
|
281
|
+
return p;
|
|
282
|
+
}
|
|
283
|
+
loadLang(lang) {
|
|
284
|
+
const scopes = [undefined, ...this.scopes];
|
|
285
|
+
const main = scopes.map((s) => this.fetch(lang, s));
|
|
286
|
+
const fb = this.fallbacks
|
|
287
|
+
.filter((f) => f !== lang)
|
|
288
|
+
.flatMap((f) => scopes.map((s) => this.fetch(f, s).catch((e) => console.error(`${TAG} Fallback "${f}" failed`, e))));
|
|
289
|
+
return Promise.all([...main, ...fb]).then(() => undefined);
|
|
290
|
+
}
|
|
291
|
+
isComplete(lang) {
|
|
292
|
+
return (this.loaded.has(`${lang}|`) && [...this.scopes].every((s) => this.loaded.has(`${lang}|${s}`)));
|
|
293
|
+
}
|
|
294
|
+
storageKey() {
|
|
295
|
+
return this.cfg.storageKey === undefined ? 'lazy-i18n.lang' : this.cfg.storageKey;
|
|
296
|
+
}
|
|
297
|
+
persist(lang) {
|
|
298
|
+
const key = this.storageKey();
|
|
299
|
+
try {
|
|
300
|
+
if (key)
|
|
301
|
+
localStorage.setItem(key, lang);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
/* ignore */
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
applyDocument(lang) {
|
|
308
|
+
if (this.cfg.manageDocument === false)
|
|
309
|
+
return;
|
|
310
|
+
const el = this.doc?.documentElement;
|
|
311
|
+
if (!el)
|
|
312
|
+
return;
|
|
313
|
+
el.setAttribute('lang', lang);
|
|
314
|
+
el.setAttribute('dir', this.dir());
|
|
315
|
+
}
|
|
316
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: I18nService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
317
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: I18nService, providedIn: 'root' });
|
|
318
|
+
}
|
|
319
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: I18nService, decorators: [{
|
|
320
|
+
type: Injectable,
|
|
321
|
+
args: [{ providedIn: 'root' }]
|
|
322
|
+
}] });
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* `{{ 'orders.title' | tr }}` / `{{ 'hello' | tr: { name } }}`
|
|
326
|
+
*
|
|
327
|
+
* Impure on purpose (it must follow signals), but memoised: when key, params and the
|
|
328
|
+
* service revision are unchanged it returns the cached string in O(1) with no lookup.
|
|
329
|
+
*/
|
|
330
|
+
class TrPipe {
|
|
331
|
+
svc = inject(I18nService);
|
|
332
|
+
k;
|
|
333
|
+
p;
|
|
334
|
+
rev = -1;
|
|
335
|
+
out = '';
|
|
336
|
+
transform(key, params) {
|
|
337
|
+
if (!key)
|
|
338
|
+
return '';
|
|
339
|
+
const rev = this.svc.revision(); // tracked: marks the view dirty on lang / dictionary change
|
|
340
|
+
if (rev === this.rev && key === this.k && shallowEqual(params, this.p))
|
|
341
|
+
return this.out;
|
|
342
|
+
this.k = key;
|
|
343
|
+
this.p = params;
|
|
344
|
+
this.rev = rev;
|
|
345
|
+
return (this.out = this.svc.t(key, params));
|
|
346
|
+
}
|
|
347
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: TrPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
|
348
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.4", ngImport: i0, type: TrPipe, isStandalone: true, name: "tr", pure: false });
|
|
349
|
+
}
|
|
350
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: TrPipe, decorators: [{
|
|
351
|
+
type: Pipe,
|
|
352
|
+
args: [{ name: 'tr', pure: false }]
|
|
353
|
+
}] });
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Structural directive. The view is created ONCE; `t` reads signals, so only the bindings that use it update.
|
|
357
|
+
*
|
|
358
|
+
* <ng-container *tr="let t; prefix: 'orders'">
|
|
359
|
+
* <h1>{{ t('title') }}</h1> <!-- orders.title -->
|
|
360
|
+
* </ng-container>
|
|
361
|
+
*/
|
|
362
|
+
class TrDirective {
|
|
363
|
+
tpl = inject(TemplateRef);
|
|
364
|
+
vcr = inject(ViewContainerRef);
|
|
365
|
+
svc = inject(I18nService);
|
|
366
|
+
prefix = signal(undefined, /* @ts-ignore */
|
|
367
|
+
...(ngDevMode ? [{ debugName: "prefix" }] : /* istanbul ignore next */ []));
|
|
368
|
+
/** `prefix: 'orders'` in the microsyntax. */
|
|
369
|
+
set trPrefix(v) { this.prefix.set(v); }
|
|
370
|
+
static ngTemplateContextGuard(_dir, _ctx) {
|
|
371
|
+
return true;
|
|
372
|
+
}
|
|
373
|
+
constructor() {
|
|
374
|
+
const t = (key, params) => {
|
|
375
|
+
const p = this.prefix();
|
|
376
|
+
return this.svc.t(p ? p + '.' + key : key, params);
|
|
377
|
+
};
|
|
378
|
+
this.vcr.createEmbeddedView(this.tpl, { $implicit: t, t, lang: this.svc.lang });
|
|
379
|
+
}
|
|
380
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: TrDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
381
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.4", type: TrDirective, isStandalone: true, selector: "[tr],[translate]", inputs: { trPrefix: "trPrefix" }, ngImport: i0 });
|
|
382
|
+
}
|
|
383
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: TrDirective, decorators: [{
|
|
384
|
+
type: Directive,
|
|
385
|
+
args: [{ selector: '[tr],[translate]' }]
|
|
386
|
+
}], ctorParameters: () => [], propDecorators: { trPrefix: [{
|
|
387
|
+
type: Input,
|
|
388
|
+
args: ['trPrefix']
|
|
389
|
+
}] } });
|
|
390
|
+
/**
|
|
391
|
+
* Attribute directive.
|
|
392
|
+
* <span trKey="orders.title"></span>
|
|
393
|
+
* <input trKey="form.name" trAttr="placeholder" />
|
|
394
|
+
* <p trKey="hello" [trParams]="{ name: user.name }"></p>
|
|
395
|
+
*/
|
|
396
|
+
class TrKeyDirective {
|
|
397
|
+
// @Input setters feeding signals: works with JIT, AOT and every supported Angular version.
|
|
398
|
+
key = signal(undefined, /* @ts-ignore */
|
|
399
|
+
...(ngDevMode ? [{ debugName: "key" }] : /* istanbul ignore next */ []));
|
|
400
|
+
params = signal(undefined, /* @ts-ignore */
|
|
401
|
+
...(ngDevMode ? [{ debugName: "params" }] : /* istanbul ignore next */ []));
|
|
402
|
+
attr = signal(undefined, /* @ts-ignore */
|
|
403
|
+
...(ngDevMode ? [{ debugName: "attr" }] : /* istanbul ignore next */ []));
|
|
404
|
+
prefix = signal(undefined, /* @ts-ignore */
|
|
405
|
+
...(ngDevMode ? [{ debugName: "prefix" }] : /* istanbul ignore next */ []));
|
|
406
|
+
set trKey(v) { this.key.set(v); }
|
|
407
|
+
set trParams(v) { this.params.set(v); }
|
|
408
|
+
set trAttr(v) { this.attr.set(v); }
|
|
409
|
+
set trPrefix(v) { this.prefix.set(v); }
|
|
410
|
+
constructor() {
|
|
411
|
+
const svc = inject(I18nService);
|
|
412
|
+
const el = inject(ElementRef).nativeElement;
|
|
413
|
+
const r = inject(Renderer2);
|
|
414
|
+
effect(() => {
|
|
415
|
+
const k = this.key();
|
|
416
|
+
if (!k)
|
|
417
|
+
return;
|
|
418
|
+
const p = this.prefix();
|
|
419
|
+
const text = svc.t(p ? p + '.' + k : k, this.params());
|
|
420
|
+
const a = this.attr();
|
|
421
|
+
if (a)
|
|
422
|
+
r.setAttribute(el, a, text);
|
|
423
|
+
else if (el.textContent !== text)
|
|
424
|
+
el.textContent = text;
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: TrKeyDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
428
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.4", type: TrKeyDirective, isStandalone: true, selector: "[trKey]", inputs: { trKey: "trKey", trParams: "trParams", trAttr: "trAttr", trPrefix: "trPrefix" }, ngImport: i0 });
|
|
429
|
+
}
|
|
430
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: TrKeyDirective, decorators: [{
|
|
431
|
+
type: Directive,
|
|
432
|
+
args: [{ selector: '[trKey]' }]
|
|
433
|
+
}], ctorParameters: () => [], propDecorators: { trKey: [{
|
|
434
|
+
type: Input,
|
|
435
|
+
args: ['trKey']
|
|
436
|
+
}], trParams: [{
|
|
437
|
+
type: Input,
|
|
438
|
+
args: ['trParams']
|
|
439
|
+
}], trAttr: [{
|
|
440
|
+
type: Input,
|
|
441
|
+
args: ['trAttr']
|
|
442
|
+
}], trPrefix: [{
|
|
443
|
+
type: Input,
|
|
444
|
+
args: ['trPrefix']
|
|
445
|
+
}] } });
|
|
446
|
+
|
|
447
|
+
/** Default loader: GET `{baseUrl}/{lang}` and `{baseUrl}/{scope}/{lang}`. Goes through your HttpClient interceptors. */
|
|
448
|
+
class HttpI18nLoader {
|
|
449
|
+
http = inject(HttpClient);
|
|
450
|
+
cfg = inject(I18N_CONFIG);
|
|
451
|
+
load(lang, scope) {
|
|
452
|
+
const { url, baseUrl = '/i18n', fileExtension = '' } = this.cfg;
|
|
453
|
+
const u = url ? url(lang, scope) : `${baseUrl}/${scope ? scope + '/' : ''}${lang}${fileExtension}`;
|
|
454
|
+
return this.http.get(u);
|
|
455
|
+
}
|
|
456
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: HttpI18nLoader, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
457
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: HttpI18nLoader, providedIn: 'root' });
|
|
458
|
+
}
|
|
459
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.4", ngImport: i0, type: HttpI18nLoader, decorators: [{
|
|
460
|
+
type: Injectable,
|
|
461
|
+
args: [{ providedIn: 'root' }]
|
|
462
|
+
}] });
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* ```ts
|
|
466
|
+
* provideHttpClient(),
|
|
467
|
+
* provideI18n({ langs: ['fa', 'en'], defaultLang: 'fa', fallbackLangs: ['en'] })
|
|
468
|
+
* ```
|
|
469
|
+
* The global dictionary of the initial language is loaded before the app starts.
|
|
470
|
+
*/
|
|
471
|
+
function provideI18n(config) {
|
|
472
|
+
const c = { ...new I18nConfig(), ...config };
|
|
473
|
+
return makeEnvironmentProviders([
|
|
474
|
+
{ provide: I18N_CONFIG, useValue: c },
|
|
475
|
+
...(c.loader ? [c.loader] : []),
|
|
476
|
+
{ provide: I18N_LOADER, useFactory: () => inject(c.loader ?? HttpI18nLoader) },
|
|
477
|
+
provideAppInitializer(() => {
|
|
478
|
+
const svc = inject(I18nService);
|
|
479
|
+
return svc.use(svc.detectInitialLang());
|
|
480
|
+
}),
|
|
481
|
+
]);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Put in `route.providers`. Runs when the route injector is created (before guards),
|
|
486
|
+
* so loading starts as early as possible. Child routes need nothing.
|
|
487
|
+
*/
|
|
488
|
+
function provideI18nScope(scope) {
|
|
489
|
+
const scopes = Array.isArray(scope) ? scope : [scope];
|
|
490
|
+
return makeEnvironmentProviders([
|
|
491
|
+
provideEnvironmentInitializer(() => {
|
|
492
|
+
const svc = inject(I18nService);
|
|
493
|
+
for (const s of scopes)
|
|
494
|
+
svc.registerScope(s);
|
|
495
|
+
}),
|
|
496
|
+
]);
|
|
497
|
+
}
|
|
498
|
+
/** Put ONCE on the root path-less route: `canActivateChild: [i18nReadyGuard]`. Waits for every registered scope. */
|
|
499
|
+
const i18nReadyGuard = async () => {
|
|
500
|
+
await inject(I18nService).whenReady();
|
|
501
|
+
return true;
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
/** `readonly title = translateSignal('orders.title')` (injection context) */
|
|
505
|
+
function translateSignal(key, params) {
|
|
506
|
+
assertInInjectionContext(translateSignal);
|
|
507
|
+
return inject(I18nService).select(key, params);
|
|
508
|
+
}
|
|
509
|
+
/** `const t = injectT(); computed(() => t('a.b'))` — reactive translate function. */
|
|
510
|
+
function injectT() {
|
|
511
|
+
assertInInjectionContext(injectT);
|
|
512
|
+
const svc = inject(I18nService);
|
|
513
|
+
return (key, params) => svc.t(key, params);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** `imports: [TR]` brings the pipe and both directives into a standalone component. */
|
|
517
|
+
const TR = [TrPipe, TrDirective, TrKeyDirective];
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Generated bundle index. Do not edit.
|
|
521
|
+
*/
|
|
522
|
+
|
|
523
|
+
export { HttpI18nLoader, I18N_CONFIG, I18N_LOADER, I18nConfig, I18nService, TR, TrDirective, TrKeyDirective, TrPipe, flatten, i18nReadyGuard, injectT, interpolate, marker, provideI18n, provideI18nScope, translateSignal };
|
|
524
|
+
//# sourceMappingURL=ngx-kit-lazy-i18n.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ngx-kit-lazy-i18n.mjs","sources":["../../../projects/libs/lazy-i18n/types/types.ts","../../../projects/libs/lazy-i18n/utils/utils.ts","../../../projects/libs/lazy-i18n/services/i18n.service.ts","../../../projects/libs/lazy-i18n/pipes/pipe.ts","../../../projects/libs/lazy-i18n/directives/directives.ts","../../../projects/libs/lazy-i18n/loader/http-loader.ts","../../../projects/libs/lazy-i18n/providers/provide-i18n.ts","../../../projects/libs/lazy-i18n/providers/routing.ts","../../../projects/libs/lazy-i18n/signals/signals.ts","../../../projects/libs/lazy-i18n/public-api.ts","../../../projects/libs/lazy-i18n/ngx-kit-lazy-i18n.ts"],"sourcesContent":["import { InjectionToken, Type } from '@angular/core';\nimport type { Observable } from 'rxjs';\nimport { HttpI18nLoader } from '../public-api';\n\n/** A (possibly nested) translation file as returned by the server. */\nexport interface Dictionary {\n [key: string]: string | number | boolean | null | Dictionary | Array<unknown>;\n}\nexport type Params = Record<string, unknown>;\nexport type TranslateFn = (key: string, params?: Params) => string;\nexport type MissingHandler = (key: string, lang: string) => string;\nexport type Transpiler = (value: string, params: Params | undefined, lang: string) => string;\n\nexport interface I18nLoader {\n /** `scope === undefined` means the global file. */\n load(lang: string, scope?: string): Observable<Dictionary> | Promise<Dictionary>;\n}\n\nexport class I18nConfig {\n /** All supported languages. */\n langs!: string[];\n defaultLang!: string;\n /** Looked up (in order) when a key is empty/missing in the active language. */\n fallbackLangs?: string[];\n /** Default `'key'` (returns the key itself). */\n missing?: 'key' | 'empty' | MissingHandler;\n /** Log each missing key once. Default: `isDevMode()`. */\n warnMissing?: boolean;\n /** Replace the default `{{ name }}` interpolation (ICU, plurals, ...). */\n transpile?: Transpiler;\n /** Custom loader class (must be `@Injectable({providedIn:'root'})` or constructible without args). Default: HTTP. */\n loader?: Type<I18nLoader> = HttpI18nLoader;\n /** Default HTTP loader: base URL. Default `'/i18n'` -> `/i18n/fa`, `/i18n/setting/fa`. */\n baseUrl?: string;\n /** Default HTTP loader: file extension appended to the URL, e.g. `'.json'`. Default `'.json'`. */\n fileExtension?: string = '.json';\n /** Default HTTP loader: full control over the URL. */\n url?: (lang: string, scope?: string) => string;\n /** Retries per failed request. Default `1`. */\n retries?: number;\n /** localStorage key used to remember the language. `null` disables. Default `'lazy-i18n.lang'`. */\n storageKey?: string | null;\n /** Pick the initial language from `navigator.languages` when nothing is stored. Default `false`. */\n detectBrowserLang?: boolean;\n /** Keep `<html lang dir>` in sync. Default `true`. */\n manageDocument?: boolean;\n}\n\nexport const I18N_CONFIG = new InjectionToken<I18nConfig>('I18N_CONFIG');\nexport const I18N_LOADER = new InjectionToken<I18nLoader>('I18N_LOADER');\n","import type { Dictionary, Params } from '../types/types';\n\n/** Flatten a nested dictionary into `target` (dot-separated keys). Non-string leaves are stringified. */\nexport function flatten(\n dict: Dictionary,\n target: Map<string, string> = new Map(),\n onConflict?: (key: string) => void,\n prefix = '',\n): Map<string, string> {\n for (const k of Object.keys(dict)) {\n const v = (dict as Record<string, unknown>)[k];\n const key = prefix ? `${prefix}.${k}` : k;\n if (v !== null && typeof v === 'object') {\n flatten(v as Dictionary, target, onConflict, key);\n } else if (v !== null && v !== undefined) {\n const s = String(v);\n if (onConflict) {\n const prev = target.get(key);\n if (prev !== undefined && prev !== '' && s !== '' && prev !== s) onConflict(key);\n }\n target.set(key, s);\n }\n }\n return target;\n}\n\nconst RE = /\\{\\{\\s*([\\w$.-]+)\\s*\\}\\}/g;\n\nfunction getPath(obj: Params, path: string): unknown {\n if (path in obj) return obj[path];\n let cur: any = obj;\n for (const part of path.split('.')) {\n if (cur == null) return undefined;\n cur = cur[part];\n }\n return cur;\n}\n\n/** `Hello {{ name }}` + `{name:'Ali'}` -> `Hello Ali`. Unknown placeholders are left untouched. */\nexport function interpolate(text: string, params?: Params): string {\n if (!params || text.indexOf('{{') === -1) return text; // fast path: no regex\n return text.replace(RE, (m, k: string) => {\n const v = getPath(params, k);\n return v == null ? m : String(v);\n });\n}\n\nexport function shallowEqual(a?: Params, b?: Params): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n const ka = Object.keys(a);\n if (ka.length !== Object.keys(b).length) return false;\n for (const k of ka) if (a[k] !== b[k]) return false;\n return true;\n}\n\nexport const RTL_LANGS = new Set(['ar', 'fa', 'he', 'ur', 'ps', 'sd', 'ug', 'yi', 'ckb', 'dv']);\n\n/** Identity function the extractor recognises: `const k = marker('orders.title')`. */\nexport function marker<T extends string>(key: T): T {\n return key;\n}\n","import { DOCUMENT } from '@angular/common';\nimport { Injectable, Signal, computed, inject, isDevMode, signal } from '@angular/core';\nimport { defer, firstValueFrom, from, retry } from 'rxjs';\nimport { I18N_CONFIG, I18N_LOADER, type Dictionary, type Params } from '../types/types';\nimport { RTL_LANGS, flatten, interpolate } from '../utils/utils';\n\nconst TAG = '[lazy-i18n]';\n@Injectable({ providedIn: 'root' })\nexport class I18nService {\n private readonly cfg = inject(I18N_CONFIG);\n private readonly loader = inject(I18N_LOADER);\n private readonly doc = inject(DOCUMENT);\n\n private readonly _lang = signal(this.cfg.defaultLang);\n private readonly _rev = signal(0);\n /** Active language (reactive). */\n readonly lang = this._lang.asReadonly();\n /** Bumps whenever the active language or any dictionary changes. */\n readonly revision = this._rev.asReadonly();\n readonly dir = computed<'rtl' | 'ltr'>(() =>\n RTL_LANGS.has(this._lang().split('-')[0]) ? 'rtl' : 'ltr',\n );\n readonly langs: readonly string[] = this.cfg.langs;\n\n private readonly fallbacks = this.cfg.fallbackLangs ?? [];\n private readonly warn = this.cfg.warnMissing ?? isDevMode();\n private readonly dicts = new Map<string, Map<string, string>>(); // lang -> flat map\n private readonly loaded = new Set<string>(); // `${lang}|${scope}`\n private readonly inflight = new Map<string, Promise<void>>();\n private readonly scopes = new Set<string>();\n private readonly pending = new Set<Promise<void>>();\n private readonly warned = new Set<string>();\n private useToken = 0;\n\n // ───────────────────────── translate ─────────────────────────\n\n /** Translate. Reads signals, so it is reactive inside `computed`, `effect` and templates. */\n t(key: string, params?: Params): string {\n this._rev();\n return this.resolve(key, params, this._lang());\n }\n\n /** Same as `t`, but never creates a reactive dependency. */\n instant(key: string, params?: Params): string {\n return this.resolve(key, params, this._lang());\n }\n\n /** `computed` signal for one key. `params` may be a function reading other signals. */\n select(key: string, params?: Params | (() => Params)): Signal<string> {\n return computed(() => this.t(key, typeof params === 'function' ? params() : params));\n }\n\n /** True when the key has a non-empty value in the active language or a fallback. */\n has(key: string): boolean {\n this._rev();\n return this.lookup(key, this._lang()) !== undefined;\n }\n\n /** Returns every key below `prefix` as a nested object (values are raw, not interpolated). */\n getObject(prefix: string): Dictionary {\n this._rev();\n const out: Dictionary = {};\n const p = prefix + '.';\n for (const lang of [this._lang(), ...this.fallbacks]) {\n for (const [k, v] of this.dicts.get(lang) ?? []) {\n if (!k.startsWith(p) || !v) continue;\n const parts = k.slice(p.length).split('.');\n let cur: any = out;\n for (let i = 0; i < parts.length - 1; i++)\n cur = typeof cur[parts[i]] === 'object' ? cur[parts[i]] : (cur[parts[i]] = {});\n if (!(parts[parts.length - 1] in cur)) cur[parts[parts.length - 1]] = v;\n }\n }\n return out;\n }\n\n private lookup(key: string, lang: string): string | undefined {\n let raw = this.dicts.get(lang)?.get(key);\n if (raw) return raw;\n for (const fb of this.fallbacks) {\n if (fb === lang) continue;\n raw = this.dicts.get(fb)?.get(key);\n if (raw) return raw;\n }\n return undefined; // '' (extractor placeholder) counts as missing -> falls back\n }\n\n private resolve(key: string, params: Params | undefined, lang: string): string {\n const raw = this.lookup(key, lang);\n if (raw === undefined) return this.onMissing(key, lang);\n return this.cfg.transpile ? this.cfg.transpile(raw, params, lang) : interpolate(raw, params);\n }\n\n private onMissing(key: string, lang: string): string {\n if (this.warn && this.dicts.has(lang) && !this.warned.has(key)) {\n this.warned.add(key);\n console.warn(`${TAG} Missing translation \"${key}\" (lang: ${lang})`);\n }\n const h = this.cfg.missing ?? 'key';\n return typeof h === 'function' ? h(key, lang) : h === 'empty' ? '' : key;\n }\n\n // ───────────────────────── language ─────────────────────────\n\n /**\n * Load global + all registered scopes (and fallback languages) for `lang`, THEN activate it.\n * Nothing renders half-translated and no \"missing\" flash occurs.\n */\n async use(lang: string): Promise<void> {\n if (!this.cfg.langs.includes(lang)) throw new Error(`${TAG} Unsupported language \"${lang}\"`);\n const token = ++this.useToken;\n do {\n await this.loadLang(lang);\n } while (!this.isComplete(lang)); // a scope may have been registered meanwhile\n if (token !== this.useToken) return; // a newer use() won\n this._lang.set(lang);\n this._rev.update((v) => v + 1);\n this.persist(lang);\n this.applyDocument(lang);\n }\n\n /** Download a language without activating it. */\n preload(lang: string): Promise<void> {\n return this.loadLang(lang);\n }\n\n /** Language to start with: stored > browser (opt-in) > default. */\n detectInitialLang(): string {\n const key = this.storageKey();\n try {\n const s = key ? localStorage.getItem(key) : null;\n if (s && this.cfg.langs.includes(s)) return s;\n } catch {\n /* storage unavailable (SSR / privacy mode) */\n }\n if (this.cfg.detectBrowserLang && typeof navigator !== 'undefined') {\n for (const n of navigator.languages ?? [navigator.language]) {\n if (this.cfg.langs.includes(n)) return n;\n const base = n.split('-')[0];\n if (this.cfg.langs.includes(base)) return base;\n }\n }\n return this.cfg.defaultLang;\n }\n\n // ───────────────────────── scopes ─────────────────────────\n\n /** Register a scope and start loading it for the active language. Idempotent. Used by `provideI18nScope`. */\n registerScope(scope: string): void {\n this.scopes.add(scope);\n const lang = this._lang();\n const p: Promise<void> = Promise.all(\n [lang, ...this.fallbacks.filter((f) => f !== lang)].map((l) =>\n this.fetch(l, scope).catch((e) => {\n console.error(`${TAG} Failed to load scope \"${scope}\" (${l})`, e);\n }),\n ),\n ).then(() => {\n this.pending.delete(p);\n });\n this.pending.add(p);\n }\n\n /** Resolves once every scope registered so far is available. Never rejects. */\n async whenReady(): Promise<void> {\n while (this.pending.size) await Promise.all([...this.pending]);\n }\n\n // ───────────────────────── data ─────────────────────────\n\n /** Manually set translations (tests, SSR transfer state, ...). */\n setTranslation(lang: string, dict: Dictionary, merge = true): void {\n if (!merge) this.dicts.delete(lang);\n this.merge(lang, dict);\n }\n\n private merge(lang: string, dict: Dictionary): void {\n let m = this.dicts.get(lang);\n if (!m) this.dicts.set(lang, (m = new Map()));\n flatten(\n dict,\n m,\n this.warn\n ? (k) => console.warn(`${TAG} Key \"${k}\" is defined twice with different values (${lang})`)\n : undefined,\n );\n this._rev.update((v) => v + 1);\n }\n\n private fetch(lang: string, scope?: string): Promise<void> {\n const id = `${lang}|${scope ?? ''}`;\n if (this.loaded.has(id)) return Promise.resolve();\n let p = this.inflight.get(id);\n if (!p) {\n p = firstValueFrom(\n defer(() => from(this.loader.load(lang, scope))).pipe(\n retry({ count: this.cfg.retries ?? 1, delay: 250 }),\n ),\n )\n .then((d) => {\n this.merge(lang, d);\n this.loaded.add(id);\n })\n .finally(() => this.inflight.delete(id));\n this.inflight.set(id, p);\n }\n return p;\n }\n\n private loadLang(lang: string): Promise<void> {\n const scopes: Array<string | undefined> = [undefined, ...this.scopes];\n const main = scopes.map((s) => this.fetch(lang, s));\n const fb = this.fallbacks\n .filter((f) => f !== lang)\n .flatMap((f) =>\n scopes.map((s) =>\n this.fetch(f, s).catch((e) => console.error(`${TAG} Fallback \"${f}\" failed`, e)),\n ),\n );\n return Promise.all([...main, ...fb]).then(() => undefined);\n }\n\n private isComplete(lang: string): boolean {\n return (\n this.loaded.has(`${lang}|`) && [...this.scopes].every((s) => this.loaded.has(`${lang}|${s}`))\n );\n }\n\n private storageKey(): string | null {\n return this.cfg.storageKey === undefined ? 'lazy-i18n.lang' : this.cfg.storageKey;\n }\n\n private persist(lang: string): void {\n const key = this.storageKey();\n try {\n if (key) localStorage.setItem(key, lang);\n } catch {\n /* ignore */\n }\n }\n\n private applyDocument(lang: string): void {\n if (this.cfg.manageDocument === false) return;\n const el = this.doc?.documentElement;\n if (!el) return;\n el.setAttribute('lang', lang);\n el.setAttribute('dir', this.dir());\n }\n}\n","import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { I18nService } from '../services/i18n.service';\nimport type { Params } from '../types/types';\nimport { shallowEqual } from '../utils/utils';\n\n/**\n * `{{ 'orders.title' | tr }}` / `{{ 'hello' | tr: { name } }}`\n *\n * Impure on purpose (it must follow signals), but memoised: when key, params and the\n * service revision are unchanged it returns the cached string in O(1) with no lookup.\n */\n@Pipe({ name: 'tr', pure: false })\nexport class TrPipe implements PipeTransform {\n private readonly svc = inject(I18nService);\n private k?: string;\n private p?: Params;\n private rev = -1;\n private out = '';\n\n transform(key: string | null | undefined, params?: Params): string {\n if (!key) return '';\n const rev = this.svc.revision(); // tracked: marks the view dirty on lang / dictionary change\n if (rev === this.rev && key === this.k && shallowEqual(params, this.p)) return this.out;\n this.k = key;\n this.p = params;\n this.rev = rev;\n return (this.out = this.svc.t(key, params));\n }\n}\n","import {\n Directive, ElementRef, Input, Renderer2, Signal, TemplateRef, ViewContainerRef, effect, inject, signal,\n} from '@angular/core';\nimport { I18nService } from '../services/i18n.service';\nimport type { Params, TranslateFn } from '../types/types';\n\nexport interface TrContext {\n $implicit: TranslateFn;\n t: TranslateFn;\n lang: Signal<string>;\n}\n\n/**\n * Structural directive. The view is created ONCE; `t` reads signals, so only the bindings that use it update.\n *\n * <ng-container *tr=\"let t; prefix: 'orders'\">\n * <h1>{{ t('title') }}</h1> <!-- orders.title -->\n * </ng-container>\n */\n@Directive({ selector: '[tr],[translate]' })\nexport class TrDirective {\n private readonly tpl = inject<TemplateRef<TrContext>>(TemplateRef);\n private readonly vcr = inject(ViewContainerRef);\n private readonly svc = inject(I18nService);\n private readonly prefix = signal<string | undefined>(undefined);\n /** `prefix: 'orders'` in the microsyntax. */\n @Input('trPrefix') set trPrefix(v: string | undefined) { this.prefix.set(v); }\n\n static ngTemplateContextGuard(_dir: TrDirective, _ctx: unknown): _ctx is TrContext {\n return true;\n }\n\n constructor() {\n const t: TranslateFn = (key, params) => {\n const p = this.prefix();\n return this.svc.t(p ? p + '.' + key : key, params);\n };\n this.vcr.createEmbeddedView(this.tpl, { $implicit: t, t, lang: this.svc.lang });\n }\n}\n\n/**\n * Attribute directive.\n * <span trKey=\"orders.title\"></span>\n * <input trKey=\"form.name\" trAttr=\"placeholder\" />\n * <p trKey=\"hello\" [trParams]=\"{ name: user.name }\"></p>\n */\n@Directive({ selector: '[trKey]' })\nexport class TrKeyDirective {\n // @Input setters feeding signals: works with JIT, AOT and every supported Angular version.\n private readonly key = signal<string | undefined>(undefined);\n private readonly params = signal<Params | undefined>(undefined);\n private readonly attr = signal<string | undefined>(undefined);\n private readonly prefix = signal<string | undefined>(undefined);\n @Input('trKey') set trKey(v: string | undefined) { this.key.set(v); }\n @Input('trParams') set trParams(v: Params | undefined) { this.params.set(v); }\n @Input('trAttr') set trAttr(v: string | undefined) { this.attr.set(v); }\n @Input('trPrefix') set trPrefix(v: string | undefined) { this.prefix.set(v); }\n\n constructor() {\n const svc = inject(I18nService);\n const el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n const r = inject(Renderer2);\n effect(() => {\n const k = this.key();\n if (!k) return;\n const p = this.prefix();\n const text = svc.t(p ? p + '.' + k : k, this.params());\n const a = this.attr();\n if (a) r.setAttribute(el, a, text);\n else if (el.textContent !== text) el.textContent = text;\n });\n }\n}\n","import { HttpClient } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport type { Observable } from 'rxjs';\nimport { I18N_CONFIG, type Dictionary, type I18nLoader } from '../types/types';\n\n/** Default loader: GET `{baseUrl}/{lang}` and `{baseUrl}/{scope}/{lang}`. Goes through your HttpClient interceptors. */\n@Injectable({ providedIn: 'root' })\nexport class HttpI18nLoader implements I18nLoader {\n private readonly http = inject(HttpClient);\n private readonly cfg = inject(I18N_CONFIG);\n\n load(lang: string, scope?: string): Observable<Dictionary> {\n const { url, baseUrl = '/i18n', fileExtension = '' } = this.cfg;\n const u = url ? url(lang, scope) : `${baseUrl}/${scope ? scope + '/' : ''}${lang}${fileExtension}`;\n return this.http.get<Dictionary>(u);\n }\n}\n","import {\n EnvironmentProviders,\n inject,\n makeEnvironmentProviders,\n provideAppInitializer,\n} from '@angular/core';\nimport { HttpI18nLoader } from '../loader/http-loader';\nimport { I18nService } from '../services/i18n.service';\nimport { I18N_CONFIG, I18N_LOADER, I18nConfig } from '../types/types';\n\n/**\n * ```ts\n * provideHttpClient(),\n * provideI18n({ langs: ['fa', 'en'], defaultLang: 'fa', fallbackLangs: ['en'] })\n * ```\n * The global dictionary of the initial language is loaded before the app starts.\n */\nexport function provideI18n(config: I18nConfig): EnvironmentProviders {\n const c = { ...new I18nConfig(), ...config };\n return makeEnvironmentProviders([\n { provide: I18N_CONFIG, useValue: c },\n ...(c.loader ? [c.loader] : []),\n { provide: I18N_LOADER, useFactory: () => inject(c.loader ?? HttpI18nLoader) },\n provideAppInitializer(() => {\n const svc = inject(I18nService);\n return svc.use(svc.detectInitialLang());\n }),\n ]);\n}\n","import { EnvironmentProviders, inject, makeEnvironmentProviders, provideEnvironmentInitializer } from '@angular/core';\nimport type { CanActivateChildFn } from '@angular/router';\nimport { I18nService } from '../services/i18n.service';\n\n/**\n * Put in `route.providers`. Runs when the route injector is created (before guards),\n * so loading starts as early as possible. Child routes need nothing.\n */\nexport function provideI18nScope(scope: string | string[]): EnvironmentProviders {\n const scopes = Array.isArray(scope) ? scope : [scope];\n return makeEnvironmentProviders([\n provideEnvironmentInitializer(() => {\n const svc = inject(I18nService);\n for (const s of scopes) svc.registerScope(s);\n }),\n ]);\n}\n\n/** Put ONCE on the root path-less route: `canActivateChild: [i18nReadyGuard]`. Waits for every registered scope. */\nexport const i18nReadyGuard: CanActivateChildFn = async () => {\n await inject(I18nService).whenReady();\n return true;\n};\n","import { Signal, assertInInjectionContext, inject } from '@angular/core';\nimport { I18nService } from '../services/i18n.service';\nimport type { Params, TranslateFn } from '../types/types';\n\n/** `readonly title = translateSignal('orders.title')` (injection context) */\nexport function translateSignal(key: string, params?: Params | (() => Params)): Signal<string> {\n assertInInjectionContext(translateSignal);\n return inject(I18nService).select(key, params);\n}\n\n/** `const t = injectT(); computed(() => t('a.b'))` — reactive translate function. */\nexport function injectT(): TranslateFn {\n assertInInjectionContext(injectT);\n const svc = inject(I18nService);\n return (key, params) => svc.t(key, params);\n}\n","import { TrPipe } from './pipes/pipe';\nimport { TrDirective, TrKeyDirective } from './directives/directives';\n\nexport * from './types/types';\nexport { flatten, interpolate, marker } from './utils/utils';\nexport { HttpI18nLoader } from './loader/http-loader';\nexport { I18nService } from './services/i18n.service';\nexport { provideI18n } from './providers/provide-i18n';\nexport { provideI18nScope, i18nReadyGuard } from './providers/routing';\nexport { translateSignal, injectT } from './signals/signals';\nexport { TrPipe } from './pipes/pipe';\nexport { TrDirective, TrKeyDirective, type TrContext } from './directives/directives';\n\n/** `imports: [TR]` brings the pipe and both directives into a standalone component. */\nexport const TR = [TrPipe, TrDirective, TrKeyDirective] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|