@embedpdf/plugin-i18n 3.0.0-next.14 → 3.0.0-next.16
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/dist/contract-D2EGxESh.d.cts +140 -0
- package/dist/contract-D2EGxESh.d.ts +140 -0
- package/dist/contract-TF72RUxK.cjs +25 -0
- package/dist/contract-TF72RUxK.cjs.map +1 -0
- package/dist/contract-w0S-ipz5.js +15 -0
- package/dist/contract-w0S-ipz5.js.map +1 -0
- package/dist/contract.cjs +2 -1
- package/dist/contract.d.cts +2 -2
- package/dist/contract.d.ts +2 -2
- package/dist/contract.js +2 -2
- package/dist/index.cjs +950 -192
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -38
- package/dist/index.d.ts +26 -38
- package/dist/index.js +950 -192
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/contract-BIk4Kv-7.cjs +0 -11
- package/dist/contract-BIk4Kv-7.cjs.map +0 -1
- package/dist/contract-D08iYq9-.js +0 -7
- package/dist/contract-D08iYq9-.js.map +0 -1
- package/dist/contract-DCt9Ikqu.d.cts +0 -116
- package/dist/contract-DCt9Ikqu.d.ts +0 -116
package/dist/index.cjs
CHANGED
|
@@ -1,24 +1,789 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_contract = require("./contract-
|
|
2
|
+
const require_contract = require("./contract-TF72RUxK.cjs");
|
|
3
3
|
let _embedpdf_core = require("@embedpdf/core");
|
|
4
|
-
//#region src/
|
|
4
|
+
//#region src/model.ts
|
|
5
|
+
/** Nothing loaded yet: the controller seeds the state from the settings. */
|
|
6
|
+
const initialI18nState = () => ({
|
|
7
|
+
locale: "",
|
|
8
|
+
locales: {},
|
|
9
|
+
loading: null,
|
|
10
|
+
waitingTranslations: {}
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* The state the settings start with: their languages loaded, and their `locale` current. A start
|
|
14
|
+
* language that is still to load shows `fallbackLocale` meanwhile, and `loading` names it, so the
|
|
15
|
+
* loader fetches it once connected.
|
|
16
|
+
*/
|
|
17
|
+
function seedFromSettings(state, settings) {
|
|
18
|
+
const seeded = settings.locales.reduce(registerLocale, state);
|
|
19
|
+
const locale = settings.locale ?? settings.fallbackLocale;
|
|
20
|
+
if (seeded.locales[locale] || settings.loaders[locale] === void 0) return {
|
|
21
|
+
...seeded,
|
|
22
|
+
locale
|
|
23
|
+
};
|
|
24
|
+
return {
|
|
25
|
+
...seeded,
|
|
26
|
+
locale: settings.fallbackLocale,
|
|
27
|
+
loading: locale
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** Deep merge of two dictionaries: later leaves win, branches merge. */
|
|
31
|
+
function mergeTranslations(base, patch) {
|
|
32
|
+
const merged = { ...base };
|
|
33
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
34
|
+
const current = merged[key];
|
|
35
|
+
merged[key] = typeof value === "object" && typeof current === "object" ? mergeTranslations(current, value) : value;
|
|
36
|
+
}
|
|
37
|
+
return merged;
|
|
38
|
+
}
|
|
39
|
+
const hasDottedKey = (dictionary) => Object.entries(dictionary).some(([key, value]) => key.includes(".") || typeof value === "object" && hasDottedKey(value));
|
|
40
|
+
/**
|
|
41
|
+
* Dotted keys as branches, the shape `t()` walks: `{ 'review.reject': 'Afwijzen' }` is
|
|
42
|
+
* `{ review: { reject: 'Afwijzen' } }`. Keys merge in order, so a later one wins. A dictionary
|
|
43
|
+
* without a dotted key comes back as it is.
|
|
44
|
+
*/
|
|
45
|
+
function nestKeys(dictionary) {
|
|
46
|
+
if (!hasDottedKey(dictionary)) return dictionary;
|
|
47
|
+
let nested = {};
|
|
48
|
+
for (const [key, value] of Object.entries(dictionary)) {
|
|
49
|
+
const leaf = typeof value === "object" ? nestKeys(value) : value;
|
|
50
|
+
const branch = key.split(".").reduceRight((inner, part) => ({ [part]: inner }), leaf);
|
|
51
|
+
nested = mergeTranslations(nested, branch);
|
|
52
|
+
}
|
|
53
|
+
return nested;
|
|
54
|
+
}
|
|
55
|
+
const withNestedKeys = (locale) => {
|
|
56
|
+
const translations = nestKeys(locale.translations);
|
|
57
|
+
return translations === locale.translations ? locale : {
|
|
58
|
+
...locale,
|
|
59
|
+
translations
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Switch to a loaded language and end any load. A code that isn't loaded changes nothing: a
|
|
64
|
+
* language in `loaders` becomes current through {@link startLocaleLoad}, {@link registerLocale}
|
|
65
|
+
* and then this transition, which the loader drives.
|
|
66
|
+
*/
|
|
67
|
+
function setLocale(state, code) {
|
|
68
|
+
if (!state.locales[code]) return state;
|
|
69
|
+
if (state.locale === code && state.loading === null) return state;
|
|
70
|
+
return {
|
|
71
|
+
...state,
|
|
72
|
+
locale: code,
|
|
73
|
+
loading: null
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Add a language, with any strings that were added to it before it loaded on top. */
|
|
77
|
+
function registerLocale(state, locale) {
|
|
78
|
+
const pack = withNestedKeys(locale);
|
|
79
|
+
const waiting = state.waitingTranslations[locale.code];
|
|
80
|
+
if (!waiting) return {
|
|
81
|
+
...state,
|
|
82
|
+
locales: {
|
|
83
|
+
...state.locales,
|
|
84
|
+
[locale.code]: pack
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const { [locale.code]: _merged, ...stillWaiting } = state.waitingTranslations;
|
|
88
|
+
return {
|
|
89
|
+
...state,
|
|
90
|
+
locales: {
|
|
91
|
+
...state.locales,
|
|
92
|
+
[locale.code]: {
|
|
93
|
+
...pack,
|
|
94
|
+
translations: mergeTranslations(pack.translations, waiting)
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
waitingTranslations: stillWaiting
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function unregisterLocale(state, code) {
|
|
101
|
+
if (!state.locales[code]) return state;
|
|
102
|
+
const { [code]: _removed, ...locales } = state.locales;
|
|
103
|
+
return {
|
|
104
|
+
...state,
|
|
105
|
+
locales
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Merge strings into a language, later keys winning. For a language that isn't loaded yet (one in
|
|
110
|
+
* `loaders`), the strings wait and merge in when it loads; the controller only lets codes with a
|
|
111
|
+
* loader get here.
|
|
112
|
+
*/
|
|
113
|
+
function addTranslations(state, code, dictionary) {
|
|
114
|
+
const strings = nestKeys(dictionary);
|
|
115
|
+
const pack = state.locales[code];
|
|
116
|
+
if (!pack) {
|
|
117
|
+
const waiting = state.waitingTranslations[code] ?? {};
|
|
118
|
+
return {
|
|
119
|
+
...state,
|
|
120
|
+
waitingTranslations: {
|
|
121
|
+
...state.waitingTranslations,
|
|
122
|
+
[code]: mergeTranslations(waiting, strings)
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
...state,
|
|
128
|
+
locales: {
|
|
129
|
+
...state.locales,
|
|
130
|
+
[code]: {
|
|
131
|
+
...pack,
|
|
132
|
+
translations: mergeTranslations(pack.translations, strings)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** A language in `loaders` is wanted: the loader fetches whatever `loading` names. */
|
|
138
|
+
function startLocaleLoad(state, code) {
|
|
139
|
+
return state.loading === code ? state : {
|
|
140
|
+
...state,
|
|
141
|
+
loading: code
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** A load ended without switching: it failed or was cancelled. Only the load still wanted ends. */
|
|
145
|
+
function endLocaleLoad(state, code) {
|
|
146
|
+
return state.loading === code ? {
|
|
147
|
+
...state,
|
|
148
|
+
loading: null
|
|
149
|
+
} : state;
|
|
150
|
+
}
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/sync/loaders.ts
|
|
153
|
+
/**
|
|
154
|
+
* The plugin's only side effect: fetching languages from `loaders`. Driven by
|
|
155
|
+
* state: a load is `state.loading`, set by `setLocale` (a switch to a language
|
|
156
|
+
* still to load) or by the seeded state (a start language still to load), so a
|
|
157
|
+
* load asked for before `connect` ran is picked up here.
|
|
158
|
+
*/
|
|
159
|
+
function createLoaders(ctx, settings, settlers, onFailed) {
|
|
160
|
+
const load = (code) => {
|
|
161
|
+
const loader = settings.get().loaders[code];
|
|
162
|
+
if (!loader) {
|
|
163
|
+
ctx.state.update(endLocaleLoad, code);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
loader().then((locale) => {
|
|
167
|
+
ctx.state.update(registerLocale, locale);
|
|
168
|
+
if (ctx.state.get().loading === code) {
|
|
169
|
+
ctx.state.update(setLocale, locale.code);
|
|
170
|
+
settlers.settle(code, { ok: true });
|
|
171
|
+
}
|
|
172
|
+
}, (error) => {
|
|
173
|
+
ctx.state.update(endLocaleLoad, code);
|
|
174
|
+
onFailed(code, error);
|
|
175
|
+
settlers.settle(code, {
|
|
176
|
+
ok: false,
|
|
177
|
+
error: new _embedpdf_core.PluginError("operation-failed", "i18n", `locale '${code}' failed to load`, { details: (0, _embedpdf_core.toPluginErrorInfo)((0, _embedpdf_core.toPluginError)("i18n", error)) })
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
/** Fetch the language `loading` names now, and each one asked for after. */
|
|
182
|
+
const connect = () => {
|
|
183
|
+
const loadRequested = (code) => {
|
|
184
|
+
if (code) load(code);
|
|
185
|
+
};
|
|
186
|
+
loadRequested(ctx.state.get().loading);
|
|
187
|
+
ctx.watch(() => ctx.state.get().loading, loadRequested);
|
|
188
|
+
};
|
|
189
|
+
return { connect };
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/negotiate.ts
|
|
193
|
+
/**
|
|
194
|
+
* Pure locale negotiation over language tags (RFC 5646) — strings in, string
|
|
195
|
+
* out. The plugin never reads the platform; the embedder feeds it whatever
|
|
196
|
+
* request list its environment offers:
|
|
197
|
+
*
|
|
198
|
+
* ```ts
|
|
199
|
+
* // browser shell: negotiateLocale(['en', 'es', 'ar'], navigator.languages)
|
|
200
|
+
* // server render: negotiateLocale(codes, parseAcceptLanguage(header))
|
|
201
|
+
* ```
|
|
202
|
+
*
|
|
203
|
+
* Matching, per requested code in preference order:
|
|
204
|
+
* 1. exact match (case-insensitive): `es-MX` → `es-MX`
|
|
205
|
+
* 2. request narrowed to its language: `en-GB` → `en`
|
|
206
|
+
* 3. any available dialect of the language: `zh` → `zh-Hans`
|
|
207
|
+
*
|
|
208
|
+
* Steps 2 and 3 match only within the script the language is written in, as
|
|
209
|
+
* `Intl.Locale#maximize` gives it: `zh-TW` and `zh-HK` are Traditional
|
|
210
|
+
* Chinese, so `zh-CN` (Simplified) isn't theirs, and they get no match until
|
|
211
|
+
* a Traditional language is available. Where the platform can't tell a tag's
|
|
212
|
+
* script, the language alone decides.
|
|
213
|
+
*
|
|
214
|
+
* Returns the matched code exactly as it appears in `available`, or null.
|
|
215
|
+
*/
|
|
216
|
+
/** The script a tag's language is written in (`zh-TW` → `Hant`, `zh` → `Hans`), or null. */
|
|
217
|
+
function scriptOf(code) {
|
|
218
|
+
try {
|
|
219
|
+
return new Intl.Locale(code).maximize().script ?? null;
|
|
220
|
+
} catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/** Whether two tags are written in the same script, or one of them can't say. */
|
|
225
|
+
function sameScript(left, right) {
|
|
226
|
+
const leftScript = scriptOf(left);
|
|
227
|
+
const rightScript = scriptOf(right);
|
|
228
|
+
return leftScript === null || rightScript === null || leftScript === rightScript;
|
|
229
|
+
}
|
|
230
|
+
function negotiateLocale(available, requested) {
|
|
231
|
+
const byCanonical = /* @__PURE__ */ new Map();
|
|
232
|
+
for (const code of available) {
|
|
233
|
+
const canonical = code.toLowerCase();
|
|
234
|
+
if (!byCanonical.has(canonical)) byCanonical.set(canonical, code);
|
|
235
|
+
}
|
|
236
|
+
const languageOf = (code) => code.toLowerCase().split("-")[0];
|
|
237
|
+
for (const wanted of requested) {
|
|
238
|
+
const exact = byCanonical.get(wanted.toLowerCase());
|
|
239
|
+
if (exact !== void 0) return exact;
|
|
240
|
+
}
|
|
241
|
+
for (const wanted of requested) {
|
|
242
|
+
const narrowed = byCanonical.get(languageOf(wanted));
|
|
243
|
+
if (narrowed !== void 0 && sameScript(wanted, narrowed)) return narrowed;
|
|
244
|
+
}
|
|
245
|
+
for (const wanted of requested) {
|
|
246
|
+
const language = languageOf(wanted);
|
|
247
|
+
for (const [canonical, original] of byCanonical) if (languageOf(canonical) === language && sameScript(wanted, original)) return original;
|
|
248
|
+
}
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/strings/index.ts
|
|
253
|
+
const EMBEDPDF_STRINGS = {
|
|
254
|
+
en: {
|
|
255
|
+
commands: {
|
|
256
|
+
zoom: {
|
|
257
|
+
in: "Zoom in",
|
|
258
|
+
out: "Zoom out",
|
|
259
|
+
fitWidth: "Fit width",
|
|
260
|
+
fitPage: "Fit page"
|
|
261
|
+
},
|
|
262
|
+
page: {
|
|
263
|
+
next: "Next page",
|
|
264
|
+
previous: "Previous page",
|
|
265
|
+
first: "First page",
|
|
266
|
+
last: "Last page"
|
|
267
|
+
},
|
|
268
|
+
view: {
|
|
269
|
+
rotateClockwise: "Rotate clockwise",
|
|
270
|
+
rotateCounterclockwise: "Rotate counterclockwise"
|
|
271
|
+
},
|
|
272
|
+
selection: { copy: "Copy" },
|
|
273
|
+
annotation: { delete: "Delete" },
|
|
274
|
+
document: {
|
|
275
|
+
download: "Download",
|
|
276
|
+
print: "Print"
|
|
277
|
+
},
|
|
278
|
+
tool: {
|
|
279
|
+
pointer: "Select",
|
|
280
|
+
pan: "Pan",
|
|
281
|
+
highlight: "Highlight",
|
|
282
|
+
underline: "Underline",
|
|
283
|
+
strikeout: "Strikethrough",
|
|
284
|
+
squiggly: "Squiggly underline",
|
|
285
|
+
"insert-text": "Insert text",
|
|
286
|
+
"replace-text": "Replace text",
|
|
287
|
+
ink: "Draw",
|
|
288
|
+
"ink-highlight": "Highlighter",
|
|
289
|
+
"free-text": "Text box",
|
|
290
|
+
"free-text-callout": "Callout",
|
|
291
|
+
note: "Note",
|
|
292
|
+
square: "Rectangle",
|
|
293
|
+
circle: "Ellipse",
|
|
294
|
+
line: "Line",
|
|
295
|
+
polygon: "Polygon",
|
|
296
|
+
polyline: "Polyline",
|
|
297
|
+
link: "Link",
|
|
298
|
+
stamp: "Stamp",
|
|
299
|
+
attachment: "Attach file",
|
|
300
|
+
redact: "Redact",
|
|
301
|
+
distance: "Distance",
|
|
302
|
+
perimeter: "Perimeter",
|
|
303
|
+
area: "Area",
|
|
304
|
+
calibrate: "Calibrate",
|
|
305
|
+
"form-fill": "Fill form",
|
|
306
|
+
"form-edit": "Edit form",
|
|
307
|
+
"form-text": "Text field",
|
|
308
|
+
"form-checkbox": "Checkbox",
|
|
309
|
+
"form-radio": "Radio button",
|
|
310
|
+
"form-combobox": "Dropdown",
|
|
311
|
+
"form-listbox": "List box",
|
|
312
|
+
"form-signature": "Signature field"
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
outline: { untitled: "Untitled" }
|
|
316
|
+
},
|
|
317
|
+
nl: {
|
|
318
|
+
commands: {
|
|
319
|
+
zoom: {
|
|
320
|
+
in: "Inzoomen",
|
|
321
|
+
out: "Uitzoomen",
|
|
322
|
+
fitWidth: "Paginabreedte",
|
|
323
|
+
fitPage: "Hele pagina"
|
|
324
|
+
},
|
|
325
|
+
page: {
|
|
326
|
+
next: "Volgende pagina",
|
|
327
|
+
previous: "Vorige pagina",
|
|
328
|
+
first: "Eerste pagina",
|
|
329
|
+
last: "Laatste pagina"
|
|
330
|
+
},
|
|
331
|
+
view: {
|
|
332
|
+
rotateClockwise: "Rechtsom draaien",
|
|
333
|
+
rotateCounterclockwise: "Linksom draaien"
|
|
334
|
+
},
|
|
335
|
+
selection: { copy: "Kopiëren" },
|
|
336
|
+
annotation: { delete: "Verwijderen" },
|
|
337
|
+
document: {
|
|
338
|
+
download: "Downloaden",
|
|
339
|
+
print: "Afdrukken"
|
|
340
|
+
},
|
|
341
|
+
tool: {
|
|
342
|
+
pointer: "Selecteren",
|
|
343
|
+
pan: "Verschuiven",
|
|
344
|
+
highlight: "Markeren",
|
|
345
|
+
underline: "Onderstrepen",
|
|
346
|
+
strikeout: "Doorhalen",
|
|
347
|
+
squiggly: "Golflijn",
|
|
348
|
+
"insert-text": "Tekst invoegen",
|
|
349
|
+
"replace-text": "Tekst vervangen",
|
|
350
|
+
ink: "Tekenen",
|
|
351
|
+
"ink-highlight": "Markeerstift",
|
|
352
|
+
"free-text": "Tekstvak",
|
|
353
|
+
"free-text-callout": "Toelichting",
|
|
354
|
+
note: "Notitie",
|
|
355
|
+
square: "Rechthoek",
|
|
356
|
+
circle: "Ellips",
|
|
357
|
+
line: "Lijn",
|
|
358
|
+
polygon: "Veelhoek",
|
|
359
|
+
polyline: "Polylijn",
|
|
360
|
+
link: "Koppeling",
|
|
361
|
+
stamp: "Stempel",
|
|
362
|
+
attachment: "Bestand bijvoegen",
|
|
363
|
+
redact: "Redigeren",
|
|
364
|
+
distance: "Afstand",
|
|
365
|
+
perimeter: "Omtrek",
|
|
366
|
+
area: "Oppervlakte",
|
|
367
|
+
calibrate: "Kalibreren",
|
|
368
|
+
"form-fill": "Formulier invullen",
|
|
369
|
+
"form-edit": "Formulier bewerken",
|
|
370
|
+
"form-text": "Tekstveld",
|
|
371
|
+
"form-checkbox": "Selectievakje",
|
|
372
|
+
"form-radio": "Keuzerondje",
|
|
373
|
+
"form-combobox": "Vervolgkeuzelijst",
|
|
374
|
+
"form-listbox": "Keuzelijst",
|
|
375
|
+
"form-signature": "Handtekeningveld"
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
outline: { untitled: "Naamloos" }
|
|
379
|
+
},
|
|
380
|
+
de: {
|
|
381
|
+
commands: {
|
|
382
|
+
zoom: {
|
|
383
|
+
in: "Vergrößern",
|
|
384
|
+
out: "Verkleinern",
|
|
385
|
+
fitWidth: "Seitenbreite",
|
|
386
|
+
fitPage: "Ganze Seite"
|
|
387
|
+
},
|
|
388
|
+
page: {
|
|
389
|
+
next: "Nächste Seite",
|
|
390
|
+
previous: "Vorherige Seite",
|
|
391
|
+
first: "Erste Seite",
|
|
392
|
+
last: "Letzte Seite"
|
|
393
|
+
},
|
|
394
|
+
view: {
|
|
395
|
+
rotateClockwise: "Im Uhrzeigersinn drehen",
|
|
396
|
+
rotateCounterclockwise: "Gegen den Uhrzeigersinn drehen"
|
|
397
|
+
},
|
|
398
|
+
selection: { copy: "Kopieren" },
|
|
399
|
+
annotation: { delete: "Löschen" },
|
|
400
|
+
document: {
|
|
401
|
+
download: "Herunterladen",
|
|
402
|
+
print: "Drucken"
|
|
403
|
+
},
|
|
404
|
+
tool: {
|
|
405
|
+
pointer: "Auswählen",
|
|
406
|
+
pan: "Verschieben",
|
|
407
|
+
highlight: "Hervorheben",
|
|
408
|
+
underline: "Unterstreichen",
|
|
409
|
+
strikeout: "Durchstreichen",
|
|
410
|
+
squiggly: "Wellenlinie",
|
|
411
|
+
"insert-text": "Text einfügen",
|
|
412
|
+
"replace-text": "Text ersetzen",
|
|
413
|
+
ink: "Zeichnen",
|
|
414
|
+
"ink-highlight": "Textmarker",
|
|
415
|
+
"free-text": "Textkommentar",
|
|
416
|
+
"free-text-callout": "Legende",
|
|
417
|
+
note: "Notiz",
|
|
418
|
+
square: "Rechteck",
|
|
419
|
+
circle: "Ellipse",
|
|
420
|
+
line: "Linie",
|
|
421
|
+
polygon: "Polygon",
|
|
422
|
+
polyline: "Polylinie",
|
|
423
|
+
link: "Link",
|
|
424
|
+
stamp: "Stempel",
|
|
425
|
+
attachment: "Datei anhängen",
|
|
426
|
+
redact: "Schwärzen",
|
|
427
|
+
distance: "Abstand",
|
|
428
|
+
perimeter: "Umfang",
|
|
429
|
+
area: "Fläche",
|
|
430
|
+
calibrate: "Kalibrieren",
|
|
431
|
+
"form-fill": "Formular ausfüllen",
|
|
432
|
+
"form-edit": "Formular bearbeiten",
|
|
433
|
+
"form-text": "Textfeld",
|
|
434
|
+
"form-checkbox": "Kontrollkästchen",
|
|
435
|
+
"form-radio": "Optionsfeld",
|
|
436
|
+
"form-combobox": "Dropdown-Liste",
|
|
437
|
+
"form-listbox": "Listenfeld",
|
|
438
|
+
"form-signature": "Unterschriftsfeld"
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
outline: { untitled: "Unbenannt" }
|
|
442
|
+
},
|
|
443
|
+
fr: {
|
|
444
|
+
commands: {
|
|
445
|
+
zoom: {
|
|
446
|
+
in: "Zoom avant",
|
|
447
|
+
out: "Zoom arrière",
|
|
448
|
+
fitWidth: "Pleine largeur",
|
|
449
|
+
fitPage: "Page entière"
|
|
450
|
+
},
|
|
451
|
+
page: {
|
|
452
|
+
next: "Page suivante",
|
|
453
|
+
previous: "Page précédente",
|
|
454
|
+
first: "Première page",
|
|
455
|
+
last: "Dernière page"
|
|
456
|
+
},
|
|
457
|
+
view: {
|
|
458
|
+
rotateClockwise: "Rotation horaire",
|
|
459
|
+
rotateCounterclockwise: "Rotation antihoraire"
|
|
460
|
+
},
|
|
461
|
+
selection: { copy: "Copier" },
|
|
462
|
+
annotation: { delete: "Supprimer" },
|
|
463
|
+
document: {
|
|
464
|
+
download: "Télécharger",
|
|
465
|
+
print: "Imprimer"
|
|
466
|
+
},
|
|
467
|
+
tool: {
|
|
468
|
+
pointer: "Sélectionner",
|
|
469
|
+
pan: "Déplacer",
|
|
470
|
+
highlight: "Surligner",
|
|
471
|
+
underline: "Souligner",
|
|
472
|
+
strikeout: "Barrer",
|
|
473
|
+
squiggly: "Soulignement ondulé",
|
|
474
|
+
"insert-text": "Insérer du texte",
|
|
475
|
+
"replace-text": "Remplacer le texte",
|
|
476
|
+
ink: "Dessiner",
|
|
477
|
+
"ink-highlight": "Surligneur",
|
|
478
|
+
"free-text": "Zone de texte",
|
|
479
|
+
"free-text-callout": "Légende",
|
|
480
|
+
note: "Note",
|
|
481
|
+
square: "Rectangle",
|
|
482
|
+
circle: "Ellipse",
|
|
483
|
+
line: "Ligne",
|
|
484
|
+
polygon: "Polygone",
|
|
485
|
+
polyline: "Polyligne",
|
|
486
|
+
link: "Lien",
|
|
487
|
+
stamp: "Tampon",
|
|
488
|
+
attachment: "Joindre un fichier",
|
|
489
|
+
redact: "Biffer",
|
|
490
|
+
distance: "Distance",
|
|
491
|
+
perimeter: "Périmètre",
|
|
492
|
+
area: "Aire",
|
|
493
|
+
calibrate: "Étalonner",
|
|
494
|
+
"form-fill": "Remplir le formulaire",
|
|
495
|
+
"form-edit": "Modifier le formulaire",
|
|
496
|
+
"form-text": "Champ de texte",
|
|
497
|
+
"form-checkbox": "Case à cocher",
|
|
498
|
+
"form-radio": "Bouton radio",
|
|
499
|
+
"form-combobox": "Liste déroulante",
|
|
500
|
+
"form-listbox": "Zone de liste",
|
|
501
|
+
"form-signature": "Champ de signature"
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
outline: { untitled: "Sans titre" }
|
|
505
|
+
},
|
|
506
|
+
es: {
|
|
507
|
+
commands: {
|
|
508
|
+
zoom: {
|
|
509
|
+
in: "Acercar",
|
|
510
|
+
out: "Alejar",
|
|
511
|
+
fitWidth: "Ajustar al ancho",
|
|
512
|
+
fitPage: "Ajustar a la página"
|
|
513
|
+
},
|
|
514
|
+
page: {
|
|
515
|
+
next: "Página siguiente",
|
|
516
|
+
previous: "Página anterior",
|
|
517
|
+
first: "Primera página",
|
|
518
|
+
last: "Última página"
|
|
519
|
+
},
|
|
520
|
+
view: {
|
|
521
|
+
rotateClockwise: "Girar a la derecha",
|
|
522
|
+
rotateCounterclockwise: "Girar a la izquierda"
|
|
523
|
+
},
|
|
524
|
+
selection: { copy: "Copiar" },
|
|
525
|
+
annotation: { delete: "Eliminar" },
|
|
526
|
+
document: {
|
|
527
|
+
download: "Descargar",
|
|
528
|
+
print: "Imprimir"
|
|
529
|
+
},
|
|
530
|
+
tool: {
|
|
531
|
+
pointer: "Seleccionar",
|
|
532
|
+
pan: "Desplazar",
|
|
533
|
+
highlight: "Resaltar",
|
|
534
|
+
underline: "Subrayar",
|
|
535
|
+
strikeout: "Tachar",
|
|
536
|
+
squiggly: "Subrayado ondulado",
|
|
537
|
+
"insert-text": "Insertar texto",
|
|
538
|
+
"replace-text": "Reemplazar texto",
|
|
539
|
+
ink: "Dibujar",
|
|
540
|
+
"ink-highlight": "Marcador",
|
|
541
|
+
"free-text": "Cuadro de texto",
|
|
542
|
+
"free-text-callout": "Llamada",
|
|
543
|
+
note: "Nota",
|
|
544
|
+
square: "Rectángulo",
|
|
545
|
+
circle: "Elipse",
|
|
546
|
+
line: "Línea",
|
|
547
|
+
polygon: "Polígono",
|
|
548
|
+
polyline: "Polilínea",
|
|
549
|
+
link: "Enlace",
|
|
550
|
+
stamp: "Sello",
|
|
551
|
+
attachment: "Adjuntar archivo",
|
|
552
|
+
redact: "Censurar",
|
|
553
|
+
distance: "Distancia",
|
|
554
|
+
perimeter: "Perímetro",
|
|
555
|
+
area: "Área",
|
|
556
|
+
calibrate: "Calibrar",
|
|
557
|
+
"form-fill": "Rellenar formulario",
|
|
558
|
+
"form-edit": "Editar formulario",
|
|
559
|
+
"form-text": "Campo de texto",
|
|
560
|
+
"form-checkbox": "Casilla de verificación",
|
|
561
|
+
"form-radio": "Botón de opción",
|
|
562
|
+
"form-combobox": "Lista desplegable",
|
|
563
|
+
"form-listbox": "Cuadro de lista",
|
|
564
|
+
"form-signature": "Campo de firma"
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
outline: { untitled: "Sin título" }
|
|
568
|
+
},
|
|
569
|
+
"zh-CN": {
|
|
570
|
+
commands: {
|
|
571
|
+
zoom: {
|
|
572
|
+
in: "放大",
|
|
573
|
+
out: "缩小",
|
|
574
|
+
fitWidth: "适合宽度",
|
|
575
|
+
fitPage: "适合页面"
|
|
576
|
+
},
|
|
577
|
+
page: {
|
|
578
|
+
next: "下一页",
|
|
579
|
+
previous: "上一页",
|
|
580
|
+
first: "第一页",
|
|
581
|
+
last: "最后一页"
|
|
582
|
+
},
|
|
583
|
+
view: {
|
|
584
|
+
rotateClockwise: "顺时针旋转",
|
|
585
|
+
rotateCounterclockwise: "逆时针旋转"
|
|
586
|
+
},
|
|
587
|
+
selection: { copy: "复制" },
|
|
588
|
+
annotation: { delete: "删除" },
|
|
589
|
+
document: {
|
|
590
|
+
download: "下载",
|
|
591
|
+
print: "打印"
|
|
592
|
+
},
|
|
593
|
+
tool: {
|
|
594
|
+
pointer: "选择",
|
|
595
|
+
pan: "平移",
|
|
596
|
+
highlight: "高亮",
|
|
597
|
+
underline: "下划线",
|
|
598
|
+
strikeout: "删除线",
|
|
599
|
+
squiggly: "波浪线",
|
|
600
|
+
"insert-text": "插入文本",
|
|
601
|
+
"replace-text": "替换文本",
|
|
602
|
+
ink: "绘图",
|
|
603
|
+
"ink-highlight": "荧光笔",
|
|
604
|
+
"free-text": "文本框",
|
|
605
|
+
"free-text-callout": "标注",
|
|
606
|
+
note: "便笺",
|
|
607
|
+
square: "矩形",
|
|
608
|
+
circle: "椭圆",
|
|
609
|
+
line: "直线",
|
|
610
|
+
polygon: "多边形",
|
|
611
|
+
polyline: "折线",
|
|
612
|
+
link: "链接",
|
|
613
|
+
stamp: "图章",
|
|
614
|
+
attachment: "附加文件",
|
|
615
|
+
redact: "涂黑",
|
|
616
|
+
distance: "距离",
|
|
617
|
+
perimeter: "周长",
|
|
618
|
+
area: "面积",
|
|
619
|
+
calibrate: "校准",
|
|
620
|
+
"form-fill": "填写表单",
|
|
621
|
+
"form-edit": "编辑表单",
|
|
622
|
+
"form-text": "文本字段",
|
|
623
|
+
"form-checkbox": "复选框",
|
|
624
|
+
"form-radio": "单选按钮",
|
|
625
|
+
"form-combobox": "下拉列表",
|
|
626
|
+
"form-listbox": "列表框",
|
|
627
|
+
"form-signature": "签名字段"
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
outline: { untitled: "无标题" }
|
|
631
|
+
},
|
|
632
|
+
sv: {
|
|
633
|
+
commands: {
|
|
634
|
+
zoom: {
|
|
635
|
+
in: "Zooma in",
|
|
636
|
+
out: "Zooma ut",
|
|
637
|
+
fitWidth: "Anpassa till bredd",
|
|
638
|
+
fitPage: "Anpassa till sida"
|
|
639
|
+
},
|
|
640
|
+
page: {
|
|
641
|
+
next: "Nästa sida",
|
|
642
|
+
previous: "Föregående sida",
|
|
643
|
+
first: "Första sidan",
|
|
644
|
+
last: "Sista sidan"
|
|
645
|
+
},
|
|
646
|
+
view: {
|
|
647
|
+
rotateClockwise: "Rotera medurs",
|
|
648
|
+
rotateCounterclockwise: "Rotera moturs"
|
|
649
|
+
},
|
|
650
|
+
selection: { copy: "Kopiera" },
|
|
651
|
+
annotation: { delete: "Ta bort" },
|
|
652
|
+
document: {
|
|
653
|
+
download: "Ladda ned",
|
|
654
|
+
print: "Skriv ut"
|
|
655
|
+
},
|
|
656
|
+
tool: {
|
|
657
|
+
pointer: "Välj",
|
|
658
|
+
pan: "Panorera",
|
|
659
|
+
highlight: "Markera",
|
|
660
|
+
underline: "Stryk under",
|
|
661
|
+
strikeout: "Stryk över",
|
|
662
|
+
squiggly: "Vågig understrykning",
|
|
663
|
+
"insert-text": "Infoga text",
|
|
664
|
+
"replace-text": "Ersätt text",
|
|
665
|
+
ink: "Rita",
|
|
666
|
+
"ink-highlight": "Överstrykningspenna",
|
|
667
|
+
"free-text": "Textruta",
|
|
668
|
+
"free-text-callout": "Pratbubbla",
|
|
669
|
+
note: "Anteckning",
|
|
670
|
+
square: "Rektangel",
|
|
671
|
+
circle: "Ellips",
|
|
672
|
+
line: "Linje",
|
|
673
|
+
polygon: "Polygon",
|
|
674
|
+
polyline: "Polylinje",
|
|
675
|
+
link: "Länk",
|
|
676
|
+
stamp: "Stämpel",
|
|
677
|
+
attachment: "Bifoga fil",
|
|
678
|
+
redact: "Maskera",
|
|
679
|
+
distance: "Avstånd",
|
|
680
|
+
perimeter: "Omkrets",
|
|
681
|
+
area: "Area",
|
|
682
|
+
calibrate: "Kalibrera",
|
|
683
|
+
"form-fill": "Fyll i formulär",
|
|
684
|
+
"form-edit": "Redigera formulär",
|
|
685
|
+
"form-text": "Textfält",
|
|
686
|
+
"form-checkbox": "Kryssruta",
|
|
687
|
+
"form-radio": "Alternativknapp",
|
|
688
|
+
"form-combobox": "Rullgardinsmeny",
|
|
689
|
+
"form-listbox": "Listruta",
|
|
690
|
+
"form-signature": "Signaturfält"
|
|
691
|
+
}
|
|
692
|
+
},
|
|
693
|
+
outline: { untitled: "Namnlös" }
|
|
694
|
+
},
|
|
695
|
+
ja: {
|
|
696
|
+
commands: {
|
|
697
|
+
zoom: {
|
|
698
|
+
in: "拡大",
|
|
699
|
+
out: "縮小",
|
|
700
|
+
fitWidth: "幅に合わせる",
|
|
701
|
+
fitPage: "ページ全体を表示"
|
|
702
|
+
},
|
|
703
|
+
page: {
|
|
704
|
+
next: "次のページ",
|
|
705
|
+
previous: "前のページ",
|
|
706
|
+
first: "最初のページ",
|
|
707
|
+
last: "最後のページ"
|
|
708
|
+
},
|
|
709
|
+
view: {
|
|
710
|
+
rotateClockwise: "右に回転",
|
|
711
|
+
rotateCounterclockwise: "左に回転"
|
|
712
|
+
},
|
|
713
|
+
selection: { copy: "コピー" },
|
|
714
|
+
annotation: { delete: "削除" },
|
|
715
|
+
document: {
|
|
716
|
+
download: "ダウンロード",
|
|
717
|
+
print: "印刷"
|
|
718
|
+
},
|
|
719
|
+
tool: {
|
|
720
|
+
pointer: "選択",
|
|
721
|
+
pan: "パン",
|
|
722
|
+
highlight: "ハイライト",
|
|
723
|
+
underline: "下線",
|
|
724
|
+
strikeout: "取り消し線",
|
|
725
|
+
squiggly: "波線",
|
|
726
|
+
"insert-text": "テキストを挿入",
|
|
727
|
+
"replace-text": "テキストを置換",
|
|
728
|
+
ink: "描画",
|
|
729
|
+
"ink-highlight": "蛍光ペン",
|
|
730
|
+
"free-text": "テキストボックス",
|
|
731
|
+
"free-text-callout": "引き出し線",
|
|
732
|
+
note: "ノート",
|
|
733
|
+
square: "長方形",
|
|
734
|
+
circle: "楕円",
|
|
735
|
+
line: "線",
|
|
736
|
+
polygon: "多角形",
|
|
737
|
+
polyline: "折れ線",
|
|
738
|
+
link: "リンク",
|
|
739
|
+
stamp: "スタンプ",
|
|
740
|
+
attachment: "ファイルを添付",
|
|
741
|
+
redact: "墨消し",
|
|
742
|
+
distance: "距離",
|
|
743
|
+
perimeter: "周囲長",
|
|
744
|
+
area: "面積",
|
|
745
|
+
calibrate: "校正",
|
|
746
|
+
"form-fill": "フォームに入力",
|
|
747
|
+
"form-edit": "フォームを編集",
|
|
748
|
+
"form-text": "テキストフィールド",
|
|
749
|
+
"form-checkbox": "チェックボックス",
|
|
750
|
+
"form-radio": "ラジオボタン",
|
|
751
|
+
"form-combobox": "ドロップダウン",
|
|
752
|
+
"form-listbox": "リストボックス",
|
|
753
|
+
"form-signature": "署名フィールド"
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
outline: { untitled: "無題" }
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
const CODES = Object.keys(EMBEDPDF_STRINGS);
|
|
760
|
+
/** Which of EmbedPDF's languages each code reads, decided once per code. */
|
|
761
|
+
const languageOf = /* @__PURE__ */ new Map();
|
|
5
762
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* ports to Rust verbatim.
|
|
763
|
+
* EmbedPDF's strings for a language code, matched as `negotiateLocale` matches: `nl-BE` reads
|
|
764
|
+
* Dutch and `zh` Simplified Chinese. `undefined` for a language EmbedPDF has no strings in.
|
|
9
765
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (
|
|
13
|
-
|
|
766
|
+
function embedpdfStringsFor(code) {
|
|
767
|
+
let language = languageOf.get(code);
|
|
768
|
+
if (language === void 0) {
|
|
769
|
+
language = negotiateLocale(CODES, [code]);
|
|
770
|
+
languageOf.set(code, language);
|
|
771
|
+
}
|
|
772
|
+
return language === null ? void 0 : EMBEDPDF_STRINGS[language];
|
|
773
|
+
}
|
|
774
|
+
//#endregion
|
|
775
|
+
//#region src/translate.ts
|
|
776
|
+
/** Walk a dotted path (`'commands.zoom.in'`) through a dictionary. */
|
|
777
|
+
function lookup(dictionary, key) {
|
|
778
|
+
if (!dictionary) return void 0;
|
|
779
|
+
let node = dictionary;
|
|
14
780
|
for (const part of key.split(".")) {
|
|
15
781
|
if (node === void 0 || typeof node === "string") return void 0;
|
|
16
782
|
node = node[part];
|
|
17
783
|
}
|
|
18
784
|
return node;
|
|
19
785
|
}
|
|
20
|
-
/**
|
|
21
|
-
* lacks ICU data for the locale. */
|
|
786
|
+
/** The language's plural form for a count; `'one'` or `'other'` when the runtime has no rules for it. */
|
|
22
787
|
function pluralCategory(locale, count) {
|
|
23
788
|
try {
|
|
24
789
|
return new Intl.PluralRules(locale).select(count);
|
|
@@ -26,7 +791,7 @@ function pluralCategory(locale, count) {
|
|
|
26
791
|
return count === 1 ? "one" : "other";
|
|
27
792
|
}
|
|
28
793
|
}
|
|
29
|
-
/** A
|
|
794
|
+
/** A string is the answer; a branch needs a plural form, picked by `params.count`. */
|
|
30
795
|
function resolveNode(node, locale, params) {
|
|
31
796
|
if (typeof node === "string") return node;
|
|
32
797
|
if (node !== void 0 && typeof params?.count === "number") {
|
|
@@ -34,18 +799,22 @@ function resolveNode(node, locale, params) {
|
|
|
34
799
|
if (typeof branch === "string") return branch;
|
|
35
800
|
}
|
|
36
801
|
}
|
|
37
|
-
/**
|
|
802
|
+
/** Fill in `{name}` slots; a slot without a value stays as it is, so it shows while testing. */
|
|
38
803
|
function interpolate(text, params) {
|
|
39
804
|
if (!params) return text;
|
|
40
805
|
return text.replace(/\{(\w+)\}/g, (match, slot) => params[slot] !== void 0 ? String(params[slot]) : match);
|
|
41
806
|
}
|
|
807
|
+
/** A key in one language: the app's string, then EmbedPDF's own. */
|
|
808
|
+
const stringIn = (sources, code, key, params) => resolveNode(lookup(sources.locales[code]?.translations, key), code, params) ?? resolveNode(lookup(embedpdfStringsFor(code), key), code, params);
|
|
42
809
|
/**
|
|
43
|
-
*
|
|
44
|
-
* `options.fallback` (
|
|
810
|
+
* A key's string: in the current language (the key, then `options.fallbackKey`), then the same in
|
|
811
|
+
* the fallback language, then `options.fallback` (with its slots filled too), then the key itself.
|
|
45
812
|
*/
|
|
46
|
-
function translate(
|
|
47
|
-
const
|
|
48
|
-
const
|
|
813
|
+
function translate(sources, key, options) {
|
|
814
|
+
const params = options?.params;
|
|
815
|
+
const fallbackKey = options?.fallbackKey;
|
|
816
|
+
const inLanguage = (code) => stringIn(sources, code, key, params) ?? (fallbackKey === void 0 ? void 0 : stringIn(sources, code, fallbackKey, params));
|
|
817
|
+
const text = inLanguage(sources.locale) ?? inLanguage(sources.fallbackLocale);
|
|
49
818
|
if (text !== void 0) return {
|
|
50
819
|
text: interpolate(text, options?.params),
|
|
51
820
|
found: true
|
|
@@ -60,207 +829,196 @@ function translate(state, key, options) {
|
|
|
60
829
|
};
|
|
61
830
|
}
|
|
62
831
|
//#endregion
|
|
63
|
-
//#region src/
|
|
832
|
+
//#region src/controller.ts
|
|
64
833
|
/**
|
|
65
|
-
* The i18n
|
|
66
|
-
*
|
|
67
|
-
*
|
|
834
|
+
* The i18n controller: reads over the state, the language verbs, the
|
|
835
|
+
* `onLocaleChanged` derivation, and the loader wired at `connect`. Built
|
|
836
|
+
* synchronously in `createKernel()`, so `t()` works before the engine exists.
|
|
68
837
|
*/
|
|
69
|
-
|
|
838
|
+
const unknownLocale = (code) => new _embedpdf_core.PluginError("not-found", "i18n", `unknown locale '${code}': not loaded and not in loaders`);
|
|
839
|
+
function createI18nController(ctx) {
|
|
840
|
+
const settings = ctx.settings();
|
|
841
|
+
const localeChanged = ctx.events.source();
|
|
842
|
+
const localeLoadFailed = ctx.events.source();
|
|
843
|
+
const state = () => ctx.state.get();
|
|
844
|
+
ctx.state.update(seedFromSettings, settings.get());
|
|
845
|
+
let announced = state().locale;
|
|
846
|
+
ctx.state.onChange(({ next }) => {
|
|
847
|
+
if (next.loading !== null || next.locale === announced) return;
|
|
848
|
+
const previousLocale = announced;
|
|
849
|
+
announced = next.locale;
|
|
850
|
+
localeChanged.emit({
|
|
851
|
+
locale: next.locale,
|
|
852
|
+
previousLocale
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
const sources = () => ({
|
|
856
|
+
locale: state().locale,
|
|
857
|
+
fallbackLocale: settings.get().fallbackLocale,
|
|
858
|
+
locales: state().locales
|
|
859
|
+
});
|
|
70
860
|
const warned = /* @__PURE__ */ new Set();
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
861
|
+
const translateKey = (key, options) => {
|
|
862
|
+
const result = translate(sources(), key, options);
|
|
863
|
+
const missing = options?.fallbackKey === void 0 ? `"${key}"` : `"${key}" or "${options.fallbackKey}"`;
|
|
864
|
+
if (!result.found && options?.fallback === void 0 && !warned.has(missing)) {
|
|
865
|
+
warned.add(missing);
|
|
866
|
+
console.warn(`[i18n] missing translation ${missing} (locale: ${state().locale})`);
|
|
867
|
+
}
|
|
868
|
+
return result.text;
|
|
75
869
|
};
|
|
76
|
-
return
|
|
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
|
-
|
|
870
|
+
/** A new translate function only when the strings it can return may differ. */
|
|
871
|
+
const getTranslator = (0, _embedpdf_core.memo)(() => [
|
|
872
|
+
state().locale,
|
|
873
|
+
state().locales,
|
|
874
|
+
settings.get().fallbackLocale
|
|
875
|
+
], (_locale, _locales, _fallbackLocale) => (key, options) => translateKey(key, options));
|
|
876
|
+
/** The languages a picker shows, rebuilt only when the languages or the loaders change. */
|
|
877
|
+
const listLocales = (0, _embedpdf_core.memo)(() => [state().locales, settings.get().loaders], (locales, loaders) => {
|
|
878
|
+
const known = Object.values(locales).map((locale) => ({
|
|
879
|
+
code: locale.code,
|
|
880
|
+
name: locale.name,
|
|
881
|
+
direction: locale.direction ?? "ltr",
|
|
882
|
+
loaded: true
|
|
883
|
+
}));
|
|
884
|
+
for (const code of Object.keys(loaders)) if (!locales[code]) known.push({
|
|
885
|
+
code,
|
|
886
|
+
name: code,
|
|
887
|
+
direction: "ltr",
|
|
888
|
+
loaded: false
|
|
889
|
+
});
|
|
890
|
+
return known;
|
|
891
|
+
});
|
|
892
|
+
/** The caller waiting for a language to load; a newer switch replaces it. */
|
|
893
|
+
const pending = /* @__PURE__ */ new Map();
|
|
894
|
+
const settle = (code, outcome) => {
|
|
895
|
+
const waiter = pending.get(code);
|
|
896
|
+
if (!waiter) return;
|
|
897
|
+
pending.delete(code);
|
|
898
|
+
if (outcome.ok) waiter.resolve();
|
|
899
|
+
else waiter.reject(outcome.error);
|
|
900
|
+
};
|
|
901
|
+
const supersede = () => {
|
|
902
|
+
for (const [code, waiter] of pending) {
|
|
903
|
+
pending.delete(code);
|
|
904
|
+
waiter.reject(new _embedpdf_core.PluginError("operation-cancelled", "i18n", `switching to '${code}' was replaced`));
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
const loaders = createLoaders(ctx, settings, { settle }, (locale, error) => localeLoadFailed.emit({
|
|
908
|
+
locale,
|
|
909
|
+
error: (0, _embedpdf_core.toPluginErrorInfo)((0, _embedpdf_core.toPluginError)("i18n", error))
|
|
910
|
+
}));
|
|
911
|
+
const setLocale$1 = (code, options = {}) => {
|
|
912
|
+
const { signal } = options;
|
|
913
|
+
if (signal?.aborted) return Promise.reject(new _embedpdf_core.PluginError("operation-cancelled", "i18n", "operation cancelled"));
|
|
914
|
+
if (state().locales[code]) {
|
|
915
|
+
supersede();
|
|
916
|
+
ctx.state.update(setLocale, code);
|
|
917
|
+
return Promise.resolve();
|
|
918
|
+
}
|
|
919
|
+
if (!settings.get().loaders[code]) return Promise.reject(unknownLocale(code));
|
|
920
|
+
supersede();
|
|
921
|
+
const loaded = new Promise((resolve, reject) => {
|
|
922
|
+
pending.set(code, {
|
|
923
|
+
resolve,
|
|
924
|
+
reject
|
|
112
925
|
});
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
})
|
|
926
|
+
ctx.state.update(startLocaleLoad, code);
|
|
927
|
+
});
|
|
928
|
+
signal?.addEventListener("abort", () => {
|
|
929
|
+
if (!pending.delete(code)) return;
|
|
930
|
+
ctx.state.update(endLocaleLoad, code);
|
|
931
|
+
}, { once: true });
|
|
932
|
+
return ctx.cancellable(signal, loaded);
|
|
119
933
|
};
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const loader = config.loaders?.[code];
|
|
133
|
-
if (!loader) return;
|
|
134
|
-
loader().then((locale) => {
|
|
135
|
-
ctx.dispatch({
|
|
136
|
-
type: "I18N/REGISTER_LOCALE",
|
|
137
|
-
locale
|
|
138
|
-
});
|
|
139
|
-
if (ctx.getState().loading === code) ctx.dispatch({
|
|
140
|
-
type: "I18N/SET_LOCALE",
|
|
141
|
-
locale: locale.code
|
|
934
|
+
/** Follow a settings change: new languages register, and a new `locale` becomes current. */
|
|
935
|
+
const followSettings = () => {
|
|
936
|
+
let previous = settings.get();
|
|
937
|
+
ctx.listen(settings.api.onSettingsChanged, ({ settings: next, changed }) => {
|
|
938
|
+
if (changed.includes("locales")) {
|
|
939
|
+
for (const locale of previous.locales) if (!next.locales.some((other) => other.code === locale.code)) ctx.state.update(unregisterLocale, locale.code);
|
|
940
|
+
for (const locale of next.locales) if (!previous.locales.includes(locale)) ctx.state.update(registerLocale, locale);
|
|
941
|
+
}
|
|
942
|
+
if (changed.includes("locale")) {
|
|
943
|
+
const code = next.locale ?? next.fallbackLocale;
|
|
944
|
+
setLocale$1(code).catch((error) => {
|
|
945
|
+
if ((0, _embedpdf_core.isPluginError)(error, "not-found")) console.warn(`[i18n] ${error.message}`);
|
|
142
946
|
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
locale: code
|
|
146
|
-
}));
|
|
147
|
-
};
|
|
148
|
-
const pending = ctx.getState().loading;
|
|
149
|
-
if (pending) load(pending);
|
|
150
|
-
ctx.watch(() => ctx.getState().loading, (code) => {
|
|
151
|
-
if (code) load(code);
|
|
947
|
+
}
|
|
948
|
+
previous = next;
|
|
152
949
|
});
|
|
153
950
|
};
|
|
154
|
-
}
|
|
155
|
-
//#endregion
|
|
156
|
-
//#region src/reducer.ts
|
|
157
|
-
/**
|
|
158
|
-
* Pure and serializable — locale packs are data, so they live HERE, not in a
|
|
159
|
-
* side-table. One source of truth: `t()` is a pure function of this state and
|
|
160
|
-
* reactivity rides the kernel's one change stream (no emitters).
|
|
161
|
-
*/
|
|
162
|
-
function initialI18nState(config) {
|
|
163
|
-
const fallbackLocale = config.fallbackLocale ?? "en";
|
|
164
|
-
const locales = {};
|
|
165
|
-
for (const locale of config.locales ?? []) locales[locale.code] = locale;
|
|
166
|
-
const locale = config.locale ?? fallbackLocale;
|
|
167
951
|
return {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
952
|
+
api: {
|
|
953
|
+
...settings.api,
|
|
954
|
+
t: translateKey,
|
|
955
|
+
getTranslator,
|
|
956
|
+
hasKey: (key) => translate(sources(), key).found,
|
|
957
|
+
getLocale: () => state().locale,
|
|
958
|
+
getDirection: () => state().locales[state().locale]?.direction ?? "ltr",
|
|
959
|
+
listLocales,
|
|
960
|
+
getLoadingLocale: () => state().loading,
|
|
961
|
+
setLocale: setLocale$1,
|
|
962
|
+
registerLocale: (locale) => {
|
|
963
|
+
ctx.state.update(registerLocale, locale);
|
|
964
|
+
return () => ctx.state.update(unregisterLocale, locale.code);
|
|
965
|
+
},
|
|
966
|
+
addTranslations: (code, translations) => {
|
|
967
|
+
if (!state().locales[code] && !settings.get().loaders[code]) throw unknownLocale(code);
|
|
968
|
+
ctx.state.update(addTranslations, code, translations);
|
|
969
|
+
},
|
|
970
|
+
onLocaleChanged: localeChanged.on,
|
|
971
|
+
onLocaleLoadFailed: localeLoadFailed.on
|
|
972
|
+
},
|
|
973
|
+
connect() {
|
|
974
|
+
loaders.connect();
|
|
975
|
+
followSettings();
|
|
976
|
+
}
|
|
172
977
|
};
|
|
173
978
|
}
|
|
174
|
-
function i18nReducer(state, action) {
|
|
175
|
-
switch (action.type) {
|
|
176
|
-
case "I18N/SET_LOCALE":
|
|
177
|
-
if (!state.locales[action.locale]) return state;
|
|
178
|
-
if (state.locale === action.locale && state.loading === null) return state;
|
|
179
|
-
return {
|
|
180
|
-
...state,
|
|
181
|
-
locale: action.locale,
|
|
182
|
-
loading: null
|
|
183
|
-
};
|
|
184
|
-
case "I18N/REGISTER_LOCALE": return {
|
|
185
|
-
...state,
|
|
186
|
-
locales: {
|
|
187
|
-
...state.locales,
|
|
188
|
-
[action.locale.code]: action.locale
|
|
189
|
-
}
|
|
190
|
-
};
|
|
191
|
-
case "I18N/LOAD_STARTED": return state.loading === action.locale ? state : {
|
|
192
|
-
...state,
|
|
193
|
-
loading: action.locale
|
|
194
|
-
};
|
|
195
|
-
case "I18N/LOAD_FAILED": return state.loading === action.locale ? {
|
|
196
|
-
...state,
|
|
197
|
-
loading: null
|
|
198
|
-
} : state;
|
|
199
|
-
default: return state;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
979
|
//#endregion
|
|
203
980
|
//#region src/i18n.plugin.ts
|
|
204
981
|
/**
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
* the engine is still booting.
|
|
982
|
+
* Translations and the current language. Workspace-scoped, with no dependencies and no engine
|
|
983
|
+
* calls: the capability exists from `createKernel()`, so the viewer translates while the engine
|
|
984
|
+
* is still loading. `config` is the settings the app registers, over {@link I18N_DEFAULTS}.
|
|
209
985
|
*/
|
|
210
|
-
const i18nPlugin = (config
|
|
986
|
+
const i18nPlugin = (config) => (0, _embedpdf_core.definePlugin)({
|
|
211
987
|
id: "i18n",
|
|
212
988
|
scope: "workspace",
|
|
213
989
|
token: require_contract.I18nToken,
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
990
|
+
state: initialI18nState,
|
|
991
|
+
settings: {
|
|
992
|
+
defaults: require_contract.I18N_DEFAULTS,
|
|
993
|
+
registered: config
|
|
994
|
+
},
|
|
995
|
+
create: createI18nController
|
|
218
996
|
});
|
|
219
997
|
//#endregion
|
|
220
|
-
//#region src/
|
|
998
|
+
//#region src/state.ts
|
|
221
999
|
/**
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
* environment offers:
|
|
225
|
-
*
|
|
226
|
-
* ```ts
|
|
227
|
-
* // browser shell: negotiateLocale(['en', 'es', 'ar'], navigator.languages)
|
|
228
|
-
* // server render: negotiateLocale(codes, parseAcceptLanguage(header))
|
|
229
|
-
* ```
|
|
230
|
-
*
|
|
231
|
-
* Matching, per requested code in preference order:
|
|
232
|
-
* 1. exact match (case-insensitive): 'es-MX' → 'es-MX'
|
|
233
|
-
* 2. request narrowed to its language: 'en-GB' → 'en'
|
|
234
|
-
* 3. any available dialect of the language: 'zh' → 'zh-Hans'
|
|
235
|
-
*
|
|
236
|
-
* Returns the matched code exactly as it appears in `available`, or null.
|
|
1000
|
+
* The translations page's State table as code: what `useI18nState()` returns,
|
|
1001
|
+
* and the same fields in every other framework.
|
|
237
1002
|
*/
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
1003
|
+
const i18nState = (0, _embedpdf_core.defineState)(require_contract.I18nToken, {
|
|
1004
|
+
read: (i18n) => ({
|
|
1005
|
+
locale: i18n.getLocale(),
|
|
1006
|
+
direction: i18n.getDirection(),
|
|
1007
|
+
locales: i18n.listLocales(),
|
|
1008
|
+
loading: i18n.getLoadingLocale()
|
|
1009
|
+
}),
|
|
1010
|
+
empty: {
|
|
1011
|
+
locale: "en",
|
|
1012
|
+
direction: "ltr",
|
|
1013
|
+
locales: [],
|
|
1014
|
+
loading: null
|
|
248
1015
|
}
|
|
249
|
-
|
|
250
|
-
const narrowed = byCanon.get(languageOf(want));
|
|
251
|
-
if (narrowed !== void 0) return narrowed;
|
|
252
|
-
}
|
|
253
|
-
for (const want of requested) {
|
|
254
|
-
const language = languageOf(want);
|
|
255
|
-
for (const [canon, original] of byCanon) if (languageOf(canon) === language) return original;
|
|
256
|
-
}
|
|
257
|
-
return null;
|
|
258
|
-
}
|
|
1016
|
+
});
|
|
259
1017
|
//#endregion
|
|
1018
|
+
exports.I18N_DEFAULTS = require_contract.I18N_DEFAULTS;
|
|
260
1019
|
exports.I18nToken = require_contract.I18nToken;
|
|
261
1020
|
exports.i18nPlugin = i18nPlugin;
|
|
262
|
-
exports.
|
|
1021
|
+
exports.i18nState = i18nState;
|
|
263
1022
|
exports.negotiateLocale = negotiateLocale;
|
|
264
|
-
exports.translate = translate;
|
|
265
1023
|
|
|
266
1024
|
//# sourceMappingURL=index.cjs.map
|