@sveltekit-i18n/base 3.1.0-next.1 → 3.1.0-next.3
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 +17 -6
- package/dist/I18n.svelte.js +22 -9
- package/dist/kit/types.d.ts +4 -3
- package/dist/types.d.ts +45 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -228,11 +228,11 @@ A named capture group in a `RegExp` route is a load parameter: its match reaches
|
|
|
228
228
|
locale: 'en',
|
|
229
229
|
namespace: 'article',
|
|
230
230
|
routes: [/^\/article\/(?<articleId>[^/]+)/],
|
|
231
|
-
loader: async ({ locale, params }) => (await fetch(
|
|
231
|
+
loader: async ({ locale, params }) => (await fetch(`${API_ORIGIN}/api/articles/${params.articleId}/i18n/${locale}`)).json(),
|
|
232
232
|
}
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
-
See [route params](./docs/README.md#route-params) for the rules.
|
|
235
|
+
`API_ORIGIN` stands for an origin such as `https://api.example.com`: a loader runs on the server too, where `fetch` takes only an absolute URL. See [route params](./docs/README.md#route-params) for the rules.
|
|
236
236
|
|
|
237
237
|
A loader whose source does the caching itself — a SvelteKit remote `query`, an SWR layer, an HTTP cache — sets `cache: false`. It then runs on every load trigger that selects it, and `config.cache` does not apply to it; only a hydrated snapshot holds it back, for the locale and route it was rendered for. See [the loader's `cache`](./docs/README.md#cache-optional).
|
|
238
238
|
|
|
@@ -312,18 +312,29 @@ preprocess: 'full' // 'full' | 'preserveArrays' | 'none' | custom function
|
|
|
312
312
|
|
|
313
313
|
### `schema`
|
|
314
314
|
|
|
315
|
-
A map of translation key to the payload its message expects (`never` for a message that takes none).
|
|
315
|
+
A map of translation key to the payload its message expects (`never` for a message that takes none). It types `t`/`l` — keys autocomplete, an unknown key is a type error, and the payload argument is checked. Register it once for the app, in a global script, and every instance whose config states no `schema` is typed by it:
|
|
316
316
|
|
|
317
317
|
```typescript
|
|
318
|
-
|
|
318
|
+
// src/i18n-schema.d.ts — no top-level import or export
|
|
319
|
+
interface TranslationSchema {
|
|
319
320
|
'common.greeting': { name: string };
|
|
320
321
|
'common.farewell': never;
|
|
321
|
-
}
|
|
322
|
+
}
|
|
322
323
|
|
|
324
|
+
declare namespace SvelteKitI18n {
|
|
325
|
+
interface Register {
|
|
326
|
+
schema: TranslationSchema;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Or state it per instance — only its type is read, so the value can stay empty at runtime, and a stated schema wins over the registry:
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
323
334
|
const i18n = new I18n({ ...config, schema: {} as TranslationSchema });
|
|
324
335
|
```
|
|
325
336
|
|
|
326
|
-
Hand-write it for a small set of messages, or
|
|
337
|
+
Hand-write it for a small set of messages, or generate it — [@sveltekit-i18n/typegen](https://github.com/sveltekit-i18n/typegen), a separate package, generates one. A schema whose keys are not a closed set (`schema: {}`) types nothing and keeps the registry out, so keys stay plain strings. A library never registers. The registry needs base 3.1 or newer. See [`schema`](./docs/README.md#schema) for the full rules.
|
|
327
338
|
|
|
328
339
|
### `cache`
|
|
329
340
|
|
package/dist/I18n.svelte.js
CHANGED
|
@@ -45,7 +45,7 @@ class I18nCore {
|
|
|
45
45
|
// What each source last put into a namespace, so a loader whose params
|
|
46
46
|
// changed can replace its own part and leave its siblings' in place. Kept
|
|
47
47
|
// apart from the records: invalidation drops those, not what is displayed,
|
|
48
|
-
// and a reconfiguration hands them on to the loaders
|
|
48
|
+
// and a reconfiguration hands them on to the same loaders of the new config.
|
|
49
49
|
#deliveries = new Map();
|
|
50
50
|
#externalTranslations = {};
|
|
51
51
|
// The params signature the next trigger asks each loader for:
|
|
@@ -561,18 +561,31 @@ class I18nCore {
|
|
|
561
561
|
}
|
|
562
562
|
/**
|
|
563
563
|
* Gives each delivery of the previous config to the loader of the new one
|
|
564
|
-
* with the same id,
|
|
565
|
-
*
|
|
566
|
-
*
|
|
564
|
+
* with the same id, or else to the one loader that runs the same function
|
|
565
|
+
* for the same locale and namespace when no other delivery came from that
|
|
566
|
+
* function there: a `{ ...config, … }` hands its loaders over as they were,
|
|
567
|
+
* whatever became of their routes. Its params can then still replace it.
|
|
568
|
+
* What no loader can take over is kept as data supplied without a loader, so
|
|
569
|
+
* a namespace rebuilt later keeps it rather than losing it.
|
|
567
570
|
*/
|
|
568
571
|
#handOnDeliveries(loaders) {
|
|
569
|
-
const
|
|
572
|
+
const byId = new Map(loaders.flatMap((loader) => (loader.id === null ? [] : [[loader.id, loader]])));
|
|
570
573
|
const deliveries = Array.from(this.#deliveries.values());
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
return taker ? [[taker, { ...delivery, loader: taker }]] : [];
|
|
574
|
+
const byIdTaken = new Map(deliveries.flatMap((delivery) => {
|
|
575
|
+
const taker = delivery.loader.id === null ? undefined : byId.get(delivery.loader.id);
|
|
576
|
+
return taker ? [[delivery, taker]] : [];
|
|
575
577
|
}));
|
|
578
|
+
const free = loaders.filter((loader) => !Array.from(byIdTaken.values()).includes(loader));
|
|
579
|
+
const rest = deliveries.filter((delivery) => !byIdTaken.has(delivery));
|
|
580
|
+
const runsAs = (a) => (b) => a.loader === b.loader && a.locale === b.locale && a.namespace === b.namespace;
|
|
581
|
+
const byFunction = rest.flatMap((delivery) => {
|
|
582
|
+
const [only, ...more] = free.filter(runsAs(delivery.loader));
|
|
583
|
+
const alone = rest.filter((other) => runsAs(delivery.loader)(other.loader)).length === 1;
|
|
584
|
+
return only && !more.length && alone ? [[delivery, only]] : [];
|
|
585
|
+
});
|
|
586
|
+
const takers = new Map([...byIdTaken, ...byFunction]);
|
|
587
|
+
const orphaned = deliveries.filter((delivery) => !takers.has(delivery));
|
|
588
|
+
this.#deliveries = new Map(Array.from(takers, ([delivery, taker]) => [taker, { ...delivery, loader: taker }]));
|
|
576
589
|
this.#keepExternal(serialize(orphaned.map(({ loader, data }) => ({ ...loader, data }))));
|
|
577
590
|
}
|
|
578
591
|
/**
|
package/dist/kit/types.d.ts
CHANGED
|
@@ -53,9 +53,10 @@ export declare namespace Kit {
|
|
|
53
53
|
* locale matches is skipped; a custom `sanitizeLocales` is applied to it
|
|
54
54
|
* first. It runs on every navigation and every
|
|
55
55
|
* preload, so it must be pure: it reads the event and writes nothing.
|
|
56
|
-
* With a server load it runs
|
|
57
|
-
*
|
|
58
|
-
*
|
|
56
|
+
* With a server load it runs in the browser only on a root error page
|
|
57
|
+
* rendered without the server's data (an unknown URL a static host answers
|
|
58
|
+
* with its fallback page): a navigation to a prerendered page takes the
|
|
59
|
+
* locale it gave at build time, and otherwise keeps the tab's.
|
|
59
60
|
*
|
|
60
61
|
* @example
|
|
61
62
|
* preferredLocale: (event) => event.cookies?.get('lang')
|
package/dist/types.d.ts
CHANGED
|
@@ -129,21 +129,21 @@ export declare namespace Config {
|
|
|
129
129
|
parser: Parser.T<P, O>;
|
|
130
130
|
/**
|
|
131
131
|
* A key schema — a map of translation key to the payload its message
|
|
132
|
-
* expects (`never` for a message without parameters).
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* typed by
|
|
132
|
+
* expects (`never` for a message without parameters). It types `t`/`l`:
|
|
133
|
+
* keys autocomplete and a wrong payload is a type error. Left out, the
|
|
134
|
+
* schema the app registers in `SvelteKitI18n.Register` types the
|
|
135
|
+
* instance; stated, it wins over the registry. Only its TYPE is read, so
|
|
136
|
+
* the value may be empty at runtime — as long as it is TYPED, e.g.
|
|
137
|
+
* `{} as TranslationSchema`. A schema whose keys are not a closed set (an
|
|
138
|
+
* open index signature, or no keys at all, as in `schema: {}`) types
|
|
139
|
+
* nothing and keeps the registry out: keys stay plain strings. Read at
|
|
140
|
+
* construction time only: a later `loadConfig()` cannot retype the
|
|
141
|
+
* instance, and an extension typed by a fixed return type erases the
|
|
142
|
+
* instance's type parameters, while one typed by an `Extension.Operator`
|
|
143
|
+
* (`Extension.Generic`) keeps them.
|
|
142
144
|
*
|
|
143
145
|
* @example
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* const i18n = new I18n({ ...config, schema });
|
|
146
|
+
* const i18n = new I18n({ ...config, schema: {} as TranslationSchema });
|
|
147
147
|
*/
|
|
148
148
|
schema?: S;
|
|
149
149
|
/**
|
|
@@ -510,6 +510,19 @@ export declare namespace Parser {
|
|
|
510
510
|
*/
|
|
511
511
|
type ExtractParamsFactory<O = unknown> = (options?: O) => ExtractParams;
|
|
512
512
|
}
|
|
513
|
+
declare global {
|
|
514
|
+
namespace SvelteKitI18n {
|
|
515
|
+
/**
|
|
516
|
+
* The app's type registry, filled by a generated global script
|
|
517
|
+
* (`interface Register { schema: TranslationSchema }`). Its `schema` types
|
|
518
|
+
* every instance whose config states none. Every copy of the core declares
|
|
519
|
+
* it empty, and a library never registers: the registry covers the whole
|
|
520
|
+
* program.
|
|
521
|
+
*/
|
|
522
|
+
interface Register {
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
513
526
|
export declare namespace Schema {
|
|
514
527
|
/**
|
|
515
528
|
* A schema types calls only when its keys form a specific, closed set. An
|
|
@@ -518,10 +531,25 @@ export declare namespace Schema {
|
|
|
518
531
|
* they degrade to no schema at all.
|
|
519
532
|
*/
|
|
520
533
|
type HasClosedKeys<S> = [keyof S & string] extends [never] ? false : string extends keyof S ? false : true;
|
|
521
|
-
/**
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
534
|
+
/** `S` when its keys are a closed set, `never` otherwise. A union schema is taken whole. */
|
|
535
|
+
type Closed<S> = [S] extends [object] ? (HasClosedKeys<S> extends true ? S : never) : never;
|
|
536
|
+
/** What a config's `schema` slot holds; `unknown` when it is absent or typed `any`. */
|
|
537
|
+
type Slot<C> = C extends {
|
|
538
|
+
schema?: infer S;
|
|
539
|
+
} ? S : unknown;
|
|
540
|
+
/** The key schema `SvelteKitI18n.Register` carries; `never` when it carries none to use. */
|
|
541
|
+
export type Registered = SvelteKitI18n.Register extends {
|
|
542
|
+
schema: infer S;
|
|
543
|
+
} ? Closed<S> : never;
|
|
544
|
+
/**
|
|
545
|
+
* The key schema a config types its instance with; `never` when there is
|
|
546
|
+
* none to use. A slot the config states decides — a closed schema is that
|
|
547
|
+
* schema, one without a closed key set (`{}`) opts out to plain string keys
|
|
548
|
+
* — while an absent slot, or one typed `any` (a plain `Config.T`
|
|
549
|
+
* annotation), reads the registry. A union of configs yields the union of
|
|
550
|
+
* their schemas.
|
|
551
|
+
*/
|
|
552
|
+
export type FromConfig<C> = C extends unknown ? (unknown extends Slot<C> ? Registered : Closed<Slot<C>>) : never;
|
|
525
553
|
/**
|
|
526
554
|
* The key schema an instance was built with; `never` when it carries none.
|
|
527
555
|
* The counterpart of `FromConfig` for code handed a constructed surface
|
package/package.json
CHANGED