@feugene/fint-i18n 0.2.3 → 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fureev Eugene
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,384 @@
1
+ import { n as e, t } from "./loader-registry-B_fUe8pM.js";
2
+ import { computed as n, isRef as r, reactive as i, readonly as a, ref as o } from "vue";
3
+ //#region src/core/compiler.ts
4
+ function s(e) {
5
+ if (!e.includes("{") && !e.includes("}}")) return () => e;
6
+ let t = [], n = 0, r = !1, i = /\{\{|\}\}|\{([\w.-]+)\}/g, a = i.exec(e);
7
+ for (; a;) a.index > n && t.push(e.slice(n, a.index)), a[0] === "{{" ? t.push("{") : a[0] === "}}" ? t.push("}") : (t.push({
8
+ key: a[1],
9
+ fallback: a[0]
10
+ }), r = !0), n = a.index + a[0].length, a = i.exec(e);
11
+ if (n < e.length && t.push(e.slice(n)), !r) {
12
+ let e = t.join("");
13
+ return () => e;
14
+ }
15
+ return (e) => {
16
+ let n = "";
17
+ for (let r of t) if (typeof r == "string") n += r;
18
+ else {
19
+ let t = e?.[r.key];
20
+ n += t == null ? r.fallback : String(t);
21
+ }
22
+ return n;
23
+ };
24
+ }
25
+ //#endregion
26
+ //#region src/core/hooks.ts
27
+ var c = class {
28
+ hooks = /* @__PURE__ */ new Map();
29
+ on(e, t) {
30
+ let n = this.hooks.get(e);
31
+ return n ? n.push(t) : this.hooks.set(e, [t]), () => this.off(e, t);
32
+ }
33
+ has(e) {
34
+ let t = this.hooks.get(e);
35
+ return !!t && t.length > 0;
36
+ }
37
+ off(e, t) {
38
+ let n = this.hooks.get(e);
39
+ if (n) {
40
+ let r = n.indexOf(t);
41
+ r !== -1 && (n.splice(r, 1), n.length === 0 && this.hooks.delete(e));
42
+ }
43
+ }
44
+ emit(e, t) {
45
+ let n = this.hooks.get(e);
46
+ if (!n || n.length === 0) return Promise.resolve(t);
47
+ let r = t, i = n.slice();
48
+ return (async () => {
49
+ for (let e of i) {
50
+ let t = await e(r);
51
+ t !== void 0 && (r = t);
52
+ }
53
+ return r;
54
+ })();
55
+ }
56
+ emitSync(e, t) {
57
+ let n = this.hooks.get(e);
58
+ if (!n || n.length === 0) return t;
59
+ let r = t, i = n.slice();
60
+ for (let t of i) {
61
+ let n = t(r);
62
+ if (n instanceof Promise) {
63
+ console.warn(`[fint-i18n] Sync hook "${e}" received a promise from handler. This will be ignored in sync execution.`);
64
+ continue;
65
+ }
66
+ n !== void 0 && (r = n);
67
+ }
68
+ return r;
69
+ }
70
+ };
71
+ //#endregion
72
+ //#region src/core/message-utils.ts
73
+ function l(e) {
74
+ return !!e && typeof e == "object" && !Array.isArray(e);
75
+ }
76
+ function u(e, t) {
77
+ for (let n in t) l(t[n]) ? (l(e[n]) || (e[n] = {}), u(e[n], t[n])) : e[n] = t[n];
78
+ }
79
+ function d(e, t) {
80
+ if (l(e) && l(t)) {
81
+ let n = i({});
82
+ return u(n, e), u(n, t), n;
83
+ }
84
+ return t;
85
+ }
86
+ function f(e, t) {
87
+ let n = e, r = 0;
88
+ for (; n && typeof n == "object";) {
89
+ let e = t.indexOf(".", r), i = e === -1 ? t.slice(r) : t.slice(r, e);
90
+ if (n = n[i], n === void 0 || e === -1) return n;
91
+ r = e + 1;
92
+ }
93
+ }
94
+ //#endregion
95
+ //#region src/core/translate-params.ts
96
+ function p(e) {
97
+ if (!e) return;
98
+ let t;
99
+ for (let n in e) {
100
+ let i = e[n];
101
+ r(i) && (t ||= { ...e }, t[n] = i.value);
102
+ }
103
+ return t || e;
104
+ }
105
+ //#endregion
106
+ //#region src/core/instance.ts
107
+ var m = class {
108
+ locale;
109
+ fallbackLocale;
110
+ messagesStore = i({});
111
+ messages = a(this.messagesStore);
112
+ localeRef;
113
+ preloadFallback;
114
+ unloadUnusedBlocks;
115
+ compiledMessages = Object.create(null);
116
+ loaderRegistry;
117
+ loadingBlocks = /* @__PURE__ */ new Map();
118
+ loadedBlocks = /* @__PURE__ */ new Map();
119
+ blockUsageCounters = /* @__PURE__ */ new Map();
120
+ patternExpansionCache = /* @__PURE__ */ new Map();
121
+ pendingUsedBlockLoads = /* @__PURE__ */ new Map();
122
+ localeEpoch = 0;
123
+ missingKeyReported = /* @__PURE__ */ new Set();
124
+ localeSetterWarned = !1;
125
+ installedPlugins = [];
126
+ hooks = new c();
127
+ constructor(e) {
128
+ this.localeRef = o(e.locale), this.locale = n({
129
+ get: () => this.localeRef.value,
130
+ set: (e) => {
131
+ this.localeSetterWarned || (this.localeSetterWarned = !0, console.warn("[fint-i18n] Direct assignment to `locale.value` is deprecated, use `setLocale()` instead")), this.setLocale(e);
132
+ }
133
+ }), this.fallbackLocale = e.fallbackLocale || "", this.preloadFallback = e.preloadFallback ?? !1, this.unloadUnusedBlocks = e.unloadUnusedBlocks ?? !1, this.loaderRegistry = new t(e.loaders), e.plugins && e.plugins.forEach((e) => {
134
+ e.install(this), this.installedPlugins.push(e);
135
+ }), this.hooks.emitSync("afterInit", void 0);
136
+ }
137
+ dispose = () => {
138
+ for (let e of this.installedPlugins) e.uninstall?.(this);
139
+ this.installedPlugins.length = 0;
140
+ };
141
+ addLoaders = (e) => {
142
+ this.loaderRegistry.add(e), this.patternExpansionCache.clear();
143
+ };
144
+ getKnownLocales = () => this.loaderRegistry.getKnownLocales();
145
+ t = (e, t, n) => {
146
+ let r = this.localeRef.value, i = p(t), a = this.resolve(r, e, i);
147
+ if (a === void 0) {
148
+ let t = n?.fallbackLocale || this.fallbackLocale;
149
+ t && t !== r && (a = this.resolve(t, e, i));
150
+ }
151
+ let o = this.hooks.emitSync("onTranslate", {
152
+ key: e,
153
+ params: i,
154
+ result: a
155
+ });
156
+ return o.result === void 0 ? (this.reportMissingKey(e, r), e) : o.result;
157
+ };
158
+ reportMissingKey = (e, t) => {
159
+ let n = `${t}:${e}`;
160
+ this.missingKeyReported.has(n) || (this.missingKeyReported.add(n), this.hooks.emit("onMissingKey", {
161
+ key: e,
162
+ locale: t
163
+ }).catch((e) => {
164
+ console.error("[fint-i18n] Error in onMissingKey hook:", e);
165
+ }));
166
+ };
167
+ reportError = (e, t = {}) => {
168
+ this.hooks.has("onError") ? this.hooks.emit("onError", {
169
+ error: e,
170
+ ...t
171
+ }).catch((e) => {
172
+ console.error("[fint-i18n] Error in onError hook:", e);
173
+ }) : console.error("[fint-i18n] Unhandled error:", e, t);
174
+ };
175
+ resolve = (e, t, n) => {
176
+ let r = this.compiledMessages[e]?.[t];
177
+ if (r) return r(n);
178
+ let i = this.messagesStore[e];
179
+ if (!i) return;
180
+ let a = f(i, t);
181
+ if (typeof a == "string") {
182
+ let r = s(a);
183
+ return this.setCompiled(e, t, r), r(n);
184
+ }
185
+ if (typeof a == "function") {
186
+ let r = a;
187
+ return this.setCompiled(e, t, r), r(n);
188
+ }
189
+ if (a != null && typeof a != "object") return String(a);
190
+ };
191
+ setCompiled = (e, t, n) => {
192
+ this.compiledMessages[e] || (this.compiledMessages[e] = Object.create(null)), this.compiledMessages[e][t] = n;
193
+ };
194
+ invalidateCompiled = (e, t) => {
195
+ let n = this.compiledMessages[e];
196
+ if (!n) return;
197
+ let r = `${t}.`;
198
+ for (let e in n) (e === t || e.startsWith(r)) && delete n[e];
199
+ };
200
+ expandPattern = (e) => {
201
+ let t = this.patternExpansionCache.get(e);
202
+ if (t) return t;
203
+ let n = this.loaderRegistry.expandPattern(e);
204
+ return this.patternExpansionCache.set(e, n), n;
205
+ };
206
+ loadBlock = async (t, n) => {
207
+ let r = n || this.localeRef.value;
208
+ if (e(t)) {
209
+ let e = this.expandPattern(t);
210
+ if (e.length === 0) {
211
+ console.warn(`[fint-i18n] Pattern "${t}" did not match any registered block (locale "${r}")`);
212
+ return;
213
+ }
214
+ await Promise.all(e.map((e) => this.loadBlock(e, r)));
215
+ return;
216
+ }
217
+ let i = [this.loadConcreteBlock(t, r)];
218
+ this.preloadFallback && this.fallbackLocale && this.fallbackLocale !== r && i.push(this.loadConcreteBlock(t, this.fallbackLocale)), await Promise.all(i);
219
+ };
220
+ loadConcreteBlock = async (e, t) => {
221
+ let n = `${t}:${e}`;
222
+ if (this.isBlockLoaded(e, t)) return;
223
+ if (this.loadingBlocks.has(n)) return this.loadingBlocks.get(n);
224
+ await this.hooks.emit("beforeLoadBlock", e);
225
+ let r = (async () => {
226
+ try {
227
+ let n = this.loaderRegistry.resolve(t, e);
228
+ if (!n) {
229
+ console.warn(`[fint-i18n] No loader for block "${e}" in locale "${t}"`);
230
+ return;
231
+ }
232
+ let r;
233
+ for (let e of n.loaders) {
234
+ let i = await e(), a = i && typeof i == "object" && "default" in i && i.default ? i.default : i;
235
+ this.mergeMessages(t, n.resolvedBlockName, a), r = r === void 0 ? a : d(r, a);
236
+ }
237
+ this.markBlockLoaded(n.resolvedBlockName, t), await this.hooks.emit("afterLoadBlock", {
238
+ block: n.resolvedBlockName,
239
+ locale: t,
240
+ messages: r
241
+ });
242
+ } finally {
243
+ this.loadingBlocks.delete(n);
244
+ }
245
+ })();
246
+ return this.loadingBlocks.set(n, r), r;
247
+ };
248
+ mergeMessages = (e, t, n) => {
249
+ this.invalidateCompiled(e, t), this.messagesStore[e] || (this.messagesStore[e] = i({}));
250
+ let r = t.split("."), a = r[0];
251
+ if (r.length === 1) l(n) ? ((!this.messagesStore[e][a] || typeof this.messagesStore[e][a] != "object") && (this.messagesStore[e][a] = i({})), u(this.messagesStore[e][a], n)) : this.messagesStore[e][a] = n;
252
+ else {
253
+ (!this.messagesStore[e][a] || typeof this.messagesStore[e][a] != "object") && (this.messagesStore[e][a] = i({}));
254
+ let t = this.messagesStore[e][a];
255
+ for (let e = 1; e < r.length; e++) {
256
+ let n = r[e];
257
+ (!t[n] || typeof t[n] != "object") && (t[n] = i({})), t = t[n];
258
+ }
259
+ u(t, n);
260
+ }
261
+ this.precompileBlock(e, t, n);
262
+ };
263
+ precompileBlock = (e, t, n) => {
264
+ if (typeof n == "string") {
265
+ let r = s(n);
266
+ this.setCompiled(e, t, r);
267
+ return;
268
+ }
269
+ if (typeof n == "function") {
270
+ this.setCompiled(e, t, n);
271
+ return;
272
+ }
273
+ if (l(n)) for (let r in n) {
274
+ let i = n[r], a = `${t}.${r}`;
275
+ if (typeof i == "string") {
276
+ let t = s(i);
277
+ this.setCompiled(e, a, t);
278
+ } else typeof i == "function" ? this.setCompiled(e, a, i) : l(i) && this.precompileBlock(e, a, i);
279
+ }
280
+ };
281
+ isBlockLoaded = (e, t) => {
282
+ let n = t || this.localeRef.value, r = this.loadedBlocks.get(n);
283
+ if (!r) return !1;
284
+ if (r.has(e)) return !0;
285
+ let i = e.split("."), a = "";
286
+ for (let e = 0; e < i.length - 1; e++) if (a = a ? `${a}.${i[e]}` : i[e], r.has(a)) return !0;
287
+ return !1;
288
+ };
289
+ markBlockLoaded = (e, t) => {
290
+ this.loadedBlocks.has(t) || this.loadedBlocks.set(t, /* @__PURE__ */ new Set()), this.loadedBlocks.get(t).add(e), this.missingKeyReported.clear();
291
+ };
292
+ loadUsedBlocks = async (e) => {
293
+ let t = "";
294
+ for (;;) {
295
+ let n = this.pendingUsedBlockLoads.get(e);
296
+ if (n) {
297
+ await n;
298
+ continue;
299
+ }
300
+ let r = [];
301
+ for (let [t, n] of this.blockUsageCounters.entries()) n > 0 && !this.isBlockLoaded(t, e) && r.push(t);
302
+ if (r.length === 0) return;
303
+ let i = r.join(",");
304
+ if (i === t) return;
305
+ t = i;
306
+ let a = (async () => {
307
+ (await Promise.allSettled(r.map((t) => this.loadBlock(t, e)))).forEach((t, n) => {
308
+ t.status === "rejected" && this.reportError(t.reason, {
309
+ block: r[n],
310
+ locale: e
311
+ });
312
+ });
313
+ })();
314
+ this.pendingUsedBlockLoads.set(e, a);
315
+ try {
316
+ await a;
317
+ } finally {
318
+ this.pendingUsedBlockLoads.delete(e);
319
+ }
320
+ }
321
+ };
322
+ hasUnloadedUsedBlocks = (e) => {
323
+ if (this.pendingUsedBlockLoads.has(e)) return !0;
324
+ for (let [t, n] of this.blockUsageCounters.entries()) if (n > 0 && !this.isBlockLoaded(t, e)) return !0;
325
+ return !1;
326
+ };
327
+ setLocale = async (e) => {
328
+ let t = this.localeRef.value;
329
+ if (t === e) return;
330
+ let n = ++this.localeEpoch;
331
+ this.hasUnloadedUsedBlocks(e) && (await this.loadUsedBlocks(e), n !== this.localeEpoch) || (this.localeRef.value = e, this.missingKeyReported.clear(), this.hooks.emitSync("onLocaleChange", {
332
+ locale: e,
333
+ previous: t
334
+ }));
335
+ };
336
+ registerUsage = (t) => {
337
+ if (e(t)) {
338
+ let e = this.expandPattern(t);
339
+ if (e.length === 0) {
340
+ console.warn(`[fint-i18n] Pattern "${t}" did not match any registered block`);
341
+ return;
342
+ }
343
+ for (let t = 0; t < e.length; t++) this.incrementUsage(e[t]);
344
+ return;
345
+ }
346
+ this.incrementUsage(t);
347
+ };
348
+ registerBlocks = (e) => {
349
+ for (let t = 0; t < e.length; t++) this.registerUsage(e[t]);
350
+ };
351
+ unregisterUsage = (t) => {
352
+ if (e(t)) {
353
+ let e = this.expandPattern(t);
354
+ for (let t = 0; t < e.length; t++) this.decrementUsage(e[t]);
355
+ return;
356
+ }
357
+ this.decrementUsage(t);
358
+ };
359
+ incrementUsage = (e) => {
360
+ let t = this.blockUsageCounters.get(e) || 0;
361
+ this.blockUsageCounters.set(e, t + 1);
362
+ };
363
+ decrementUsage = (e) => {
364
+ let t = this.blockUsageCounters.get(e) || 0;
365
+ t <= 1 ? (this.blockUsageCounters.delete(e), this.unloadUnusedBlocks && this.unloadBlockAllLocales(e)) : this.blockUsageCounters.set(e, t - 1);
366
+ };
367
+ unloadBlock = (e, t) => {
368
+ let n = t || this.localeRef.value, r = this.messagesStore[n];
369
+ if (r) {
370
+ let t = e.split("."), n = r;
371
+ for (let e = 0; e < t.length - 1 && n; e++) n = typeof n == "object" ? n[t[e]] : void 0;
372
+ n && typeof n == "object" && delete n[t[t.length - 1]];
373
+ }
374
+ this.invalidateCompiled(n, e), this.loadedBlocks.get(n)?.delete(e);
375
+ };
376
+ unloadBlockAllLocales = (e) => {
377
+ for (let t of this.loadedBlocks.keys()) this.unloadBlock(e, t);
378
+ };
379
+ };
380
+ function h(e) {
381
+ return new m(e);
382
+ }
383
+ //#endregion
384
+ export { s as i, h as n, c as r, m as t };
@@ -0,0 +1,103 @@
1
+ //#region src/core/loader-registry.ts
2
+ var e = ".**", t = ".*";
3
+ function n(n) {
4
+ return n.endsWith(t) || n.endsWith(e);
5
+ }
6
+ var r = class {
7
+ loaders;
8
+ knownBlockNames;
9
+ constructor(e) {
10
+ this.loaders = this.normalize(e), this.knownBlockNames = this.collectKnownBlockNames(this.loaders);
11
+ }
12
+ add(e) {
13
+ let t = Array.isArray(e) ? e : [e];
14
+ for (let e of t) this.mergeCollection(this.loaders, e);
15
+ this.knownBlockNames = this.collectKnownBlockNames(this.loaders);
16
+ }
17
+ getKnownBlockNames() {
18
+ return this.knownBlockNames;
19
+ }
20
+ getKnownLocales() {
21
+ return Object.keys(this.loaders);
22
+ }
23
+ expandPattern(n) {
24
+ let r = n.endsWith(e), i = !r && n.endsWith(t);
25
+ if (!r && !i) return [];
26
+ let a = n.slice(0, -(r ? 3 : 2));
27
+ if (!a) return [];
28
+ let o = `${a}.`, s = o.length, c = [];
29
+ for (let e = 0; e < this.knownBlockNames.length; e++) {
30
+ let t = this.knownBlockNames[e];
31
+ if (t.length <= s) continue;
32
+ let n = !0;
33
+ for (let e = 0; e < s; e++) if (t.charCodeAt(e) !== o.charCodeAt(e)) {
34
+ n = !1;
35
+ break;
36
+ }
37
+ if (n) {
38
+ if (!r) {
39
+ let e = !1;
40
+ for (let n = s; n < t.length; n++) if (t.charCodeAt(n) === 46) {
41
+ e = !0;
42
+ break;
43
+ }
44
+ if (e) continue;
45
+ }
46
+ c.push(t);
47
+ }
48
+ }
49
+ return c;
50
+ }
51
+ resolve(e, t) {
52
+ let n = this.loaders[e];
53
+ if (!n) return null;
54
+ if (n[t]) return {
55
+ resolvedBlockName: t,
56
+ loaders: n[t]
57
+ };
58
+ if (!t.includes(".")) return null;
59
+ let r = t.split(".");
60
+ for (let e = r.length - 1; e >= 1; e--) {
61
+ let t = r.slice(0, e).join("."), i = n[t];
62
+ if (i) return {
63
+ resolvedBlockName: t,
64
+ loaders: i
65
+ };
66
+ }
67
+ return null;
68
+ }
69
+ normalize(e) {
70
+ let t = Object.create(null);
71
+ if (!e) return t;
72
+ let n = Array.isArray(e) ? e : [e];
73
+ for (let e of n) this.mergeCollection(t, e);
74
+ return t;
75
+ }
76
+ mergeCollection(e, t) {
77
+ for (let n in t) {
78
+ e[n] || (e[n] = Object.create(null));
79
+ let r = t[n];
80
+ for (let t in r) {
81
+ let i = this.normalizeEntry(r[t]);
82
+ if (!e[n][t]) {
83
+ e[n][t] = i;
84
+ continue;
85
+ }
86
+ e[n][t] = [...e[n][t], ...i];
87
+ }
88
+ }
89
+ }
90
+ normalizeEntry(e) {
91
+ return Array.isArray(e) ? e : [e];
92
+ }
93
+ collectKnownBlockNames(e) {
94
+ let t = /* @__PURE__ */ new Set();
95
+ for (let n in e) {
96
+ let r = e[n];
97
+ for (let e in r) t.add(e);
98
+ }
99
+ return Array.from(t);
100
+ }
101
+ };
102
+ //#endregion
103
+ export { n, r as t };
package/dist/core.js CHANGED
@@ -1,2 +1,2 @@
1
- import { i as e, n as t, r as n, t as r } from "./chunks/core-CJdrAmJT.js";
1
+ import { i as e, n as t, r as n, t as r } from "./chunks/core-UYOFg8gl.js";
2
2
  export { r as FintI18n, n as HookManager, e as compileTemplate, t as createFintI18n };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as e, n as t, r as n, t as r } from "./chunks/core-CJdrAmJT.js";
2
- import { FINT_I18N_KEY as i, createVTDirective as a, installI18n as o, useFintI18n as s, useI18nScope as c } from "./vue.js";
3
- import { BridgePlugin as l, HookLoggerPlugin as u, PersistencePlugin as d } from "./plugins.js";
4
- export { l as BridgePlugin, i as FINT_I18N_KEY, r as FintI18n, u as HookLoggerPlugin, n as HookManager, d as PersistencePlugin, e as compileTemplate, t as createFintI18n, a as createVTDirective, o as installI18n, s as useFintI18n, c as useI18nScope };
1
+ import { i as e, n as t, r as n, t as r } from "./chunks/core-UYOFg8gl.js";
2
+ import { FINT_I18N_KEY as i, createFintI18nPlugin as a, createVTDirective as o, installI18n as s, useFintI18n as c, useI18nScope as l, useI18nScopeSync as u } from "./vue.js";
3
+ import { BridgePlugin as d, HookLoggerPlugin as f, PersistencePlugin as p } from "./plugins.js";
4
+ export { d as BridgePlugin, i as FINT_I18N_KEY, r as FintI18n, f as HookLoggerPlugin, n as HookManager, p as PersistencePlugin, e as compileTemplate, t as createFintI18n, a as createFintI18nPlugin, o as createVTDirective, s as installI18n, c as useFintI18n, l as useI18nScope, u as useI18nScopeSync };
package/dist/plugins.js CHANGED
@@ -1,8 +1,10 @@
1
- import { watch as e } from "vue";
1
+ import { isRef as e, watch as t } from "vue";
2
2
  //#region src/plugins/persistence.ts
3
- var t = class {
3
+ var n = class {
4
4
  name = "persistence";
5
5
  options;
6
+ offLocaleChange;
7
+ storageListener;
6
8
  constructor(e = {}) {
7
9
  this.options = {
8
10
  key: "fint-i18n-locale",
@@ -13,44 +15,58 @@ var t = class {
13
15
  install(e) {
14
16
  let t = this.options.key, n = this.options.storage || (typeof window < "u" ? window.localStorage : void 0);
15
17
  if (!n || typeof n.getItem != "function") return;
16
- let r = n.getItem(t);
17
- r && (e.locale.value = r), e.hooks.on("onLocaleChange", ({ locale: e }) => {
18
+ let r = (t) => {
19
+ if (!t) return !1;
20
+ let n = e.getKnownLocales();
21
+ return n.length === 0 || n.includes(t);
22
+ }, i = n.getItem(t);
23
+ r(i) && i !== e.locale.value && e.setLocale(i), this.offLocaleChange = e.hooks.on("onLocaleChange", ({ locale: e }) => {
18
24
  n.setItem(t, e);
19
- }), this.options.syncTabs && typeof window < "u" && window.addEventListener("storage", (n) => {
20
- n.key === t && n.newValue && n.newValue !== e.locale.value && (e.locale.value = n.newValue);
21
- });
25
+ }), this.options.syncTabs && typeof window < "u" && (this.storageListener = (n) => {
26
+ n.key === t && r(n.newValue) && n.newValue !== e.locale.value && e.setLocale(n.newValue);
27
+ }, window.addEventListener("storage", this.storageListener));
22
28
  }
23
- }, n = class {
29
+ uninstall() {
30
+ this.offLocaleChange?.(), this.offLocaleChange = void 0, this.storageListener && typeof window < "u" && (window.removeEventListener("storage", this.storageListener), this.storageListener = void 0);
31
+ }
32
+ }, r = class {
24
33
  name = "bridge";
25
34
  options;
35
+ stopLocaleWatch;
36
+ hookUnsubscribers = [];
26
37
  constructor(e) {
27
38
  this.options = e;
28
39
  }
29
- install(t) {
30
- let n = this.options.i18n;
31
- e(() => n.locale.value || n.locale, (e) => {
32
- t.locale.value !== e && (t.locale.value = e);
33
- }, { immediate: !0 }), t.hooks.on("onLocaleChange", ({ locale: e }) => {
34
- typeof n.locale == "object" ? n.locale.value = e : n.locale = e;
35
- }), t.hooks.on("onTranslate", (e) => {
36
- let t = e.params ? n.t(e.key, e.params) : n.t(e.key);
40
+ install(n) {
41
+ let r = this.options.i18n, i = "global" in r ? r.global : r, a = this.options.priority ?? "fint", o = e(i.locale) ? () => i.locale.value : () => i.locale;
42
+ this.stopLocaleWatch = t(o, (e) => {
43
+ n.locale.value !== e && n.setLocale(e);
44
+ }, { immediate: !0 }), this.hookUnsubscribers.push(n.hooks.on("onLocaleChange", ({ locale: t }) => {
45
+ e(i.locale) ? i.locale.value = t : i.locale = t;
46
+ })), this.hookUnsubscribers.push(n.hooks.on("onTranslate", (e) => {
47
+ if (a === "fint" && e.result !== void 0) return e;
48
+ let t = e.params ? i.t(e.key, e.params) : i.t(e.key);
37
49
  return t === e.key ? e : {
38
50
  ...e,
39
51
  result: t
40
52
  };
41
- });
53
+ }));
54
+ }
55
+ uninstall() {
56
+ this.stopLocaleWatch?.(), this.stopLocaleWatch = void 0, this.hookUnsubscribers.forEach((e) => e()), this.hookUnsubscribers = [];
42
57
  }
43
- }, r = [
44
- "beforeInit",
58
+ }, i = [
45
59
  "afterInit",
46
60
  "onLocaleChange",
47
61
  "beforeLoadBlock",
48
62
  "afterLoadBlock",
49
63
  "onMissingKey",
50
- "onTranslate"
51
- ], i = class {
64
+ "onTranslate",
65
+ "onError"
66
+ ], a = class {
52
67
  name = "hook-logger";
53
68
  options;
69
+ hookUnsubscribers = [];
54
70
  constructor(e = {}) {
55
71
  this.options = {
56
72
  logger: console.log,
@@ -59,11 +75,14 @@ var t = class {
59
75
  };
60
76
  }
61
77
  install(e) {
62
- r.forEach((t) => this.registerHook(e, t));
78
+ i.forEach((t) => this.registerHook(e, t));
79
+ }
80
+ uninstall() {
81
+ this.hookUnsubscribers.forEach((e) => e()), this.hookUnsubscribers = [];
63
82
  }
64
83
  registerHook(e, t) {
65
- e.hooks.on(t, (e) => (this.options.logger(`${this.options.prefix} "${t}" called`, e), e));
84
+ this.hookUnsubscribers.push(e.hooks.on(t, (e) => (this.options.logger(`${this.options.prefix} "${t}" called`, e), e)));
66
85
  }
67
86
  };
68
87
  //#endregion
69
- export { n as BridgePlugin, i as HookLoggerPlugin, t as PersistencePlugin };
88
+ export { r as BridgePlugin, a as HookLoggerPlugin, n as PersistencePlugin };
@@ -2,5 +2,10 @@ export type MessageFunction = (params?: Record<string, any>) => string;
2
2
  /**
3
3
  * JIT-компилятор шаблонов.
4
4
  * Преобразует строку "Привет, {name}!" в функцию (p) => "Привет, " + p.name + "!"
5
+ *
6
+ * Синтаксис:
7
+ * - `{name}` — подстановка параметра; имя: буквы/цифры/`_`, а также `.` и `-`.
8
+ * - `{{` и `}}` — экранирование: выводятся как литеральные `{` и `}`.
9
+ * - Отсутствующий или null/undefined параметр оставляет плейсхолдер как есть.
5
10
  */
6
11
  export declare function compileTemplate(template: string): MessageFunction;
@@ -2,7 +2,7 @@ import type { Locale } from './types';
2
2
  export type HookFn<T = any> = (arg: T) => void | T | Promise<void | T>;
3
3
  type HookPayload<K extends keyof FintI18nHooks> = Parameters<FintI18nHooks[K]>[0];
4
4
  export interface FintI18nHooks {
5
- 'beforeInit': HookFn<any>;
5
+ /** Эмитится синхронно в конце конструктора — подписаться успевают только плагины. */
6
6
  'afterInit': HookFn<void>;
7
7
  'onLocaleChange': HookFn<{
8
8
  locale: Locale;
@@ -23,10 +23,17 @@ export interface FintI18nHooks {
23
23
  params?: any;
24
24
  result: string | undefined;
25
25
  }>;
26
+ /** Ошибки асинхронной загрузки блоков. Без подписчиков — console.error. */
27
+ 'onError': HookFn<{
28
+ error: unknown;
29
+ block?: string;
30
+ locale?: Locale;
31
+ }>;
26
32
  }
27
33
  export declare class HookManager {
28
34
  private hooks;
29
35
  on<K extends keyof FintI18nHooks>(name: K, fn: FintI18nHooks[K]): () => void;
36
+ has<K extends keyof FintI18nHooks>(name: K): boolean;
30
37
  off<K extends keyof FintI18nHooks>(name: K, fn: FintI18nHooks[K]): void;
31
38
  emit<K extends keyof FintI18nHooks>(name: K, arg: HookPayload<K>): Promise<HookPayload<K>>;
32
39
  emitSync<K extends keyof FintI18nHooks>(name: K, arg: HookPayload<K>): HookPayload<K>;