@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 CHANGED
@@ -34,9 +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. 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.
37
+ SvelteKit build runs it. The [`/kit`](#sveltekit) subpath needs SvelteKit 2
38
+ or 3; the SvelteKit behaviour the docs describe is checked against 2.70 and
39
+ 3.0.
40
40
 
41
41
  ## Installation
42
42
 
@@ -91,20 +91,26 @@ export const { handle, load, use, get } = defineI18n(config);
91
91
 
92
92
  ### 3. Wire it into SvelteKit
93
93
 
94
+ `#lib` is the `imports` entry `sv create` scaffolds in a SvelteKit 3 app's
95
+ `package.json`. A SvelteKit 2 app adds the same entry,
96
+ `"imports": { "#lib/*": "./src/lib/*" }`, or imports from `$lib/i18n` — as
97
+ it must on Vite 5 when `i18n` is a `.ts` file imported from a `.js` module or
98
+ a plain `<script>`.
99
+
94
100
  ```javascript
95
101
  // src/hooks.server.js
96
- export { handle } from '$lib/i18n';
102
+ export { handle } from '#lib/i18n.js';
97
103
  ```
98
104
 
99
105
  ```javascript
100
106
  // src/routes/+layout.server.js and src/routes/+layout.js — the same line in both
101
- export { load } from '$lib/i18n';
107
+ export { load } from '#lib/i18n.js';
102
108
  ```
103
109
 
104
110
  ```svelte
105
111
  <!-- src/routes/+layout.svelte -->
106
112
  <script>
107
- import { use } from '$lib/i18n';
113
+ import { use } from '#lib/i18n.js';
108
114
 
109
115
  let { data, children } = $props();
110
116
 
@@ -122,15 +128,15 @@ export { load } from '$lib/i18n';
122
128
  The server picks the visitor's locale from the `Accept-Language` header (or
123
129
  from a cookie, with `preferredLocale`), loads it per request and hands it to
124
130
  the browser, so nothing loads twice and no visitor sees another's locale. See
125
- [SvelteKit](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md#sveltekit) for the details and
126
- [Server-Side Rendering](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md#server-side-rendering) for wiring it
131
+ [SvelteKit](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#sveltekit) for the details and
132
+ [Server-Side Rendering](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#server-side-rendering) for wiring it
127
133
  by hand.
128
134
 
129
135
  ### 4. Use in components
130
136
 
131
137
  ```svelte
132
138
  <script>
133
- import { get } from '$lib/i18n';
139
+ import { get } from '#lib/i18n.js';
134
140
 
135
141
  const i18n = get();
136
142
  </script>
@@ -232,30 +238,29 @@ A named capture group in a `RegExp` route is a load parameter: its match reaches
232
238
  }
233
239
  ```
234
240
 
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](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md#route-params) for the rules.
241
+ `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](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#route-params) for the rules.
236
242
 
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`](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md#cache-optional).
243
+ 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, sharing a fetch of it already in flight for the same params and route, and `config.cache` does not apply to it; only a hydrated snapshot holds it back, for the locale and route it was rendered for, until another locale or route is requested or a `preload()` runs, and a call handed a [`preload()`](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#preloadlocale-route) token shows what that preload fetched instead of running it again. See [the loader's `cache`](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#cache-optional).
238
244
 
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](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md#loader-required).
245
+ 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](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#loader-required).
240
246
 
241
247
  Both `loaders` and a loader's `routes` accept readonly arrays, so a whole-config `as const` is fine.
242
248
 
243
249
  ### `basePath`
244
250
 
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:
251
+ The path the app is served under — SvelteKit's `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
252
 
247
253
  ```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
254
+ // SvelteKit 3, vite.config.js: sveltekit({ paths: { base: process.env.VITE_BASE_PATH ?? '' } })
255
+ // SvelteKit 2, svelte.config.js: kit: { paths: { base: process.env.VITE_BASE_PATH ?? '' } }
256
+ basePath: import.meta.env.VITE_BASE_PATH
252
257
  ```
253
258
 
254
- See [`basePath`](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md#basepath).
259
+ See [`basePath`](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#basepath).
255
260
 
256
261
  ### `translations`
257
262
 
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()`](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md#hydrateenvelope) instead:
263
+ 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()`](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#hydrateenvelope) instead:
259
264
 
260
265
  ```javascript
261
266
  translations: {
@@ -273,7 +278,7 @@ The initial locale, used when nothing else decides:
273
278
  initLocale: 'en'
274
279
  ```
275
280
 
276
- With [`defineI18n()`](#sveltekit) it is the locale a visitor gets when nothing they prefer is served, and it loads only when negotiation picks it ([Which locale](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md#which-locale)). With `new I18n(config)` the constructor loads it right away, so leave it out of a config whose instance you [`hydrate()`](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md#hydrateenvelope) by hand — its load starts before the hand-off can be applied.
281
+ With [`defineI18n()`](#sveltekit) it is the locale a visitor gets when nothing they prefer is served, and it loads only when negotiation picks it ([Which locale](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#which-locale)). With `new I18n(config)` the constructor loads it right away, so leave it out of a config whose instance you [`hydrate()`](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#hydrateenvelope) by hand — its load starts before the hand-off can be applied.
277
282
 
278
283
  ### `fallbackLocale`
279
284
 
@@ -295,7 +300,7 @@ fallbackValue: '...' // Default: returns the key itself
295
300
 
296
301
  ### `sanitizeLocales`
297
302
 
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`](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md#sanitizelocales).
303
+ 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`](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#sanitizelocales).
299
304
 
300
305
  ### `preprocess`
301
306
 
@@ -334,11 +339,11 @@ Or state it per instance — only its type is read, so the value can stay empty
334
339
  const i18n = new I18n({ ...config, schema: {} as TranslationSchema });
335
340
  ```
336
341
 
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`](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md#schema) for the full rules.
342
+ 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`](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#schema) for the full rules.
338
343
 
339
344
  ### `cache`
340
345
 
341
- Time in milliseconds the loaded translations stay fresh for. By default, loaded translations never expire — each loader (but one with [`cache: false`](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md#cache-optional)) runs once per locale and [route params](https://github.com/sveltekit-i18n/base/blob/3.2.0/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).
346
+ Time in milliseconds the loaded translations stay fresh for. By default, loaded translations never expire — each loader (but one with [`cache: false`](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#cache-optional)) runs once per locale and [route params](https://github.com/sveltekit-i18n/base/blob/3.3.1/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).
342
347
 
343
348
  Set a finite value when your loaders fetch from a source that can change at runtime (e.g. a CMS):
344
349
 
@@ -346,7 +351,7 @@ Set a finite value when your loaders fetch from a source that can change at runt
346
351
  cache: 3600000 // Translations older than 1 hour refetch on the next activating load
347
352
  ```
348
353
 
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).
354
+ Expiry is evaluated by the next activating load trigger (`setLocale`, `setRoute`, `loadTranslations`) or `preload()`; a warm load — `loadTranslations(…, { activate: false })` or `loadNamespace()` — fills the tables without evaluating it, and a call handed a `preload()` token leaves it to that preload. 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).
350
355
 
351
356
  ### `extensions`
352
357
 
@@ -405,14 +410,15 @@ log: {
405
410
 
406
411
  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.
407
412
 
408
- - `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
413
+ - `loadTranslations(locale, route?, options?)` – load translations for locale and route; `route` defaults to the current one, `{ activate: false }` only fills the tables without switching to them, and `{ preloaded }` takes a `preload()` token
414
+ - `preload(locale, route?)` – the request of a navigation that may never commit (a router's `load`, a hover's included): it judges freshness and runs `cache: false` loaders as an activating call would, shows nothing, and resolves to a token the commit's `loadTranslations()` or `setRoute()` takes as `{ preloaded }` to show what it fetched instead of fetching it again
409
415
  - `loadNamespace(namespace, locale?)` – load one namespace on demand, whatever its loaders' routes, without switching to it; it stays loaded across routes
410
416
  - `setLocale(locale)` – request a locale; loads once a route is known
411
- - `setRoute(route)` – update the current route
417
+ - `setRoute(route, options?)` – update the current route; `{ preloaded }` takes a `preload()` token
412
418
  - `loadConfig(config)` – (re)configure the instance
413
419
  - `addTranslations(translations)` – seed synchronous translations; the loaders of their namespaces still run and merge into them
414
420
  - `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 })`
415
- - `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
421
+ - `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, until a `preload()` runs or `invalidate()` covers it), its locale and its route; an envelope without records keeps the loaders of the namespaces its data names from running
416
422
  - `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
417
423
  - `destroy()` – detach a per-request or per-component instance: in-flight loads settle discarded, further load and mutation calls are ignored, reads keep working
418
424
 
@@ -443,12 +449,12 @@ export const { handle, load, use, get } = defineI18n(config, { preferredLocale }
443
449
  - `use(() => data)` – called once in the root `+layout.svelte`; provides the instance, follows every navigation and keeps `<html lang>` and `<html dir>` in sync
444
450
  - `get()` – the instance, in any component below the root layout
445
451
 
446
- Full API documentation: [docs/README.md](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md)
452
+ Full API documentation: [docs/README.md](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md)
447
453
 
448
454
  ## Documentation
449
455
 
450
456
  - 🌐 [sveltekit-i18n.github.io](https://sveltekit-i18n.github.io) – The documentation site, with a live playground
451
- - 📖 [Full API Documentation](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md) – Complete reference
457
+ - 📖 [Full API Documentation](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md) – Complete reference
452
458
  - 📚 [Main Library Docs](https://github.com/sveltekit-i18n/lib/tree/master/docs/INDEX.md) – Guides, tutorials, and best practices
453
459
  - 🎨 [Parsers](https://github.com/sveltekit-i18n/parsers) – Available parsers and how to create your own
454
460
  - 💡 [Examples](https://github.com/sveltekit-i18n/lib/tree/master/examples) – Real-world usage examples
@@ -470,7 +476,7 @@ const config: Config.T<Params> = {
470
476
  };
471
477
  ```
472
478
 
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`):
479
+ 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`, `preload`, `invalidate`, `l`, `locale`, `locales`):
474
480
 
475
481
  ```typescript
476
482
  const i18n = new I18n({ parser: parser({ onReport: null }), initLocale: 'en', fallbackLocale: 'de' });
@@ -479,7 +485,7 @@ i18n.setLocale('en'); // 'en' | 'de' autocomplete here
479
485
  i18n.setLocale('sv'); // still accepted — the union is a hint, not a constraint
480
486
  ```
481
487
 
482
- The locales survive only when the config reaches the constructor as a literal — inline, as above, or a separate object with `as const`. An annotated or separately widened config, and any config with one dynamic locale source (`loaders: locales.map(...)`), leaves them plain `string`. See [TypeScript](https://github.com/sveltekit-i18n/base/blob/3.2.0/docs/README.md#typescript) for both.
488
+ The locales survive only when the config reaches the constructor as a literal — inline, as above, or a separate object with `as const`. An annotated or separately widened config, and any config with one dynamic locale source (`loaders: locales.map(...)`), leaves them plain `string`. See [TypeScript](https://github.com/sveltekit-i18n/base/blob/3.3.1/docs/README.md#typescript) for both.
483
489
 
484
490
  ## Related Packages
485
491
 
@@ -35,7 +35,14 @@ declare class I18nCore<ParserParams extends Parser.Params = any, ParserOutput =
35
35
  */
36
36
  loadConfig: (config: Config.T<ParserParams, ParserOutput>) => Promise<void>;
37
37
  setLocale: (locale?: Config.LocaleInput<LocaleUnion>) => Promise<void>;
38
- setRoute: (input: string) => Promise<void>;
38
+ /**
39
+ * With `{ preloaded }`, the token of a `preload()` for the requested locale
40
+ * and this route, it shows what that preload fetched — see
41
+ * `loadTranslations()`.
42
+ */
43
+ setRoute: (input: string, options?: {
44
+ preloaded?: Loader.Preloaded | undefined;
45
+ }) => Promise<void>;
39
46
  /**
40
47
  * `{ activate: false }` only fills the tables: it leaves the requested
41
48
  * locale, the route and `locale` untouched and does not count towards
@@ -45,10 +52,46 @@ declare class I18nCore<ParserParams extends Parser.Params = any, ParserOutput =
45
52
  * it. It
46
53
  * leaves `cache` expiry to the next activating trigger. A loader's
47
54
  * `redirect()` or `error()` below 500 rejects it all the same.
55
+ *
56
+ * `{ preloaded }` hands an activating call the token of a `preload()` for
57
+ * the same locale and route. The call then evaluates no `cache` window — the
58
+ * preload did — and shows what the preload fetched instead of fetching it
59
+ * again, at once when nothing else is left to fetch, and otherwise with the
60
+ * rest, in one go. It fetches what the preload did not deliver, and what it
61
+ * delivered to a loader that shows something else since — a seed or
62
+ * another delivery replaced what it showed — wherever the call has that
63
+ * loader to load anyway (one with `cache: false`, or one whose record an
64
+ * expiry dropped, say). A token serves one call: the first activating call
65
+ * that reads it spends it, whether or not it serves that call, even with
66
+ * nothing left to fetch; a call left without a locale to show — handed an
67
+ * empty one or one nothing serves, say — reads none. Another instance's,
68
+ * one of another locale or route, one spent already and one older than a
69
+ * `loadConfig()` or an `invalidate()` are ignored, as is any with
70
+ * `{ activate: false }`, which leaves it unspent. A window a later request
71
+ * found elapsed does not void it: the preload judged the window at its own
72
+ * request.
48
73
  */
49
- loadTranslations: (locale: Config.LocaleInput<LocaleUnion>, route?: string, { activate }?: {
74
+ loadTranslations: (locale: Config.LocaleInput<LocaleUnion>, route?: string, { activate, preloaded }?: {
50
75
  activate?: boolean;
76
+ preloaded?: Loader.Preloaded | undefined;
51
77
  }) => Promise<void>;
78
+ /**
79
+ * A request for `locale` on `route` (the current route by default) that
80
+ * shows nothing new by itself: what a router's `load` runs ahead of a
81
+ * navigation, a hover's included. Like an activating call it evaluates the
82
+ * `cache` window, ends the pass a hand-off serves, and runs a loader with
83
+ * `cache: false` — sharing a fetch of it for the same params and route
84
+ * already in flight. Like `{ activate: false }` it writes neither the locale
85
+ * nor the route, does not count towards `loading`, and lands what it fetched
86
+ * as a warm load does. It resolves to a token for the next activating call of
87
+ * that locale and route, which shows what it fetched (see
88
+ * `loadTranslations()`), and to `undefined` once the instance was destroyed
89
+ * or when nothing serves the locale. A loader's `redirect()` or `error()`
90
+ * below 500 rejects it, unless it shares its load with an activating call,
91
+ * whose outcome it then gets; one nobody awaits never becomes an unhandled
92
+ * rejection.
93
+ */
94
+ preload: (locale: Config.LocaleInput<LocaleUnion>, route?: string) => Promise<Loader.Preloaded | undefined>;
52
95
  /**
53
96
  * Loads one namespace for the active locale (or `locale`), whatever the
54
97
  * routes of its loaders say — for what an interaction needs rather than a