@sveltekit-i18n/base 3.2.0 → 3.3.1
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/README.md +37 -31
- package/dist/I18n.svelte.d.ts +45 -2
- package/dist/I18n.svelte.js +404 -109
- package/dist/kit/define.svelte.js +137 -37
- package/dist/kit/internal.d.ts +8 -0
- package/dist/kit/server.browser.js +1 -1
- package/dist/kit/server.d.ts +1 -1
- package/dist/kit/server.js +17 -2
- package/dist/types.d.ts +38 -6
- package/dist/utils.d.ts +8 -13
- package/dist/utils.js +91 -62
- package/package.json +3 -2
|
@@ -3,7 +3,7 @@ import { BROWSER } from '#kit-env';
|
|
|
3
3
|
import { serverHalf } from '#kit-server';
|
|
4
4
|
import { I18n } from '../I18n.svelte.js';
|
|
5
5
|
import { logError, loggerFactory, setLogger } from '../logger.js';
|
|
6
|
-
import { configLocales, matchLocale, sanitizerFactory, textDirection } from '../utils.js';
|
|
6
|
+
import { configLocales, matchLocale, paramsSignature, resolveLoaders, routeParams, sanitizerFactory, textDirection, withoutBasePath } from '../utils.js';
|
|
7
7
|
// Registry-wide, so two copies of this package meet: the context key, and the
|
|
8
8
|
// key of the pass the universal branch hands `use()` in `data`.
|
|
9
9
|
const KEY = Symbol.for('@sveltekit-i18n/base/kit');
|
|
@@ -28,21 +28,23 @@ export const defineI18n = (config, options = {}) => {
|
|
|
28
28
|
let defaults = [];
|
|
29
29
|
// Resolved on first use, never at import, and once: resolving the loaders
|
|
30
30
|
// and sanitizing reports what is wrong with them.
|
|
31
|
-
const
|
|
31
|
+
const resolved = () => {
|
|
32
32
|
if (configured)
|
|
33
33
|
return configured;
|
|
34
34
|
if (config.log)
|
|
35
35
|
setLogger(loggerFactory(config.log));
|
|
36
36
|
try {
|
|
37
|
-
|
|
37
|
+
const loaders = resolveLoaders(config.loaders, config.sanitizeLocales);
|
|
38
|
+
configured = { locales: configLocales(config, loaders), handOver: !loaders.some(({ cache }) => cache === false), loaders };
|
|
38
39
|
}
|
|
39
40
|
catch {
|
|
40
41
|
// The instance reports a malformed config itself.
|
|
41
|
-
configured = [];
|
|
42
|
+
configured = { locales: [], handOver: false, loaders: [] };
|
|
42
43
|
}
|
|
43
44
|
defaults = [sanitized(config.initLocale), sanitized(config.fallbackLocale)];
|
|
44
45
|
return configured;
|
|
45
46
|
};
|
|
47
|
+
const locales = () => resolved().locales;
|
|
46
48
|
let reported = false;
|
|
47
49
|
const preferred = (event) => {
|
|
48
50
|
try {
|
|
@@ -84,50 +86,114 @@ export const defineI18n = (config, options = {}) => {
|
|
|
84
86
|
preferred: false,
|
|
85
87
|
};
|
|
86
88
|
};
|
|
87
|
-
const server = serverHalf({ create, negotiate, locales, basePath: config.basePath });
|
|
89
|
+
const server = serverHalf({ create, negotiate, locales, basePath: config.basePath, handOver: () => resolved().handOver });
|
|
88
90
|
// Browser only: the tab's instance, the server's answer at the last commit,
|
|
89
|
-
//
|
|
90
|
-
// which only `use()` writes
|
|
91
|
-
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
// the switch under way, what the last commit waits on, how many commits
|
|
92
|
+
// there were, which only `use()` writes, and the tables the pass that built
|
|
93
|
+
// the instance left when it activated it without a hand-off for a config
|
|
94
|
+
// with a `cache: false` loader, until the first commit reads them. None of
|
|
95
|
+
// it lives in `data`, which the app may hold in deep state. A server keeps
|
|
96
|
+
// nothing between requests.
|
|
97
|
+
const tab = { commits: 0 };
|
|
98
|
+
// The wiring's own switch, while the locale has not moved from where it
|
|
99
|
+
// started: that switch is no client change, while a locale that moved is.
|
|
100
|
+
const underway = (i18n) => (tab.switching?.from === untrack(() => i18n.locale) ? tab.switching : undefined);
|
|
101
|
+
// The locale the tab shows once the wiring's own switch lands.
|
|
102
|
+
const heading = (i18n) => underway(i18n)?.to ?? untrack(() => i18n.locale);
|
|
103
|
+
// The records an activation of `locale` on `route` leaves once every loader
|
|
104
|
+
// it selects delivered, in `loaders` order as `snapshot()` lists them, or
|
|
105
|
+
// `undefined` when none of them has `cache: false`, so the commit runs
|
|
106
|
+
// nothing again, or one of them has no id, whose record no snapshot shows.
|
|
107
|
+
const records = (locale, route) => {
|
|
108
|
+
const selected = resolved().loaders.flatMap((loader) => {
|
|
109
|
+
const params = loader.locale === locale || loader.locale === defaults[1] ? routeParams(loader.routes, route) : undefined;
|
|
110
|
+
return params ? [{ id: loader.id, signature: paramsSignature(params), cache: loader.cache }] : [];
|
|
111
|
+
});
|
|
112
|
+
if (!selected.some(({ cache }) => cache === false))
|
|
113
|
+
return undefined;
|
|
114
|
+
const listed = selected.flatMap(({ id, signature }) => (id === null ? [] : [signature ? { id, signature } : { id }]));
|
|
115
|
+
return listed.length === selected.length ? listed : undefined;
|
|
116
|
+
};
|
|
117
|
+
// Whether an instance still stands where the activation of `locale` on
|
|
118
|
+
// `route` left it, with `tables`: nothing loading or landed since, its
|
|
119
|
+
// records whole and every loader delivered, which a failed loader, another
|
|
120
|
+
// load, a reconfiguration and an invalidation of what it loaded each undo.
|
|
121
|
+
const stands = (i18n, locale, route, tables) => untrack(() => {
|
|
122
|
+
if (i18n.loading || i18n.locale !== locale || i18n.rawTranslations !== tables)
|
|
123
|
+
return false;
|
|
124
|
+
const path = withoutBasePath(route, config.basePath);
|
|
125
|
+
const expected = records(locale, path);
|
|
126
|
+
if (!expected)
|
|
127
|
+
return false;
|
|
128
|
+
const envelope = i18n.snapshot({ records: true });
|
|
129
|
+
return envelope.route === path && JSON.stringify(envelope.records) === JSON.stringify(expected);
|
|
130
|
+
});
|
|
131
|
+
const fallBack = (commit, switching) => {
|
|
132
|
+
const lapse = { commit, switching };
|
|
133
|
+
tab.lapse = lapse;
|
|
134
|
+
void Promise.all([switching.landed, ...switching.stays]).then((outcomes) => { lapse.outcomes = outcomes; });
|
|
135
|
+
};
|
|
136
|
+
// The answer of the last commit. Once its switch and every stay committed
|
|
137
|
+
// while that switch was under way settled, a failure that left the tab
|
|
138
|
+
// short of the locale switched to leaves the answer to the next commit:
|
|
139
|
+
// short, as long as the tab has not shown that locale since the switch
|
|
140
|
+
// started, whoever's call landed it. A switch to the locale it started from
|
|
141
|
+
// fell short unless a call landed.
|
|
142
|
+
const answered = (i18n) => {
|
|
143
|
+
const { lapse } = tab;
|
|
144
|
+
if (!lapse?.outcomes || lapse.commit !== tab.commits || lapse.outcomes.every(Boolean))
|
|
145
|
+
return tab.answer;
|
|
146
|
+
const { outcomes, switching } = lapse;
|
|
147
|
+
const { from, to, previous } = switching;
|
|
148
|
+
if (untrack(() => i18n.locale) === to)
|
|
149
|
+
switching.reached = true;
|
|
150
|
+
return (from === to ? !outcomes.some(Boolean) : !switching.reached) ? previous : tab.answer;
|
|
97
151
|
};
|
|
98
152
|
const universalLoad = async (event) => {
|
|
99
153
|
const route = event.url.pathname;
|
|
100
154
|
const payload = event.data?.i18n;
|
|
155
|
+
// A page render's own instance: the server branch loaded it for this very
|
|
156
|
+
// payload, which SvelteKit hands over as it was returned.
|
|
157
|
+
const rendered = BROWSER ? undefined : server.take(payload);
|
|
101
158
|
const fresh = !BROWSER || !tab.i18n;
|
|
102
|
-
const i18n = fresh ? create() : tab.i18n;
|
|
159
|
+
const i18n = rendered ?? (fresh ? create() : tab.i18n);
|
|
103
160
|
const surface = fresh ? pipe(i18n) : tab.surface;
|
|
104
|
-
const
|
|
161
|
+
const via = underway(i18n);
|
|
162
|
+
const seen = via?.to ?? untrack(() => i18n.locale);
|
|
105
163
|
// A live server sends the tables on a page render only, so a later pass
|
|
106
164
|
// that carries them read a prerendered file, whose locale was negotiated
|
|
107
165
|
// at build time, without the visitor: it counts only when the build's
|
|
108
166
|
// `preferredLocale` gave it. Node and Deno define `navigator.languages`
|
|
109
167
|
// too, from the server's own environment.
|
|
110
168
|
const prerendered = !fresh && payload?.translations;
|
|
111
|
-
const
|
|
112
|
-
|
|
169
|
+
const follows = Boolean(prerendered && !payload.preferred);
|
|
170
|
+
const answer = follows
|
|
171
|
+
? answered(i18n)
|
|
113
172
|
: payload ? payload.locale : negotiate(event, BROWSER ? navigator.languages : undefined).locale;
|
|
114
173
|
if (BROWSER)
|
|
115
174
|
Object.assign(tab, { i18n, surface });
|
|
175
|
+
let preloaded;
|
|
116
176
|
if (fresh) {
|
|
117
|
-
if (payload?.translations)
|
|
177
|
+
if (!rendered && payload?.translations)
|
|
118
178
|
i18n.hydrate({ ...payload, translations: payload.translations });
|
|
119
179
|
// No preload runs before the first navigation completes, so the pass
|
|
120
180
|
// that builds the instance may activate it.
|
|
121
181
|
await (answer ? i18n.loadTranslations(answer, route) : i18n.setRoute(route));
|
|
122
182
|
}
|
|
123
183
|
else {
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
|
|
184
|
+
// The request of a navigation that may never commit, a hover's
|
|
185
|
+
// included: it activates nothing, and its token lets the commit show
|
|
186
|
+
// what it fetched. The commit switches to a changed answer, or else
|
|
187
|
+
// stays.
|
|
188
|
+
const target = (answer !== answered(i18n) ? answer : undefined) ?? heading(i18n);
|
|
127
189
|
if (target)
|
|
128
|
-
await i18n.
|
|
190
|
+
preloaded = await i18n.preload(target, route);
|
|
129
191
|
}
|
|
130
|
-
|
|
192
|
+
// Unless a hand-off holds it back, the commit would run a loader with
|
|
193
|
+
// `cache: false` again for the very request this pass activated.
|
|
194
|
+
if (BROWSER && fresh && !payload?.translations && !resolved().handOver)
|
|
195
|
+
tab.activated = untrack(() => i18n.rawTranslations);
|
|
196
|
+
const pass = { i18n, surface, locale: answer, follows, route, seen, via, preloaded };
|
|
131
197
|
return { ...event.data, i18n: surface, [KEY]: pass };
|
|
132
198
|
};
|
|
133
199
|
const load = ((event) => (isServerEvent(event) ? server.load(event) : universalLoad(event)));
|
|
@@ -145,34 +211,68 @@ export const defineI18n = (config, options = {}) => {
|
|
|
145
211
|
const pass = passOf(data());
|
|
146
212
|
if (!pass)
|
|
147
213
|
return;
|
|
148
|
-
|
|
149
|
-
|
|
214
|
+
const first = !tab.commits;
|
|
215
|
+
const { activated } = tab;
|
|
216
|
+
// Read by the first commit alone: the tables would outlive their
|
|
217
|
+
// replacement for the life of the tab.
|
|
218
|
+
tab.activated = undefined;
|
|
219
|
+
tab.answer = answered(pass.i18n);
|
|
220
|
+
const commit = ++tab.commits;
|
|
221
|
+
const now = heading(pass.i18n);
|
|
222
|
+
// A switch that failed before this commit may have headed nowhere: its
|
|
223
|
+
// undo puts back the locale it started from or the one the tab was
|
|
224
|
+
// heading for, unless a later call landed the one it headed for.
|
|
225
|
+
const current = now === pass.seen || (pass.via?.failed === true && (now === pass.via.from || now === pass.via.back));
|
|
226
|
+
// A pass that takes the tab's answer never switches.
|
|
227
|
+
const locale = pass.follows ? tab.answer : pass.locale;
|
|
228
|
+
if (first || (locale !== tab.answer && current)) {
|
|
150
229
|
const previous = tab.answer;
|
|
151
|
-
tab.committed = true;
|
|
152
230
|
tab.answer = locale;
|
|
153
231
|
if (locale !== undefined) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
232
|
+
// The pass that built the instance activated it for this request:
|
|
233
|
+
// while it stands there, that activation is the commit's.
|
|
234
|
+
const taken = first && activated !== undefined && stands(pass.i18n, locale, pass.route, activated);
|
|
235
|
+
const switching = {
|
|
236
|
+
from: untrack(() => pass.i18n.locale),
|
|
237
|
+
back: now,
|
|
238
|
+
to: locale,
|
|
239
|
+
previous,
|
|
240
|
+
failed: false,
|
|
241
|
+
stays: [],
|
|
242
|
+
reached: false,
|
|
243
|
+
landed: (taken ? Promise.resolve() : pass.i18n.loadTranslations(locale, pass.route, { preloaded: pass.preloaded })).then(() => true, () => {
|
|
244
|
+
switching.failed = true;
|
|
245
|
+
return false;
|
|
246
|
+
}),
|
|
158
247
|
};
|
|
159
248
|
tab.switching = switching;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
done();
|
|
164
|
-
if (tab.answer === locale && untrack(() => pass.i18n.locale) !== locale)
|
|
165
|
-
tab.answer = previous;
|
|
249
|
+
void switching.landed.then(() => {
|
|
250
|
+
if (tab.switching === switching)
|
|
251
|
+
tab.switching = undefined;
|
|
166
252
|
});
|
|
253
|
+
fallBack(commit, switching);
|
|
167
254
|
return;
|
|
168
255
|
}
|
|
169
256
|
}
|
|
170
|
-
|
|
257
|
+
// A stay committed while what the previous commit waits on is under way
|
|
258
|
+
// carries it on, unless the locale moved from where the switch started.
|
|
259
|
+
const { lapse } = tab;
|
|
260
|
+
const switching = lapse && !lapse.outcomes && lapse.commit === commit - 1 && lapse.switching.from === untrack(() => pass.i18n.locale)
|
|
261
|
+
? lapse.switching
|
|
262
|
+
: undefined;
|
|
263
|
+
const stayed = pass.i18n.setRoute(pass.route, { preloaded: pass.preloaded }).then(() => true, () => false);
|
|
264
|
+
if (!switching)
|
|
265
|
+
return;
|
|
266
|
+
switching.stays = [...switching.stays, stayed];
|
|
267
|
+
fallBack(commit, switching);
|
|
171
268
|
});
|
|
172
269
|
$effect(() => {
|
|
173
270
|
const { locale } = i18n;
|
|
174
271
|
if (!locale)
|
|
175
272
|
return;
|
|
273
|
+
const switching = tab.lapse?.switching;
|
|
274
|
+
if (switching?.to === locale)
|
|
275
|
+
switching.reached = true;
|
|
176
276
|
document.documentElement.lang = locale;
|
|
177
277
|
document.documentElement.dir = textDirection(locale);
|
|
178
278
|
});
|
package/dist/kit/internal.d.ts
CHANGED
|
@@ -12,10 +12,18 @@ export type Shared = {
|
|
|
12
12
|
/** The locales the config serves. */
|
|
13
13
|
locales: () => string[];
|
|
14
14
|
basePath: string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the universal branch of a page render may take over the instance
|
|
17
|
+
* the server branch loaded: not while a loader has `cache: false`, which only
|
|
18
|
+
* a hand-off holds back for the rest of the render.
|
|
19
|
+
*/
|
|
20
|
+
handOver: () => boolean;
|
|
15
21
|
};
|
|
16
22
|
export type ServerHalf = {
|
|
17
23
|
handle: Kit.T['handle'];
|
|
18
24
|
load: (event: Kit.ServerLoadEvent) => Promise<{
|
|
19
25
|
i18n: Kit.Payload;
|
|
20
26
|
}>;
|
|
27
|
+
/** The instance a page render loaded for `payload`, handed out once. */
|
|
28
|
+
take: (payload: Kit.Payload | undefined) => I18n | undefined;
|
|
21
29
|
};
|
|
@@ -3,4 +3,4 @@ const serverOnly = () => {
|
|
|
3
3
|
};
|
|
4
4
|
// What `#kit-server` resolves to under the `browser` condition: the browser
|
|
5
5
|
// bundle carries this instead of the server half.
|
|
6
|
-
export const serverHalf = () => ({ handle: serverOnly, load: serverOnly });
|
|
6
|
+
export const serverHalf = () => ({ handle: serverOnly, load: serverOnly, take: serverOnly });
|
package/dist/kit/server.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ServerHalf, Shared } from './internal.js';
|
|
2
|
-
export declare const serverHalf: ({ create, negotiate, locales, basePath }: Shared) => ServerHalf;
|
|
2
|
+
export declare const serverHalf: ({ create, negotiate, locales, basePath, handOver }: Shared) => ServerHalf;
|
package/dist/kit/server.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { logger } from '../logger.js';
|
|
2
2
|
import { matchLocale, routePrefix, textDirection, withoutBasePath } from '../utils.js';
|
|
3
|
-
export const serverHalf = ({ create, negotiate, locales, basePath }) => {
|
|
3
|
+
export const serverHalf = ({ create, negotiate, locales, basePath, handOver }) => {
|
|
4
4
|
const answer = (event) => negotiate(event, event.request.headers.get('accept-language'));
|
|
5
|
+
// The instance each page render loaded, under the payload it returned:
|
|
6
|
+
// SvelteKit hands that very object to the universal load of the same
|
|
7
|
+
// request.
|
|
8
|
+
const built = new WeakMap();
|
|
5
9
|
let warned = false;
|
|
6
10
|
// A base path SvelteKit strips and `basePath` does not keeps every
|
|
7
11
|
// route-scoped loader from matching, silently. Here, on the server only, so
|
|
@@ -66,7 +70,18 @@ export const serverHalf = ({ create, negotiate, locales, basePath }) => {
|
|
|
66
70
|
return { i18n: { locale, route: withoutBasePath(route, basePath) } };
|
|
67
71
|
const i18n = create();
|
|
68
72
|
await (locale ? i18n.loadTranslations(locale, route) : i18n.setRoute(route));
|
|
69
|
-
|
|
73
|
+
const payload = { ...i18n.snapshot({ records: true }), ...(preferred ? { preferred: true } : {}) };
|
|
74
|
+
if (handOver())
|
|
75
|
+
built.set(payload, i18n);
|
|
76
|
+
return { i18n: payload };
|
|
77
|
+
},
|
|
78
|
+
// Once, so a payload an app keeps and returns again shares no instance.
|
|
79
|
+
take: (payload) => {
|
|
80
|
+
if (!payload)
|
|
81
|
+
return undefined;
|
|
82
|
+
const i18n = built.get(payload);
|
|
83
|
+
built.delete(payload);
|
|
84
|
+
return i18n;
|
|
70
85
|
},
|
|
71
86
|
};
|
|
72
87
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -70,7 +70,7 @@ export declare namespace Config {
|
|
|
70
70
|
export type SanitizeLocales = boolean | ((locale: Locale) => Locale);
|
|
71
71
|
export type T<P extends Parser.Params = Parser.Params, O = Parser.Output, S = any> = {
|
|
72
72
|
/**
|
|
73
|
-
* You can use loaders to define your asyncronous translation load. All loaded data are stored so loader is triggered only once – in case there is no previous version of the translation. It can get triggered again when the params its `routes` capture change, once the `config.cache` window elapses, or after `invalidate()` is called. A loader with `cache: false` runs on every load trigger that selects it.
|
|
73
|
+
* You can use loaders to define your asyncronous translation load. All loaded data are stored so loader is triggered only once – in case there is no previous version of the translation. It can get triggered again when the params its `routes` capture change, once the `config.cache` window elapses, or after `invalidate()` is called. A loader with `cache: false` runs on every load trigger that selects it, except a call handed the token of a `preload()` that ran it.
|
|
74
74
|
*/
|
|
75
75
|
loaders?: readonly Loader.LoaderModule[];
|
|
76
76
|
/**
|
|
@@ -163,7 +163,7 @@ export declare namespace Config {
|
|
|
163
163
|
*/
|
|
164
164
|
basePath?: string;
|
|
165
165
|
/**
|
|
166
|
-
* Time in milliseconds the loaded translations stay fresh for. Once a locale's translations are older, the next activating load trigger (`setLocale()`, `setRoute()`, `loadTranslations()`) runs its loaders again; a warm load – `loadTranslations(…, { activate: false })` or `loadNamespace()` – fills the tables without evaluating the window. By default, loaded translations never expire – call `invalidate()` (or set a finite `cache`) when your translation source can change at runtime, e.g. a CMS. A loader with `cache: false` is outside the window.
|
|
166
|
+
* Time in milliseconds the loaded translations stay fresh for. Once a locale's translations are older, the next activating load trigger (`setLocale()`, `setRoute()`, `loadTranslations()`) or `preload()` runs its loaders again; a warm load – `loadTranslations(…, { activate: false })` or `loadNamespace()` – fills the tables without evaluating the window, and a call handed a `preload()`'s token leaves it to that preload. By default, loaded translations never expire – call `invalidate()` (or set a finite `cache`) when your translation source can change at runtime, e.g. a CMS. A loader with `cache: false` is outside the window.
|
|
167
167
|
*
|
|
168
168
|
* @default Number.POSITIVE_INFINITY
|
|
169
169
|
*
|
|
@@ -192,6 +192,7 @@ export declare namespace Config {
|
|
|
192
192
|
export {};
|
|
193
193
|
}
|
|
194
194
|
declare const operator: unique symbol;
|
|
195
|
+
declare const preloaded: unique symbol;
|
|
195
196
|
export declare namespace Extension {
|
|
196
197
|
type Input = any;
|
|
197
198
|
type Output = any;
|
|
@@ -316,7 +317,7 @@ export declare namespace Loader {
|
|
|
316
317
|
*/
|
|
317
318
|
routes?: readonly Route[];
|
|
318
319
|
/**
|
|
319
|
-
* Set to `false` when the loader's source does the caching – a SvelteKit remote `query`, an SWR layer, an HTTP cache. The core then keeps no freshness of its own for it: it runs on every load trigger that selects it, its data is applied each time like any refetch, and `config.cache` does not apply to it – refreshing the source is the app's business. Data hydrated from a snapshot still holds it back for the pass it arrived with, until an activating trigger asks for another locale or route; `invalidate()` ends that hand-off and discards a fetch of it in flight. Only `false` is accepted.
|
|
320
|
+
* Set to `false` when the loader's source does the caching – a SvelteKit remote `query`, an SWR layer, an HTTP cache. The core then keeps no freshness of its own for it: it runs on every load trigger that selects it – unless a fetch of it for the same params and route is in flight, which it shares – its data is applied each time like any refetch, and `config.cache` does not apply to it – refreshing the source is the app's business. A call handed a `preload()`'s token shows what the preload fetched instead of running it again; what the preload shared from a fetch already in flight is shown, then fetched again. Data hydrated from a snapshot still holds it back for the pass it arrived with, until an activating trigger asks for another locale or route, or a `preload()` runs; `invalidate()` ends that hand-off and discards a fetch of it in flight. Only `false` is accepted.
|
|
320
321
|
*/
|
|
321
322
|
cache?: false;
|
|
322
323
|
};
|
|
@@ -364,6 +365,17 @@ export declare namespace Loader {
|
|
|
364
365
|
*/
|
|
365
366
|
id: string | null;
|
|
366
367
|
};
|
|
368
|
+
/**
|
|
369
|
+
* What `preload()` resolves to: a token for the next activating call of the
|
|
370
|
+
* same locale and route, which takes it as `{ preloaded }` and shows what the
|
|
371
|
+
* preload fetched instead of fetching it again. It serves one call, of the
|
|
372
|
+
* instance that made it: the first activating call that reads it spends it,
|
|
373
|
+
* whether or not it serves that call. Hold it by reference: it carries no
|
|
374
|
+
* data, and a copy is no token.
|
|
375
|
+
*/
|
|
376
|
+
export type Preloaded = {
|
|
377
|
+
readonly [preloaded]: true;
|
|
378
|
+
};
|
|
367
379
|
/**
|
|
368
380
|
* Loads translation data. Receives the load context (`locale`, `namespace`, `route`, `params`) –
|
|
369
381
|
* loaders that don't need it can simply take no parameters.
|
|
@@ -564,6 +576,13 @@ export declare namespace Schema {
|
|
|
564
576
|
/** The keys a schema allows; any string when there is no schema. */
|
|
565
577
|
export type Key<S> = [S] extends [never] ? string : keyof S & string;
|
|
566
578
|
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
579
|
+
/**
|
|
580
|
+
* `keyof S`, read once per schema. TypeScript rebuilds `keyof` of an object
|
|
581
|
+
* type over every key each time it reads it, and a plain alias of it too;
|
|
582
|
+
* the instantiation of a conditional type is cached per type argument, so
|
|
583
|
+
* each call reads the keys this computed once.
|
|
584
|
+
*/
|
|
585
|
+
type Keys<S> = [S] extends [unknown] ? keyof S : never;
|
|
567
586
|
/**
|
|
568
587
|
* What satisfies every key in `K` — see `Params`. The fold runs over the
|
|
569
588
|
* KEYS, so each key's own payload reaches the intersection whole; folding
|
|
@@ -573,8 +592,21 @@ export declare namespace Schema {
|
|
|
573
592
|
* empty union stays `never`: no member means no payload, not an
|
|
574
593
|
* unconstrained one.
|
|
575
594
|
*/
|
|
576
|
-
type BoxedPayload<S, K extends string> = K extends
|
|
577
|
-
|
|
595
|
+
type BoxedPayload<S, K extends string> = K extends Keys<S> ? [Exclude<S[K], undefined>] extends [never] ? never : (payload: Exclude<S[K], undefined>) => void : never;
|
|
596
|
+
/** Whether a key in `K` marks its payload optional; `boolean` when only some do. */
|
|
597
|
+
type OptionalOf<S, K extends string> = K extends Keys<S> ? (undefined extends S[K] ? true : false) : never;
|
|
598
|
+
/**
|
|
599
|
+
* The payloads fold into their intersection by inference: `V` stands as the
|
|
600
|
+
* parameter in the false branch of a conditional deferred on `V` itself, so
|
|
601
|
+
* a union of functions contributes each parameter as a contravariant
|
|
602
|
+
* candidate, and those combine into their intersection. Once `V` is
|
|
603
|
+
* inferred, that conditional reads `unknown` or `never`, so the union is
|
|
604
|
+
* never related to the intersection itself. Matched against a function over
|
|
605
|
+
* the intersection instead, the union would relate the intersection to each
|
|
606
|
+
* payload, which for a key outside the schema, typed over every key, costs
|
|
607
|
+
* time about cubic in their number.
|
|
608
|
+
*/
|
|
609
|
+
type PayloadOf<S, K extends string> = [BoxedPayload<S, K>] extends [never] ? never : BoxedPayload<S, K> extends (infer V extends unknown ? unknown : (payload: infer V) => void) ? V : never;
|
|
578
610
|
/**
|
|
579
611
|
* The parser's own params minus the payload slot the schema takes over. A
|
|
580
612
|
* params tuple that is unknown or open-ended contributes no trailing slots –
|
|
@@ -594,7 +626,7 @@ export declare namespace Schema {
|
|
|
594
626
|
* A union of keys takes the INTERSECTION of their payloads, since one call
|
|
595
627
|
* has to satisfy every key it might be.
|
|
596
628
|
*/
|
|
597
|
-
export type Params<S, K extends string, P extends Parser.Params> = [S] extends [never] ? P : [K] extends [
|
|
629
|
+
export type Params<S, K extends string, P extends Parser.Params> = [S] extends [never] ? P : [K] extends [Keys<S>] ? Payload<P, PayloadOf<S, K>, OptionalOf<S, K>> : P;
|
|
598
630
|
export {};
|
|
599
631
|
}
|
|
600
632
|
export declare namespace Snapshot {
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
import type { Config, DotNotation, Translations, Loader, Parser } from './types.js';
|
|
2
2
|
export declare const hasOwn: (obj: any, key: PropertyKey) => boolean;
|
|
3
3
|
export declare const read: <T = any>(obj: any, key: PropertyKey) => T | undefined;
|
|
4
|
-
export declare const translate: <P extends Parser.Params = Parser.Params, O = Parser.Output>(
|
|
5
|
-
parser: Parser.T<P, O>;
|
|
6
|
-
key: string;
|
|
7
|
-
params: Parser.Params;
|
|
8
|
-
translations: Translations.SerializedTranslations;
|
|
9
|
-
locale: Translations.Locales[number] | undefined;
|
|
10
|
-
fallbackLocale?: Config.FallbackLocale;
|
|
11
|
-
fallbackValue?: Config.FallbackValue;
|
|
12
|
-
}) => Translations.Translated<O>;
|
|
4
|
+
export declare const translate: <P extends Parser.Params = Parser.Params, O = Parser.Output>(config: Pick<Config.T<P, O>, "parser" | "fallbackLocale" | "fallbackValue"> | undefined, locale: Translations.Locales[number] | undefined, key: string, params: Parser.Params, table: DotNotation.Input, fallbackTable: DotNotation.Input) => Translations.Translated<O>;
|
|
13
5
|
type Sanitizer = (...locales: any[]) => Config.Locale[];
|
|
14
6
|
export declare const sanitizeLocales: Sanitizer;
|
|
15
7
|
export declare const sanitizerFactory: (sanitize?: Config.SanitizeLocales) => Sanitizer;
|
|
@@ -50,6 +42,8 @@ export declare const withoutBasePath: (route: string, basePath: string | undefin
|
|
|
50
42
|
* absorbs a prefix. No regex runs on the pathname.
|
|
51
43
|
*/
|
|
52
44
|
export declare const routePrefix: (pathname: string, routeId: string | null) => string | undefined;
|
|
45
|
+
/** Whether `config.preprocess` is the built-in dot notation. */
|
|
46
|
+
export declare const dotNotates: (preprocess: Config.T["preprocess"]) => boolean;
|
|
53
47
|
export declare const toDotNotation: DotNotation.T;
|
|
54
48
|
export declare const unique: <V>(values: readonly V[]) => V[];
|
|
55
49
|
export declare const resolveLoaders: (input?: readonly Loader.LoaderModule[], sanitizeLocales?: Config.SanitizeLocales) => Loader.Resolved[];
|
|
@@ -63,11 +57,12 @@ export declare const serialize: (input: Array<Loader.Resolved & {
|
|
|
63
57
|
/** The locales the loaders serve, then the ones the tables hold. */
|
|
64
58
|
export declare const servedLocales: (loaders: readonly Loader.Resolved[], tables: Translations.SerializedTranslations) => Config.Locale[];
|
|
65
59
|
/**
|
|
66
|
-
* The locales a config serves,
|
|
67
|
-
*
|
|
68
|
-
* caller
|
|
60
|
+
* The locales a config serves, from its loaders as `resolveLoaders` resolved
|
|
61
|
+
* them, sanitized as an instance built from it sanitizes them. Resolving the
|
|
62
|
+
* loaders logs what is wrong with them, so a caller resolves them once per
|
|
63
|
+
* config.
|
|
69
64
|
*/
|
|
70
|
-
export declare const configLocales: ({
|
|
65
|
+
export declare const configLocales: ({ translations, sanitizeLocales: strategy }: Pick<Config.T, "translations" | "sanitizeLocales">, loaders: readonly Loader.Resolved[]) => Config.Locale[];
|
|
71
66
|
/** A loader selected for a load, with the params its route yielded. */
|
|
72
67
|
export type LoadRequest = {
|
|
73
68
|
loader: Loader.Resolved;
|