@sveltekit-i18n/base 3.1.0-next.0 → 3.1.0-next.2
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 +49 -13
- package/dist/kit/define.svelte.js +14 -6
- package/dist/kit/internal.d.ts +6 -1
- package/dist/kit/server.js +3 -3
- package/dist/kit/types.d.ts +11 -1
- package/dist/types.d.ts +51 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,9 @@ Core i18n functionality for SvelteKit with support for custom message parsers. T
|
|
|
34
34
|
|
|
35
35
|
Svelte 5 or newer, and one of Node 22+, Bun 1.2+ or Deno 2+. The package is
|
|
36
36
|
ESM-only and imports no `node:` module, so every runtime that runs your
|
|
37
|
-
SvelteKit build runs it.
|
|
37
|
+
SvelteKit build runs it. The [`/kit`](#sveltekit) subpath needs SvelteKit 2;
|
|
38
|
+
the SvelteKit behaviour the docs describe is checked against 2.70 and the 3.0
|
|
39
|
+
prerelease.
|
|
38
40
|
|
|
39
41
|
## Installation
|
|
40
42
|
|
|
@@ -171,8 +173,10 @@ import i18n from '@sveltekit-i18n/base';
|
|
|
171
173
|
|
|
172
174
|
const customParser = () => ({
|
|
173
175
|
parse: (value, params) => {
|
|
174
|
-
// Your custom interpolation logic
|
|
175
|
-
return value
|
|
176
|
+
// Your custom interpolation logic; `parse` must not throw on a non-string value
|
|
177
|
+
return typeof value === 'string'
|
|
178
|
+
? value.replace(/\{(\w+)\}/g, (_, key) => params[0]?.[key] ?? key)
|
|
179
|
+
: value;
|
|
176
180
|
},
|
|
177
181
|
});
|
|
178
182
|
|
|
@@ -236,6 +240,19 @@ A loader that throws is logged, and the rest of the load lands without its data;
|
|
|
236
240
|
|
|
237
241
|
Both `loaders` and a loader's `routes` accept readonly arrays, so a whole-config `as const` is fine.
|
|
238
242
|
|
|
243
|
+
### `basePath`
|
|
244
|
+
|
|
245
|
+
The path the app is served under — SvelteKit's `kit.paths.base`. Every route handed in loses it on the way in, on a segment boundary only (under `/repo`, `/repo/about` is `/about`), so loader `routes` name the app's own paths. Set both from one environment variable:
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
// svelte.config.js: kit: { paths: { base: process.env.PUBLIC_BASE_PATH ?? '' } }
|
|
249
|
+
import { PUBLIC_BASE_PATH } from '$env/static/public';
|
|
250
|
+
|
|
251
|
+
basePath: PUBLIC_BASE_PATH
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
See [`basePath`](./docs/README.md#basepath).
|
|
255
|
+
|
|
239
256
|
### `translations`
|
|
240
257
|
|
|
241
258
|
Synchronous translations, available immediately. They seed the tables: the loaders of a namespace they name still run and merge into it. Hand a server's state over with [`hydrate()`](./docs/README.md#hydrateenvelope) instead:
|
|
@@ -256,6 +273,8 @@ Initialize with a specific locale immediately:
|
|
|
256
273
|
initLocale: 'en'
|
|
257
274
|
```
|
|
258
275
|
|
|
276
|
+
With [`defineI18n()`](#sveltekit) it loads nothing: it is a negotiation candidate. Leave it out of a config whose instance you [`hydrate()`](./docs/README.md#hydrateenvelope) by hand — its load starts in the constructor, before the hand-off can be applied.
|
|
277
|
+
|
|
259
278
|
### `fallbackLocale`
|
|
260
279
|
|
|
261
280
|
Fallback when translation is missing:
|
|
@@ -274,6 +293,10 @@ Default return value when translation key is not found:
|
|
|
274
293
|
fallbackValue: '...' // Default: returns the key itself
|
|
275
294
|
```
|
|
276
295
|
|
|
296
|
+
### `sanitizeLocales`
|
|
297
|
+
|
|
298
|
+
How locale identifiers are normalized before they key anything: `true` (default) resolves them to their ISO form through `Intl` (`'en-us'` is `'en-US'`), `false` keeps them as authored, and a function normalizes them your way. See [`sanitizeLocales`](./docs/README.md#sanitizelocales).
|
|
299
|
+
|
|
277
300
|
### `preprocess`
|
|
278
301
|
|
|
279
302
|
Transform translations after loading:
|
|
@@ -289,18 +312,29 @@ preprocess: 'full' // 'full' | 'preserveArrays' | 'none' | custom function
|
|
|
289
312
|
|
|
290
313
|
### `schema`
|
|
291
314
|
|
|
292
|
-
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:
|
|
293
316
|
|
|
294
317
|
```typescript
|
|
295
|
-
|
|
318
|
+
// src/i18n-schema.d.ts — no top-level import or export
|
|
319
|
+
interface TranslationSchema {
|
|
296
320
|
'common.greeting': { name: string };
|
|
297
321
|
'common.farewell': never;
|
|
298
|
-
}
|
|
322
|
+
}
|
|
299
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
|
|
300
334
|
const i18n = new I18n({ ...config, schema: {} as TranslationSchema });
|
|
301
335
|
```
|
|
302
336
|
|
|
303
|
-
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.
|
|
304
338
|
|
|
305
339
|
### `cache`
|
|
306
340
|
|
|
@@ -309,10 +343,10 @@ Time in milliseconds the loaded translations stay fresh for. By default, loaded
|
|
|
309
343
|
Set a finite value when your loaders fetch from a source that can change at runtime (e.g. a CMS):
|
|
310
344
|
|
|
311
345
|
```javascript
|
|
312
|
-
cache: 3600000 // Translations older than 1 hour refetch on the next load
|
|
346
|
+
cache: 3600000 // Translations older than 1 hour refetch on the next activating load
|
|
313
347
|
```
|
|
314
348
|
|
|
315
|
-
Set to `0` to treat translations as always stale (refetch on every load trigger). You can also drop the loaded state manually at any time with [`invalidate()`](#methods).
|
|
349
|
+
Expiry is evaluated by the next activating load trigger (`setLocale`, `setRoute`, `loadTranslations`); a warm load — `loadTranslations(…, { activate: false })` or `loadNamespace()` — fills the tables without evaluating it. Set to `0` to treat translations as always stale (refetch on every activating load trigger). A loader with `cache: false` is outside the window. You can also drop the loaded state manually at any time with [`invalidate()`](#methods).
|
|
316
350
|
|
|
317
351
|
### `extensions`
|
|
318
352
|
|
|
@@ -423,11 +457,12 @@ Full API documentation: [docs/README.md](./docs/README.md)
|
|
|
423
457
|
|
|
424
458
|
```typescript
|
|
425
459
|
import { I18n, type Config } from '@sveltekit-i18n/base';
|
|
426
|
-
import parser from '@sveltekit-i18n/parser-curly';
|
|
460
|
+
import parser, { type Parser } from '@sveltekit-i18n/parser-curly';
|
|
427
461
|
|
|
428
462
|
// The parser's params – the rest parameters of `t`/`l`. Annotate only when the
|
|
429
|
-
// config lives on its own; `new I18n({ ... })` infers them.
|
|
430
|
-
|
|
463
|
+
// config lives on its own; `new I18n({ ... })` infers them. Take the tuple from
|
|
464
|
+
// the parser rather than spelling it by hand.
|
|
465
|
+
type Params = Parser.Params;
|
|
431
466
|
|
|
432
467
|
const config: Config.T<Params> = {
|
|
433
468
|
parser: parser({ onReport: null }),
|
|
@@ -435,7 +470,7 @@ const config: Config.T<Params> = {
|
|
|
435
470
|
};
|
|
436
471
|
```
|
|
437
472
|
|
|
438
|
-
Two more things are inferred from the config itself. [`schema`](#schema) types the keys and payloads of `t`/`l`, and every locale the config names — loader locales, `initLocale`, `fallbackLocale` and the keys of `translations` — completes the locale arguments and reads (`setLocale`, `loadTranslations`, `invalidate`, `l`, `locale`, `locales`):
|
|
473
|
+
Two more things are inferred from the config itself. [`schema`](#schema) types the keys and payloads of `t`/`l`, and every locale the config names — loader locales, `initLocale`, `fallbackLocale` and the keys of `translations` — completes the locale arguments and reads (`setLocale`, `loadTranslations`, `loadNamespace`, `invalidate`, `l`, `locale`, `locales`):
|
|
439
474
|
|
|
440
475
|
```typescript
|
|
441
476
|
const i18n = new I18n({ parser: parser({ onReport: null }), initLocale: 'en', fallbackLocale: 'de' });
|
|
@@ -454,6 +489,7 @@ The locales survive only when the config reaches the constructor as a literal
|
|
|
454
489
|
- [@sveltekit-i18n/parser-mf2](https://github.com/sveltekit-i18n/parsers/tree/master/parser-mf2) – [Unicode MessageFormat 2](https://unicode.org/reports/tr35/tr35-messageFormat.html) parser
|
|
455
490
|
- [@sveltekit-i18n/parser-i18next](https://github.com/sveltekit-i18n/parsers/tree/master/parser-i18next) – [i18next](https://www.i18next.com) interpolation and formatting syntax parser
|
|
456
491
|
- [Extensions](https://github.com/sveltekit-i18n/extensions) – Official extensions for the `config.extensions` pipe
|
|
492
|
+
- [@sveltekit-i18n/typegen](https://github.com/sveltekit-i18n/typegen) – Generates the [`schema`](#schema) type from your translation files
|
|
457
493
|
|
|
458
494
|
## Contributing
|
|
459
495
|
|
|
@@ -74,7 +74,13 @@ export const defineI18n = (config, options = {}) => {
|
|
|
74
74
|
const negotiate = (event, ranges) => {
|
|
75
75
|
// First: it sets the config's logger, which `preferred` reports through.
|
|
76
76
|
const available = locales();
|
|
77
|
-
|
|
77
|
+
const chosen = matchLocale(visitorSanitized(preferred(event)), available);
|
|
78
|
+
if (chosen !== undefined)
|
|
79
|
+
return { locale: chosen, preferred: true };
|
|
80
|
+
return {
|
|
81
|
+
locale: [ranges, ...defaults].reduce((found, candidate) => found ?? matchLocale(candidate, available), undefined),
|
|
82
|
+
preferred: false,
|
|
83
|
+
};
|
|
78
84
|
};
|
|
79
85
|
const server = serverHalf({ create, negotiate, locales, basePath: config.basePath });
|
|
80
86
|
// Browser only: the tab's instance, the server's answer at the last commit,
|
|
@@ -96,11 +102,13 @@ export const defineI18n = (config, options = {}) => {
|
|
|
96
102
|
const seen = heading(i18n);
|
|
97
103
|
// A live server sends the tables on a page render only, so a later pass
|
|
98
104
|
// that carries them read a prerendered file, whose locale was negotiated
|
|
99
|
-
// at build time, without the visitor
|
|
100
|
-
// `
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
// at build time, without the visitor: it counts only when the build's
|
|
106
|
+
// `preferredLocale` gave it. Node and Deno define `navigator.languages`
|
|
107
|
+
// too, from the server's own environment.
|
|
108
|
+
const prerendered = !fresh && payload?.translations;
|
|
109
|
+
const answer = prerendered
|
|
110
|
+
? (payload.preferred ? payload.locale : tab.answer)
|
|
111
|
+
: payload ? payload.locale : negotiate(event, BROWSER ? navigator.languages : undefined).locale;
|
|
104
112
|
if (BROWSER)
|
|
105
113
|
Object.assign(tab, { i18n, surface });
|
|
106
114
|
if (fresh) {
|
package/dist/kit/internal.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { I18n } from '../I18n.svelte.js';
|
|
2
2
|
import type { Kit } from './types.js';
|
|
3
|
+
/** The negotiated locale, and whether `preferredLocale` gave it. */
|
|
4
|
+
export type Negotiated = {
|
|
5
|
+
locale: string | undefined;
|
|
6
|
+
preferred: boolean;
|
|
7
|
+
};
|
|
3
8
|
/** What the server half needs from the factory. */
|
|
4
9
|
export type Shared = {
|
|
5
10
|
create: () => I18n;
|
|
6
|
-
negotiate: (event: Kit.Event, ranges: string | readonly string[] | null | undefined) =>
|
|
11
|
+
negotiate: (event: Kit.Event, ranges: string | readonly string[] | null | undefined) => Negotiated;
|
|
7
12
|
/** The locales the config serves. */
|
|
8
13
|
locales: () => string[];
|
|
9
14
|
basePath: string | undefined;
|
package/dist/kit/server.js
CHANGED
|
@@ -32,7 +32,7 @@ export const serverHalf = ({ create, negotiate, locales, basePath }) => {
|
|
|
32
32
|
const negotiated = () => {
|
|
33
33
|
if (lang === undefined) {
|
|
34
34
|
check(event);
|
|
35
|
-
lang = answer(event) ?? '';
|
|
35
|
+
lang = answer(event).locale ?? '';
|
|
36
36
|
}
|
|
37
37
|
return lang;
|
|
38
38
|
};
|
|
@@ -61,12 +61,12 @@ export const serverHalf = ({ create, negotiate, locales, basePath }) => {
|
|
|
61
61
|
// navigation only for what it read.
|
|
62
62
|
const route = event.url.pathname;
|
|
63
63
|
check(event);
|
|
64
|
-
const locale = answer(event);
|
|
64
|
+
const { locale, preferred } = answer(event);
|
|
65
65
|
if (event.isDataRequest)
|
|
66
66
|
return { i18n: { locale, route: withoutBasePath(route, basePath) } };
|
|
67
67
|
const i18n = create();
|
|
68
68
|
await (locale ? i18n.loadTranslations(locale, route) : i18n.setRoute(route));
|
|
69
|
-
return { i18n: i18n.snapshot({ records: true }) };
|
|
69
|
+
return { i18n: { ...i18n.snapshot({ records: true }), ...(preferred ? { preferred: true } : {}) } };
|
|
70
70
|
},
|
|
71
71
|
};
|
|
72
72
|
};
|
package/dist/kit/types.d.ts
CHANGED
|
@@ -37,7 +37,14 @@ export declare namespace Kit {
|
|
|
37
37
|
* locale and the route always, the tables and their records on a page
|
|
38
38
|
* render only. Plain data, for `devalue`.
|
|
39
39
|
*/
|
|
40
|
-
type Payload = Omit<Snapshot.Envelope, 'translations'> & Partial<Pick<Snapshot.Envelope, 'translations'
|
|
40
|
+
type Payload = Omit<Snapshot.Envelope, 'translations'> & Partial<Pick<Snapshot.Envelope, 'translations'>> & {
|
|
41
|
+
/**
|
|
42
|
+
* Set on a page render when `preferredLocale` gave the locale. A
|
|
43
|
+
* prerendered page's data is that render, so a navigation to one takes
|
|
44
|
+
* its locale only when the build's `preferredLocale` gave it.
|
|
45
|
+
*/
|
|
46
|
+
preferred?: true;
|
|
47
|
+
};
|
|
41
48
|
type Options = {
|
|
42
49
|
/**
|
|
43
50
|
* The visitor's choice, read from the event: a cookie, a route param, a
|
|
@@ -46,6 +53,9 @@ export declare namespace Kit {
|
|
|
46
53
|
* locale matches is skipped; a custom `sanitizeLocales` is applied to it
|
|
47
54
|
* first. It runs on every navigation and every
|
|
48
55
|
* preload, so it must be pure: it reads the event and writes nothing.
|
|
56
|
+
* With a server load it runs on the server only: a navigation to a
|
|
57
|
+
* prerendered page takes the locale it gave at build time, and otherwise
|
|
58
|
+
* keeps the tab's.
|
|
49
59
|
*
|
|
50
60
|
* @example
|
|
51
61
|
* preferredLocale: (event) => event.cookies?.get('lang')
|
package/dist/types.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ export declare namespace Config {
|
|
|
83
83
|
*/
|
|
84
84
|
translations?: Translations.T;
|
|
85
85
|
/**
|
|
86
|
-
* If you set this property, translations will be initialized immediately using this locale.
|
|
86
|
+
* If you set this property, translations will be initialized immediately using this locale. `defineI18n()` from `/kit` loads nothing for it: there it is a negotiation candidate. Leave it out of a config whose instance you `hydrate()` by hand – its load starts in the constructor, before the hand-off can be applied.
|
|
87
87
|
*/
|
|
88
88
|
initLocale?: InitLocale;
|
|
89
89
|
/**
|
|
@@ -129,20 +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
|
-
*
|
|
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.
|
|
141
144
|
*
|
|
142
145
|
* @example
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* const i18n = new I18n({ ...config, schema });
|
|
146
|
+
* const i18n = new I18n({ ...config, schema: {} as TranslationSchema });
|
|
146
147
|
*/
|
|
147
148
|
schema?: S;
|
|
148
149
|
/**
|
|
@@ -162,11 +163,11 @@ export declare namespace Config {
|
|
|
162
163
|
*/
|
|
163
164
|
basePath?: string;
|
|
164
165
|
/**
|
|
165
|
-
* Time in milliseconds the loaded translations stay fresh for. Once a locale's translations are older, the next load trigger runs its loaders again. 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.
|
|
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
167
|
*
|
|
167
168
|
* @default Number.POSITIVE_INFINITY
|
|
168
169
|
*
|
|
169
|
-
* @tip Set to `0` to treat translations as always stale (refetch on every load trigger).
|
|
170
|
+
* @tip Set to `0` to treat translations as always stale (refetch on every activating load trigger).
|
|
170
171
|
*/
|
|
171
172
|
cache?: number;
|
|
172
173
|
/**
|
|
@@ -442,7 +443,7 @@ export declare namespace Parser {
|
|
|
442
443
|
* with anything yields the other kind. `'date'` covers both date and time
|
|
443
444
|
* formatting and means `Date | number`. `'function'` is a rich-text callback,
|
|
444
445
|
* the shape ICU tags require. `'boolean'` is here for parsers that can prove
|
|
445
|
-
* it –
|
|
446
|
+
* it – no official parser can, since each compares stringified values.
|
|
446
447
|
*/
|
|
447
448
|
type ParamKind = 'unknown' | 'string' | 'number' | 'boolean' | 'date' | 'function';
|
|
448
449
|
/**
|
|
@@ -462,7 +463,7 @@ export declare namespace Parser {
|
|
|
462
463
|
kind?: ParamKind | readonly ParamKind[];
|
|
463
464
|
/**
|
|
464
465
|
* Values the message names explicitly – a hint for authoring tools, never
|
|
465
|
-
* an exhaustive set.
|
|
466
|
+
* an exhaustive set. Every official parser falls back to a default branch
|
|
466
467
|
* for anything unlisted, so this must not be used to close a union. Omit it
|
|
467
468
|
* where the listed values are not values at all (numeric thresholds, plural
|
|
468
469
|
* categories) or mean the opposite (an inequality's operands).
|
|
@@ -485,7 +486,7 @@ export declare namespace Parser {
|
|
|
485
486
|
branch: string;
|
|
486
487
|
}[];
|
|
487
488
|
};
|
|
488
|
-
/** Diagnostic context for an extractor.
|
|
489
|
+
/** Diagnostic context for an extractor. No official parser needs it to extract. */
|
|
489
490
|
type ExtractContext = {
|
|
490
491
|
key?: Key;
|
|
491
492
|
locale?: Locale;
|
|
@@ -509,6 +510,19 @@ export declare namespace Parser {
|
|
|
509
510
|
*/
|
|
510
511
|
type ExtractParamsFactory<O = unknown> = (options?: O) => ExtractParams;
|
|
511
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
|
+
}
|
|
512
526
|
export declare namespace Schema {
|
|
513
527
|
/**
|
|
514
528
|
* A schema types calls only when its keys form a specific, closed set. An
|
|
@@ -517,10 +531,25 @@ export declare namespace Schema {
|
|
|
517
531
|
* they degrade to no schema at all.
|
|
518
532
|
*/
|
|
519
533
|
type HasClosedKeys<S> = [keyof S & string] extends [never] ? false : string extends keyof S ? false : true;
|
|
520
|
-
/**
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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;
|
|
524
553
|
/**
|
|
525
554
|
* The key schema an instance was built with; `never` when it carries none.
|
|
526
555
|
* The counterpart of `FromConfig` for code handed a constructed surface
|
package/package.json
CHANGED