@i18n-micro/types 1.2.18 → 2.0.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016
3
+ Copyright (c) 2024 s00d
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/dist/index.d.cts CHANGED
@@ -77,14 +77,17 @@ export interface Locale {
77
77
  /** Human-readable name shown in language switchers (e.g. `'English'`, `'Deutsch'`). */
78
78
  displayName?: string;
79
79
  /**
80
- * Per-locale base URL for SEO meta tags.
81
- * Useful for multi-domain setups where each locale lives on its own domain
82
- * (e.g. `'https://en.example.com'`).
80
+ * Origin of this locale's own domain (e.g. `'https://de.example.com'`), plus `app.baseURL` when
81
+ * the app has one. One build then serves every domain: a request for that host gets this locale,
82
+ * the other locales of the host and the locales with no `baseUrl`. Links into a locale of another
83
+ * domain (`$localePath`, `$switchLocalePath`, the switcher, `hreflang`) become absolute URLs on
84
+ * its origin, and switching to it navigates there. Any other host gets the build config.
83
85
  */
84
86
  baseUrl?: string;
85
87
  /**
86
- * When `true`, this locale is treated as the "base default" for the domain
87
- * specified in `baseUrl`. Affects canonical URL generation in multi-domain setups.
88
+ * With `baseUrl`: this locale is the default of its host, unprefixed there, and links to it from
89
+ * other domains drop `/<code>`, because the domain already names the locale
90
+ * (`https://de.example.com/about`, not `…/de/about`). `prefix` keeps the prefix in both.
88
91
  */
89
92
  baseDefault?: boolean;
90
93
  /**
@@ -96,7 +99,7 @@ export interface Locale {
96
99
  [key: string]: unknown;
97
100
  }
98
101
  /**
99
- * Per-page i18n configuration declared via `defineI18nRoute()` in page components.
102
+ * Per-page i18n configuration declared via `$defineI18nRoute()` in page components.
100
103
  */
101
104
  export interface DefineI18nRouteConfig {
102
105
  /**
@@ -184,6 +187,12 @@ export interface ModuleOptions {
184
187
  * @default true
185
188
  */
186
189
  metaTrustForwardedProto?: boolean;
190
+ /**
191
+ * Choose the domain of a request (`locales[].baseUrl`) from `X-Forwarded-Host` instead of
192
+ * `Host`. Enable it only behind a proxy that overwrites that header.
193
+ * @default false
194
+ */
195
+ trustForwardedHost?: boolean;
187
196
  /**
188
197
  * Also emit a bare-language `hreflang` derived from each locale's `iso`
189
198
  * (e.g. `es-ES` → also `es`). The first regional locale in `locales` claims
@@ -192,17 +201,28 @@ export interface ModuleOptions {
192
201
  */
193
202
  hreflangBaseLanguage?: boolean;
194
203
  /**
195
- * Register the `defineI18nRoute()` macro plugin, enabling per-page `defineI18nRoute()` calls.
204
+ * Provide `$defineI18nRoute()` at runtime. The build reads `$defineI18nRoute()` calls in pages
205
+ * either way.
196
206
  * @default true
197
207
  */
198
208
  define?: boolean;
199
209
  /**
200
- * Enable automatic locale-based redirects.
201
- * When `true`, visitors are redirected to their preferred locale (detected from cookie,
202
- * `Accept-Language` header, or the default) on the first visit.
210
+ * Enable automatic locale redirects (server plugin and client route middleware).
211
+ * When `true`, the prefix strategies redirect an unprefixed URL to its localized form. The locale
212
+ * comes from the app state, the cookie or `Accept-Language` where `autoDetectPath` allows, otherwise
213
+ * the default. Under `no_prefix` detection never changes the URL (see `noPrefixRedirect` for custom paths).
203
214
  * @default true
204
215
  */
205
216
  redirects?: boolean;
217
+ /**
218
+ * Redirect a locale prefix in another case or encoding to the page in its own spelling
219
+ * (`/DE/about` → `/de/about`, `/EN/about` → `/about` under `prefix_except_default`). The router
220
+ * matches the prefix case-insensitively, so without the redirect such a URL would show the page
221
+ * under a second address. When `false`, or when `redirects` is `false`, the URL is a 404. A URL
222
+ * with no page behind it in its own spelling is a 404 either way.
223
+ * @default true
224
+ */
225
+ prefixCaseRedirect?: boolean;
206
226
  /**
207
227
  * Register the core i18n plugin that provides `$t()`, `$tc()`, `$getLocale()`,
208
228
  * `$switchLocale()`, and other runtime helpers.
@@ -210,8 +230,7 @@ export interface ModuleOptions {
210
230
  */
211
231
  plugin?: boolean;
212
232
  /**
213
- * Register the i18n hooks plugin that provides `i18n:register` and `i18n:beforeLocaleSwitch`
214
- * / `i18n:afterLocaleSwitch` app-level hooks.
233
+ * Call the `i18n:register` app hook on load and on navigation so plugins can add translations.
215
234
  * @default true
216
235
  */
217
236
  hooks?: boolean;
@@ -229,7 +248,7 @@ export interface ModuleOptions {
229
248
  serverTranslationPreload?: boolean;
230
249
  /**
231
250
  * The locale to use when no locale can be determined from URL or user preferences.
232
- * Also used as the fallback locale for missing translations when `fallbackLocale` is not set.
251
+ * It is not a translation fallback: set `fallbackLocale` for that.
233
252
  * @default 'en'
234
253
  */
235
254
  defaultLocale?: string;
@@ -292,9 +311,11 @@ export interface ModuleOptions {
292
311
  * - `'no_prefix'` — only paths without a locale prefix
293
312
  * - `'*'` — every path, including rewriting an explicit locale prefix
294
313
  * (e.g. `/fr/about` → `/de/about` when the cookie prefers `de`)
295
- * - any other string — exact path match (e.g. `'/welcome'`)
314
+ * - any other string — that path, trailing slashes ignored (e.g. `'/welcome'`)
296
315
  *
297
- * Prefixed strategy cleanup (e.g. `/en` → `/` under `prefix_except_default`) is not gated.
316
+ * Under `prefix`, an unprefixed URL outside the allowed paths still redirects to the default
317
+ * locale's form (`/about` → `/en/about`). The default locale with a prefix under
318
+ * `prefix_except_default` (`/en`) is a 404, not a redirect.
298
319
  * @default '/'
299
320
  */
300
321
  autoDetectPath?: string;
@@ -327,7 +348,7 @@ export interface ModuleOptions {
327
348
  * For the Nuxt module the function is serialized with `.toString()` into
328
349
  * `.nuxt/i18n.plural.mjs` — it must be self-contained (no imports / outer scope).
329
350
  * A file path string is **not** supported.
330
- * @default built-in pluralization (form index by count)
351
+ * @default built-in pluralization (a non-negative integer count picks the form at that index, clamped to the last; negative, fractional and `NaN` counts take the last; `=0 ` opens a template of `Intl.PluralRules` categories)
331
352
  */
332
353
  plural?: PluralFunc;
333
354
  /**
@@ -339,15 +360,16 @@ export interface ModuleOptions {
339
360
  disablePageLocales?: boolean;
340
361
  /**
341
362
  * Global fallback locale code.
342
- * When a translation key is missing in the active locale, the module looks it up
343
- * in this locale before returning the key itself.
363
+ * Keys missing in a locale are taken from this locale (merged into each payload when it is
364
+ * built or loaded); without it a missing key renders as the key itself.
344
365
  * @default undefined (no fallback; returns the raw key)
345
366
  */
346
367
  fallbackLocale?: string;
347
368
  /**
348
369
  * Cookie name for persisting the user's locale preference across sessions.
349
370
  * Set to `null` to disable cookie-based persistence.
350
- * Automatically set to `'user-locale'` for the `no_prefix` strategy if not provided.
371
+ * Automatically set to `'user-locale'` for the `no_prefix` strategy if not provided; an explicit
372
+ * `null` in the `i18n` config block keeps it off there too.
351
373
  * @default null
352
374
  */
353
375
  localeCookie?: string | null;
@@ -373,13 +395,16 @@ export interface ModuleOptions {
373
395
  globalLocaleRoutes?: GlobalLocaleRoutes;
374
396
  /**
375
397
  * Custom regular expression (or its string source) for matching locale codes in URL segments.
376
- * All locale codes defined in `locales` must match this pattern, or a warning is emitted.
398
+ * Every locale code in `locales` must match the whole pattern, without its flags, or the build fails.
377
399
  * @default undefined (uses built-in pattern based on locale codes)
378
400
  */
379
401
  customRegexMatcher?: string | RegExp;
380
402
  /**
381
- * For `no_prefix` strategy: enable redirect from a locale-prefixed URL
382
- * (e.g. `/en/about`) to the unprefixed version (`/about`).
403
+ * For `no_prefix` strategy: when the default locale has a custom path for a page
404
+ * (`localeRoutes` / `globalLocaleRoutes`), redirect the page's original path to the custom
405
+ * path of the visitor's locale, the default locale's otherwise (e.g. `/about` → `/about-us`).
406
+ * A locale with no custom path of its own stays on the original path. Pages with dynamic
407
+ * segments are not redirected.
383
408
  * @default false
384
409
  */
385
410
  noPrefixRedirect?: boolean;
@@ -397,9 +422,11 @@ export interface ModuleOptions {
397
422
  */
398
423
  trailingSlash?: 'append' | 'remove';
399
424
  /**
400
- * URL patterns (strings or RegExp) to exclude from i18n processing entirely.
401
- * Matching routes won't get locale prefixes, redirects, or translation loading.
402
- * Internal Nuxt paths (`/__nuxt_error`, etc.) are always excluded automatically.
425
+ * Paths to leave out of i18n routing: a string is a path prefix (`/admin` covers `/admin/x`)
426
+ * or a `*` / `?` glob; a RegExp is tested against the path. Matching pages get no locale
427
+ * variants, no locale redirect or 404, and are dropped from prerender; they still render with
428
+ * translations. `/api`, `/_nuxt`, `/__*`, the translations route and common static files
429
+ * (sitemap, robots.txt, favicon, assets by extension) are always excluded.
403
430
  * @default undefined
404
431
  */
405
432
  excludePatterns?: (string | RegExp)[];
@@ -410,19 +437,19 @@ export interface ModuleOptions {
410
437
  */
411
438
  localizedRouteNamePrefix?: string;
412
439
  /**
413
- * Per-route locale restrictions, extracted from `defineI18nRoute()` calls.
440
+ * Per-route locale restrictions, extracted at build time from `$defineI18nRoute()` calls.
414
441
  * Maps a route path (e.g. `'/about'`) to an array of allowed locale codes.
415
442
  * Routes not listed have no restrictions (all locales allowed).
416
443
  */
417
444
  routeLocales?: Record<string, string[]>;
418
445
  /**
419
- * Per-route meta tag disabling, extracted from `defineI18nRoute()` calls.
420
- * Maps a route path to `true` (disable all meta) or an array of locale codes
421
- * for which meta should be disabled.
446
+ * Per-route meta tag disabling. Maps a route name or path to `true` (disable all meta) or an array
447
+ * of locale codes for which meta should be disabled. Entries found in `$defineI18nRoute()`
448
+ * calls at build time are added to it.
422
449
  */
423
450
  routeDisableMeta?: Record<string, boolean | string[]>;
424
451
  /**
425
- * Show console warnings when a translation key is missing.
452
+ * Show console warnings when a translation key is missing (development build, in the browser).
426
453
  * @default true
427
454
  */
428
455
  missingWarn?: boolean;
@@ -434,17 +461,29 @@ export interface ModuleOptions {
434
461
  */
435
462
  hmr?: boolean;
436
463
  /**
437
- * Maximum number of entries in the in-memory translation cache.
438
- * `0` means no limit.
464
+ * Maximum number of entries in the in-memory translation caches. The fetch and server caches
465
+ * drop the least recently used entry; the browser's dictionary map drops the oldest one (FIFO).
466
+ * `0` means no limit in the browser; server caches keep at most 1000 entries then, so request
467
+ * URLs naming made-up pages cannot grow server memory.
439
468
  * @default 0
440
469
  */
441
470
  cacheMaxSize?: number;
442
471
  /**
443
- * Time-to-live (in seconds) for cached translation entries.
444
- * `0` means entries never expire.
472
+ * Time-to-live (in seconds) of the fetch and server translation caches, counted from when an
473
+ * entry was stored (reads do not extend it), so data from `apiBaseServerHost` is refreshed at
474
+ * least that often. The browser's active dictionaries are not expired by it. `0` means entries
475
+ * never expire.
445
476
  * @default 0
446
477
  */
447
478
  cacheTtl?: number;
479
+ /**
480
+ * Name of a Nitro storage mount (`nitro.storage`, e.g. `'redis'`) that several server instances
481
+ * share as a dictionary cache, read after the in-process cache and before the source. Pays off
482
+ * mostly with `apiBaseServerHost`: one fetch from the backend instead of one per instance.
483
+ * Entries are keyed by build and expire after `cacheTtl`, or after one day when it is `0`.
484
+ * Off in dev and prerender. A failing storage falls back to the source. Unset: no shared cache.
485
+ */
486
+ cacheStorage?: string;
448
487
  /**
449
488
  * Named number formats per locale (Vue I18n-compatible).
450
489
  * Enables `$tn(1000, 'currency')` style calls.
@@ -456,6 +495,12 @@ export interface ModuleOptions {
456
495
  * }
457
496
  */
458
497
  numberFormats?: Record<string, Record<string, Intl.NumberFormatOptions>>;
498
+ /**
499
+ * IANA time zone for `$td` and `<i18n-t date>` (`Europe/Berlin`). Set it to render one date on
500
+ * the server and in every browser; a named format's own `timeZone` wins.
501
+ * @default the runtime's zone
502
+ */
503
+ timeZone?: string;
459
504
  /**
460
505
  * Named datetime formats per locale (Vue I18n-compatible `datetimeFormats`).
461
506
  * Enables `$td(date, 'short')` style calls.
@@ -471,7 +516,8 @@ export interface ModuleOptions {
471
516
  * HTTP `Cache-Control` max-age (seconds) for `/{apiBaseUrl}/:page/:locale/data.json`.
472
517
  *
473
518
  * Applies in full only while `dateBuild` busts the URL (`?v=...`), which is the default:
474
- * the response is then `public, max-age=…, immutable` and safe for browsers and CDNs.
519
+ * the response is then `private, max-age=…, immutable`: a browser pins it, and a CDN that ignores the query cannot
520
+ * (`public` with `httpCacheControl: false`).
475
521
  *
476
522
  * - `dateBuild: 0`/`''` — the URL is stable, so this duration is *not* honoured; the
477
523
  * response becomes `public, max-age=0, must-revalidate` instead. A long `max-age` on an
@@ -486,12 +532,22 @@ export interface ModuleOptions {
486
532
  * @default 31536000
487
533
  */
488
534
  httpCacheDuration?: number;
535
+ /**
536
+ * `Cache-Control` of a payload file answered by a host or by a path-wide rule, which cannot see `?v=`.
537
+ * On a Node server a response to the current `?v=` is pinned for `httpCacheDuration` and any other revalidates;
538
+ * with `source`, `cacheTtl` or `apiBaseServerHost`, where a dictionary changes with no build, every response takes this header.
539
+ * `false` keeps every file on `max-age=0, must-revalidate`.
540
+ * @default 'public, max-age=60'
541
+ */
542
+ httpCacheControl?: string | false;
489
543
  /**
490
544
  * Value used for cache-busting translation requests (`?v=...`).
491
545
  *
492
- * When not provided, the module falls back to `Date.now()` (non-deterministic).
493
- * For reproducible/rolling deployments, set this to a stable value
494
- * (e.g. a git SHA or build number).
546
+ * When not provided, the module uses a hash of the translation files, so the value changes
547
+ * only when translations do (`Date.now()` when no hash can be computed). `0` or `''` turns
548
+ * cache-busting off. Set a stable value (a git SHA, a build number) to pin it yourself.
549
+ * `NUXT_I18N_DATE_BUILD` or `runtimeConfig.public.i18nRuntime.dateBuild` replaces it at runtime, after a dictionary
550
+ * was replaced in place: pinned URLs are left behind, with no rebuild.
495
551
  *
496
552
  * @example
497
553
  * i18n: { dateBuild: 'e3b0c442' }
@@ -549,6 +605,13 @@ export interface TranslationPayloadOptions {
549
605
  * So static files match client fetch URLs (`/{apiBaseUrl}/{page}/{locale}/data.json`).
550
606
  */
551
607
  publicDir?: string;
608
+ /**
609
+ * Write one root file per locale, and in each page file only what differs from it. The client and SSR merge them;
610
+ * a whole chunk still works wherever a file is read. `false` keeps a whole dictionary in every page file.
611
+ * `premerged` only, and off with `disablePageLocales`.
612
+ * @default true
613
+ */
614
+ rootDelta?: boolean;
552
615
  /**
553
616
  * Warn during build when generated payload file count exceeds this threshold.
554
617
  * @default 500
@@ -569,24 +632,42 @@ export interface ModuleOptionsExtend extends ModuleOptions {
569
632
  dateBuild: string | number;
570
633
  /** Whether the Nuxt app uses hash-based routing (`router.options.hashMode`). */
571
634
  hashMode: boolean;
572
- /** Whether the current build is a static site generation (SSG / `nuxi generate`). */
573
- isSSG: boolean;
635
+ /** @deprecated No longer set at runtime. Kept so code typed against the released module still compiles. */
636
+ isSSG?: boolean;
574
637
  /** Resolved translations API base URL (after env variable / config merging). */
575
638
  apiBaseUrl: string;
576
639
  /** Resolved flag: whether per-page translations are disabled. */
577
640
  disablePageLocales: boolean;
578
641
  /** Resolved flag: whether locale-based redirects are enabled. */
579
642
  redirects?: boolean;
643
+ /** Resolved flag: whether a locale prefix in another case or encoding redirects to its own spelling. */
644
+ prefixCaseRedirect?: boolean;
580
645
  /** Resolved translation payload loading strategy. */
581
646
  translationPayloadMode?: 'premerged' | 'source';
647
+ /** Premerged files are in `public/`, so they can sit on `app.cdnURL`. */
648
+ publicPayloads?: boolean;
649
+ /** A page's file needs its root: the browser loads the two together. */
650
+ rootDelta?: boolean;
651
+ /** Nuxt's `features.noScripts` is on: no page runs a script, so none preloads a dictionary. */
652
+ noScripts?: boolean;
653
+ /** Host this config was resolved for (`locales[].baseUrl`); unset on any other host. */
654
+ domain?: string;
655
+ /** With `domain`: every locale, those of other domains included. `locales` holds what the host serves. */
656
+ linkLocales?: Locale[];
657
+ /** With `domain`: the build's `defaultLocale`, the same on every host (`x-default`). `defaultLocale` is the host's. */
658
+ linkDefaultLocale?: string;
582
659
  }
660
+ /** The keys `buildFullConfig` always sets: read at runtime with no default of their own. */
661
+ type ResolvedI18nConfigKey = 'apiBaseUrl' | 'autoDetectPath' | 'cacheMaxSize' | 'cacheTtl' | 'canonicalQueryWhitelist' | 'defaultLocale' | 'hreflangBaseLanguage' | 'locales' | 'missingWarn' | 'strategy';
662
+ /** The config the build writes, as `getI18nConfig()` and `$getI18nConfig()` give it. */
663
+ export type ResolvedI18nConfig = Omit<ModuleOptionsExtend, ResolvedI18nConfigKey> & Required<Pick<ModuleOptionsExtend, ResolvedI18nConfigKey>>;
583
664
  /**
584
665
  * Private (server-only) module options passed to Nitro plugins and server middleware.
585
666
  * Extends `ModuleOptions` with resolved root paths and directory info.
586
667
  */
587
668
  export interface ModulePrivateOptionsExtend extends ModuleOptions {
588
- /** Absolute path to the Nuxt project root directory. */
589
- rootDir: string;
669
+ /** Nuxt's `buildId`: `cacheStorage` keys of two builds never meet. */
670
+ cacheStorageBuild?: string;
590
671
  /**
591
672
  * Absolute Nuxt layer roots in merge order (extends first, project last).
592
673
  * Used by live/HMR translation reads so layers match build-time merge.
@@ -596,20 +677,8 @@ export interface ModulePrivateOptionsExtend extends ModuleOptions {
596
677
  debug: boolean;
597
678
  /** Resolved path to the translations directory. */
598
679
  translationDir: string;
599
- /** Resolved fallback locale code. */
600
- fallbackLocale: string;
601
- /** Resolved translations API base URL. */
602
- apiBaseUrl: string;
603
- /** Resolved client-side host override. */
604
- apiBaseClientHost?: string;
605
680
  /** Resolved server-side host override. */
606
681
  apiBaseServerHost?: string;
607
- /** Resolved custom regex matcher (RegExp is serialized to its `.source` string). */
608
- customRegexMatcher?: string | RegExp;
609
- /** Resolved route-to-route translation sharing map. */
610
- routesLocaleLinks?: {
611
- [key: string]: string;
612
- };
613
682
  /** Preload index translations in Nitro global middleware. */
614
683
  serverTranslationPreload?: boolean;
615
684
  /**
@@ -673,3 +742,16 @@ export interface I18nHeadInput {
673
742
  }
674
743
  declare const init: () => void;
675
744
  export { init };
745
+ /** `meta.i18n` that route-strategy puts on every localized record, and path-strategy reads at runtime. */
746
+ export interface RouteI18nMeta {
747
+ /** Base route name of the page (its translation key and the name `localeRoute({ name })` takes). */
748
+ name: string;
749
+ /** Locales this record serves. */
750
+ locales: string[];
751
+ /** `disableMeta` of the page: SEO tags off in every locale (`true`) or in the listed ones. */
752
+ noMeta?: boolean | string[];
753
+ /** On the default locale's record: the path of a page with a custom default path, where it is no record of its own. A link can still name the page by it. */
754
+ original?: string;
755
+ /** `noPrefixRedirect`, on the page's original path: the custom path of each locale. The locale is the visitor's, so the runtime redirects. */
756
+ paths?: Record<string, string>;
757
+ }
package/dist/index.d.ts CHANGED
@@ -77,14 +77,17 @@ export interface Locale {
77
77
  /** Human-readable name shown in language switchers (e.g. `'English'`, `'Deutsch'`). */
78
78
  displayName?: string;
79
79
  /**
80
- * Per-locale base URL for SEO meta tags.
81
- * Useful for multi-domain setups where each locale lives on its own domain
82
- * (e.g. `'https://en.example.com'`).
80
+ * Origin of this locale's own domain (e.g. `'https://de.example.com'`), plus `app.baseURL` when
81
+ * the app has one. One build then serves every domain: a request for that host gets this locale,
82
+ * the other locales of the host and the locales with no `baseUrl`. Links into a locale of another
83
+ * domain (`$localePath`, `$switchLocalePath`, the switcher, `hreflang`) become absolute URLs on
84
+ * its origin, and switching to it navigates there. Any other host gets the build config.
83
85
  */
84
86
  baseUrl?: string;
85
87
  /**
86
- * When `true`, this locale is treated as the "base default" for the domain
87
- * specified in `baseUrl`. Affects canonical URL generation in multi-domain setups.
88
+ * With `baseUrl`: this locale is the default of its host, unprefixed there, and links to it from
89
+ * other domains drop `/<code>`, because the domain already names the locale
90
+ * (`https://de.example.com/about`, not `…/de/about`). `prefix` keeps the prefix in both.
88
91
  */
89
92
  baseDefault?: boolean;
90
93
  /**
@@ -96,7 +99,7 @@ export interface Locale {
96
99
  [key: string]: unknown;
97
100
  }
98
101
  /**
99
- * Per-page i18n configuration declared via `defineI18nRoute()` in page components.
102
+ * Per-page i18n configuration declared via `$defineI18nRoute()` in page components.
100
103
  */
101
104
  export interface DefineI18nRouteConfig {
102
105
  /**
@@ -184,6 +187,12 @@ export interface ModuleOptions {
184
187
  * @default true
185
188
  */
186
189
  metaTrustForwardedProto?: boolean;
190
+ /**
191
+ * Choose the domain of a request (`locales[].baseUrl`) from `X-Forwarded-Host` instead of
192
+ * `Host`. Enable it only behind a proxy that overwrites that header.
193
+ * @default false
194
+ */
195
+ trustForwardedHost?: boolean;
187
196
  /**
188
197
  * Also emit a bare-language `hreflang` derived from each locale's `iso`
189
198
  * (e.g. `es-ES` → also `es`). The first regional locale in `locales` claims
@@ -192,17 +201,28 @@ export interface ModuleOptions {
192
201
  */
193
202
  hreflangBaseLanguage?: boolean;
194
203
  /**
195
- * Register the `defineI18nRoute()` macro plugin, enabling per-page `defineI18nRoute()` calls.
204
+ * Provide `$defineI18nRoute()` at runtime. The build reads `$defineI18nRoute()` calls in pages
205
+ * either way.
196
206
  * @default true
197
207
  */
198
208
  define?: boolean;
199
209
  /**
200
- * Enable automatic locale-based redirects.
201
- * When `true`, visitors are redirected to their preferred locale (detected from cookie,
202
- * `Accept-Language` header, or the default) on the first visit.
210
+ * Enable automatic locale redirects (server plugin and client route middleware).
211
+ * When `true`, the prefix strategies redirect an unprefixed URL to its localized form. The locale
212
+ * comes from the app state, the cookie or `Accept-Language` where `autoDetectPath` allows, otherwise
213
+ * the default. Under `no_prefix` detection never changes the URL (see `noPrefixRedirect` for custom paths).
203
214
  * @default true
204
215
  */
205
216
  redirects?: boolean;
217
+ /**
218
+ * Redirect a locale prefix in another case or encoding to the page in its own spelling
219
+ * (`/DE/about` → `/de/about`, `/EN/about` → `/about` under `prefix_except_default`). The router
220
+ * matches the prefix case-insensitively, so without the redirect such a URL would show the page
221
+ * under a second address. When `false`, or when `redirects` is `false`, the URL is a 404. A URL
222
+ * with no page behind it in its own spelling is a 404 either way.
223
+ * @default true
224
+ */
225
+ prefixCaseRedirect?: boolean;
206
226
  /**
207
227
  * Register the core i18n plugin that provides `$t()`, `$tc()`, `$getLocale()`,
208
228
  * `$switchLocale()`, and other runtime helpers.
@@ -210,8 +230,7 @@ export interface ModuleOptions {
210
230
  */
211
231
  plugin?: boolean;
212
232
  /**
213
- * Register the i18n hooks plugin that provides `i18n:register` and `i18n:beforeLocaleSwitch`
214
- * / `i18n:afterLocaleSwitch` app-level hooks.
233
+ * Call the `i18n:register` app hook on load and on navigation so plugins can add translations.
215
234
  * @default true
216
235
  */
217
236
  hooks?: boolean;
@@ -229,7 +248,7 @@ export interface ModuleOptions {
229
248
  serverTranslationPreload?: boolean;
230
249
  /**
231
250
  * The locale to use when no locale can be determined from URL or user preferences.
232
- * Also used as the fallback locale for missing translations when `fallbackLocale` is not set.
251
+ * It is not a translation fallback: set `fallbackLocale` for that.
233
252
  * @default 'en'
234
253
  */
235
254
  defaultLocale?: string;
@@ -292,9 +311,11 @@ export interface ModuleOptions {
292
311
  * - `'no_prefix'` — only paths without a locale prefix
293
312
  * - `'*'` — every path, including rewriting an explicit locale prefix
294
313
  * (e.g. `/fr/about` → `/de/about` when the cookie prefers `de`)
295
- * - any other string — exact path match (e.g. `'/welcome'`)
314
+ * - any other string — that path, trailing slashes ignored (e.g. `'/welcome'`)
296
315
  *
297
- * Prefixed strategy cleanup (e.g. `/en` → `/` under `prefix_except_default`) is not gated.
316
+ * Under `prefix`, an unprefixed URL outside the allowed paths still redirects to the default
317
+ * locale's form (`/about` → `/en/about`). The default locale with a prefix under
318
+ * `prefix_except_default` (`/en`) is a 404, not a redirect.
298
319
  * @default '/'
299
320
  */
300
321
  autoDetectPath?: string;
@@ -327,7 +348,7 @@ export interface ModuleOptions {
327
348
  * For the Nuxt module the function is serialized with `.toString()` into
328
349
  * `.nuxt/i18n.plural.mjs` — it must be self-contained (no imports / outer scope).
329
350
  * A file path string is **not** supported.
330
- * @default built-in pluralization (form index by count)
351
+ * @default built-in pluralization (a non-negative integer count picks the form at that index, clamped to the last; negative, fractional and `NaN` counts take the last; `=0 ` opens a template of `Intl.PluralRules` categories)
331
352
  */
332
353
  plural?: PluralFunc;
333
354
  /**
@@ -339,15 +360,16 @@ export interface ModuleOptions {
339
360
  disablePageLocales?: boolean;
340
361
  /**
341
362
  * Global fallback locale code.
342
- * When a translation key is missing in the active locale, the module looks it up
343
- * in this locale before returning the key itself.
363
+ * Keys missing in a locale are taken from this locale (merged into each payload when it is
364
+ * built or loaded); without it a missing key renders as the key itself.
344
365
  * @default undefined (no fallback; returns the raw key)
345
366
  */
346
367
  fallbackLocale?: string;
347
368
  /**
348
369
  * Cookie name for persisting the user's locale preference across sessions.
349
370
  * Set to `null` to disable cookie-based persistence.
350
- * Automatically set to `'user-locale'` for the `no_prefix` strategy if not provided.
371
+ * Automatically set to `'user-locale'` for the `no_prefix` strategy if not provided; an explicit
372
+ * `null` in the `i18n` config block keeps it off there too.
351
373
  * @default null
352
374
  */
353
375
  localeCookie?: string | null;
@@ -373,13 +395,16 @@ export interface ModuleOptions {
373
395
  globalLocaleRoutes?: GlobalLocaleRoutes;
374
396
  /**
375
397
  * Custom regular expression (or its string source) for matching locale codes in URL segments.
376
- * All locale codes defined in `locales` must match this pattern, or a warning is emitted.
398
+ * Every locale code in `locales` must match the whole pattern, without its flags, or the build fails.
377
399
  * @default undefined (uses built-in pattern based on locale codes)
378
400
  */
379
401
  customRegexMatcher?: string | RegExp;
380
402
  /**
381
- * For `no_prefix` strategy: enable redirect from a locale-prefixed URL
382
- * (e.g. `/en/about`) to the unprefixed version (`/about`).
403
+ * For `no_prefix` strategy: when the default locale has a custom path for a page
404
+ * (`localeRoutes` / `globalLocaleRoutes`), redirect the page's original path to the custom
405
+ * path of the visitor's locale, the default locale's otherwise (e.g. `/about` → `/about-us`).
406
+ * A locale with no custom path of its own stays on the original path. Pages with dynamic
407
+ * segments are not redirected.
383
408
  * @default false
384
409
  */
385
410
  noPrefixRedirect?: boolean;
@@ -397,9 +422,11 @@ export interface ModuleOptions {
397
422
  */
398
423
  trailingSlash?: 'append' | 'remove';
399
424
  /**
400
- * URL patterns (strings or RegExp) to exclude from i18n processing entirely.
401
- * Matching routes won't get locale prefixes, redirects, or translation loading.
402
- * Internal Nuxt paths (`/__nuxt_error`, etc.) are always excluded automatically.
425
+ * Paths to leave out of i18n routing: a string is a path prefix (`/admin` covers `/admin/x`)
426
+ * or a `*` / `?` glob; a RegExp is tested against the path. Matching pages get no locale
427
+ * variants, no locale redirect or 404, and are dropped from prerender; they still render with
428
+ * translations. `/api`, `/_nuxt`, `/__*`, the translations route and common static files
429
+ * (sitemap, robots.txt, favicon, assets by extension) are always excluded.
403
430
  * @default undefined
404
431
  */
405
432
  excludePatterns?: (string | RegExp)[];
@@ -410,19 +437,19 @@ export interface ModuleOptions {
410
437
  */
411
438
  localizedRouteNamePrefix?: string;
412
439
  /**
413
- * Per-route locale restrictions, extracted from `defineI18nRoute()` calls.
440
+ * Per-route locale restrictions, extracted at build time from `$defineI18nRoute()` calls.
414
441
  * Maps a route path (e.g. `'/about'`) to an array of allowed locale codes.
415
442
  * Routes not listed have no restrictions (all locales allowed).
416
443
  */
417
444
  routeLocales?: Record<string, string[]>;
418
445
  /**
419
- * Per-route meta tag disabling, extracted from `defineI18nRoute()` calls.
420
- * Maps a route path to `true` (disable all meta) or an array of locale codes
421
- * for which meta should be disabled.
446
+ * Per-route meta tag disabling. Maps a route name or path to `true` (disable all meta) or an array
447
+ * of locale codes for which meta should be disabled. Entries found in `$defineI18nRoute()`
448
+ * calls at build time are added to it.
422
449
  */
423
450
  routeDisableMeta?: Record<string, boolean | string[]>;
424
451
  /**
425
- * Show console warnings when a translation key is missing.
452
+ * Show console warnings when a translation key is missing (development build, in the browser).
426
453
  * @default true
427
454
  */
428
455
  missingWarn?: boolean;
@@ -434,17 +461,29 @@ export interface ModuleOptions {
434
461
  */
435
462
  hmr?: boolean;
436
463
  /**
437
- * Maximum number of entries in the in-memory translation cache.
438
- * `0` means no limit.
464
+ * Maximum number of entries in the in-memory translation caches. The fetch and server caches
465
+ * drop the least recently used entry; the browser's dictionary map drops the oldest one (FIFO).
466
+ * `0` means no limit in the browser; server caches keep at most 1000 entries then, so request
467
+ * URLs naming made-up pages cannot grow server memory.
439
468
  * @default 0
440
469
  */
441
470
  cacheMaxSize?: number;
442
471
  /**
443
- * Time-to-live (in seconds) for cached translation entries.
444
- * `0` means entries never expire.
472
+ * Time-to-live (in seconds) of the fetch and server translation caches, counted from when an
473
+ * entry was stored (reads do not extend it), so data from `apiBaseServerHost` is refreshed at
474
+ * least that often. The browser's active dictionaries are not expired by it. `0` means entries
475
+ * never expire.
445
476
  * @default 0
446
477
  */
447
478
  cacheTtl?: number;
479
+ /**
480
+ * Name of a Nitro storage mount (`nitro.storage`, e.g. `'redis'`) that several server instances
481
+ * share as a dictionary cache, read after the in-process cache and before the source. Pays off
482
+ * mostly with `apiBaseServerHost`: one fetch from the backend instead of one per instance.
483
+ * Entries are keyed by build and expire after `cacheTtl`, or after one day when it is `0`.
484
+ * Off in dev and prerender. A failing storage falls back to the source. Unset: no shared cache.
485
+ */
486
+ cacheStorage?: string;
448
487
  /**
449
488
  * Named number formats per locale (Vue I18n-compatible).
450
489
  * Enables `$tn(1000, 'currency')` style calls.
@@ -456,6 +495,12 @@ export interface ModuleOptions {
456
495
  * }
457
496
  */
458
497
  numberFormats?: Record<string, Record<string, Intl.NumberFormatOptions>>;
498
+ /**
499
+ * IANA time zone for `$td` and `<i18n-t date>` (`Europe/Berlin`). Set it to render one date on
500
+ * the server and in every browser; a named format's own `timeZone` wins.
501
+ * @default the runtime's zone
502
+ */
503
+ timeZone?: string;
459
504
  /**
460
505
  * Named datetime formats per locale (Vue I18n-compatible `datetimeFormats`).
461
506
  * Enables `$td(date, 'short')` style calls.
@@ -471,7 +516,8 @@ export interface ModuleOptions {
471
516
  * HTTP `Cache-Control` max-age (seconds) for `/{apiBaseUrl}/:page/:locale/data.json`.
472
517
  *
473
518
  * Applies in full only while `dateBuild` busts the URL (`?v=...`), which is the default:
474
- * the response is then `public, max-age=…, immutable` and safe for browsers and CDNs.
519
+ * the response is then `private, max-age=…, immutable`: a browser pins it, and a CDN that ignores the query cannot
520
+ * (`public` with `httpCacheControl: false`).
475
521
  *
476
522
  * - `dateBuild: 0`/`''` — the URL is stable, so this duration is *not* honoured; the
477
523
  * response becomes `public, max-age=0, must-revalidate` instead. A long `max-age` on an
@@ -486,12 +532,22 @@ export interface ModuleOptions {
486
532
  * @default 31536000
487
533
  */
488
534
  httpCacheDuration?: number;
535
+ /**
536
+ * `Cache-Control` of a payload file answered by a host or by a path-wide rule, which cannot see `?v=`.
537
+ * On a Node server a response to the current `?v=` is pinned for `httpCacheDuration` and any other revalidates;
538
+ * with `source`, `cacheTtl` or `apiBaseServerHost`, where a dictionary changes with no build, every response takes this header.
539
+ * `false` keeps every file on `max-age=0, must-revalidate`.
540
+ * @default 'public, max-age=60'
541
+ */
542
+ httpCacheControl?: string | false;
489
543
  /**
490
544
  * Value used for cache-busting translation requests (`?v=...`).
491
545
  *
492
- * When not provided, the module falls back to `Date.now()` (non-deterministic).
493
- * For reproducible/rolling deployments, set this to a stable value
494
- * (e.g. a git SHA or build number).
546
+ * When not provided, the module uses a hash of the translation files, so the value changes
547
+ * only when translations do (`Date.now()` when no hash can be computed). `0` or `''` turns
548
+ * cache-busting off. Set a stable value (a git SHA, a build number) to pin it yourself.
549
+ * `NUXT_I18N_DATE_BUILD` or `runtimeConfig.public.i18nRuntime.dateBuild` replaces it at runtime, after a dictionary
550
+ * was replaced in place: pinned URLs are left behind, with no rebuild.
495
551
  *
496
552
  * @example
497
553
  * i18n: { dateBuild: 'e3b0c442' }
@@ -549,6 +605,13 @@ export interface TranslationPayloadOptions {
549
605
  * So static files match client fetch URLs (`/{apiBaseUrl}/{page}/{locale}/data.json`).
550
606
  */
551
607
  publicDir?: string;
608
+ /**
609
+ * Write one root file per locale, and in each page file only what differs from it. The client and SSR merge them;
610
+ * a whole chunk still works wherever a file is read. `false` keeps a whole dictionary in every page file.
611
+ * `premerged` only, and off with `disablePageLocales`.
612
+ * @default true
613
+ */
614
+ rootDelta?: boolean;
552
615
  /**
553
616
  * Warn during build when generated payload file count exceeds this threshold.
554
617
  * @default 500
@@ -569,24 +632,42 @@ export interface ModuleOptionsExtend extends ModuleOptions {
569
632
  dateBuild: string | number;
570
633
  /** Whether the Nuxt app uses hash-based routing (`router.options.hashMode`). */
571
634
  hashMode: boolean;
572
- /** Whether the current build is a static site generation (SSG / `nuxi generate`). */
573
- isSSG: boolean;
635
+ /** @deprecated No longer set at runtime. Kept so code typed against the released module still compiles. */
636
+ isSSG?: boolean;
574
637
  /** Resolved translations API base URL (after env variable / config merging). */
575
638
  apiBaseUrl: string;
576
639
  /** Resolved flag: whether per-page translations are disabled. */
577
640
  disablePageLocales: boolean;
578
641
  /** Resolved flag: whether locale-based redirects are enabled. */
579
642
  redirects?: boolean;
643
+ /** Resolved flag: whether a locale prefix in another case or encoding redirects to its own spelling. */
644
+ prefixCaseRedirect?: boolean;
580
645
  /** Resolved translation payload loading strategy. */
581
646
  translationPayloadMode?: 'premerged' | 'source';
647
+ /** Premerged files are in `public/`, so they can sit on `app.cdnURL`. */
648
+ publicPayloads?: boolean;
649
+ /** A page's file needs its root: the browser loads the two together. */
650
+ rootDelta?: boolean;
651
+ /** Nuxt's `features.noScripts` is on: no page runs a script, so none preloads a dictionary. */
652
+ noScripts?: boolean;
653
+ /** Host this config was resolved for (`locales[].baseUrl`); unset on any other host. */
654
+ domain?: string;
655
+ /** With `domain`: every locale, those of other domains included. `locales` holds what the host serves. */
656
+ linkLocales?: Locale[];
657
+ /** With `domain`: the build's `defaultLocale`, the same on every host (`x-default`). `defaultLocale` is the host's. */
658
+ linkDefaultLocale?: string;
582
659
  }
660
+ /** The keys `buildFullConfig` always sets: read at runtime with no default of their own. */
661
+ type ResolvedI18nConfigKey = 'apiBaseUrl' | 'autoDetectPath' | 'cacheMaxSize' | 'cacheTtl' | 'canonicalQueryWhitelist' | 'defaultLocale' | 'hreflangBaseLanguage' | 'locales' | 'missingWarn' | 'strategy';
662
+ /** The config the build writes, as `getI18nConfig()` and `$getI18nConfig()` give it. */
663
+ export type ResolvedI18nConfig = Omit<ModuleOptionsExtend, ResolvedI18nConfigKey> & Required<Pick<ModuleOptionsExtend, ResolvedI18nConfigKey>>;
583
664
  /**
584
665
  * Private (server-only) module options passed to Nitro plugins and server middleware.
585
666
  * Extends `ModuleOptions` with resolved root paths and directory info.
586
667
  */
587
668
  export interface ModulePrivateOptionsExtend extends ModuleOptions {
588
- /** Absolute path to the Nuxt project root directory. */
589
- rootDir: string;
669
+ /** Nuxt's `buildId`: `cacheStorage` keys of two builds never meet. */
670
+ cacheStorageBuild?: string;
590
671
  /**
591
672
  * Absolute Nuxt layer roots in merge order (extends first, project last).
592
673
  * Used by live/HMR translation reads so layers match build-time merge.
@@ -596,20 +677,8 @@ export interface ModulePrivateOptionsExtend extends ModuleOptions {
596
677
  debug: boolean;
597
678
  /** Resolved path to the translations directory. */
598
679
  translationDir: string;
599
- /** Resolved fallback locale code. */
600
- fallbackLocale: string;
601
- /** Resolved translations API base URL. */
602
- apiBaseUrl: string;
603
- /** Resolved client-side host override. */
604
- apiBaseClientHost?: string;
605
680
  /** Resolved server-side host override. */
606
681
  apiBaseServerHost?: string;
607
- /** Resolved custom regex matcher (RegExp is serialized to its `.source` string). */
608
- customRegexMatcher?: string | RegExp;
609
- /** Resolved route-to-route translation sharing map. */
610
- routesLocaleLinks?: {
611
- [key: string]: string;
612
- };
613
682
  /** Preload index translations in Nitro global middleware. */
614
683
  serverTranslationPreload?: boolean;
615
684
  /**
@@ -673,3 +742,16 @@ export interface I18nHeadInput {
673
742
  }
674
743
  declare const init: () => void;
675
744
  export { init };
745
+ /** `meta.i18n` that route-strategy puts on every localized record, and path-strategy reads at runtime. */
746
+ export interface RouteI18nMeta {
747
+ /** Base route name of the page (its translation key and the name `localeRoute({ name })` takes). */
748
+ name: string;
749
+ /** Locales this record serves. */
750
+ locales: string[];
751
+ /** `disableMeta` of the page: SEO tags off in every locale (`true`) or in the listed ones. */
752
+ noMeta?: boolean | string[];
753
+ /** On the default locale's record: the path of a page with a custom default path, where it is no record of its own. A link can still name the page by it. */
754
+ original?: string;
755
+ /** `noPrefixRedirect`, on the page's original path: the custom path of each locale. The locale is the visitor's, so the runtime redirects. */
756
+ paths?: Record<string, string>;
757
+ }
package/dist/index.mjs CHANGED
@@ -1,4 +1,3 @@
1
- //#region src/index.ts
1
+ // src/index.ts
2
2
  var e = () => {};
3
- //#endregion
4
3
  export { e as init };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@i18n-micro/types",
3
- "version": "1.2.18",
3
+ "version": "2.0.1",
4
4
  "description": "TypeScript definitions for the Nuxt I18n Micro ecosystem.",
5
5
  "keywords": [
6
6
  "i18n",
@@ -30,8 +30,8 @@
30
30
  ],
31
31
  "type": "module",
32
32
  "sideEffects": false,
33
- "main": "dist/index.cjs",
34
- "types": "dist/index.d.ts",
33
+ "main": "./dist/index.cjs",
34
+ "types": "./dist/index.d.ts",
35
35
  "exports": {
36
36
  ".": {
37
37
  "import": {
@@ -50,13 +50,13 @@
50
50
  "access": "public"
51
51
  },
52
52
  "devDependencies": {
53
- "publint": "^0.3.24",
54
- "vite": "^8.3.1",
55
- "vite-plugin-dts": "^5.1.1",
56
- "vitest": "^5.0.2"
53
+ "publint": "^0.3.25",
54
+ "vite": "^8.3.5",
55
+ "vite-plugin-dts": "^5.1.2",
56
+ "vitest": "^5.1.0"
57
57
  },
58
58
  "engines": {
59
- "node": ">=18"
59
+ "node": ">=20"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "vite build",