@mk-kit/ui 0.55.0 → 0.56.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/mk-kit-ui-core.mjs +8 -0
- package/fesm2022/mk-kit-ui-core.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-de.mjs +8 -0
- package/fesm2022/mk-kit-ui-locales-de.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-es.mjs +8 -0
- package/fesm2022/mk-kit-ui-locales-es.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-fr.mjs +8 -0
- package/fesm2022/mk-kit-ui-locales-fr.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-pl.mjs +8 -0
- package/fesm2022/mk-kit-ui-locales-pl.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-uk.mjs +8 -0
- package/fesm2022/mk-kit-ui-locales-uk.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-translate-editor.mjs +157 -0
- package/fesm2022/mk-kit-ui-translate-editor.mjs.map +1 -0
- package/fesm2022/mk-kit-ui-translate.mjs +110 -23
- package/fesm2022/mk-kit-ui-translate.mjs.map +1 -1
- package/package.json +5 -1
- package/types/mk-kit-ui-core.d.ts +9 -0
- package/types/mk-kit-ui-translate-editor.d.ts +76 -0
- package/types/mk-kit-ui-translate.d.ts +24 -0
|
@@ -76,12 +76,21 @@ class MkTranslate {
|
|
|
76
76
|
overridesLoader = null;
|
|
77
77
|
dictionaries = new Map();
|
|
78
78
|
declared = new Set();
|
|
79
|
+
/** Languages holding only the keys a server render used (see `transfer`). */
|
|
80
|
+
partial = new Set();
|
|
81
|
+
/** Keys read on the server, per language — what `transfer: 'used'` ships. */
|
|
82
|
+
used = new Map();
|
|
79
83
|
pending = new Map();
|
|
80
84
|
/** Bumped whenever a dictionary changes, so readers recompute. */
|
|
81
85
|
version = signal(0, /* @ts-ignore */
|
|
82
86
|
...(ngDevMode ? [{ debugName: "version" }] : /* istanbul ignore next */ []));
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
// Missing keys are noticed while templates render, where writing a signal
|
|
88
|
+
// is forbidden (NG0600) — so the set is plain and a tick signal is bumped
|
|
89
|
+
// in a microtask, once per batch of misses.
|
|
90
|
+
missing = new Set();
|
|
91
|
+
missingTick = signal(0, /* @ts-ignore */
|
|
92
|
+
...(ngDevMode ? [{ debugName: "missingTick" }] : /* istanbul ignore next */ []));
|
|
93
|
+
missingFlush = null;
|
|
85
94
|
/** The active language. Read it in a `computed()` to follow switches. */
|
|
86
95
|
lang = signal(this.config?.lang ?? 'en', /* @ts-ignore */
|
|
87
96
|
...(ngDevMode ? [{ debugName: "lang" }] : /* istanbul ignore next */ []));
|
|
@@ -94,7 +103,10 @@ class MkTranslate {
|
|
|
94
103
|
/** `lang` as an observable, for code still written around streams. */
|
|
95
104
|
langChange = toObservable(this.lang);
|
|
96
105
|
/** Keys asked for that neither the active nor the fallback language has. */
|
|
97
|
-
missingKeys = computed(() =>
|
|
106
|
+
missingKeys = computed(() => {
|
|
107
|
+
this.missingTick();
|
|
108
|
+
return [...this.missing].sort();
|
|
109
|
+
}, /* @ts-ignore */
|
|
98
110
|
...(ngDevMode ? [{ debugName: "missingKeys" }] : /* istanbul ignore next */ []));
|
|
99
111
|
constructor() {
|
|
100
112
|
if (!this.config && isDevMode()) {
|
|
@@ -136,21 +148,31 @@ class MkTranslate {
|
|
|
136
148
|
* when the strings are in memory; a failed load rejects and leaves the
|
|
137
149
|
* previous language active.
|
|
138
150
|
*/
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
151
|
+
use(lang) {
|
|
152
|
+
// A language already in memory switches synchronously — no microtask
|
|
153
|
+
// between the click and the re-render, and code that reads the service
|
|
154
|
+
// right after `use()` (tests, `computed()` setups) sees the new state.
|
|
155
|
+
const fallback = this.config?.fallbackLang;
|
|
156
|
+
const needsFallback = !!fallback && fallback !== lang && !this.dictionaries.has(fallback);
|
|
157
|
+
if (this.dictionaries.has(lang) && !needsFallback) {
|
|
158
|
+
this.activate(lang);
|
|
159
|
+
return Promise.resolve();
|
|
144
160
|
}
|
|
161
|
+
// The fallback rides along so that, once `use()` resolves, per-key
|
|
162
|
+
// fallback works too. Its failure never blocks the switch.
|
|
163
|
+
const fallbackLoad = needsFallback ? this.load(fallback).catch(() => undefined) : Promise.resolve();
|
|
164
|
+
return Promise.all([this.load(lang), fallbackLoad]).then(() => this.activate(lang));
|
|
165
|
+
}
|
|
166
|
+
activate(lang) {
|
|
145
167
|
this.lang.set(lang);
|
|
146
168
|
if (this.config?.documentLang !== false) {
|
|
147
169
|
this.document?.documentElement?.setAttribute('lang', lang);
|
|
148
170
|
}
|
|
149
171
|
}
|
|
150
172
|
/** Load a language into memory without switching to it. */
|
|
151
|
-
|
|
173
|
+
load(lang) {
|
|
152
174
|
if (this.dictionaries.has(lang))
|
|
153
|
-
return;
|
|
175
|
+
return Promise.resolve();
|
|
154
176
|
let task = this.pending.get(lang);
|
|
155
177
|
if (!task) {
|
|
156
178
|
task = this.fetch(lang).finally(() => this.pending.delete(lang));
|
|
@@ -168,9 +190,14 @@ class MkTranslate {
|
|
|
168
190
|
instant(key, params) {
|
|
169
191
|
const lang = this.lang();
|
|
170
192
|
this.version();
|
|
193
|
+
if (this.isServer)
|
|
194
|
+
this.noteUsed(lang, key);
|
|
171
195
|
const found = this.lookup(key, lang);
|
|
172
196
|
if (found === undefined) {
|
|
173
|
-
|
|
197
|
+
// A partial (hydration) dictionary is expected to lack keys until the
|
|
198
|
+
// background load merges — those are not misses.
|
|
199
|
+
if (!this.partial.has(lang))
|
|
200
|
+
this.recordMissing(key);
|
|
174
201
|
const replacement = this.config?.onMissing?.(key, lang);
|
|
175
202
|
return typeof replacement === 'string' ? replacement : key;
|
|
176
203
|
}
|
|
@@ -240,11 +267,26 @@ class MkTranslate {
|
|
|
240
267
|
}
|
|
241
268
|
async fetch(lang) {
|
|
242
269
|
const stateKey = makeStateKey(`mk-translate:${lang}`);
|
|
243
|
-
// Hydration: the server already loaded
|
|
270
|
+
// Hydration: the server already loaded (some of) this language.
|
|
244
271
|
if (!this.isServer && this.transfer?.hasKey(stateKey)) {
|
|
245
|
-
const
|
|
272
|
+
const payload = this.transfer.get(stateKey, { strings: {}, partial: false });
|
|
246
273
|
this.transfer.remove(stateKey);
|
|
247
|
-
this.dictionaries.set(lang,
|
|
274
|
+
this.dictionaries.set(lang, payload.strings);
|
|
275
|
+
if (payload.partial) {
|
|
276
|
+
// Enough to hydrate without a flash; the rest arrives in the
|
|
277
|
+
// background and merges under the same keys.
|
|
278
|
+
this.partial.add(lang);
|
|
279
|
+
void this.fetchFull(lang)
|
|
280
|
+
.then((flat) => {
|
|
281
|
+
this.dictionaries.set(lang, { ...flat, ...this.dictionaries.get(lang) });
|
|
282
|
+
this.partial.delete(lang);
|
|
283
|
+
this.bump();
|
|
284
|
+
})
|
|
285
|
+
.catch((err) => {
|
|
286
|
+
if (isDevMode())
|
|
287
|
+
console.warn(`[mk-translate] background load of "${lang}" failed`, err);
|
|
288
|
+
});
|
|
289
|
+
}
|
|
248
290
|
this.bump();
|
|
249
291
|
return;
|
|
250
292
|
}
|
|
@@ -253,6 +295,14 @@ class MkTranslate {
|
|
|
253
295
|
this.bump();
|
|
254
296
|
return;
|
|
255
297
|
}
|
|
298
|
+
const flat = await this.fetchFull(lang);
|
|
299
|
+
this.dictionaries.set(lang, flat);
|
|
300
|
+
if (this.isServer && this.transfer)
|
|
301
|
+
this.serialize(lang, stateKey, flat);
|
|
302
|
+
this.bump();
|
|
303
|
+
}
|
|
304
|
+
/** Base + overrides for `lang`, flattened. */
|
|
305
|
+
async fetchFull(lang) {
|
|
256
306
|
const base = mkFlattenTranslations(await this.baseLoader().load(lang));
|
|
257
307
|
let overrides = {};
|
|
258
308
|
const overridesLoader = this.overridesLoaderOrNull();
|
|
@@ -265,11 +315,46 @@ class MkTranslate {
|
|
|
265
315
|
console.warn(`[mk-translate] overrides for "${lang}" failed to load`, err);
|
|
266
316
|
}
|
|
267
317
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
this.
|
|
318
|
+
return { ...base, ...overrides };
|
|
319
|
+
}
|
|
320
|
+
/** Server: decide what rides to the browser (see `MkTranslateConfig.transfer`). */
|
|
321
|
+
serialize(lang, stateKey, flat) {
|
|
322
|
+
const mode = this.config?.transfer ?? 'used';
|
|
323
|
+
if (mode === 'none' || !this.transfer)
|
|
324
|
+
return;
|
|
325
|
+
if (mode === 'all') {
|
|
326
|
+
this.transfer.set(stateKey, { strings: flat, partial: false });
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// 'used': resolved lazily when the page serialises, after every
|
|
330
|
+
// template ran — only the keys this render actually read.
|
|
331
|
+
this.transfer.onSerialize(stateKey, () => {
|
|
332
|
+
const keys = this.used.get(lang);
|
|
333
|
+
const strings = {};
|
|
334
|
+
if (keys)
|
|
335
|
+
for (const k of keys)
|
|
336
|
+
if (flat[k] !== undefined)
|
|
337
|
+
strings[k] = flat[k];
|
|
338
|
+
return { strings, partial: true };
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
noteUsed(lang, key) {
|
|
342
|
+
let set = this.used.get(lang);
|
|
343
|
+
if (!set)
|
|
344
|
+
this.used.set(lang, (set = new Set()));
|
|
345
|
+
set.add(key);
|
|
346
|
+
const fallback = this.config?.fallbackLang;
|
|
347
|
+
if (fallback && fallback !== lang) {
|
|
348
|
+
let fb = this.used.get(fallback);
|
|
349
|
+
if (!fb)
|
|
350
|
+
this.used.set(fallback, (fb = new Set()));
|
|
351
|
+
fb.add(key);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/** `true` while `lang` holds only a server render's keys and the rest is still loading. */
|
|
355
|
+
isPartial(lang = this.lang()) {
|
|
356
|
+
this.version();
|
|
357
|
+
return this.partial.has(lang);
|
|
273
358
|
}
|
|
274
359
|
baseLoader() {
|
|
275
360
|
if (!this.loader) {
|
|
@@ -290,11 +375,13 @@ class MkTranslate {
|
|
|
290
375
|
this.version.update((v) => v + 1);
|
|
291
376
|
}
|
|
292
377
|
recordMissing(key) {
|
|
293
|
-
if (this.missing
|
|
378
|
+
if (this.missing.has(key))
|
|
294
379
|
return;
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
380
|
+
this.missing.add(key);
|
|
381
|
+
this.missingFlush ??= Promise.resolve().then(() => {
|
|
382
|
+
this.missingFlush = null;
|
|
383
|
+
this.missingTick.update((v) => v + 1);
|
|
384
|
+
});
|
|
298
385
|
}
|
|
299
386
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTranslate, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
300
387
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTranslate, providedIn: 'root' });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mk-kit-ui-translate.mjs","sources":["../../../projects/mk-kit/translate/translate.service.ts","../../../projects/mk-kit/translate/translate.pipe.ts","../../../projects/mk-kit/translate/translate.loaders.ts","../../../projects/mk-kit/translate/translate.providers.ts","../../../projects/mk-kit/translate/mk-kit-ui-translate.ts"],"sourcesContent":["import {\n computed,\n inject,\n Injectable,\n InjectionToken,\n Injector,\n isDevMode,\n makeStateKey,\n PLATFORM_ID,\n runInInjectionContext,\n signal,\n TransferState,\n} from '@angular/core';\nimport { DOCUMENT, isPlatformServer } from '@angular/common';\nimport { toObservable } from '@angular/core/rxjs-interop';\nimport type {\n MkFlatTranslations,\n MkTranslateConfig,\n MkTranslateLoader,\n MkTranslateParams,\n MkTranslationTree,\n} from './translate.types';\n\n/** Configuration token; set by {@link provideMkTranslate}. */\nexport const MK_TRANSLATE_CONFIG = new InjectionToken<MkTranslateConfig>('MK_TRANSLATE_CONFIG');\n\nconst PLACEHOLDER = /\\{\\{\\s*([\\w.-]+)\\s*\\}\\}/g;\n\n/** `{ a: { b: 'x' } }` → `{ 'a.b': 'x' }`; already-dotted keys pass through. */\nexport function mkFlattenTranslations(\n tree: MkTranslationTree | null | undefined,\n prefix = '',\n out: MkFlatTranslations = {},\n): MkFlatTranslations {\n if (!tree) return out;\n for (const [k, v] of Object.entries(tree)) {\n const key = prefix ? `${prefix}.${k}` : k;\n if (v && typeof v === 'object') mkFlattenTranslations(v, key, out);\n else if (typeof v === 'string') out[key] = v;\n }\n return out;\n}\n\n/** `{ 'a.b': 'x' }` → `{ a: { b: 'x' } }` — for editors that write files back. */\nexport function mkUnflattenTranslations(flat: MkFlatTranslations): MkTranslationTree {\n const tree: MkTranslationTree = {};\n for (const key of Object.keys(flat).sort()) {\n const parts = key.split('.');\n let node = tree;\n for (let i = 0; i < parts.length - 1; i++) {\n const next = node[parts[i]];\n if (!next || typeof next !== 'object') node[parts[i]] = {};\n node = node[parts[i]] as MkTranslationTree;\n }\n node[parts[parts.length - 1]] = flat[key];\n }\n return tree;\n}\n\n/** Replace `{{name}}` placeholders; unknown names are left in place. */\nexport function mkInterpolate(template: string, params?: MkTranslateParams): string {\n if (!params) return template;\n return template.replace(PLACEHOLDER, (match, name: string) => {\n const value = name.split('.').reduce<unknown>(\n (acc, part) => (acc && typeof acc === 'object' ? (acc as Record<string, unknown>)[part] : undefined),\n params,\n );\n return value === undefined || value === null ? match : String(value);\n });\n}\n\n/**\n * App translations as signals: a `lang` you switch with `use()`, `instant()`\n * for code, the `translate` pipe for templates, and `plural()` for CLDR\n * count forms. Dictionaries are the plain nested JSON you already have; an\n * optional overrides loader (a database of edits, say) is merged on top per\n * language. On the server the loaded strings ride to the browser through\n * `TransferState`, so hydration never refetches or flashes raw keys.\n *\n * ```ts\n * provideMkTranslate({\n * lang: 'pl',\n * fallbackLang: 'pl',\n * loader: mkHttpTranslateLoader({ prefix: '/assets/i18n/' }),\n * });\n * ```\n * ```html\n * {{ 'checkout.cart.total' | translate }}\n * {{ 'checkout.cart.freeDeliveryMissing' | translate: { amount: 12 } }}\n * ```\n */\n@Injectable({ providedIn: 'root' })\nexport class MkTranslate {\n private readonly config = inject(MK_TRANSLATE_CONFIG, { optional: true });\n private readonly injector = inject(Injector);\n private readonly transfer = inject(TransferState, { optional: true });\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n private readonly document = inject(DOCUMENT, { optional: true });\n\n private loader: MkTranslateLoader | null = null;\n private overridesLoader: MkTranslateLoader | null = null;\n private readonly dictionaries = new Map<string, MkFlatTranslations>();\n private readonly declared = new Set<string>();\n private readonly pending = new Map<string, Promise<void>>();\n /** Bumped whenever a dictionary changes, so readers recompute. */\n private readonly version = signal(0);\n private readonly missing = signal<ReadonlySet<string>>(new Set());\n\n /** The active language. Read it in a `computed()` to follow switches. */\n readonly lang = signal<string>(this.config?.lang ?? 'en');\n /** `true` once the active language's strings are in memory. */\n readonly ready = computed(() => {\n this.version();\n return this.dictionaries.has(this.lang());\n });\n /** `lang` as an observable, for code still written around streams. */\n readonly langChange = toObservable(this.lang);\n /** Keys asked for that neither the active nor the fallback language has. */\n readonly missingKeys = computed(() => [...this.missing()].sort());\n\n constructor() {\n if (!this.config && isDevMode()) {\n console.warn('[mk-translate] provideMkTranslate() is missing — every key renders as itself.');\n }\n }\n\n /** The active language as a plain string (for non-reactive call sites). */\n getCurrentLang(): string {\n return this.lang();\n }\n\n /** ngx-translate-compatible alias of {@link getCurrentLang}. */\n get currentLang(): string {\n return this.lang();\n }\n\n /** Languages known to the service: loaded ones plus any added with {@link addLangs}. */\n getLangs(): string[] {\n this.version();\n return [...new Set([...this.declared, ...this.dictionaries.keys()])];\n }\n\n /** Declare languages up front (a switcher's list); loading still happens on `use()`. */\n addLangs(langs: string[]): void {\n for (const lang of langs) this.declared.add(lang);\n this.bump();\n }\n\n /**\n * ngx-translate-compatible alias: `setTranslation(lang, strings, true)`\n * merges like {@link patch}, `false` (the default there) replaces like\n * {@link set}.\n */\n setTranslation(lang: string, strings: MkTranslationTree | MkFlatTranslations, shouldMerge = false): void {\n if (shouldMerge) this.patch(lang, strings);\n else this.set(lang, strings);\n }\n\n /**\n * Load `lang` (once — later calls are cached) and make it active. Resolves\n * when the strings are in memory; a failed load rejects and leaves the\n * previous language active.\n */\n async use(lang: string): Promise<void> {\n await this.load(lang);\n if (this.config?.fallbackLang && this.config.fallbackLang !== lang) {\n // Best effort: a missing fallback file must not block the switch.\n this.load(this.config.fallbackLang).catch(() => undefined);\n }\n this.lang.set(lang);\n if (this.config?.documentLang !== false) {\n this.document?.documentElement?.setAttribute('lang', lang);\n }\n }\n\n /** Load a language into memory without switching to it. */\n async load(lang: string): Promise<void> {\n if (this.dictionaries.has(lang)) return;\n let task = this.pending.get(lang);\n if (!task) {\n task = this.fetch(lang).finally(() => this.pending.delete(lang));\n this.pending.set(lang, task);\n }\n return task;\n }\n\n /**\n * Translate `key` in the active language, then the fallback; `{{name}}`\n * placeholders come from `params`. A missing key renders as the key\n * itself (or whatever `onMissing` returns) and is recorded in\n * {@link missingKeys}. Reactive: reading it inside a template or a\n * `computed()` re-runs on language switch and after `patch()`.\n */\n instant(key: string, params?: MkTranslateParams): string {\n const lang = this.lang();\n this.version();\n const found = this.lookup(key, lang);\n if (found === undefined) {\n this.recordMissing(key);\n const replacement = this.config?.onMissing?.(key, lang);\n return typeof replacement === 'string' ? replacement : key;\n }\n return mkInterpolate(found, params);\n }\n\n /** Whether `key` exists in `lang` (default: the active language) or its fallback. */\n has(key: string, lang = this.lang()): boolean {\n this.version();\n return this.lookup(key, lang) !== undefined;\n }\n\n /**\n * CLDR plural form: `keyBase.{zero|one|two|few|many|other}` picked with\n * `Intl.PluralRules` for the active language, `other` as the fallback,\n * interpolated with `{ count, ...params }`.\n *\n * ```json\n * { \"guests\": { \"one\": \"{{count}} osoba\", \"few\": \"{{count}} osoby\", \"many\": \"{{count}} osób\", \"other\": \"{{count}} osoby\" } }\n * ```\n */\n plural(keyBase: string, count: number, params?: MkTranslateParams): string {\n const n = Number(count) || 0;\n const lang = this.lang();\n let category: string = 'other';\n try {\n category = new Intl.PluralRules(lang).select(n);\n } catch {\n /* unknown locale → other */\n }\n const merged = { count: n, ...params };\n const key = `${keyBase}.${category}`;\n return this.has(key) ? this.instant(key, merged) : this.instant(`${keyBase}.other`, merged);\n }\n\n /** The flat dictionary of `lang` (default: active), `{}` before it loads. */\n translations(lang = this.lang()): MkFlatTranslations {\n this.version();\n return { ...(this.dictionaries.get(lang) ?? {}) };\n }\n\n /** Languages currently in memory. */\n loadedLangs(): string[] {\n this.version();\n return [...this.dictionaries.keys()];\n }\n\n /**\n * Merge strings into `lang` at runtime — a translation editor previewing\n * an edit, or a late-arriving overrides payload. Nested or flat.\n */\n patch(lang: string, strings: MkTranslationTree | MkFlatTranslations): void {\n const current = this.dictionaries.get(lang) ?? {};\n this.dictionaries.set(lang, { ...current, ...mkFlattenTranslations(strings as MkTranslationTree) });\n this.bump();\n }\n\n /** Replace `lang` entirely (tests, editors reloading from source). */\n set(lang: string, strings: MkTranslationTree | MkFlatTranslations): void {\n this.dictionaries.set(lang, mkFlattenTranslations(strings as MkTranslationTree));\n this.bump();\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private lookup(key: string, lang: string): string | undefined {\n const direct = this.dictionaries.get(lang)?.[key];\n if (direct !== undefined) return direct;\n const fallback = this.config?.fallbackLang;\n if (fallback && fallback !== lang) return this.dictionaries.get(fallback)?.[key];\n return undefined;\n }\n\n private async fetch(lang: string): Promise<void> {\n const stateKey = makeStateKey<MkFlatTranslations>(`mk-translate:${lang}`);\n // Hydration: the server already loaded and serialised this language.\n if (!this.isServer && this.transfer?.hasKey(stateKey)) {\n const flat = this.transfer.get(stateKey, {});\n this.transfer.remove(stateKey);\n this.dictionaries.set(lang, flat);\n this.bump();\n return;\n }\n if (!this.config) {\n this.dictionaries.set(lang, {});\n this.bump();\n return;\n }\n const base = mkFlattenTranslations(await this.baseLoader().load(lang));\n let overrides: MkFlatTranslations = {};\n const overridesLoader = this.overridesLoaderOrNull();\n if (overridesLoader) {\n try {\n overrides = mkFlattenTranslations(await overridesLoader.load(lang));\n } catch (err) {\n if (isDevMode()) console.warn(`[mk-translate] overrides for \"${lang}\" failed to load`, err);\n }\n }\n const flat = { ...base, ...overrides };\n this.dictionaries.set(lang, flat);\n if (this.isServer && this.transfer) this.transfer.set(stateKey, flat);\n this.bump();\n }\n\n private baseLoader(): MkTranslateLoader {\n if (!this.loader) {\n this.loader = runInInjectionContext(this.injector, () => this.config!.loader());\n }\n return this.loader;\n }\n\n private overridesLoaderOrNull(): MkTranslateLoader | null {\n const factory = this.config?.overrides;\n if (!factory) return null;\n if (!this.overridesLoader) {\n this.overridesLoader = runInInjectionContext(this.injector, factory);\n }\n return this.overridesLoader;\n }\n\n private bump(): void {\n this.version.update((v) => v + 1);\n }\n\n private recordMissing(key: string): void {\n if (this.missing().has(key)) return;\n const next = new Set(this.missing());\n next.add(key);\n this.missing.set(next);\n }\n}\n","import { inject, Pipe, type PipeTransform } from '@angular/core';\nimport { MkTranslate } from './translate.service';\nimport type { MkTranslateParams } from './translate.types';\n\n/**\n * `translate` — the key's string in the active language, with `{{name}}`\n * placeholders filled from `params`. Impure so a language switch (or a\n * `patch()`) re-renders every use; the signal reads inside `instant()` mark\n * the host view dirty, so this stays cheap under OnPush and zoneless.\n *\n * ```html\n * {{ 'menu.title' | translate }}\n * {{ 'cart.items' | translate: { count: 3 } }}\n * ```\n */\n@Pipe({ name: 'translate', pure: false })\nexport class MkTranslatePipe implements PipeTransform {\n private readonly translate = inject(MkTranslate);\n\n transform(key: string | null | undefined, params?: MkTranslateParams): string {\n if (!key) return '';\n return this.translate.instant(key, params);\n }\n}\n\n/**\n * `translatePlural` — the CLDR plural form under `keyBase` for a count\n * (see {@link MkTranslate.plural}), interpolated with `{ count, ...params }`.\n *\n * ```html\n * {{ guests | translatePlural: 'reservation.guests' }} <!-- 2 osoby / 5 osób -->\n * ```\n */\n@Pipe({ name: 'translatePlural', pure: false })\nexport class MkTranslatePluralPipe implements PipeTransform {\n private readonly translate = inject(MkTranslate);\n\n transform(count: number | string | null | undefined, keyBase: string, params?: MkTranslateParams): string {\n return this.translate.plural(keyBase, Number(count) || 0, params);\n }\n}\n\n/**\n * Everything a template needs, for `imports: [...MkTranslateImports]` — the\n * one-line replacement for an ngx-translate `TranslateModule` import.\n */\nexport const MkTranslateImports = [MkTranslatePipe, MkTranslatePluralPipe] as const;\n","import { inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\nimport type { MkTranslateLoaderFactory, MkTranslationTree } from './translate.types';\n\n/** Options for {@link mkHttpTranslateLoader}. */\nexport interface MkHttpTranslateLoaderOptions {\n /** URL prefix; the language code is appended. Default `/assets/i18n/`. */\n prefix?: string;\n /** URL suffix after the language code. Default `.json`. */\n suffix?: string;\n}\n\n/**\n * Fetch `<prefix><lang><suffix>` with `HttpClient` — the bundled JSON files\n * in `assets/i18n/`. Needs `provideHttpClient()`; SSR apps that read the\n * files from disk can supply their own {@link MkTranslateLoader} instead.\n */\nexport function mkHttpTranslateLoader(\n options: MkHttpTranslateLoaderOptions = {},\n): MkTranslateLoaderFactory {\n const { prefix = '/assets/i18n/', suffix = '.json' } = options;\n return () => {\n const http = inject(HttpClient);\n return {\n load: (lang: string) => firstValueFrom(http.get<MkTranslationTree>(`${prefix}${lang}${suffix}`)),\n };\n };\n}\n\n/**\n * Strings given up front, keyed by language — tests, storybooks, tiny apps.\n * A language not in the map resolves to `{}`.\n */\nexport function mkStaticTranslateLoader(\n byLang: Record<string, MkTranslationTree>,\n): MkTranslateLoaderFactory {\n return () => ({ load: (lang: string) => byLang[lang] ?? {} });\n}\n","import {\n type EnvironmentProviders,\n inject,\n makeEnvironmentProviders,\n provideAppInitializer,\n} from '@angular/core';\nimport { MK_TRANSLATE_CONFIG, MkTranslate } from './translate.service';\nimport type { MkTranslateConfig } from './translate.types';\n\n/**\n * Register app translations. By default the initial language is loaded\n * before the first render (`preload`), so no view ever shows raw keys and\n * nothing caches them.\n *\n * ```ts\n * providers: [\n * provideHttpClient(withFetch()),\n * provideMkTranslate({\n * lang: 'pl',\n * fallbackLang: 'pl',\n * loader: mkHttpTranslateLoader(),\n * overrides: () => inject(TranslationOverridesApi), // optional\n * }),\n * ]\n * ```\n */\nexport function provideMkTranslate(config: MkTranslateConfig): EnvironmentProviders {\n const providers: Parameters<typeof makeEnvironmentProviders>[0] = [\n { provide: MK_TRANSLATE_CONFIG, useValue: config },\n ];\n if (config.preload !== false) {\n providers.push(\n provideAppInitializer(() =>\n inject(MkTranslate)\n .use(config.lang)\n .catch((err: unknown) => {\n // A broken file must not blank the app — keys render as themselves.\n console.error(`[mk-translate] \"${config.lang}\" failed to load before first render`, err);\n }),\n ),\n );\n }\n return makeEnvironmentProviders(providers);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"mk-kit-ui-translate.mjs","sources":["../../../projects/mk-kit/translate/translate.service.ts","../../../projects/mk-kit/translate/translate.pipe.ts","../../../projects/mk-kit/translate/translate.loaders.ts","../../../projects/mk-kit/translate/translate.providers.ts","../../../projects/mk-kit/translate/mk-kit-ui-translate.ts"],"sourcesContent":["import {\n computed,\n inject,\n Injectable,\n InjectionToken,\n Injector,\n isDevMode,\n makeStateKey,\n PLATFORM_ID,\n runInInjectionContext,\n signal,\n TransferState,\n} from '@angular/core';\nimport { DOCUMENT, isPlatformServer } from '@angular/common';\nimport { toObservable } from '@angular/core/rxjs-interop';\nimport type {\n MkFlatTranslations,\n MkTranslateConfig,\n MkTranslateLoader,\n MkTranslateParams,\n MkTranslationTree,\n} from './translate.types';\n\n/** Configuration token; set by {@link provideMkTranslate}. */\nexport const MK_TRANSLATE_CONFIG = new InjectionToken<MkTranslateConfig>('MK_TRANSLATE_CONFIG');\n\nconst PLACEHOLDER = /\\{\\{\\s*([\\w.-]+)\\s*\\}\\}/g;\n\n/** What a server render hands to the browser for one language. */\ninterface MkTransferPayload {\n strings: MkFlatTranslations;\n /** Only the keys the render used — the rest is loading in the background. */\n partial: boolean;\n}\n\n/** `{ a: { b: 'x' } }` → `{ 'a.b': 'x' }`; already-dotted keys pass through. */\nexport function mkFlattenTranslations(\n tree: MkTranslationTree | null | undefined,\n prefix = '',\n out: MkFlatTranslations = {},\n): MkFlatTranslations {\n if (!tree) return out;\n for (const [k, v] of Object.entries(tree)) {\n const key = prefix ? `${prefix}.${k}` : k;\n if (v && typeof v === 'object') mkFlattenTranslations(v, key, out);\n else if (typeof v === 'string') out[key] = v;\n }\n return out;\n}\n\n/** `{ 'a.b': 'x' }` → `{ a: { b: 'x' } }` — for editors that write files back. */\nexport function mkUnflattenTranslations(flat: MkFlatTranslations): MkTranslationTree {\n const tree: MkTranslationTree = {};\n for (const key of Object.keys(flat).sort()) {\n const parts = key.split('.');\n let node = tree;\n for (let i = 0; i < parts.length - 1; i++) {\n const next = node[parts[i]];\n if (!next || typeof next !== 'object') node[parts[i]] = {};\n node = node[parts[i]] as MkTranslationTree;\n }\n node[parts[parts.length - 1]] = flat[key];\n }\n return tree;\n}\n\n/** Replace `{{name}}` placeholders; unknown names are left in place. */\nexport function mkInterpolate(template: string, params?: MkTranslateParams): string {\n if (!params) return template;\n return template.replace(PLACEHOLDER, (match, name: string) => {\n const value = name.split('.').reduce<unknown>(\n (acc, part) => (acc && typeof acc === 'object' ? (acc as Record<string, unknown>)[part] : undefined),\n params,\n );\n return value === undefined || value === null ? match : String(value);\n });\n}\n\n/**\n * App translations as signals: a `lang` you switch with `use()`, `instant()`\n * for code, the `translate` pipe for templates, and `plural()` for CLDR\n * count forms. Dictionaries are the plain nested JSON you already have; an\n * optional overrides loader (a database of edits, say) is merged on top per\n * language. On the server the loaded strings ride to the browser through\n * `TransferState`, so hydration never refetches or flashes raw keys.\n *\n * ```ts\n * provideMkTranslate({\n * lang: 'pl',\n * fallbackLang: 'pl',\n * loader: mkHttpTranslateLoader({ prefix: '/assets/i18n/' }),\n * });\n * ```\n * ```html\n * {{ 'checkout.cart.total' | translate }}\n * {{ 'checkout.cart.freeDeliveryMissing' | translate: { amount: 12 } }}\n * ```\n */\n@Injectable({ providedIn: 'root' })\nexport class MkTranslate {\n private readonly config = inject(MK_TRANSLATE_CONFIG, { optional: true });\n private readonly injector = inject(Injector);\n private readonly transfer = inject(TransferState, { optional: true });\n private readonly isServer = isPlatformServer(inject(PLATFORM_ID));\n private readonly document = inject(DOCUMENT, { optional: true });\n\n private loader: MkTranslateLoader | null = null;\n private overridesLoader: MkTranslateLoader | null = null;\n private readonly dictionaries = new Map<string, MkFlatTranslations>();\n private readonly declared = new Set<string>();\n /** Languages holding only the keys a server render used (see `transfer`). */\n private readonly partial = new Set<string>();\n /** Keys read on the server, per language — what `transfer: 'used'` ships. */\n private readonly used = new Map<string, Set<string>>();\n private readonly pending = new Map<string, Promise<void>>();\n /** Bumped whenever a dictionary changes, so readers recompute. */\n private readonly version = signal(0);\n // Missing keys are noticed while templates render, where writing a signal\n // is forbidden (NG0600) — so the set is plain and a tick signal is bumped\n // in a microtask, once per batch of misses.\n private readonly missing = new Set<string>();\n private readonly missingTick = signal(0);\n private missingFlush: Promise<void> | null = null;\n\n /** The active language. Read it in a `computed()` to follow switches. */\n readonly lang = signal<string>(this.config?.lang ?? 'en');\n /** `true` once the active language's strings are in memory. */\n readonly ready = computed(() => {\n this.version();\n return this.dictionaries.has(this.lang());\n });\n /** `lang` as an observable, for code still written around streams. */\n readonly langChange = toObservable(this.lang);\n /** Keys asked for that neither the active nor the fallback language has. */\n readonly missingKeys = computed(() => {\n this.missingTick();\n return [...this.missing].sort();\n });\n\n constructor() {\n if (!this.config && isDevMode()) {\n console.warn('[mk-translate] provideMkTranslate() is missing — every key renders as itself.');\n }\n }\n\n /** The active language as a plain string (for non-reactive call sites). */\n getCurrentLang(): string {\n return this.lang();\n }\n\n /** ngx-translate-compatible alias of {@link getCurrentLang}. */\n get currentLang(): string {\n return this.lang();\n }\n\n /** Languages known to the service: loaded ones plus any added with {@link addLangs}. */\n getLangs(): string[] {\n this.version();\n return [...new Set([...this.declared, ...this.dictionaries.keys()])];\n }\n\n /** Declare languages up front (a switcher's list); loading still happens on `use()`. */\n addLangs(langs: string[]): void {\n for (const lang of langs) this.declared.add(lang);\n this.bump();\n }\n\n /**\n * ngx-translate-compatible alias: `setTranslation(lang, strings, true)`\n * merges like {@link patch}, `false` (the default there) replaces like\n * {@link set}.\n */\n setTranslation(lang: string, strings: MkTranslationTree | MkFlatTranslations, shouldMerge = false): void {\n if (shouldMerge) this.patch(lang, strings);\n else this.set(lang, strings);\n }\n\n /**\n * Load `lang` (once — later calls are cached) and make it active. Resolves\n * when the strings are in memory; a failed load rejects and leaves the\n * previous language active.\n */\n use(lang: string): Promise<void> {\n // A language already in memory switches synchronously — no microtask\n // between the click and the re-render, and code that reads the service\n // right after `use()` (tests, `computed()` setups) sees the new state.\n const fallback = this.config?.fallbackLang;\n const needsFallback = !!fallback && fallback !== lang && !this.dictionaries.has(fallback);\n if (this.dictionaries.has(lang) && !needsFallback) {\n this.activate(lang);\n return Promise.resolve();\n }\n // The fallback rides along so that, once `use()` resolves, per-key\n // fallback works too. Its failure never blocks the switch.\n const fallbackLoad = needsFallback ? this.load(fallback!).catch(() => undefined) : Promise.resolve();\n return Promise.all([this.load(lang), fallbackLoad]).then(() => this.activate(lang));\n }\n\n private activate(lang: string): void {\n this.lang.set(lang);\n if (this.config?.documentLang !== false) {\n this.document?.documentElement?.setAttribute('lang', lang);\n }\n }\n\n /** Load a language into memory without switching to it. */\n load(lang: string): Promise<void> {\n if (this.dictionaries.has(lang)) return Promise.resolve();\n let task = this.pending.get(lang);\n if (!task) {\n task = this.fetch(lang).finally(() => this.pending.delete(lang));\n this.pending.set(lang, task);\n }\n return task;\n }\n\n /**\n * Translate `key` in the active language, then the fallback; `{{name}}`\n * placeholders come from `params`. A missing key renders as the key\n * itself (or whatever `onMissing` returns) and is recorded in\n * {@link missingKeys}. Reactive: reading it inside a template or a\n * `computed()` re-runs on language switch and after `patch()`.\n */\n instant(key: string, params?: MkTranslateParams): string {\n const lang = this.lang();\n this.version();\n if (this.isServer) this.noteUsed(lang, key);\n const found = this.lookup(key, lang);\n if (found === undefined) {\n // A partial (hydration) dictionary is expected to lack keys until the\n // background load merges — those are not misses.\n if (!this.partial.has(lang)) this.recordMissing(key);\n const replacement = this.config?.onMissing?.(key, lang);\n return typeof replacement === 'string' ? replacement : key;\n }\n return mkInterpolate(found, params);\n }\n\n /** Whether `key` exists in `lang` (default: the active language) or its fallback. */\n has(key: string, lang = this.lang()): boolean {\n this.version();\n return this.lookup(key, lang) !== undefined;\n }\n\n /**\n * CLDR plural form: `keyBase.{zero|one|two|few|many|other}` picked with\n * `Intl.PluralRules` for the active language, `other` as the fallback,\n * interpolated with `{ count, ...params }`.\n *\n * ```json\n * { \"guests\": { \"one\": \"{{count}} osoba\", \"few\": \"{{count}} osoby\", \"many\": \"{{count}} osób\", \"other\": \"{{count}} osoby\" } }\n * ```\n */\n plural(keyBase: string, count: number, params?: MkTranslateParams): string {\n const n = Number(count) || 0;\n const lang = this.lang();\n let category: string = 'other';\n try {\n category = new Intl.PluralRules(lang).select(n);\n } catch {\n /* unknown locale → other */\n }\n const merged = { count: n, ...params };\n const key = `${keyBase}.${category}`;\n return this.has(key) ? this.instant(key, merged) : this.instant(`${keyBase}.other`, merged);\n }\n\n /** The flat dictionary of `lang` (default: active), `{}` before it loads. */\n translations(lang = this.lang()): MkFlatTranslations {\n this.version();\n return { ...(this.dictionaries.get(lang) ?? {}) };\n }\n\n /** Languages currently in memory. */\n loadedLangs(): string[] {\n this.version();\n return [...this.dictionaries.keys()];\n }\n\n /**\n * Merge strings into `lang` at runtime — a translation editor previewing\n * an edit, or a late-arriving overrides payload. Nested or flat.\n */\n patch(lang: string, strings: MkTranslationTree | MkFlatTranslations): void {\n const current = this.dictionaries.get(lang) ?? {};\n this.dictionaries.set(lang, { ...current, ...mkFlattenTranslations(strings as MkTranslationTree) });\n this.bump();\n }\n\n /** Replace `lang` entirely (tests, editors reloading from source). */\n set(lang: string, strings: MkTranslationTree | MkFlatTranslations): void {\n this.dictionaries.set(lang, mkFlattenTranslations(strings as MkTranslationTree));\n this.bump();\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private lookup(key: string, lang: string): string | undefined {\n const direct = this.dictionaries.get(lang)?.[key];\n if (direct !== undefined) return direct;\n const fallback = this.config?.fallbackLang;\n if (fallback && fallback !== lang) return this.dictionaries.get(fallback)?.[key];\n return undefined;\n }\n\n private async fetch(lang: string): Promise<void> {\n const stateKey = makeStateKey<MkTransferPayload>(`mk-translate:${lang}`);\n // Hydration: the server already loaded (some of) this language.\n if (!this.isServer && this.transfer?.hasKey(stateKey)) {\n const payload = this.transfer.get(stateKey, { strings: {}, partial: false });\n this.transfer.remove(stateKey);\n this.dictionaries.set(lang, payload.strings);\n if (payload.partial) {\n // Enough to hydrate without a flash; the rest arrives in the\n // background and merges under the same keys.\n this.partial.add(lang);\n void this.fetchFull(lang)\n .then((flat) => {\n this.dictionaries.set(lang, { ...flat, ...this.dictionaries.get(lang) });\n this.partial.delete(lang);\n this.bump();\n })\n .catch((err) => {\n if (isDevMode()) console.warn(`[mk-translate] background load of \"${lang}\" failed`, err);\n });\n }\n this.bump();\n return;\n }\n if (!this.config) {\n this.dictionaries.set(lang, {});\n this.bump();\n return;\n }\n const flat = await this.fetchFull(lang);\n this.dictionaries.set(lang, flat);\n if (this.isServer && this.transfer) this.serialize(lang, stateKey, flat);\n this.bump();\n }\n\n /** Base + overrides for `lang`, flattened. */\n private async fetchFull(lang: string): Promise<MkFlatTranslations> {\n const base = mkFlattenTranslations(await this.baseLoader().load(lang));\n let overrides: MkFlatTranslations = {};\n const overridesLoader = this.overridesLoaderOrNull();\n if (overridesLoader) {\n try {\n overrides = mkFlattenTranslations(await overridesLoader.load(lang));\n } catch (err) {\n if (isDevMode()) console.warn(`[mk-translate] overrides for \"${lang}\" failed to load`, err);\n }\n }\n return { ...base, ...overrides };\n }\n\n /** Server: decide what rides to the browser (see `MkTranslateConfig.transfer`). */\n private serialize(lang: string, stateKey: ReturnType<typeof makeStateKey<MkTransferPayload>>, flat: MkFlatTranslations): void {\n const mode = this.config?.transfer ?? 'used';\n if (mode === 'none' || !this.transfer) return;\n if (mode === 'all') {\n this.transfer.set(stateKey, { strings: flat, partial: false });\n return;\n }\n // 'used': resolved lazily when the page serialises, after every\n // template ran — only the keys this render actually read.\n this.transfer.onSerialize(stateKey, () => {\n const keys = this.used.get(lang);\n const strings: MkFlatTranslations = {};\n if (keys) for (const k of keys) if (flat[k] !== undefined) strings[k] = flat[k];\n return { strings, partial: true };\n });\n }\n\n private noteUsed(lang: string, key: string): void {\n let set = this.used.get(lang);\n if (!set) this.used.set(lang, (set = new Set()));\n set.add(key);\n const fallback = this.config?.fallbackLang;\n if (fallback && fallback !== lang) {\n let fb = this.used.get(fallback);\n if (!fb) this.used.set(fallback, (fb = new Set()));\n fb.add(key);\n }\n }\n\n /** `true` while `lang` holds only a server render's keys and the rest is still loading. */\n isPartial(lang = this.lang()): boolean {\n this.version();\n return this.partial.has(lang);\n }\n\n private baseLoader(): MkTranslateLoader {\n if (!this.loader) {\n this.loader = runInInjectionContext(this.injector, () => this.config!.loader());\n }\n return this.loader;\n }\n\n private overridesLoaderOrNull(): MkTranslateLoader | null {\n const factory = this.config?.overrides;\n if (!factory) return null;\n if (!this.overridesLoader) {\n this.overridesLoader = runInInjectionContext(this.injector, factory);\n }\n return this.overridesLoader;\n }\n\n private bump(): void {\n this.version.update((v) => v + 1);\n }\n\n private recordMissing(key: string): void {\n if (this.missing.has(key)) return;\n this.missing.add(key);\n this.missingFlush ??= Promise.resolve().then(() => {\n this.missingFlush = null;\n this.missingTick.update((v) => v + 1);\n });\n }\n}\n","import { inject, Pipe, type PipeTransform } from '@angular/core';\nimport { MkTranslate } from './translate.service';\nimport type { MkTranslateParams } from './translate.types';\n\n/**\n * `translate` — the key's string in the active language, with `{{name}}`\n * placeholders filled from `params`. Impure so a language switch (or a\n * `patch()`) re-renders every use; the signal reads inside `instant()` mark\n * the host view dirty, so this stays cheap under OnPush and zoneless.\n *\n * ```html\n * {{ 'menu.title' | translate }}\n * {{ 'cart.items' | translate: { count: 3 } }}\n * ```\n */\n@Pipe({ name: 'translate', pure: false })\nexport class MkTranslatePipe implements PipeTransform {\n private readonly translate = inject(MkTranslate);\n\n transform(key: string | null | undefined, params?: MkTranslateParams): string {\n if (!key) return '';\n return this.translate.instant(key, params);\n }\n}\n\n/**\n * `translatePlural` — the CLDR plural form under `keyBase` for a count\n * (see {@link MkTranslate.plural}), interpolated with `{ count, ...params }`.\n *\n * ```html\n * {{ guests | translatePlural: 'reservation.guests' }} <!-- 2 osoby / 5 osób -->\n * ```\n */\n@Pipe({ name: 'translatePlural', pure: false })\nexport class MkTranslatePluralPipe implements PipeTransform {\n private readonly translate = inject(MkTranslate);\n\n transform(count: number | string | null | undefined, keyBase: string, params?: MkTranslateParams): string {\n return this.translate.plural(keyBase, Number(count) || 0, params);\n }\n}\n\n/**\n * Everything a template needs, for `imports: [...MkTranslateImports]` — the\n * one-line replacement for an ngx-translate `TranslateModule` import.\n */\nexport const MkTranslateImports = [MkTranslatePipe, MkTranslatePluralPipe] as const;\n","import { inject } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { firstValueFrom } from 'rxjs';\nimport type { MkTranslateLoaderFactory, MkTranslationTree } from './translate.types';\n\n/** Options for {@link mkHttpTranslateLoader}. */\nexport interface MkHttpTranslateLoaderOptions {\n /** URL prefix; the language code is appended. Default `/assets/i18n/`. */\n prefix?: string;\n /** URL suffix after the language code. Default `.json`. */\n suffix?: string;\n}\n\n/**\n * Fetch `<prefix><lang><suffix>` with `HttpClient` — the bundled JSON files\n * in `assets/i18n/`. Needs `provideHttpClient()`; SSR apps that read the\n * files from disk can supply their own {@link MkTranslateLoader} instead.\n */\nexport function mkHttpTranslateLoader(\n options: MkHttpTranslateLoaderOptions = {},\n): MkTranslateLoaderFactory {\n const { prefix = '/assets/i18n/', suffix = '.json' } = options;\n return () => {\n const http = inject(HttpClient);\n return {\n load: (lang: string) => firstValueFrom(http.get<MkTranslationTree>(`${prefix}${lang}${suffix}`)),\n };\n };\n}\n\n/**\n * Strings given up front, keyed by language — tests, storybooks, tiny apps.\n * A language not in the map resolves to `{}`.\n */\nexport function mkStaticTranslateLoader(\n byLang: Record<string, MkTranslationTree>,\n): MkTranslateLoaderFactory {\n return () => ({ load: (lang: string) => byLang[lang] ?? {} });\n}\n","import {\n type EnvironmentProviders,\n inject,\n makeEnvironmentProviders,\n provideAppInitializer,\n} from '@angular/core';\nimport { MK_TRANSLATE_CONFIG, MkTranslate } from './translate.service';\nimport type { MkTranslateConfig } from './translate.types';\n\n/**\n * Register app translations. By default the initial language is loaded\n * before the first render (`preload`), so no view ever shows raw keys and\n * nothing caches them.\n *\n * ```ts\n * providers: [\n * provideHttpClient(withFetch()),\n * provideMkTranslate({\n * lang: 'pl',\n * fallbackLang: 'pl',\n * loader: mkHttpTranslateLoader(),\n * overrides: () => inject(TranslationOverridesApi), // optional\n * }),\n * ]\n * ```\n */\nexport function provideMkTranslate(config: MkTranslateConfig): EnvironmentProviders {\n const providers: Parameters<typeof makeEnvironmentProviders>[0] = [\n { provide: MK_TRANSLATE_CONFIG, useValue: config },\n ];\n if (config.preload !== false) {\n providers.push(\n provideAppInitializer(() =>\n inject(MkTranslate)\n .use(config.lang)\n .catch((err: unknown) => {\n // A broken file must not blank the app — keys render as themselves.\n console.error(`[mk-translate] \"${config.lang}\" failed to load before first render`, err);\n }),\n ),\n );\n }\n return makeEnvironmentProviders(providers);\n}\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": "@mk-kit/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -193,6 +193,10 @@
|
|
|
193
193
|
"types": "./types/mk-kit-ui-translate.d.ts",
|
|
194
194
|
"default": "./fesm2022/mk-kit-ui-translate.mjs"
|
|
195
195
|
},
|
|
196
|
+
"./translate/editor": {
|
|
197
|
+
"types": "./types/mk-kit-ui-translate-editor.d.ts",
|
|
198
|
+
"default": "./fesm2022/mk-kit-ui-translate-editor.mjs"
|
|
199
|
+
},
|
|
196
200
|
"./translate/server": {
|
|
197
201
|
"types": "./types/mk-kit-ui-translate-server.d.ts",
|
|
198
202
|
"default": "./fesm2022/mk-kit-ui-translate-server.mjs"
|
|
@@ -771,6 +771,15 @@ interface MkI18nStrings {
|
|
|
771
771
|
noResults: string;
|
|
772
772
|
/** Empty data table / list. */
|
|
773
773
|
noData: string;
|
|
774
|
+
/** Translation editor (`@mk-kit/ui/translate/editor`). */
|
|
775
|
+
translationEditorSearch: string;
|
|
776
|
+
translationEditorAll: string;
|
|
777
|
+
translationEditorOverridden: string;
|
|
778
|
+
translationEditorMissing: string;
|
|
779
|
+
translationEditorKey: string;
|
|
780
|
+
translationEditorReset: string;
|
|
781
|
+
translationEditorExport: string;
|
|
782
|
+
translationEditorKeys: string;
|
|
774
783
|
/** Announced when a filterable list updates (autocomplete, multi-select, command palette). */
|
|
775
784
|
resultsCount: (count: number) => string;
|
|
776
785
|
/** Pagination: previous page control. */
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import * as _mk_kit_ui_core from '@mk-kit/ui/core';
|
|
3
|
+
import { MkTableColumn } from '@mk-kit/ui/table';
|
|
4
|
+
import { MkFlatTranslations } from '@mk-kit/ui/translate';
|
|
5
|
+
|
|
6
|
+
/** One edit made in the editor. `value: null` clears the override. */
|
|
7
|
+
interface MkTranslationChange {
|
|
8
|
+
locale: string;
|
|
9
|
+
key: string;
|
|
10
|
+
value: string | null;
|
|
11
|
+
/** The text shown before the edit (override, or the base string). */
|
|
12
|
+
previous: string | null;
|
|
13
|
+
}
|
|
14
|
+
/** A row of the editor: the key plus the effective text per locale. */
|
|
15
|
+
interface MkTranslationRow {
|
|
16
|
+
key: string;
|
|
17
|
+
[locale: string]: string;
|
|
18
|
+
}
|
|
19
|
+
type Filter = 'all' | 'overridden' | `missing:${string}`;
|
|
20
|
+
/**
|
|
21
|
+
* Translation editor — keys as rows, locales as columns, click a cell to
|
|
22
|
+
* edit. The base strings are your bundled files; edits are **overrides**
|
|
23
|
+
* kept apart from them, so a rebuild never loses them and a cell can be
|
|
24
|
+
* restored to the file text. Search by key or text, filter to the keys a
|
|
25
|
+
* locale is missing or the ones already edited, export the grid as CSV.
|
|
26
|
+
* Persistence is yours: every edit is emitted as `changed`, feed the result
|
|
27
|
+
* back through `overrides`.
|
|
28
|
+
*
|
|
29
|
+
* ```html
|
|
30
|
+
* <mk-translation-editor
|
|
31
|
+
* [locales]="['pl', 'en', 'ru']"
|
|
32
|
+
* [base]="base()"
|
|
33
|
+
* [overrides]="overrides()"
|
|
34
|
+
* (changed)="save($event)" />
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare class MkTranslationEditor {
|
|
38
|
+
protected readonly i18n: _mk_kit_ui_core.MkI18nStrings;
|
|
39
|
+
/** Locale codes, in column order. The first one is the reference language. */
|
|
40
|
+
readonly locales: _angular_core.InputSignal<string[]>;
|
|
41
|
+
/** Bundled strings per locale, flat (`'a.b.c': 'text'`) or nested. */
|
|
42
|
+
readonly base: _angular_core.InputSignal<Record<string, MkFlatTranslations>>;
|
|
43
|
+
/** Edits per locale, flat. Missing locales are fine. */
|
|
44
|
+
readonly overrides: _angular_core.InputSignal<Record<string, MkFlatTranslations>>;
|
|
45
|
+
/** Show but do not edit. */
|
|
46
|
+
readonly readonly: _angular_core.InputSignal<boolean>;
|
|
47
|
+
/** File name of the CSV export. */
|
|
48
|
+
readonly exportFilename: _angular_core.InputSignal<string>;
|
|
49
|
+
/** An edit or a restore; persist it and update `overrides`. */
|
|
50
|
+
readonly changed: _angular_core.OutputEmitterRef<MkTranslationChange>;
|
|
51
|
+
protected readonly query: _angular_core.WritableSignal<string>;
|
|
52
|
+
protected readonly filter: _angular_core.WritableSignal<Filter>;
|
|
53
|
+
/** Every key any locale knows, sorted. */
|
|
54
|
+
protected readonly keys: _angular_core.Signal<string[]>;
|
|
55
|
+
/** Per-locale counts shown as filter chips. */
|
|
56
|
+
protected readonly stats: _angular_core.Signal<{
|
|
57
|
+
locale: string;
|
|
58
|
+
missing: number;
|
|
59
|
+
overridden: number;
|
|
60
|
+
}[]>;
|
|
61
|
+
protected readonly overriddenTotal: _angular_core.Signal<number>;
|
|
62
|
+
protected readonly rows: _angular_core.Signal<MkTranslationRow[]>;
|
|
63
|
+
protected readonly columns: _angular_core.Signal<MkTableColumn<MkTranslationRow>[]>;
|
|
64
|
+
protected effective(locale: string, key: string): string | null;
|
|
65
|
+
protected hasOverride(locale: string, key: string): boolean;
|
|
66
|
+
protected isMissing(locale: string, key: string): boolean;
|
|
67
|
+
protected onSaved(locale: string, key: string, value: string): void;
|
|
68
|
+
protected reset(locale: string, key: string): void;
|
|
69
|
+
protected setFilter(next: string): void;
|
|
70
|
+
protected exportCsv(): void;
|
|
71
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkTranslationEditor, never>;
|
|
72
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkTranslationEditor, "mk-translation-editor", never, { "locales": { "alias": "locales"; "required": true; "isSignal": true; }; "base": { "alias": "base"; "required": true; "isSignal": true; }; "overrides": { "alias": "overrides"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "exportFilename": { "alias": "exportFilename"; "required": false; "isSignal": true; }; }, { "changed": "changed"; }, never, never, true, never>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { MkTranslationEditor };
|
|
76
|
+
export type { MkTranslationChange, MkTranslationRow };
|
|
@@ -44,6 +44,16 @@ interface MkTranslateConfig {
|
|
|
44
44
|
* `computed()` never cache them. Default `true`.
|
|
45
45
|
*/
|
|
46
46
|
preload?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* What a server render hands to the browser through `TransferState`:
|
|
49
|
+
* - `'used'` (default): only the keys read while rendering this page —
|
|
50
|
+
* a few KB — so hydration never flashes raw keys, while the full
|
|
51
|
+
* dictionary loads in the background right after;
|
|
52
|
+
* - `'all'`: the whole dictionary (no second request, but every page
|
|
53
|
+
* carries it);
|
|
54
|
+
* - `'none'`: nothing; the browser loads the file itself before bootstrap.
|
|
55
|
+
*/
|
|
56
|
+
transfer?: 'used' | 'all' | 'none';
|
|
47
57
|
/**
|
|
48
58
|
* Mirror the active language onto `<html lang>` (server and browser), so
|
|
49
59
|
* screen readers, hyphenation and search engines follow `use()`. Default
|
|
@@ -96,10 +106,16 @@ declare class MkTranslate {
|
|
|
96
106
|
private overridesLoader;
|
|
97
107
|
private readonly dictionaries;
|
|
98
108
|
private readonly declared;
|
|
109
|
+
/** Languages holding only the keys a server render used (see `transfer`). */
|
|
110
|
+
private readonly partial;
|
|
111
|
+
/** Keys read on the server, per language — what `transfer: 'used'` ships. */
|
|
112
|
+
private readonly used;
|
|
99
113
|
private readonly pending;
|
|
100
114
|
/** Bumped whenever a dictionary changes, so readers recompute. */
|
|
101
115
|
private readonly version;
|
|
102
116
|
private readonly missing;
|
|
117
|
+
private readonly missingTick;
|
|
118
|
+
private missingFlush;
|
|
103
119
|
/** The active language. Read it in a `computed()` to follow switches. */
|
|
104
120
|
readonly lang: i0.WritableSignal<string>;
|
|
105
121
|
/** `true` once the active language's strings are in memory. */
|
|
@@ -129,6 +145,7 @@ declare class MkTranslate {
|
|
|
129
145
|
* previous language active.
|
|
130
146
|
*/
|
|
131
147
|
use(lang: string): Promise<void>;
|
|
148
|
+
private activate;
|
|
132
149
|
/** Load a language into memory without switching to it. */
|
|
133
150
|
load(lang: string): Promise<void>;
|
|
134
151
|
/**
|
|
@@ -164,6 +181,13 @@ declare class MkTranslate {
|
|
|
164
181
|
set(lang: string, strings: MkTranslationTree | MkFlatTranslations): void;
|
|
165
182
|
private lookup;
|
|
166
183
|
private fetch;
|
|
184
|
+
/** Base + overrides for `lang`, flattened. */
|
|
185
|
+
private fetchFull;
|
|
186
|
+
/** Server: decide what rides to the browser (see `MkTranslateConfig.transfer`). */
|
|
187
|
+
private serialize;
|
|
188
|
+
private noteUsed;
|
|
189
|
+
/** `true` while `lang` holds only a server render's keys and the rest is still loading. */
|
|
190
|
+
isPartial(lang?: string): boolean;
|
|
167
191
|
private baseLoader;
|
|
168
192
|
private overridesLoaderOrNull;
|
|
169
193
|
private bump;
|