@sveltekit-i18n/base 3.0.1 → 3.1.0-next.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 CHANGED
@@ -30,10 +30,20 @@ Core i18n functionality for SvelteKit with support for custom message parsers. T
30
30
  ✅ **TypeScript** – Locales inferred from your config, keys and payloads from a [`schema`](#schema)
31
31
  ✅ **Zero dependencies** – Lightweight and fast
32
32
 
33
+ ## Requirements
34
+
35
+ Svelte 5 or newer, and one of Node 22+, Bun 1.2+ or Deno 2+. The package is
36
+ ESM-only and imports no `node:` module, so every runtime that runs your
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.
40
+
33
41
  ## Installation
34
42
 
35
43
  ```bash
36
44
  npm install @sveltekit-i18n/base
45
+ # bun add @sveltekit-i18n/base
46
+ # deno add npm:@sveltekit-i18n/base
37
47
  ```
38
48
 
39
49
  You'll also need a parser:
@@ -42,7 +52,8 @@ You'll also need a parser:
42
52
  # Choose one:
43
53
  npm install @sveltekit-i18n/parser-curly
44
54
  npm install @sveltekit-i18n/parser-icu
45
- # or create your own
55
+ npm install @sveltekit-i18n/parser-mf2
56
+ npm install @sveltekit-i18n/parser-i18next
46
57
  ```
47
58
 
48
59
  ## Quick Start
@@ -60,70 +71,80 @@ npm install @sveltekit-i18n/parser-icu
60
71
  ### 2. Setup with a parser
61
72
 
62
73
  ```javascript
63
- // src/lib/translations/index.js
64
- import { I18n } from '@sveltekit-i18n/base';
74
+ // src/lib/i18n.js
75
+ import { defineI18n } from '@sveltekit-i18n/base/kit';
65
76
  import parser from '@sveltekit-i18n/parser-curly';
66
77
 
67
- /** @type {import('@sveltekit-i18n/base').Config.T} */
68
- const config = {
78
+ export const config = {
69
79
  parser: parser({ onReport: null, /* other parser options */ }),
70
80
  loaders: [
71
81
  {
72
- locale: 'en',
73
- key: 'common',
74
- loader: async () => (await import('./en/common.json')).default,
75
- },
76
- {
77
- locale: 'cs',
78
- key: 'common',
79
- loader: async () => (await import('./cs/common.json')).default,
82
+ locale: ['en', 'cs'],
83
+ namespace: 'common',
84
+ loader: async ({ locale, namespace }) => (await import(`./translations/${locale}/${namespace}.json`)).default,
80
85
  },
81
86
  ],
82
87
  };
83
88
 
84
- // One reactive instance. Do NOT destructure its value properties — reading
85
- // them off the instance is what makes templates reactive. (`t`/`l` are
86
- // functions and stay reactive even when destructured, since the tracked reads
87
- // happen at call time. In a component, `const { loading } = $derived(i18n)`
88
- // destructures value reads without losing reactivity.)
89
- export const i18n = new I18n(config);
89
+ export const { handle, load, use, get } = defineI18n(config);
90
90
  ```
91
91
 
92
- ### 3. Load translations in your layout
92
+ ### 3. Wire it into SvelteKit
93
+
94
+ ```javascript
95
+ // src/hooks.server.js
96
+ export { handle } from '$lib/i18n';
97
+ ```
93
98
 
94
99
  ```javascript
95
- // src/routes/+layout.js
96
- import { i18n } from '$lib/translations';
100
+ // src/routes/+layout.server.js and src/routes/+layout.js — the same line in both
101
+ export { load } from '$lib/i18n';
102
+ ```
97
103
 
98
- /** @type {import('./$types').LayoutLoad} */
99
- export const load = async ({ url }) => {
100
- const { pathname } = url;
101
- const initLocale = 'en';
104
+ ```svelte
105
+ <!-- src/routes/+layout.svelte -->
106
+ <script>
107
+ import { use } from '$lib/i18n';
102
108
 
103
- await i18n.loadTranslations(initLocale, pathname);
109
+ let { data, children } = $props();
104
110
 
105
- return {};
106
- };
111
+ use(() => data);
112
+ </script>
113
+
114
+ {@render children()}
107
115
  ```
108
116
 
109
- > **Rendering per-visitor locales on the server?** The instance above is a
110
- > module-level singleton — on the server it is shared by every request in the
111
- > process, so concurrent visitors overwrite each other's locale. Use one
112
- > instance per request and hand its data to the client with `snapshot()`:
113
- > see [Server-Side Rendering](./docs/README.md#server-side-rendering).
117
+ ```html
118
+ <!-- src/app.html -->
119
+ <html lang="%lang%" dir="%dir%">
120
+ ```
121
+
122
+ The server picks the visitor's locale from the `Accept-Language` header (or
123
+ from a cookie, with `preferredLocale`), loads it per request and hands it to
124
+ the browser, so nothing loads twice and no visitor sees another's locale. See
125
+ [SvelteKit](./docs/README.md#sveltekit) for the details and
126
+ [Server-Side Rendering](./docs/README.md#server-side-rendering) for wiring it
127
+ by hand.
114
128
 
115
129
  ### 4. Use in components
116
130
 
117
131
  ```svelte
118
132
  <script>
119
- import { i18n } from '$lib/translations';
133
+ import { get } from '$lib/i18n';
134
+
135
+ const i18n = get();
120
136
  </script>
121
137
 
122
138
  <p>{i18n.t('common.greeting', { name: 'World' })}</p>
123
139
  ```
124
140
 
125
141
  The call reads the reactive translation table and locale, so the text updates
126
- automatically when either changes — no stores, no `$` prefix.
142
+ automatically when either changes — no stores, no `$` prefix. Do NOT
143
+ destructure the instance's value properties: reading them off the instance is
144
+ what makes templates reactive. (`t`/`l` are functions and stay reactive even
145
+ when destructured, since the tracked reads happen at call time. In a component,
146
+ `const { loading } = $derived(i18n)` destructures value reads without losing
147
+ reactivity.)
127
148
 
128
149
  ## Using Different Parsers
129
150
 
@@ -152,8 +173,10 @@ import i18n from '@sveltekit-i18n/base';
152
173
 
153
174
  const customParser = () => ({
154
175
  parse: (value, params) => {
155
- // Your custom interpolation logic
156
- return value.replace(/\{(\w+)\}/g, (_, key) => params[0]?.[key] ?? key);
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;
157
180
  },
158
181
  });
159
182
 
@@ -179,18 +202,60 @@ Array of loader configurations:
179
202
  loaders: [
180
203
  {
181
204
  locale: 'en', // Required: locale identifier
182
- key: 'common', // Required: translation namespace
205
+ namespace: 'common', // Required: translation namespace
183
206
  loader: async () => {}, // Required: async function returning translations
184
207
  routes: ['/about'], // Optional: load only for specific routes
185
208
  },
186
209
  ]
187
210
  ```
188
211
 
212
+ `locale` and `namespace` each take a list as well. Such a descriptor stands for one loader per locale and namespace pair, and the loader receives the pair it is loading, so one computed loader can replace a descriptor per file:
213
+
214
+ ```javascript
215
+ loaders: [
216
+ {
217
+ locale: ['en', 'cs'],
218
+ namespace: ['common', 'nav'],
219
+ loader: async ({ locale, namespace }) => (await import(`./${locale}/${namespace}.json`)).default,
220
+ },
221
+ ]
222
+ ```
223
+
224
+ A named capture group in a `RegExp` route is a load parameter: its match reaches the loader as `params`, and the loader runs again when it changes, its new data replacing the old:
225
+
226
+ ```javascript
227
+ {
228
+ locale: 'en',
229
+ namespace: 'article',
230
+ routes: [/^\/article\/(?<articleId>[^/]+)/],
231
+ loader: async ({ locale, params }) => (await fetch(`/api/articles/${params.articleId}/i18n/${locale}`)).json(),
232
+ }
233
+ ```
234
+
235
+ See [route params](./docs/README.md#route-params) for the rules.
236
+
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
+
239
+ A loader that throws is logged, and the rest of the load lands without its data; it runs again on the next load trigger. SvelteKit's `redirect()` and an `error()` below 500 (told by their shape: an own `status` with a `location` or a `body`, on a value that is not an `Error`) are logged too, but they also reject the load, so a SvelteKit `load` awaiting the call hands them to SvelteKit; the rejected call is undone — what it replaced goes back — unless a later call that has not failed came in the meantime. See [the loader](./docs/README.md#loader-required).
240
+
189
241
  Both `loaders` and a loader's `routes` accept readonly arrays, so a whole-config `as const` is fine.
190
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
+
191
256
  ### `translations`
192
257
 
193
- Synchronous translations loaded immediately:
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:
194
259
 
195
260
  ```javascript
196
261
  translations: {
@@ -208,6 +273,8 @@ Initialize with a specific locale immediately:
208
273
  initLocale: 'en'
209
274
  ```
210
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
+
211
278
  ### `fallbackLocale`
212
279
 
213
280
  Fallback when translation is missing:
@@ -226,6 +293,10 @@ Default return value when translation key is not found:
226
293
  fallbackValue: '...' // Default: returns the key itself
227
294
  ```
228
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
+
229
300
  ### `preprocess`
230
301
 
231
302
  Transform translations after loading:
@@ -252,19 +323,19 @@ type TranslationSchema = {
252
323
  const i18n = new I18n({ ...config, schema: {} as TranslationSchema });
253
324
  ```
254
325
 
255
- Hand-write it for a small set of messages, or point the slot at a generated artifact. A schema whose keys are not a closed set is ignored, and keys stay plain strings. See [`schema`](./docs/README.md#schema) for the full rules.
326
+ Hand-write it for a small set of messages, or point the slot at a generated artifact — [@sveltekit-i18n/typegen](https://github.com/sveltekit-i18n/typegen), a separate package, generates one. A schema whose keys are not a closed set is ignored, and keys stay plain strings. See [`schema`](./docs/README.md#schema) for the full rules.
256
327
 
257
328
  ### `cache`
258
329
 
259
- Time in milliseconds the loaded translations stay fresh for. By default, loaded translations never expire — loaders run once per locale and key (a loader's `routes` only decide whether a load trigger considers it, not how often it runs).
330
+ Time in milliseconds the loaded translations stay fresh for. By default, loaded translations never expire — each loader (but one with [`cache: false`](./docs/README.md#cache-optional)) runs once per locale and [route params](./docs/README.md#route-params) (a loader's `routes` decide whether a load trigger considers it, and the params their named groups capture decide when it runs again).
260
331
 
261
332
  Set a finite value when your loaders fetch from a source that can change at runtime (e.g. a CMS):
262
333
 
263
334
  ```javascript
264
- cache: 3600000 // Translations older than 1 hour refetch on the next load
335
+ cache: 3600000 // Translations older than 1 hour refetch on the next activating load
265
336
  ```
266
337
 
267
- 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).
338
+ 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).
268
339
 
269
340
  ### `extensions`
270
341
 
@@ -315,38 +386,57 @@ log: {
315
386
  - `l(locale, key, ...params)` – translate for an explicit locale
316
387
  - `locale` – the ACTIVE locale; assignment is a fire-and-forget `setLocale()`
317
388
  - `locales` – available locales
318
- - `loading` – `true` while any load is in flight
389
+ - `loading` – `true` while any activating load is in flight; a `{ activate: false }` load counts only once an activating trigger joins it
319
390
  - `initialized` – locale and route set, translations present
320
391
  - `translations` / `rawTranslations` – the (pre/post-preprocess) tables
321
392
 
322
393
  ### Methods
323
394
 
324
- Load-triggering methods return the promise of the matching load — concurrent duplicate triggers share one in-flight load (and its promise) instead of fetching twice.
395
+ Load-triggering methods return the promise of the matching load — concurrent duplicate triggers from one route share one in-flight load (and its promise) instead of fetching twice.
325
396
 
326
- - `loadTranslations(locale, route?)` – load translations for locale and route; `route` defaults to the current one
397
+ - `loadTranslations(locale, route?, options?)` – load translations for locale and route; `route` defaults to the current one, and `{ activate: false }` only fills the tables without switching to them
398
+ - `loadNamespace(namespace, locale?)` – load one namespace on demand, whatever its loaders' routes, without switching to it; it stays loaded across routes
327
399
  - `setLocale(locale)` – request a locale; loads once a route is known
328
400
  - `setRoute(route)` – update the current route
329
401
  - `loadConfig(config)` – (re)configure the instance
330
- - `addTranslations(translations)` – add synchronous translations
331
- - `snapshot()` – serialize the active locale (and the fallback) for the current route, shaped like `config.translations` so the receiving instance hydrates by passing it to `addTranslations()`
332
- - `invalidate(locale?)` – mark loaded translations stale (one locale, or all); loaders run again on the next load trigger, and a load still in flight for an invalidated locale settles with its data discarded
402
+ - `addTranslations(translations)` – seed synchronous translations; the loaders of their namespaces still run and merge into them
403
+ - `snapshot(options?)` – serialize what the active locale (and the fallback) holds; `{ records: true }` returns the envelope `hydrate()` restores, with the loaders that delivered, the active locale and the route, and no argument returns the data alone, shaped like `config.translations`, for a plain `hydrate({ translations })`
404
+ - `hydrate(envelope?)` – restore a server's snapshot: its data, its load records (so those loaders do not run again — one with `cache: false` only for the locale and route it was rendered for), its locale and its route; an envelope without records keeps the loaders of the namespaces its data names from running
405
+ - `invalidate(locale?, namespace?)` – mark loaded translations stale (one locale or all, one namespace or all); loaders run again on the next load trigger, and a loader still in flight for what was invalidated settles with whatever it returns or throws discarded — an activating trigger fetches it again before it activates, unless another loader of its load threw SvelteKit's control flow
333
406
  - `destroy()` – detach a per-request or per-component instance: in-flight loads settle discarded, further load and mutation calls are ignored, reads keep working
334
407
 
335
408
  ### Utilities
336
409
 
337
- Two helpers the instance uses internally ship from a separate subpath, for code that has to match the library's own behavior:
410
+ Pure helpers ship from a separate subpath, for the code around the instance that has to match the library's own behavior or decide which locale to ask for:
338
411
 
339
412
  ```javascript
340
- import { sanitizeLocales, toDotNotation } from '@sveltekit-i18n/base/utils';
413
+ import { matchLocale, resolveLoaders, sanitizeLocales, textDirection, toDotNotation } from '@sveltekit-i18n/base/utils';
341
414
  ```
342
415
 
343
416
  - `toDotNotation(input, preserveArrays?)` – the flattening behind [`preprocess`](#preprocess), for a custom `preprocess` that still wants dot notation
417
+ - `resolveLoaders(loaders, sanitizeLocales?)` – normalizes `config.loaders` the way the instance does, into one loader per locale and namespace pair, for code that reads a config from outside the instance
344
418
  - `sanitizeLocales(...locales)` – normalizes a locale from a URL, cookie or `Accept-Language` header the way the instance does, so it can be compared against `locale`
419
+ - `matchLocale(requested, available)` – picks the configured locale a visitor asked for, from an `Accept-Language` header or `navigator.languages`, falling back from `en-GB` to `en` and answering `undefined` when nothing matches
420
+ - `textDirection(locale)` – `'ltr'` or `'rtl'` for a `dir` attribute, from the script the tag spells or the one `Intl.Locale#maximize()` adds, with no list of languages to keep
421
+
422
+ ### SvelteKit
423
+
424
+ ```javascript
425
+ import { defineI18n } from '@sveltekit-i18n/base/kit';
426
+
427
+ export const { handle, load, use, get } = defineI18n(config, { preferredLocale });
428
+ ```
429
+
430
+ - `handle` – the `hooks.server.js` hook; fills `%lang%` and `%dir%` in `app.html`'s `<html>` tag
431
+ - `load` – the root layout's `load`, exported from `+layout.server.js` and `+layout.js` alike: negotiates the locale, loads it on the server per request and hands it to the one instance a browser tab keeps
432
+ - `use(() => data)` – called once in the root `+layout.svelte`; provides the instance, follows every navigation and keeps `<html lang>` and `<html dir>` in sync
433
+ - `get()` – the instance, in any component below the root layout
345
434
 
346
435
  Full API documentation: [docs/README.md](./docs/README.md)
347
436
 
348
437
  ## Documentation
349
438
 
439
+ - 🌐 [sveltekit-i18n.github.io](https://sveltekit-i18n.github.io) – The documentation site, with a live playground
350
440
  - 📖 [Full API Documentation](./docs/README.md) – Complete reference
351
441
  - 📚 [Main Library Docs](https://github.com/sveltekit-i18n/lib/tree/master/docs/INDEX.md) – Guides, tutorials, and best practices
352
442
  - 🎨 [Parsers](https://github.com/sveltekit-i18n/parsers) – Available parsers and how to create your own
@@ -356,11 +446,12 @@ Full API documentation: [docs/README.md](./docs/README.md)
356
446
 
357
447
  ```typescript
358
448
  import { I18n, type Config } from '@sveltekit-i18n/base';
359
- import parser from '@sveltekit-i18n/parser-curly';
449
+ import parser, { type Parser } from '@sveltekit-i18n/parser-curly';
360
450
 
361
451
  // The parser's params – the rest parameters of `t`/`l`. Annotate only when the
362
- // config lives on its own; `new I18n({ ... })` infers them.
363
- type Params = [payload?: Record<string, unknown>];
452
+ // config lives on its own; `new I18n({ ... })` infers them. Take the tuple from
453
+ // the parser rather than spelling it by hand.
454
+ type Params = Parser.Params;
364
455
 
365
456
  const config: Config.T<Params> = {
366
457
  parser: parser({ onReport: null }),
@@ -368,7 +459,7 @@ const config: Config.T<Params> = {
368
459
  };
369
460
  ```
370
461
 
371
- 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`):
462
+ 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`):
372
463
 
373
464
  ```typescript
374
465
  const i18n = new I18n({ parser: parser({ onReport: null }), initLocale: 'en', fallbackLocale: 'de' });
@@ -382,9 +473,12 @@ The locales survive only when the config reaches the constructor as a literal
382
473
  ## Related Packages
383
474
 
384
475
  - [sveltekit-i18n](https://github.com/sveltekit-i18n/lib) – Complete solution, with the Curly Message Format parser included
385
- - [@sveltekit-i18n/parser-curly](https://github.com/sveltekit-i18n/parsers/tree/master/parser-curly) – [Curly Message Format](https://github.com/curly-message/spec) parser
476
+ - [@sveltekit-i18n/parser-curly](https://github.com/sveltekit-i18n/parsers/tree/master/parser-curly) – [Curly Message Format](https://curlymessage.dev) parser
386
477
  - [@sveltekit-i18n/parser-icu](https://github.com/sveltekit-i18n/parsers/tree/master/parser-icu) – ICU message format parser
478
+ - [@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
479
+ - [@sveltekit-i18n/parser-i18next](https://github.com/sveltekit-i18n/parsers/tree/master/parser-i18next) – [i18next](https://www.i18next.com) interpolation and formatting syntax parser
387
480
  - [Extensions](https://github.com/sveltekit-i18n/extensions) – Official extensions for the `config.extensions` pipe
481
+ - [@sveltekit-i18n/typegen](https://github.com/sveltekit-i18n/typegen) – Generates the [`schema`](#schema) type from your translation files
388
482
 
389
483
  ## Contributing
390
484
 
@@ -396,6 +490,11 @@ For issues specific to base functionality, create a ticket [here](https://github
396
490
 
397
491
  See [Releases](https://github.com/sveltekit-i18n/base/releases) for version history.
398
492
 
493
+ ## Sponsor
494
+
495
+ You can support the maintenance of this package through
496
+ [GitHub Sponsors](https://github.com/sponsors/sveltekit-i18n).
497
+
399
498
  ## License
400
499
 
401
500
  MIT
@@ -1,11 +1,13 @@
1
- import type { Config, Extension, Parser, Schema, Translations } from './types.js';
1
+ import type { Config, Extension, Loader, Parser, Schema, Snapshot, Translations } from './types.js';
2
2
  declare class I18nCore<ParserParams extends Parser.Params = any, ParserOutput = string, TranslationSchema = never, LocaleUnion extends string = string> {
3
3
  #private;
4
4
  constructor(config?: Config.T<ParserParams, ParserOutput>);
5
5
  /**
6
6
  * The active locale. Reading it is reactive; assigning it is a shorthand for
7
7
  * a fire-and-forget `setLocale()` — the value therefore updates once the
8
- * locale's translations resolved, not synchronously on assignment.
8
+ * locale's translations resolved, not synchronously on assignment. It does
9
+ * not advance when a loader throws SvelteKit's `redirect()` or an `error()`
10
+ * below 500, which an assignment only logs.
9
11
  */
10
12
  get locale(): Config.LocaleInput<LocaleUnion> | undefined;
11
13
  set locale(value: Config.LocaleInput<LocaleUnion> | undefined);
@@ -25,43 +27,114 @@ declare class I18nCore<ParserParams extends Parser.Params = any, ParserOutput =
25
27
  /** Like `t`, for an explicit locale. */
26
28
  l: Translations.LocalTranslationFunction<ParserParams, ParserOutput, TranslationSchema, LocaleUnion>;
27
29
  /**
28
- * Public entry for (re)configuration. The failure is reported here and the
29
- * promise marked handled, so a fire-and-forget call cannot become an
30
- * unhandled rejection; an awaiting caller still receives it.
30
+ * Public entry for (re)configuration. It returns the promise of the
31
+ * `initLocale` load, which reports its own failure; a config that fails to
32
+ * apply is reported here. Either way the promise is marked handled, so a
33
+ * fire-and-forget call cannot become an unhandled rejection; an awaiting
34
+ * caller still receives it.
31
35
  */
32
36
  loadConfig: (config: Config.T<ParserParams, ParserOutput>) => Promise<void>;
33
37
  setLocale: (locale?: Config.LocaleInput<LocaleUnion>) => Promise<void>;
34
- setRoute: (route: string) => Promise<void>;
35
- loadTranslations: (locale: Config.LocaleInput<LocaleUnion>, route?: string) => Promise<void>;
38
+ setRoute: (input: string) => Promise<void>;
36
39
  /**
37
- * Marks loaded translations stale — for one locale, or all of them. Loaders
38
- * run again on the NEXT load trigger; the call itself starts no load and
39
- * keeps the currently displayed translations in place. A load still in
40
- * flight for an invalidated locale is severed: it settles, but its data is
41
- * discarded — it predates the invalidation.
40
+ * `{ activate: false }` only fills the tables: it leaves the requested
41
+ * locale, the route and `locale` untouched and does not count towards
42
+ * `loading` — what is rendered does not change: data of a loader whose route
43
+ * params differ from the ones the current route asks for is kept aside, the
44
+ * latest per loader, and the activating trigger that asks for them applies
45
+ * it. It
46
+ * leaves `cache` expiry to the next activating trigger. A loader's
47
+ * `redirect()` or `error()` below 500 rejects it all the same.
42
48
  */
43
- invalidate: (locale?: Config.LocaleInput<LocaleUnion>) => void;
49
+ loadTranslations: (locale: Config.LocaleInput<LocaleUnion>, route?: string, { activate }?: {
50
+ activate?: boolean;
51
+ }) => Promise<void>;
52
+ /**
53
+ * Loads one namespace for the active locale (or `locale`), whatever the
54
+ * routes of its loaders say — for what an interaction needs rather than a
55
+ * route: a modal, a panel, an editor. Warm, like `{ activate: false }`: it
56
+ * changes neither the locale nor `loading`, and it leaves `cache` expiry to
57
+ * the next activating trigger. It honours the load records, so calling it
58
+ * on every interaction fetches once — a loader with `cache: false` runs each
59
+ * time on its routes — and what it loads stays loaded across routes. A
60
+ * loader's `redirect()` or `error()` below 500 rejects it.
61
+ */
62
+ loadNamespace: (namespace: Loader.Key, locale?: Config.LocaleInput<LocaleUnion>) => Promise<void>;
63
+ /**
64
+ * Marks loaded translations stale — for one locale or all of them, and for
65
+ * one namespace or all of them. Loaders run again on the NEXT load trigger;
66
+ * the call itself starts no load and keeps the currently displayed
67
+ * translations in place. A loader still in flight for what was invalidated
68
+ * is severed: its load settles, but what it returns or throws is discarded —
69
+ * it predates the invalidation — and an activating trigger fetches it again,
70
+ * once, before its locale activates. It leaves it to the next trigger when
71
+ * another loader of its load threw SvelteKit's control flow that still
72
+ * counts, which rejects the trigger, and when the refetch is severed too. A
73
+ * namespace invalidation leaves the locale's `cache` window where it was.
74
+ */
75
+ invalidate: (locale?: Config.LocaleInput<LocaleUnion>, namespace?: Loader.Key) => void;
44
76
  addTranslations: (translations?: Translations.SerializedTranslations) => void;
77
+ /**
78
+ * Restores the state `snapshot({ records: true })` captured on another
79
+ * instance: its data, its load records, the active locale and the route.
80
+ * A loader named by a record does not run again for the same params — one
81
+ * with `cache: false` only for the pass the envelope arrived with; data no
82
+ * record names is displayed but keeps no loader from running. An envelope
83
+ * without `records` is a plain hand-off instead: every namespace its data
84
+ * names keeps its loaders without params from running, and holds one with
85
+ * `cache: false` back for that pass. Nothing
86
+ * happens for `undefined`, so a load whose server half sent nothing can call
87
+ * it unconditionally.
88
+ */
89
+ hydrate: (envelope?: Snapshot.Envelope) => void;
45
90
  /**
46
91
  * Serializes what this instance holds for the active locale and the fallback
47
- * locale, narrowed to the current route: a key owned only by loaders that do
48
- * not match the route is left out. The result is shaped like
49
- * `config.translations`, so a client hydrates by passing it to
50
- * `addTranslations()` — the bookkeeping derived from it then keeps the
51
- * matching loaders from fetching the same data again. Apply it to the
52
- * instance rather than assigning it to `config.translations`: the payload is
53
- * a subset — two locales, and nothing of a key its loaders claim for another
54
- * route — so assigning it would drop the rest of the config's own data.
55
- * A literal `__proto__` key is left out: the serializer SvelteKit hands load
56
- * data to refuses an object that carries one.
92
+ * locale. The result is shaped like `config.translations`, and a client
93
+ * hands it over with `hydrate({ translations })`: a plain hand-off, whose
94
+ * namespace records keep the matching loaders without params from fetching
95
+ * it again and hold one with `cache: false` back for that pass. Passed to
96
+ * `addTranslations()` or assigned to `config.translations` it only seeds,
97
+ * and every loader runs again.
98
+ * A namespace plain data cannot hand over is left out, for the client to
99
+ * load: one fed by several loaders, whose record would suppress a part the
100
+ * payload lacks; one whose loader's routes can capture params, whose data a
101
+ * plain hand-off keeps as data no loader delivered, so the next params could
102
+ * not replace it; and one none of whose loaders delivered here and no
103
+ * hand-off named, whose seeded data would keep the client's loaders from
104
+ * ever running.
105
+ * A literal `__proto__` key is left out too, and with records its
106
+ * namespace's loaders are, and a namespace whose loader captures params,
107
+ * without them a namespace a loader serves, so the client loads it whole: the serializer SvelteKit hands load data to refuses an
108
+ * object that carries one. A locale
109
+ * named `__proto__` is left out altogether.
110
+ *
111
+ * `{ records: true }` returns an envelope for `hydrate()` instead: the same
112
+ * data, the loaders that delivered it, the active locale and the route. The
113
+ * records name each loader, so a namespace fed by several loaders is handed
114
+ * over too, and so is one a loader delivered for route params while its
115
+ * record says so — not a namespace with both, whose data the client could
116
+ * not split between them. What was seeded into the namespace of a loader
117
+ * whose routes capture params travels apart as `seeds`, so it outlives new
118
+ * params there, and reaches the client where the namespace is left out.
57
119
  */
58
- snapshot: () => Translations.SerializedTranslations;
120
+ snapshot: {
121
+ (options?: {
122
+ records?: false;
123
+ }): Translations.SerializedTranslations;
124
+ (options: {
125
+ records: true;
126
+ }): Snapshot.Envelope;
127
+ (options?: {
128
+ records?: boolean;
129
+ }): Translations.SerializedTranslations | Snapshot.Envelope;
130
+ };
59
131
  /**
60
132
  * Detaches the instance from its loading lifecycle: in-flight loads settle
61
- * with their data discarded, `loading` drops to `false`, and every further
62
- * load or mutation call is ignored with a warning. Reads (`t`, `l`, `locale`,
63
- * `translations`, `snapshot`) keep working, so a component still tearing down
64
- * renders its last state instead of breaking. Idempotent.
133
+ * with whatever their loaders return or throw discarded, `loading` drops to
134
+ * `false`, and every further load or mutation call is ignored with a warning.
135
+ * Reads (`t`, `l`, `locale`, `translations`, `snapshot`) keep working, so a
136
+ * component still tearing down renders its last state instead of breaking.
137
+ * Idempotent.
65
138
  */
66
139
  destroy: () => void;
67
140
  }