@embedpdf/plugin-i18n 2.15.0 → 3.0.0-next.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 +192 -21
- package/dist/index.cjs +268 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +159 -0
- package/dist/index.d.ts +159 -1
- package/dist/index.js +262 -397
- package/dist/index.js.map +1 -1
- package/package.json +18 -58
- package/dist/lib/actions.d.ts +0 -15
- package/dist/lib/i18n-plugin.d.ts +0 -28
- package/dist/lib/index.d.ts +0 -9
- package/dist/lib/locales/en.d.ts +0 -2
- package/dist/lib/locales/es.d.ts +0 -2
- package/dist/lib/locales/index.d.ts +0 -2
- package/dist/lib/manifest.d.ts +0 -4
- package/dist/lib/reducer.d.ts +0 -5
- package/dist/lib/types.d.ts +0 -104
- package/dist/preact/adapter.d.ts +0 -5
- package/dist/preact/core.d.ts +0 -1
- package/dist/preact/index.cjs +0 -2
- package/dist/preact/index.cjs.map +0 -1
- package/dist/preact/index.d.ts +0 -1
- package/dist/preact/index.js +0 -70
- package/dist/preact/index.js.map +0 -1
- package/dist/react/adapter.d.ts +0 -2
- package/dist/react/core.d.ts +0 -1
- package/dist/react/index.cjs +0 -2
- package/dist/react/index.cjs.map +0 -1
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.js +0 -69
- package/dist/react/index.js.map +0 -1
- package/dist/shared/components/index.d.ts +0 -1
- package/dist/shared/components/translate.d.ts +0 -35
- package/dist/shared/hooks/index.d.ts +0 -1
- package/dist/shared/hooks/use-i18n.d.ts +0 -43
- package/dist/shared/index.d.ts +0 -3
- package/dist/shared-preact/components/index.d.ts +0 -1
- package/dist/shared-preact/components/translate.d.ts +0 -35
- package/dist/shared-preact/hooks/index.d.ts +0 -1
- package/dist/shared-preact/hooks/use-i18n.d.ts +0 -43
- package/dist/shared-preact/index.d.ts +0 -3
- package/dist/shared-react/components/index.d.ts +0 -1
- package/dist/shared-react/components/translate.d.ts +0 -35
- package/dist/shared-react/hooks/index.d.ts +0 -1
- package/dist/shared-react/hooks/use-i18n.d.ts +0 -43
- package/dist/shared-react/index.d.ts +0 -3
- package/dist/svelte/components/Translate.svelte.d.ts +0 -9
- package/dist/svelte/components/index.d.ts +0 -1
- package/dist/svelte/hooks/index.d.ts +0 -1
- package/dist/svelte/hooks/use-i18n.svelte.d.ts +0 -41
- package/dist/svelte/index.cjs +0 -2
- package/dist/svelte/index.cjs.map +0 -1
- package/dist/svelte/index.d.ts +0 -3
- package/dist/svelte/index.js +0 -90
- package/dist/svelte/index.js.map +0 -1
- package/dist/vue/components/index.d.ts +0 -1
- package/dist/vue/components/translate.vue.d.ts +0 -9
- package/dist/vue/hooks/index.d.ts +0 -1
- package/dist/vue/hooks/use-i18n.d.ts +0 -30
- package/dist/vue/index.cjs +0 -2
- package/dist/vue/index.cjs.map +0 -1
- package/dist/vue/index.d.ts +0 -3
- package/dist/vue/index.js +0 -99
- package/dist/vue/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,398 +1,263 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
1
|
+
import { createCapabilityToken, definePlugin } from "@embedpdf/core";
|
|
2
|
+
//#region src/translate.ts
|
|
3
|
+
/**
|
|
4
|
+
* The pure lookup core — state in, string out. No platform access anywhere
|
|
5
|
+
* (`Intl.PluralRules` is core ECMAScript, not DOM); this is the part that
|
|
6
|
+
* ports to Rust verbatim.
|
|
7
|
+
*/
|
|
8
|
+
/** Walk a dotted path ('commands.zoom.in') through a dictionary tree. */
|
|
9
|
+
function lookup(dict, key) {
|
|
10
|
+
if (!dict) return void 0;
|
|
11
|
+
let node = dict;
|
|
12
|
+
for (const part of key.split(".")) {
|
|
13
|
+
if (node === void 0 || typeof node === "string") return void 0;
|
|
14
|
+
node = node[part];
|
|
15
|
+
}
|
|
16
|
+
return node;
|
|
17
|
+
}
|
|
18
|
+
/** CLDR plural category for a count, with a safe fallback when the runtime
|
|
19
|
+
* lacks ICU data for the locale. */
|
|
20
|
+
function pluralCategory(locale, count) {
|
|
21
|
+
try {
|
|
22
|
+
return new Intl.PluralRules(locale).select(count);
|
|
23
|
+
} catch {
|
|
24
|
+
return count === 1 ? "one" : "other";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** A leaf hit is a string; a branch object needs a plural pick via `count`. */
|
|
28
|
+
function resolveNode(node, locale, params) {
|
|
29
|
+
if (typeof node === "string") return node;
|
|
30
|
+
if (node !== void 0 && typeof params?.count === "number") {
|
|
31
|
+
const branch = node[pluralCategory(locale, params.count)] ?? node["other"];
|
|
32
|
+
if (typeof branch === "string") return branch;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Replace `{slot}` markers; unknown slots stay verbatim (visible in QA). */
|
|
36
|
+
function interpolate(text, params) {
|
|
37
|
+
if (!params) return text;
|
|
38
|
+
return text.replace(/\{(\w+)\}/g, (match, slot) => params[slot] !== void 0 ? String(params[slot]) : match);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolve a key against the state: current locale → fallback locale →
|
|
42
|
+
* `options.fallback` (interpolated too) → the key itself.
|
|
43
|
+
*/
|
|
44
|
+
function translate(state, key, options) {
|
|
45
|
+
const { locale, fallbackLocale, locales } = state;
|
|
46
|
+
const text = resolveNode(lookup(locales[locale]?.translations, key), locale, options?.params) ?? resolveNode(lookup(locales[fallbackLocale]?.translations, key), fallbackLocale, options?.params);
|
|
47
|
+
if (text !== void 0) return {
|
|
48
|
+
text: interpolate(text, options?.params),
|
|
49
|
+
found: true
|
|
50
|
+
};
|
|
51
|
+
if (options?.fallback !== void 0) return {
|
|
52
|
+
text: interpolate(options.fallback, options.params),
|
|
53
|
+
found: false
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
text: key,
|
|
57
|
+
found: false
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/capability.ts
|
|
62
|
+
/**
|
|
63
|
+
* The i18n capability — selectors (pure reads over state) + intents (the only
|
|
64
|
+
* writers). Built synchronously in `createKernel()`, so `t()` works before the
|
|
65
|
+
* engine exists — including inside the viewer shell's loading UI.
|
|
66
|
+
*/
|
|
67
|
+
function createI18nCapability(ctx, config) {
|
|
68
|
+
const warned = /* @__PURE__ */ new Set();
|
|
69
|
+
const warnOnce = (id, message) => {
|
|
70
|
+
if (warned.has(id)) return;
|
|
71
|
+
warned.add(id);
|
|
72
|
+
console.warn(message);
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
t: (key, options) => {
|
|
76
|
+
const result = translate(ctx.getState(), key, options);
|
|
77
|
+
if (!result.found && options?.fallback === void 0) warnOnce(`key:${key}`, `[i18n] missing translation "${key}" (locale: ${ctx.getState().locale})`);
|
|
78
|
+
return result.text;
|
|
79
|
+
},
|
|
80
|
+
locale: () => ctx.getState().locale,
|
|
81
|
+
dir: () => {
|
|
82
|
+
const { locales, locale } = ctx.getState();
|
|
83
|
+
return locales[locale]?.dir ?? "ltr";
|
|
84
|
+
},
|
|
85
|
+
locales: () => {
|
|
86
|
+
const { locales } = ctx.getState();
|
|
87
|
+
const known = Object.values(locales).map((locale) => ({
|
|
88
|
+
code: locale.code,
|
|
89
|
+
name: locale.name,
|
|
90
|
+
dir: locale.dir ?? "ltr",
|
|
91
|
+
loaded: true
|
|
92
|
+
}));
|
|
93
|
+
for (const code of Object.keys(config.loaders ?? {})) if (!locales[code]) known.push({
|
|
94
|
+
code,
|
|
95
|
+
name: code,
|
|
96
|
+
dir: "ltr",
|
|
97
|
+
loaded: false
|
|
98
|
+
});
|
|
99
|
+
return known;
|
|
100
|
+
},
|
|
101
|
+
loading: () => ctx.getState().loading,
|
|
102
|
+
setLocale: (code) => {
|
|
103
|
+
if (ctx.getState().locales[code]) ctx.dispatch({
|
|
104
|
+
type: "I18N/SET_LOCALE",
|
|
105
|
+
locale: code
|
|
106
|
+
});
|
|
107
|
+
else if (config.loaders?.[code]) ctx.dispatch({
|
|
108
|
+
type: "I18N/LOAD_STARTED",
|
|
109
|
+
locale: code
|
|
110
|
+
});
|
|
111
|
+
else warnOnce(`locale:${code}`, `[i18n] unknown locale "${code}" — not registered and no loader configured`);
|
|
112
|
+
},
|
|
113
|
+
registerLocale: (locale) => ctx.dispatch({
|
|
114
|
+
type: "I18N/REGISTER_LOCALE",
|
|
115
|
+
locale
|
|
116
|
+
})
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/effects.ts
|
|
121
|
+
/**
|
|
122
|
+
* The plugin's ONE side-effect: fetching lazy locale packs. State-driven — a
|
|
123
|
+
* load is `state.loading`, set by the capability (a switch to a lazy pack) or
|
|
124
|
+
* by the initial state (a lazy startup locale), so a load requested before
|
|
125
|
+
* effects ran is picked up here and nothing is lost to boot ordering.
|
|
126
|
+
*/
|
|
127
|
+
function registerI18nEffects(config) {
|
|
128
|
+
return (ctx) => {
|
|
129
|
+
const load = (code) => {
|
|
130
|
+
const loader = config.loaders?.[code];
|
|
131
|
+
if (!loader) return;
|
|
132
|
+
loader().then((locale) => {
|
|
133
|
+
ctx.dispatch({
|
|
134
|
+
type: "I18N/REGISTER_LOCALE",
|
|
135
|
+
locale
|
|
136
|
+
});
|
|
137
|
+
if (ctx.getState().loading === code) ctx.dispatch({
|
|
138
|
+
type: "I18N/SET_LOCALE",
|
|
139
|
+
locale: locale.code
|
|
140
|
+
});
|
|
141
|
+
}, () => ctx.dispatch({
|
|
142
|
+
type: "I18N/LOAD_FAILED",
|
|
143
|
+
locale: code
|
|
144
|
+
}));
|
|
145
|
+
};
|
|
146
|
+
const pending = ctx.getState().loading;
|
|
147
|
+
if (pending) load(pending);
|
|
148
|
+
ctx.watch(() => ctx.getState().loading, (code) => {
|
|
149
|
+
if (code) load(code);
|
|
150
|
+
});
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/reducer.ts
|
|
155
|
+
/**
|
|
156
|
+
* Pure and serializable — locale packs are data, so they live HERE, not in a
|
|
157
|
+
* side-table. One source of truth: `t()` is a pure function of this state and
|
|
158
|
+
* reactivity rides the kernel's one change stream (no emitters).
|
|
159
|
+
*/
|
|
160
|
+
function initialI18nState(config) {
|
|
161
|
+
const fallbackLocale = config.fallbackLocale ?? "en";
|
|
162
|
+
const locales = {};
|
|
163
|
+
for (const locale of config.locales ?? []) locales[locale.code] = locale;
|
|
164
|
+
const locale = config.locale ?? fallbackLocale;
|
|
165
|
+
return {
|
|
166
|
+
locale,
|
|
167
|
+
fallbackLocale,
|
|
168
|
+
locales,
|
|
169
|
+
loading: !locales[locale] && config.loaders?.[locale] !== void 0 ? locale : null
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function i18nReducer(state, action) {
|
|
173
|
+
switch (action.type) {
|
|
174
|
+
case "I18N/SET_LOCALE":
|
|
175
|
+
if (!state.locales[action.locale]) return state;
|
|
176
|
+
if (state.locale === action.locale && state.loading === null) return state;
|
|
177
|
+
return {
|
|
178
|
+
...state,
|
|
179
|
+
locale: action.locale,
|
|
180
|
+
loading: null
|
|
181
|
+
};
|
|
182
|
+
case "I18N/REGISTER_LOCALE": return {
|
|
183
|
+
...state,
|
|
184
|
+
locales: {
|
|
185
|
+
...state.locales,
|
|
186
|
+
[action.locale.code]: action.locale
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
case "I18N/LOAD_STARTED": return state.loading === action.locale ? state : {
|
|
190
|
+
...state,
|
|
191
|
+
loading: action.locale
|
|
192
|
+
};
|
|
193
|
+
case "I18N/LOAD_FAILED": return state.loading === action.locale ? {
|
|
194
|
+
...state,
|
|
195
|
+
loading: null
|
|
196
|
+
} : state;
|
|
197
|
+
default: return state;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/types.ts
|
|
202
|
+
const I18nToken = createCapabilityToken("i18n");
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region src/i18n.plugin.ts
|
|
205
|
+
/**
|
|
206
|
+
* The i18n plugin: workspace-scoped (locale is a workspace concern) with NO
|
|
207
|
+
* dependencies — engine-free, DOM-free. Its capability is built synchronously
|
|
208
|
+
* in `createKernel()`, so the shell translates from the first frame, while
|
|
209
|
+
* the engine is still booting.
|
|
210
|
+
*/
|
|
211
|
+
const i18nPlugin = (config = {}) => definePlugin({
|
|
212
|
+
id: "i18n",
|
|
213
|
+
scope: "workspace",
|
|
214
|
+
token: I18nToken,
|
|
215
|
+
initialState: () => initialI18nState(config),
|
|
216
|
+
reduce: i18nReducer,
|
|
217
|
+
capability: (ctx) => createI18nCapability(ctx, config),
|
|
218
|
+
effects: registerI18nEffects(config)
|
|
116
219
|
});
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
buildCapability() {
|
|
164
|
-
return {
|
|
165
|
-
t: (key, options) => this.translate(key, options),
|
|
166
|
-
forDocument: (documentId) => this.createI18nScope(documentId),
|
|
167
|
-
registerParamResolver: (key, resolver) => this.registerParamResolver(key, resolver),
|
|
168
|
-
unregisterParamResolver: (key) => this.unregisterParamResolver(key),
|
|
169
|
-
setLocale: (locale) => this.setLocale(locale),
|
|
170
|
-
getLocale: () => this.state.currentLocale,
|
|
171
|
-
getAvailableLocales: () => [...this.state.availableLocales],
|
|
172
|
-
getLocaleInfo: (code) => this.locales.get(code) ?? null,
|
|
173
|
-
registerLocale: (locale) => this.registerLocale(locale),
|
|
174
|
-
hasLocale: (code) => this.locales.has(code),
|
|
175
|
-
onLocaleChange: this.localeChange$.on,
|
|
176
|
-
onParamsChanged: this.paramsChanged$.onGlobal
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
// ─────────────────────────────────────────────────────────
|
|
180
|
-
// Document Scoping
|
|
181
|
-
// ─────────────────────────────────────────────────────────
|
|
182
|
-
createI18nScope(documentId) {
|
|
183
|
-
return {
|
|
184
|
-
t: (key, options) => this.translate(key, { documentId, ...options }),
|
|
185
|
-
onParamsChanged: this.paramsChanged$.forScope(documentId)
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
// ─────────────────────────────────────────────────────────
|
|
189
|
-
// Translation Logic
|
|
190
|
-
// ─────────────────────────────────────────────────────────
|
|
191
|
-
translate(key, options) {
|
|
192
|
-
const locale = this.locales.get(this.state.currentLocale);
|
|
193
|
-
const fallbackLocale = this.config.fallbackLocale ? this.locales.get(this.config.fallbackLocale) : null;
|
|
194
|
-
let value = this.getNestedValue(locale == null ? void 0 : locale.translations, key);
|
|
195
|
-
if (!value && fallbackLocale) {
|
|
196
|
-
value = this.getNestedValue(fallbackLocale.translations, key);
|
|
197
|
-
}
|
|
198
|
-
if (!value) {
|
|
199
|
-
if (options == null ? void 0 : options.fallback) {
|
|
200
|
-
value = options.fallback;
|
|
201
|
-
} else {
|
|
202
|
-
this.logger.warn(
|
|
203
|
-
"I18nPlugin",
|
|
204
|
-
"MissingTranslation",
|
|
205
|
-
`Translation not found for key: ${key}`
|
|
206
|
-
);
|
|
207
|
-
return key;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
let params = options == null ? void 0 : options.params;
|
|
211
|
-
if (!params && this.paramResolvers.has(key)) {
|
|
212
|
-
params = this.resolveParams(key, options == null ? void 0 : options.documentId);
|
|
213
|
-
}
|
|
214
|
-
return this.interpolate(value, params);
|
|
215
|
-
}
|
|
216
|
-
resolveParams(key, documentId) {
|
|
217
|
-
const resolver = this.paramResolvers.get(key);
|
|
218
|
-
if (!resolver) return void 0;
|
|
219
|
-
const state = this.registry.getStore().getState();
|
|
220
|
-
try {
|
|
221
|
-
return resolver({ state, documentId });
|
|
222
|
-
} catch (error) {
|
|
223
|
-
this.logger.error(
|
|
224
|
-
"I18nPlugin",
|
|
225
|
-
"ParamResolverError",
|
|
226
|
-
`Error resolving params for key "${key}":`,
|
|
227
|
-
error
|
|
228
|
-
);
|
|
229
|
-
return void 0;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
getNestedValue(obj, path) {
|
|
233
|
-
if (!obj) return void 0;
|
|
234
|
-
const parts = path.split(".");
|
|
235
|
-
let current = obj;
|
|
236
|
-
for (const part of parts) {
|
|
237
|
-
if (current === void 0 || current === null) return void 0;
|
|
238
|
-
current = current[part];
|
|
239
|
-
}
|
|
240
|
-
return typeof current === "string" ? current : void 0;
|
|
241
|
-
}
|
|
242
|
-
interpolate(str, params) {
|
|
243
|
-
if (!params) return str;
|
|
244
|
-
return str.replace(/\{(\w+)\}/g, (match, key) => {
|
|
245
|
-
const value = params[key];
|
|
246
|
-
return value !== void 0 ? String(value) : match;
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
// ─────────────────────────────────────────────────────────
|
|
250
|
-
// Smart Param Change Detection
|
|
251
|
-
// ─────────────────────────────────────────────────────────
|
|
252
|
-
detectParamChanges(newState) {
|
|
253
|
-
const documentIds = Object.keys(newState.core.documents);
|
|
254
|
-
documentIds.forEach((documentId) => {
|
|
255
|
-
this.detectDocumentParamChanges(documentId, newState);
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
detectDocumentParamChanges(documentId, newState) {
|
|
259
|
-
const previousCache = this.paramsCache.get(documentId);
|
|
260
|
-
const changedKeys = [];
|
|
261
|
-
this.paramResolvers.forEach((resolver, key) => {
|
|
262
|
-
try {
|
|
263
|
-
const newParams = resolver({ state: newState, documentId });
|
|
264
|
-
const prevParams = previousCache == null ? void 0 : previousCache.get(key);
|
|
265
|
-
if (!arePropsEqual(prevParams, newParams)) {
|
|
266
|
-
changedKeys.push(key);
|
|
267
|
-
if (!this.paramsCache.has(documentId)) {
|
|
268
|
-
this.paramsCache.set(documentId, /* @__PURE__ */ new Map());
|
|
269
|
-
}
|
|
270
|
-
this.paramsCache.get(documentId).set(key, newParams);
|
|
271
|
-
}
|
|
272
|
-
} catch (error) {
|
|
273
|
-
this.logger.error(
|
|
274
|
-
"I18nPlugin",
|
|
275
|
-
"ParamDetectionError",
|
|
276
|
-
`Error detecting param changes for key "${key}":`,
|
|
277
|
-
error
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
if (changedKeys.length > 0) {
|
|
282
|
-
this.paramsChanged$.emit(documentId, { changedKeys });
|
|
283
|
-
this.logger.debug(
|
|
284
|
-
"I18nPlugin",
|
|
285
|
-
"ParamsChanged",
|
|
286
|
-
`Translation params changed for document ${documentId}:`,
|
|
287
|
-
changedKeys
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
// ─────────────────────────────────────────────────────────
|
|
292
|
-
// Param Resolver Management
|
|
293
|
-
// ─────────────────────────────────────────────────────────
|
|
294
|
-
registerParamResolver(key, resolver) {
|
|
295
|
-
if (this.paramResolvers.has(key)) {
|
|
296
|
-
this.logger.warn(
|
|
297
|
-
"I18nPlugin",
|
|
298
|
-
"ResolverOverwrite",
|
|
299
|
-
`Param resolver for "${key}" already exists and will be overwritten`
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
this.paramResolvers.set(key, resolver);
|
|
303
|
-
this.paramsCache.forEach((docCache) => {
|
|
304
|
-
docCache.delete(key);
|
|
305
|
-
});
|
|
306
|
-
this.logger.debug("I18nPlugin", "ResolverRegistered", `Param resolver registered for: ${key}`);
|
|
307
|
-
}
|
|
308
|
-
unregisterParamResolver(key) {
|
|
309
|
-
if (this.paramResolvers.delete(key)) {
|
|
310
|
-
this.paramsCache.forEach((docCache) => {
|
|
311
|
-
docCache.delete(key);
|
|
312
|
-
});
|
|
313
|
-
this.logger.debug(
|
|
314
|
-
"I18nPlugin",
|
|
315
|
-
"ResolverUnregistered",
|
|
316
|
-
`Param resolver unregistered for: ${key}`
|
|
317
|
-
);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
// ─────────────────────────────────────────────────────────
|
|
321
|
-
// Locale Management
|
|
322
|
-
// ─────────────────────────────────────────────────────────
|
|
323
|
-
setLocale(locale) {
|
|
324
|
-
if (!this.locales.has(locale)) {
|
|
325
|
-
this.logger.warn("I18nPlugin", "LocaleNotFound", `Locale '${locale}' is not registered`);
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
const previousLocale = this.state.currentLocale;
|
|
329
|
-
if (previousLocale === locale) return;
|
|
330
|
-
this.dispatch(setLocale(locale));
|
|
331
|
-
this.localeChange$.emit({
|
|
332
|
-
previousLocale,
|
|
333
|
-
currentLocale: locale
|
|
334
|
-
});
|
|
335
|
-
this.logger.info("I18nPlugin", "LocaleChanged", `Locale changed to: ${locale}`);
|
|
336
|
-
}
|
|
337
|
-
registerLocale(locale) {
|
|
338
|
-
if (this.locales.has(locale.code)) {
|
|
339
|
-
this.logger.warn(
|
|
340
|
-
"I18nPlugin",
|
|
341
|
-
"LocaleAlreadyRegistered",
|
|
342
|
-
`Locale '${locale.code}' is already registered`
|
|
343
|
-
);
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
this.locales.set(locale.code, locale);
|
|
347
|
-
this.dispatch(registerLocale(locale.code));
|
|
348
|
-
this.logger.info("I18nPlugin", "LocaleRegistered", `Locale registered: ${locale.code}`);
|
|
349
|
-
}
|
|
350
|
-
};
|
|
351
|
-
_I18nPlugin.id = "i18n";
|
|
352
|
-
let I18nPlugin = _I18nPlugin;
|
|
353
|
-
const initialState = {
|
|
354
|
-
currentLocale: "en",
|
|
355
|
-
availableLocales: []
|
|
356
|
-
};
|
|
357
|
-
const i18nReducer = (state = initialState, action) => {
|
|
358
|
-
switch (action.type) {
|
|
359
|
-
case SET_LOCALE: {
|
|
360
|
-
const locale = action.payload;
|
|
361
|
-
if (!state.availableLocales.includes(locale)) {
|
|
362
|
-
console.warn(`I18nPlugin: Locale '${locale}' not available`);
|
|
363
|
-
return state;
|
|
364
|
-
}
|
|
365
|
-
return {
|
|
366
|
-
...state,
|
|
367
|
-
currentLocale: locale
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
case REGISTER_LOCALE: {
|
|
371
|
-
const locale = action.payload;
|
|
372
|
-
if (state.availableLocales.includes(locale)) {
|
|
373
|
-
return state;
|
|
374
|
-
}
|
|
375
|
-
return {
|
|
376
|
-
...state,
|
|
377
|
-
availableLocales: [...state.availableLocales, locale]
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
default:
|
|
381
|
-
return state;
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
const I18nPluginPackage = {
|
|
385
|
-
manifest,
|
|
386
|
-
create: (registry, config) => new I18nPlugin(I18N_PLUGIN_ID, registry, config),
|
|
387
|
-
reducer: i18nReducer,
|
|
388
|
-
initialState
|
|
389
|
-
};
|
|
390
|
-
export {
|
|
391
|
-
I18N_PLUGIN_ID,
|
|
392
|
-
I18nPlugin,
|
|
393
|
-
I18nPluginPackage,
|
|
394
|
-
enUS,
|
|
395
|
-
esES,
|
|
396
|
-
manifest
|
|
397
|
-
};
|
|
398
|
-
//# sourceMappingURL=index.js.map
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/negotiate.ts
|
|
222
|
+
/**
|
|
223
|
+
* Pure BCP-47 locale negotiation — strings in, string out. The plugin never
|
|
224
|
+
* reads the platform; the EMBEDDER feeds it whatever request list its
|
|
225
|
+
* environment offers:
|
|
226
|
+
*
|
|
227
|
+
* ```ts
|
|
228
|
+
* // browser shell: negotiateLocale(['en', 'es', 'ar'], navigator.languages)
|
|
229
|
+
* // server render: negotiateLocale(codes, parseAcceptLanguage(header))
|
|
230
|
+
* ```
|
|
231
|
+
*
|
|
232
|
+
* Matching, per requested code in preference order:
|
|
233
|
+
* 1. exact match (case-insensitive): 'es-MX' → 'es-MX'
|
|
234
|
+
* 2. request narrowed to its language: 'en-GB' → 'en'
|
|
235
|
+
* 3. any available dialect of the language: 'zh' → 'zh-Hans'
|
|
236
|
+
*
|
|
237
|
+
* Returns the matched code exactly as it appears in `available`, or null.
|
|
238
|
+
*/
|
|
239
|
+
function negotiateLocale(available, requested) {
|
|
240
|
+
const byCanon = /* @__PURE__ */ new Map();
|
|
241
|
+
for (const code of available) {
|
|
242
|
+
const canon = code.toLowerCase();
|
|
243
|
+
if (!byCanon.has(canon)) byCanon.set(canon, code);
|
|
244
|
+
}
|
|
245
|
+
const languageOf = (code) => code.toLowerCase().split("-")[0];
|
|
246
|
+
for (const want of requested) {
|
|
247
|
+
const exact = byCanon.get(want.toLowerCase());
|
|
248
|
+
if (exact !== void 0) return exact;
|
|
249
|
+
}
|
|
250
|
+
for (const want of requested) {
|
|
251
|
+
const narrowed = byCanon.get(languageOf(want));
|
|
252
|
+
if (narrowed !== void 0) return narrowed;
|
|
253
|
+
}
|
|
254
|
+
for (const want of requested) {
|
|
255
|
+
const language = languageOf(want);
|
|
256
|
+
for (const [canon, original] of byCanon) if (languageOf(canon) === language) return original;
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
//#endregion
|
|
261
|
+
export { I18nToken, i18nPlugin, interpolate, negotiateLocale, translate };
|
|
262
|
+
|
|
263
|
+
//# sourceMappingURL=index.js.map
|