nuxt-i18n-micro 3.30.3 → 4.0.0

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.
Files changed (111) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -2
  3. package/dist/client/200.html +1 -1
  4. package/dist/client/404.html +1 -1
  5. package/dist/client/_nuxt/BVna_KKx.js +3 -0
  6. package/dist/client/_nuxt/Bw81RhAv.js +1 -0
  7. package/dist/client/_nuxt/CPz_jhsD.js +1 -0
  8. package/dist/client/_nuxt/CmvObVGq.js +1 -0
  9. package/dist/client/_nuxt/DKUOBR6F.js +5 -0
  10. package/dist/client/_nuxt/builds/latest.json +1 -1
  11. package/dist/client/_nuxt/builds/meta/f0c8d4bf-40d2-4915-8b4a-ebe46bde61c8.json +1 -0
  12. package/dist/client/_nuxt/{entry.BM-mexAv.css → entry.PSWtHhay.css} +1 -1
  13. package/dist/client/_nuxt/error-404.xs3fxhVJ.css +1 -0
  14. package/dist/client/_nuxt/error-500.C2S-4iLg.css +1 -0
  15. package/dist/client/index.html +1 -1
  16. package/dist/module.d.mts +14 -5
  17. package/dist/module.json +4 -1
  18. package/dist/module.mjs +850 -475
  19. package/dist/runtime/components/i18n-group.d.vue.ts +17 -9
  20. package/dist/runtime/components/i18n-group.vue +2 -4
  21. package/dist/runtime/components/i18n-group.vue.d.ts +17 -9
  22. package/dist/runtime/components/i18n-link.d.vue.ts +9 -6
  23. package/dist/runtime/components/i18n-link.vue +7 -28
  24. package/dist/runtime/components/i18n-link.vue.d.ts +9 -6
  25. package/dist/runtime/components/i18n-switcher.d.vue.ts +128 -54
  26. package/dist/runtime/components/i18n-switcher.vue +56 -156
  27. package/dist/runtime/components/i18n-switcher.vue.d.ts +128 -54
  28. package/dist/runtime/components/i18n-t.d.vue.ts +59 -116
  29. package/dist/runtime/components/i18n-t.vue +24 -71
  30. package/dist/runtime/components/i18n-t.vue.d.ts +59 -116
  31. package/dist/runtime/composables/useI18n.js +2 -38
  32. package/dist/runtime/composables/useI18nHead.js +21 -6
  33. package/dist/runtime/composables/useI18nLocale.d.ts +4 -1
  34. package/dist/runtime/composables/useI18nLocale.js +20 -22
  35. package/dist/runtime/composables/useLocaleHead.d.ts +26 -31
  36. package/dist/runtime/composables/useLocaleHead.js +47 -167
  37. package/dist/runtime/devtools/bridge.d.ts +2 -2
  38. package/dist/runtime/devtools/vue-devtools.d.ts +2 -2
  39. package/dist/runtime/devtools/vue-devtools.js +5 -5
  40. package/dist/runtime/middleware/i18n-redirect.global.d.ts +3 -2
  41. package/dist/runtime/middleware/i18n-redirect.global.js +30 -41
  42. package/dist/runtime/plugins/01.plugin.d.ts +156 -34
  43. package/dist/runtime/plugins/01.plugin.js +184 -138
  44. package/dist/runtime/plugins/02.meta.js +36 -43
  45. package/dist/runtime/plugins/04.vue-devtools.js +1 -1
  46. package/dist/runtime/plugins/06.redirect.d.ts +3 -3
  47. package/dist/runtime/plugins/06.redirect.js +65 -91
  48. package/dist/runtime/server/{utils → internal}/dev-live-translations.js +4 -3
  49. package/dist/runtime/server/internal/server-translation-cache.d.ts +9 -0
  50. package/dist/runtime/server/internal/server-translation-cache.js +10 -0
  51. package/dist/runtime/server/internal/shared-cache.d.ts +24 -0
  52. package/dist/runtime/server/internal/shared-cache.js +71 -0
  53. package/dist/runtime/server/internal/translation-watcher.d.ts +79 -0
  54. package/dist/runtime/server/internal/translation-watcher.js +105 -0
  55. package/dist/runtime/server/middleware/i18n.global.js +24 -37
  56. package/dist/runtime/server/payload-source.assets.d.ts +1 -0
  57. package/dist/runtime/server/payload-source.assets.js +3 -8
  58. package/dist/runtime/server/payload-source.fs.d.ts +1 -0
  59. package/dist/runtime/server/payload-source.fs.js +18 -7
  60. package/dist/runtime/server/plugins/failed-read.d.ts +7 -0
  61. package/dist/runtime/server/plugins/failed-read.js +23 -0
  62. package/dist/runtime/server/plugins/payload-cache.d.ts +6 -0
  63. package/dist/runtime/server/plugins/payload-cache.js +16 -0
  64. package/dist/runtime/server/plugins/watcher.dev.js +15 -79
  65. package/dist/runtime/server/routes/i18n.js +13 -14
  66. package/dist/runtime/server/utils/locale-detector.d.ts +32 -2
  67. package/dist/runtime/server/utils/locale-detector.js +48 -27
  68. package/dist/runtime/server/utils/locale-server-middleware.js +14 -33
  69. package/dist/runtime/server/utils/payload-cache.d.ts +4 -0
  70. package/dist/runtime/server/utils/payload-cache.js +13 -0
  71. package/dist/runtime/server/utils/server-loader.d.ts +17 -6
  72. package/dist/runtime/server/utils/server-loader.js +105 -51
  73. package/dist/runtime/server/utils/translation-server-middleware.js +18 -35
  74. package/dist/runtime/utils/cache-keys.d.ts +6 -0
  75. package/dist/runtime/utils/cache-keys.js +2 -0
  76. package/dist/runtime/utils/i18n-api.d.ts +185 -0
  77. package/dist/runtime/utils/i18n-api.js +196 -0
  78. package/dist/runtime/utils/i18n-config.d.ts +18 -0
  79. package/dist/runtime/utils/i18n-config.js +47 -0
  80. package/dist/runtime/utils/i18n-navigation.d.ts +26 -0
  81. package/dist/runtime/utils/i18n-navigation.js +96 -0
  82. package/dist/runtime/utils/nuxt-i18n-server.d.ts +26 -0
  83. package/dist/runtime/utils/nuxt-i18n-server.js +83 -0
  84. package/dist/runtime/utils/nuxt-i18n.d.ts +81 -96
  85. package/dist/runtime/utils/nuxt-i18n.js +180 -252
  86. package/dist/runtime/utils/page-head.d.ts +12 -0
  87. package/dist/runtime/utils/page-head.js +6 -0
  88. package/dist/runtime/utils/request-url.d.ts +6 -0
  89. package/dist/runtime/utils/request-url.js +11 -0
  90. package/dist/runtime/utils/storage.d.ts +31 -19
  91. package/dist/runtime/utils/storage.js +100 -49
  92. package/dist/runtime/utils/translation-entry.d.ts +6 -0
  93. package/dist/runtime/utils/translation-entry.js +16 -0
  94. package/dist/types.d.mts +4 -6
  95. package/internals.d.mts +9 -2
  96. package/package.json +78 -79
  97. package/dist/client/_nuxt/BOxWElQP.js +0 -21
  98. package/dist/client/_nuxt/CUWB4CwN.js +0 -3
  99. package/dist/client/_nuxt/CmUbe-uv.js +0 -1
  100. package/dist/client/_nuxt/DAzShkZF.js +0 -1
  101. package/dist/client/_nuxt/MokPP5zi.js +0 -1
  102. package/dist/client/_nuxt/builds/meta/60974e48-d00e-4f9b-9467-9d4b322519da.json +0 -1
  103. package/dist/client/_nuxt/error-404.D9Svp6Hk.css +0 -1
  104. package/dist/client/_nuxt/error-500.B-mjI9_9.css +0 -1
  105. package/dist/runtime/plugins/03.define.d.ts +0 -7
  106. package/dist/runtime/plugins/03.define.js +0 -42
  107. package/dist/runtime/plugins/05.hooks.d.ts +0 -2
  108. package/dist/runtime/plugins/05.hooks.js +0 -47
  109. package/dist/runtime/server/utils/server-translation-cache.d.ts +0 -11
  110. package/dist/runtime/server/utils/server-translation-cache.js +0 -20
  111. /package/dist/runtime/server/{utils → internal}/dev-live-translations.d.ts +0 -0
@@ -1,26 +1,73 @@
1
1
  import type { PathStrategy } from '@i18n-micro/path-strategy';
2
- import type { CleanTranslation, DefineI18nRouteConfig, I18nRouteParams, Locale, MissingHandler, ModuleOptionsExtend, Params, Translations, TranslationKey } from '@i18n-micro/types';
2
+ import type { CleanTranslation, DefineI18nRouteConfig, I18nRouteParams, Locale, MissingHandler, ResolvedI18nConfig, Params, PluralFunc, Translations, TranslationKey } from '@i18n-micro/types';
3
3
  import type { RouteLocationNamedRaw, RouteLocationNormalizedLoaded, RouteLocationRaw, RouteLocationResolved, RouteLocationResolvedGeneric } from 'vue-router';
4
+ import { type NuxtI18nPluginProvide } from '../utils/i18n-api.js';
4
5
  import { type TranslationFn } from '../utils/nuxt-i18n.js';
5
6
  export type { TranslationFn };
6
7
  declare const _default: import("nuxt/app").Plugin<{
7
- getI18nConfig: () => ModuleOptionsExtend;
8
+ i18n: {
9
+ helper: {
10
+ mergeTranslation(locale: string, routeName: string, newTranslations: Translations): Promise<void>;
11
+ };
12
+ getI18nConfig: () => ResolvedI18nConfig;
13
+ __micro: boolean;
14
+ i18nStrategy: PathStrategy;
15
+ getLocale: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric) => string;
16
+ getLocaleName: () => string | null;
17
+ defaultLocale: () => string;
18
+ getLocales: () => Locale[];
19
+ getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
20
+ t: (key: TranslationKey, params?: Params, defaultValue?: string | null, routeContext?: unknown) => CleanTranslation;
21
+ ts: (key: TranslationKey, params?: Params, defaultValue?: string, routeContext?: unknown) => string;
22
+ _t: (route: RouteLocationNormalizedLoaded) => TranslationFn<CleanTranslation>;
23
+ _ts: (route: RouteLocationNormalizedLoaded) => TranslationFn<string>;
24
+ tc: (key: TranslationKey, count: number | Params, defaultValue?: string) => string;
25
+ pluralFunc: PluralFunc;
26
+ pluralLocale: () => string;
27
+ tn: {
28
+ (value: number, options?: Intl.NumberFormatOptions): string;
29
+ (value: number, key: string, overrides?: Intl.NumberFormatOptions): string;
30
+ (value: number, key: string, locale: string, overrides?: Intl.NumberFormatOptions): string;
31
+ };
32
+ td: {
33
+ (value: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
34
+ (value: Date | number | string, key: string, overrides?: Intl.DateTimeFormatOptions): string;
35
+ (value: Date | number | string, key: string, locale: string, overrides?: Intl.DateTimeFormatOptions): string;
36
+ };
37
+ tdr: (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions) => string;
38
+ has: (key: TranslationKey, routeContext?: unknown) => boolean;
39
+ resolveTranslations: (routeContext?: unknown) => Translations;
40
+ setTranslation: (key: TranslationKey, value: unknown) => void;
41
+ mergeTranslations: (newTranslations: Translations) => void;
42
+ switchLocaleRoute: (toLocale: string) => string | import("vue-router").RouteLocationAsRelativeGeneric | import("vue-router").RouteLocationAsPathGeneric;
43
+ clearCache: () => void;
44
+ switchLocalePath: (toLocale: string) => string;
45
+ switchLocale: (toLocale: string) => Promise<boolean>;
46
+ switchRoute: (route: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, toLocale?: string) => Promise<unknown>;
47
+ localeRoute(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): RouteLocationResolved;
48
+ localePath(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): string;
49
+ loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
50
+ setMissingHandler: (handler: MissingHandler | null) => void;
51
+ };
52
+ defineI18nRoute: ReturnType<({ getLocale, mergeTranslations }: Pick<NuxtI18nPluginProvide, "getLocale" | "mergeTranslations">) => ({ locales }: DefineI18nRouteConfig) => Promise<void>>;
8
53
  helper: {
9
- mergeTranslation(locale: string, routeName: string, newTranslations: Translations, _force?: boolean): Promise<void>;
54
+ mergeTranslation(locale: string, routeName: string, newTranslations: Translations): Promise<void>;
10
55
  };
11
- i18n: unknown;
56
+ getI18nConfig: () => ResolvedI18nConfig;
12
57
  __micro: boolean;
13
58
  i18nStrategy: PathStrategy;
14
59
  getLocale: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric) => string;
15
60
  getLocaleName: () => string | null;
16
- defaultLocale: () => string | undefined;
61
+ defaultLocale: () => string;
17
62
  getLocales: () => Locale[];
18
- getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, locale?: string) => string;
63
+ getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
19
64
  t: (key: TranslationKey, params?: Params, defaultValue?: string | null, routeContext?: unknown) => CleanTranslation;
20
- ts: (key: TranslationKey, params?: Params, defaultValue?: string, route?: RouteLocationNormalizedLoaded) => string;
65
+ ts: (key: TranslationKey, params?: Params, defaultValue?: string, routeContext?: unknown) => string;
21
66
  _t: (route: RouteLocationNormalizedLoaded) => TranslationFn<CleanTranslation>;
22
67
  _ts: (route: RouteLocationNormalizedLoaded) => TranslationFn<string>;
23
68
  tc: (key: TranslationKey, count: number | Params, defaultValue?: string) => string;
69
+ pluralFunc: PluralFunc;
70
+ pluralLocale: () => string;
24
71
  tn: {
25
72
  (value: number, options?: Intl.NumberFormatOptions): string;
26
73
  (value: number, key: string, overrides?: Intl.NumberFormatOptions): string;
@@ -36,34 +83,79 @@ declare const _default: import("nuxt/app").Plugin<{
36
83
  resolveTranslations: (routeContext?: unknown) => Translations;
37
84
  setTranslation: (key: TranslationKey, value: unknown) => void;
38
85
  mergeTranslations: (newTranslations: Translations) => void;
39
- switchLocaleRoute: (toLocale: string) => RouteLocationRaw;
86
+ switchLocaleRoute: (toLocale: string) => string | import("vue-router").RouteLocationAsRelativeGeneric | import("vue-router").RouteLocationAsPathGeneric;
40
87
  clearCache: () => void;
41
88
  switchLocalePath: (toLocale: string) => string;
42
- switchLocale: (toLocale: string) => Promise<unknown>;
43
- switchRoute: (route: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, toLocale?: string) => unknown;
89
+ switchLocale: (toLocale: string) => Promise<boolean>;
90
+ switchRoute: (route: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, toLocale?: string) => Promise<unknown>;
44
91
  localeRoute(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): RouteLocationResolved;
45
92
  localePath(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): string;
46
- setI18nRouteParams: (value: I18nRouteParams) => I18nRouteParams;
47
93
  loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
48
94
  setMissingHandler: (handler: MissingHandler | null) => void;
49
95
  }> & import("nuxt/app").ObjectPlugin<{
50
- getI18nConfig: () => ModuleOptionsExtend;
96
+ i18n: {
97
+ helper: {
98
+ mergeTranslation(locale: string, routeName: string, newTranslations: Translations): Promise<void>;
99
+ };
100
+ getI18nConfig: () => ResolvedI18nConfig;
101
+ __micro: boolean;
102
+ i18nStrategy: PathStrategy;
103
+ getLocale: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric) => string;
104
+ getLocaleName: () => string | null;
105
+ defaultLocale: () => string;
106
+ getLocales: () => Locale[];
107
+ getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
108
+ t: (key: TranslationKey, params?: Params, defaultValue?: string | null, routeContext?: unknown) => CleanTranslation;
109
+ ts: (key: TranslationKey, params?: Params, defaultValue?: string, routeContext?: unknown) => string;
110
+ _t: (route: RouteLocationNormalizedLoaded) => TranslationFn<CleanTranslation>;
111
+ _ts: (route: RouteLocationNormalizedLoaded) => TranslationFn<string>;
112
+ tc: (key: TranslationKey, count: number | Params, defaultValue?: string) => string;
113
+ pluralFunc: PluralFunc;
114
+ pluralLocale: () => string;
115
+ tn: {
116
+ (value: number, options?: Intl.NumberFormatOptions): string;
117
+ (value: number, key: string, overrides?: Intl.NumberFormatOptions): string;
118
+ (value: number, key: string, locale: string, overrides?: Intl.NumberFormatOptions): string;
119
+ };
120
+ td: {
121
+ (value: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
122
+ (value: Date | number | string, key: string, overrides?: Intl.DateTimeFormatOptions): string;
123
+ (value: Date | number | string, key: string, locale: string, overrides?: Intl.DateTimeFormatOptions): string;
124
+ };
125
+ tdr: (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions) => string;
126
+ has: (key: TranslationKey, routeContext?: unknown) => boolean;
127
+ resolveTranslations: (routeContext?: unknown) => Translations;
128
+ setTranslation: (key: TranslationKey, value: unknown) => void;
129
+ mergeTranslations: (newTranslations: Translations) => void;
130
+ switchLocaleRoute: (toLocale: string) => string | import("vue-router").RouteLocationAsRelativeGeneric | import("vue-router").RouteLocationAsPathGeneric;
131
+ clearCache: () => void;
132
+ switchLocalePath: (toLocale: string) => string;
133
+ switchLocale: (toLocale: string) => Promise<boolean>;
134
+ switchRoute: (route: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, toLocale?: string) => Promise<unknown>;
135
+ localeRoute(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): RouteLocationResolved;
136
+ localePath(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): string;
137
+ loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
138
+ setMissingHandler: (handler: MissingHandler | null) => void;
139
+ };
140
+ defineI18nRoute: ReturnType<({ getLocale, mergeTranslations }: Pick<NuxtI18nPluginProvide, "getLocale" | "mergeTranslations">) => ({ locales }: DefineI18nRouteConfig) => Promise<void>>;
51
141
  helper: {
52
- mergeTranslation(locale: string, routeName: string, newTranslations: Translations, _force?: boolean): Promise<void>;
142
+ mergeTranslation(locale: string, routeName: string, newTranslations: Translations): Promise<void>;
53
143
  };
54
- i18n: unknown;
144
+ getI18nConfig: () => ResolvedI18nConfig;
55
145
  __micro: boolean;
56
146
  i18nStrategy: PathStrategy;
57
147
  getLocale: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric) => string;
58
148
  getLocaleName: () => string | null;
59
- defaultLocale: () => string | undefined;
149
+ defaultLocale: () => string;
60
150
  getLocales: () => Locale[];
61
- getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, locale?: string) => string;
151
+ getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
62
152
  t: (key: TranslationKey, params?: Params, defaultValue?: string | null, routeContext?: unknown) => CleanTranslation;
63
- ts: (key: TranslationKey, params?: Params, defaultValue?: string, route?: RouteLocationNormalizedLoaded) => string;
153
+ ts: (key: TranslationKey, params?: Params, defaultValue?: string, routeContext?: unknown) => string;
64
154
  _t: (route: RouteLocationNormalizedLoaded) => TranslationFn<CleanTranslation>;
65
155
  _ts: (route: RouteLocationNormalizedLoaded) => TranslationFn<string>;
66
156
  tc: (key: TranslationKey, count: number | Params, defaultValue?: string) => string;
157
+ pluralFunc: PluralFunc;
158
+ pluralLocale: () => string;
67
159
  tn: {
68
160
  (value: number, options?: Intl.NumberFormatOptions): string;
69
161
  (value: number, key: string, overrides?: Intl.NumberFormatOptions): string;
@@ -79,14 +171,13 @@ declare const _default: import("nuxt/app").Plugin<{
79
171
  resolveTranslations: (routeContext?: unknown) => Translations;
80
172
  setTranslation: (key: TranslationKey, value: unknown) => void;
81
173
  mergeTranslations: (newTranslations: Translations) => void;
82
- switchLocaleRoute: (toLocale: string) => RouteLocationRaw;
174
+ switchLocaleRoute: (toLocale: string) => string | import("vue-router").RouteLocationAsRelativeGeneric | import("vue-router").RouteLocationAsPathGeneric;
83
175
  clearCache: () => void;
84
176
  switchLocalePath: (toLocale: string) => string;
85
- switchLocale: (toLocale: string) => Promise<unknown>;
86
- switchRoute: (route: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, toLocale?: string) => unknown;
177
+ switchLocale: (toLocale: string) => Promise<boolean>;
178
+ switchRoute: (route: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, toLocale?: string) => Promise<unknown>;
87
179
  localeRoute(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): RouteLocationResolved;
88
180
  localePath(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): string;
89
- setI18nRouteParams: (value: I18nRouteParams) => I18nRouteParams;
90
181
  loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
91
182
  setMissingHandler: (handler: MissingHandler | null) => void;
92
183
  }>;
@@ -95,7 +186,7 @@ export interface PluginsInjections {
95
186
  /** The active routing strategy, resolving locales to and from paths. */
96
187
  $i18nStrategy: PathStrategy;
97
188
  /** The resolved module configuration, as the runtime sees it. */
98
- $getI18nConfig: () => ModuleOptionsExtend;
189
+ $getI18nConfig: () => ResolvedI18nConfig;
99
190
  /** Code of the active locale. Pass a route to read the locale that route belongs to. */
100
191
  $getLocale: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric) => string;
101
192
  /** The active locale's `displayName` from the config, or `null` when it has none. */
@@ -117,8 +208,8 @@ export interface PluginsInjections {
117
208
  */
118
209
  $_t: (route: RouteLocationNormalizedLoaded) => TranslationFn<CleanTranslation>;
119
210
  /**
120
- * Like `$t`, but always returns a string: an object or array value is stringified rather than
121
- * returned as-is.
211
+ * Like `$t`, but always returns a string: the value goes through `toString()`, so a nested
212
+ * object becomes `'[object Object]'`. Point the key at a leaf.
122
213
  */
123
214
  $ts: TranslationFn<string>;
124
215
  /** Bind `$ts` to a specific route. See `$_t`. */
@@ -128,6 +219,10 @@ export interface PluginsInjections {
128
219
  * `count`.
129
220
  */
130
221
  $tc: (key: TranslationKey, params: number | Params, defaultValue?: string) => string;
222
+ /** The plural rule `$tc` uses: `<i18n-t>` asks for it to pick a form from the raw translation. */
223
+ $pluralFunc: PluralFunc;
224
+ /** The tag `$tc` plurals by: the locale's `iso` when it has one, else its code. */
225
+ $pluralLocale: () => string;
131
226
  /**
132
227
  * Format a number with `Intl.NumberFormat` in the active locale. A key selects a named format
133
228
  * from the config.
@@ -150,7 +245,8 @@ export interface PluginsInjections {
150
245
  $tdr: (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions) => string;
151
246
  /**
152
247
  * Whether a key resolves in the active locale. Use it to branch on optional copy instead of
153
- * rendering a raw key.
248
+ * rendering a raw key. Like `$t`, it also sees the leaving page's keys during a same-locale
249
+ * page transition.
154
250
  */
155
251
  $has: (key: string) => boolean;
156
252
  /**
@@ -165,17 +261,39 @@ export interface PluginsInjections {
165
261
  $setTranslation: (key: string, value: unknown) => void;
166
262
  /** Merge translations into the active locale at runtime, overriding what is loaded. */
167
263
  $mergeTranslations: (newTranslations: Translations) => void;
168
- /** The route object for the current page in another locale, without navigating. */
264
+ /**
265
+ * The route object for the current page in another locale, without navigating, or a URL string
266
+ * for a locale on its own `baseUrl`. Under `trailingSlash` the object has `path` and `fullPath`,
267
+ * following the rule, with the `query` and `hash` as they are, and `router.push` or a link keep them.
268
+ */
169
269
  $switchLocaleRoute: (locale: string) => RouteLocationRaw;
170
270
  /** The path of the current page in another locale, without navigating. */
171
271
  $switchLocalePath: (locale: string) => string;
172
- /** Navigate to the current page in another locale. */
173
- $switchLocale: (locale: string) => void;
174
- /** Navigate to another route, keeping the active locale or switching to `toLocale`. */
175
- $switchRoute: (route: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, toLocale?: string) => void;
176
- /** Resolve a route in the given locale, or the active one. */
272
+ /**
273
+ * Navigate to the current page in another locale. Under `no_prefix` and hash mode it loads the
274
+ * locale's translations before navigating; under `no_prefix` the route is replaced in place (no
275
+ * new history entry). An unknown locale is ignored.
276
+ * The call is async: `await` it to run code after the navigation. It resolves `true` when the
277
+ * locale's dictionary is active, `false` when it is not: the chunk failed to load (the current
278
+ * dictionary stays; call it again to retry) or a newer switch won.
279
+ */
280
+ $switchLocale: (locale: string) => Promise<boolean>;
281
+ /**
282
+ * Navigate to another route, keeping the active locale or switching to `toLocale`. Async like
283
+ * `$switchLocale`.
284
+ */
285
+ $switchRoute: (route: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, toLocale?: string) => Promise<void>;
286
+ /**
287
+ * Resolve a route in the given locale, or the active one. A plain object, safe to keep in state. When a param needs
288
+ * escaping it has `path`, `fullPath`, `query` and `hash` but no `name` or `params`: the path is what gives the router
289
+ * the encoded URL. For a locale on another domain `path` and `fullPath` are absolute, which the router cannot take:
290
+ * such a link goes through `<i18n-link>`, or `$localePath`.
291
+ */
177
292
  $localeRoute: (to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string) => RouteLocationResolved;
178
- /** Resolve a path in the given locale, or the active one. */
293
+ /**
294
+ * Resolve a path in the given locale, or the active one. A string with an `http(s):`, `mailto:`
295
+ * or `tel:` scheme comes back as is: do not pass user input as a link target unchecked.
296
+ */
179
297
  $localePath: (to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string) => string;
180
298
  /**
181
299
  * Set per-locale params for the current route, so a dynamic segment can differ per language.
@@ -186,6 +304,12 @@ export interface PluginsInjections {
186
304
  $loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
187
305
  /** Install a callback invoked for every unresolved key. Pass `null` to remove it. */
188
306
  $setMissingHandler: (handler: MissingHandler | null) => void;
307
+ /**
308
+ * Drop every cached translation chunk and reload the current page's dictionary. The page
309
+ * keeps showing its current translations until the fresh ones arrive. Dev HMR calls it on
310
+ * every translation file change.
311
+ */
312
+ $clearCache: () => void;
189
313
  }
190
314
  /**
191
315
  * Helpers reachable through `useNuxtApp()` but deliberately absent from `useI18n()`.
@@ -200,6 +324,4 @@ export interface NuxtAppOnlyInjections {
200
324
  * Merges into the active locale and re-applies when the locale changes.
201
325
  */
202
326
  $defineI18nRoute: (routeDefinition: DefineI18nRouteConfig) => Promise<void>;
203
- /** Drop every cached chunk, on the client and in the active dictionary. */
204
- $clearCache: () => void;
205
327
  }
@@ -1,160 +1,206 @@
1
- import { isNoPrefixStrategy } from "@i18n-micro/core";
2
- import { resolveI18nConfigWithRuntimeOverrides } from "@i18n-micro/utils/runtime-config";
3
- import { useState } from "#app";
1
+ import { escapeHtml, isNoPrefixStrategy, localeIsoMap } from "@i18n-micro/core";
2
+ import { detectLocaleFromAcceptLanguage } from "@i18n-micro/utils/accept-language";
3
+ import { PREMERGED_ROOT_PAGE } from "@i18n-micro/utils/merge-source";
4
+ import { hasProtocol } from "ufo";
5
+ import { shallowRef } from "vue";
6
+ import { useState } from "#app/composables/state";
4
7
  import { plural } from "#build/i18n.plural.mjs";
5
- import { createI18nStrategy, getI18nConfig } from "#build/i18n.strategy.mjs";
6
- import { createError, defineNuxtPlugin, navigateTo, useRouter, useRuntimeConfig } from "#imports";
7
- import { useI18nHead } from "../composables/useI18nHead.js";
8
+ import { createI18nStrategy, domains } from "#build/i18n.strategy.mjs";
9
+ import { createError, defineNuxtPlugin, injectHead, navigateTo, useHead, useRouter, useRuntimeConfig } from "#imports";
10
+ import { usePageHead } from "../utils/page-head.js";
8
11
  import { useI18nLocale } from "../composables/useI18nLocale.js";
9
- import { createNuxtI18nPluginApi, NuxtI18n, NuxtTranslationLoader } from "../utils/nuxt-i18n.js";
12
+ import { createI18nProvide, createNuxtI18nPluginApi } from "../utils/i18n-api.js";
13
+ import { useResolvedI18nConfig } from "../utils/i18n-config.js";
14
+ import { setupI18nNavigation } from "../utils/i18n-navigation.js";
15
+ import { formatterFor, NuxtI18n, NuxtTranslationLoader } from "../utils/nuxt-i18n.js";
16
+ import { ServerNuxtI18n } from "../utils/nuxt-i18n-server.js";
10
17
  import { setI18nDevtoolsBridge } from "../devtools/bridge.js";
18
+ import { DEFAULT_SERVER_CACHE_SIZE } from "../utils/cache-keys.js";
11
19
  import { translationStorage } from "../utils/storage.js";
12
- const isDev = process.env.NODE_ENV !== "production";
13
- export default defineNuxtPlugin(async (nuxtApp) => {
14
- const router = useRouter();
15
- const i18nStrategy = createI18nStrategy(router);
16
- const runtimeConfig = useRuntimeConfig();
17
- const i18nConfig = resolveI18nConfigWithRuntimeOverrides(
18
- getI18nConfig(),
19
- runtimeConfig.public
20
- );
21
- translationStorage.configure({
22
- maxSize: i18nConfig.cacheMaxSize ?? 0,
23
- ttl: i18nConfig.cacheTtl ?? 0
24
- });
25
- const { locale: localeState, setLocale, getLocale, getEffectiveLocale, resolveInitialLocale, isValidLocale } = useI18nLocale();
26
- const i18nRouteParams = useState("i18n-route-params", () => ({}));
27
- const { resetPageHead } = useI18nHead();
28
- const customMissingHandler = useState("i18n-missing-handler", () => null);
29
- const i18n = new NuxtI18n({
20
+ const SWITCHER_LINK = /class="switcher-locale-([^"]+)" href="[^"]*"/g;
21
+ function createI18n(i18nConfig, missingHandler) {
22
+ return new (import.meta.server ? ServerNuxtI18n : NuxtI18n)({
30
23
  plural,
31
- missingWarn: i18nConfig.missingWarn ?? true,
32
- cacheMaxSize: i18nConfig.cacheMaxSize ?? 0,
33
- getCustomMissingHandler: () => customMissingHandler.value,
24
+ missingWarn: i18nConfig.missingWarn,
25
+ cacheMaxSize: i18nConfig.cacheMaxSize,
26
+ getCustomMissingHandler: () => missingHandler.value,
27
+ formatter: import.meta.server ? formatterFor(i18nConfig) : void 0,
34
28
  numberFormats: i18nConfig.numberFormats,
35
- datetimeFormats: i18nConfig.datetimeFormats
36
- });
37
- const getCurrentLocale = (route) => {
38
- const r = route ?? router.currentRoute.value;
39
- return i18nStrategy.getCurrentLocale(r, getLocale() ?? null);
40
- };
41
- const getPluginRouteName = (route, locale) => {
42
- return i18nStrategy.getPluginRouteName(route, locale);
43
- };
44
- i18n.setRouteContextResolver((route) => {
45
- const resolvedRoute = route ?? router.currentRoute.value;
46
- const locale = getCurrentLocale(resolvedRoute);
47
- return {
48
- locale,
49
- routeName: getPluginRouteName(resolvedRoute, locale)
50
- };
29
+ datetimeFormats: i18nConfig.datetimeFormats,
30
+ timeZone: i18nConfig.timeZone,
31
+ localeIso: localeIsoMap(i18nConfig.linkLocales ?? i18nConfig.locales)
51
32
  });
52
- nuxtApp.hook("page:transition:finish", () => {
53
- i18n.finishTransition();
54
- });
55
- const loadOptions = {
56
- apiBaseUrl: i18nConfig.apiBaseUrl ?? "_locales",
57
- baseURL: runtimeConfig.app.baseURL,
33
+ }
34
+ function createLoadOptions(nuxtApp, i18nConfig) {
35
+ const { baseURL, cdnURL } = useRuntimeConfig().app;
36
+ const options = {
37
+ apiBaseUrl: i18nConfig.apiBaseUrl,
38
+ // files can move to the CDN with the rest of `public/`. A dictionary only the route answers cannot
39
+ baseURL: i18nConfig.publicPayloads && cdnURL || baseURL,
58
40
  apiBaseClientHost: i18nConfig.apiBaseClientHost,
59
41
  dateBuild: i18nConfig.dateBuild,
60
- routesLocaleLinks: i18nConfig.routesLocaleLinks
42
+ routesLocaleLinks: i18nConfig.routesLocaleLinks,
43
+ rootDelta: i18nConfig.rootDelta
61
44
  };
62
- const loader = new NuxtTranslationLoader({
63
- i18n,
64
- loadOptions,
65
- isDev
66
- });
67
- const serverLocale = import.meta.server ? nuxtApp.ssrContext?.event?.context?.i18n?.locale : void 0;
45
+ if (import.meta.server) options.serverLoad = nuxtApp.ssrContext?.event?.context?.i18n?.load;
46
+ return options;
47
+ }
48
+ function resolveStartLocale({ nuxtApp, i18nConfig, i18nStrategy, renderLocale, initialRoute, locale, getCurrentLocale }) {
49
+ const { locale: localeState, getPreferredLocale, resolveInitialLocale, isValidLocale, validLocales } = locale;
50
+ const noPrefix = isNoPrefixStrategy(i18nConfig.strategy);
51
+ const shellLocale = () => {
52
+ const shell = i18nConfig.hashMode && i18nConfig.strategy === "prefix" && !nuxtApp.payload.serverRendered;
53
+ if (!shell || !i18nConfig.autoDetectLanguage || getPreferredLocale()) return void 0;
54
+ return detectLocaleFromAcceptLanguage(navigator.languages.join(","), validLocales);
55
+ };
56
+ const serverRequestLocale = () => renderLocale ?? nuxtApp.ssrContext?.event?.context?.i18n?.locale;
57
+ const requestLocale = import.meta.server ? serverRequestLocale() : shellLocale();
58
+ const preferredLocale = isValidLocale(localeState.value) ? localeState.value : requestLocale ?? getPreferredLocale();
59
+ const pathLocale = () => noPrefix ? i18nStrategy.getCurrentLocale(initialRoute, preferredLocale) : i18nStrategy.getLocaleFromPath(initialRoute.path);
68
60
  const initialLocale = resolveInitialLocale({
69
- route: router.currentRoute.value,
70
- serverLocale,
61
+ route: initialRoute,
62
+ urlLocale: i18nConfig.hashMode ? null : pathLocale(),
63
+ serverLocale: requestLocale,
64
+ preference: noPrefix ? preferredLocale ?? i18nConfig.defaultLocale : void 0,
71
65
  getLocaleFromRoute: (r) => getCurrentLocale(r)
72
66
  });
73
- const initialRouteName = getPluginRouteName(router.currentRoute.value, initialLocale);
74
- if (import.meta.server && initialRouteName === "index") {
75
- const preloaded = nuxtApp.ssrContext?.event?.context?.i18n?.translations;
76
- if (preloaded && typeof preloaded === "object" && Object.keys(preloaded).length > 0) {
77
- translationStorage.seed(initialLocale, "index", preloaded);
78
- }
79
- }
80
- try {
81
- await loader.switchContext(initialLocale, initialRouteName);
82
- } catch (e) {
83
- if (isDev) console.error("[i18n] Initial load error:", e);
84
- throw createError({ statusCode: 404, statusMessage: "Page Not Found" });
67
+ return initialLocale;
68
+ }
69
+ function rewriteSwitcherLinks(nuxtApp, api, params, resolveHref) {
70
+ nuxtApp.hooks.hook("app:rendered", ({ renderResult }) => {
71
+ if (!Object.keys(params.value ?? {}).length || !renderResult?.html?.includes('class="switcher-locale-')) return;
72
+ const codes = new Set(api.getLocales().map((locale) => locale.code));
73
+ renderResult.html = renderResult.html.replace(SWITCHER_LINK, (link, code) => {
74
+ if (!codes.has(code)) return link;
75
+ const path = api.switchLocalePath(code);
76
+ return `class="switcher-locale-${code}" href="${escapeHtml(hasProtocol(path) ? path : resolveHref(path))}"`;
77
+ });
78
+ });
79
+ }
80
+ function createPageHeadDrop(pageHead) {
81
+ let headAdopted = import.meta.server;
82
+ let headToDrop = null;
83
+ if (import.meta.client) {
84
+ const stop = injectHead().hooks?.hook("dom:rendered", () => {
85
+ stop?.();
86
+ headAdopted = true;
87
+ if (pageHead.value === headToDrop) pageHead.value = null;
88
+ });
85
89
  }
86
- const getRouteName = (route, locale) => {
87
- const selectedRoute = route ?? router.currentRoute.value;
88
- const selectedLocale = locale ?? getCurrentLocale(selectedRoute);
89
- return i18nStrategy.getRouteBaseName(selectedRoute, selectedLocale) ?? "";
90
+ return () => {
91
+ if (headAdopted) pageHead.value = null;
92
+ else headToDrop = pageHead.value;
90
93
  };
91
- const {
92
- helper,
93
- switchContext,
94
- provide: provideApi
95
- } = createNuxtI18nPluginApi({
96
- i18n,
97
- loader,
98
- i18nStrategy,
99
- i18nConfig,
100
- router,
101
- getCurrentLocale,
102
- getEffectiveLocale,
103
- getPluginRouteName,
104
- getRouteName,
105
- i18nRouteParams,
106
- setLocale,
107
- isValidLocale,
108
- navigateTo,
109
- setMissingHandler: (handler) => {
110
- customMissingHandler.value = handler;
111
- }
112
- });
113
- if (import.meta.dev && import.meta.client) {
114
- setI18nDevtoolsBridge({
115
- i18n,
94
+ }
95
+ export default defineNuxtPlugin({
96
+ name: "i18n-plugin-loader",
97
+ async setup(nuxtApp) {
98
+ const router = useRouter();
99
+ const i18nConfig = useResolvedI18nConfig(nuxtApp);
100
+ const i18nStrategy = createI18nStrategy(router, i18nConfig);
101
+ translationStorage.configure({
102
+ // the SSR copy is process-wide, so it is bounded like the payload cache
103
+ maxSize: i18nConfig.cacheMaxSize || (import.meta.server ? DEFAULT_SERVER_CACHE_SIZE : 0),
104
+ ttl: i18nConfig.cacheTtl
105
+ });
106
+ const locale = useI18nLocale();
107
+ const { locale: localeState, getPreferredLocale, isValidLocale } = locale;
108
+ const i18nRouteParams = useState("i18n-route-params", () => ({}));
109
+ const pageHead = usePageHead();
110
+ const customMissingHandler = shallowRef(null);
111
+ const i18n = createI18n(i18nConfig, customMissingHandler);
112
+ const islandLocale = nuxtApp.ssrContext?.islandContext?.props?.locale;
113
+ const renderLocale = isValidLocale(islandLocale) ? islandLocale : void 0;
114
+ const getCurrentLocale = (route) => {
115
+ if (!route && renderLocale) return renderLocale;
116
+ const r = route ?? router.currentRoute.value;
117
+ return i18nStrategy.getCurrentLocale(r, getPreferredLocale());
118
+ };
119
+ i18n.setRouteContextResolver((route) => {
120
+ const resolvedRoute = route ?? router.currentRoute.value;
121
+ const routeLocale = getCurrentLocale(resolvedRoute);
122
+ return { locale: routeLocale, routeName: i18nStrategy.getPluginRouteName(resolvedRoute, routeLocale) };
123
+ });
124
+ const prerender = import.meta.server && import.meta.prerender;
125
+ const loadOptions = createLoadOptions(nuxtApp, i18nConfig);
126
+ const onFailedRead = () => {
127
+ const context = nuxtApp.ssrContext?.event?.context.i18n;
128
+ if (context && !prerender) context.failedRead = true;
129
+ };
130
+ const loader = new NuxtTranslationLoader({ i18n, loadOptions, prerender, onFailedRead });
131
+ const initialRoute = router.currentRoute.value;
132
+ const initialLocale = resolveStartLocale({
133
+ nuxtApp,
116
134
  i18nConfig,
117
- localeState
135
+ i18nStrategy,
136
+ renderLocale,
137
+ initialRoute,
138
+ locale,
139
+ getCurrentLocale
118
140
  });
119
- }
120
- router.beforeEach(async (to, from) => {
121
- if (to.name !== from.name) {
122
- i18nRouteParams.value = {};
123
- resetPageHead();
124
- }
125
- const shouldSwitchContext = to.path !== from.path || isNoPrefixStrategy(i18nConfig.strategy);
126
- if (!shouldSwitchContext) {
127
- return;
128
- }
141
+ const initialRouteName = i18nStrategy.getPluginRouteName(initialRoute, initialLocale);
129
142
  try {
130
- const targetLocale = getEffectiveLocale(to, (r) => getCurrentLocale(r));
131
- const targetRouteName = getPluginRouteName(to, targetLocale);
132
- let applied = true;
133
- if (targetLocale !== i18n.getCurrentLocale() || targetRouteName !== i18n.getCurrentRouteName()) {
134
- applied = await switchContext(targetLocale, targetRouteName);
135
- } else {
136
- loader.invalidatePendingSwitches();
137
- }
138
- if (applied && targetLocale && isValidLocale(targetLocale) && localeState.value !== targetLocale) {
139
- setLocale(targetLocale);
140
- }
143
+ await loader.switchContext(initialLocale, initialRouteName);
141
144
  } catch (e) {
142
- if (isDev) console.error("[i18n] Navigation error:", e);
145
+ if (process.env.NODE_ENV !== "production") console.error("[i18n] Initial load error:", e);
146
+ if (prerender) throw e;
147
+ throw createError({ statusCode: 404, statusMessage: "Page Not Found" });
143
148
  }
144
- return;
145
- });
146
- const provideData = {
147
- ...provideApi,
148
- helper,
149
- i18n: void 0,
150
- __micro: true
151
- };
152
- const $provideData = Object.fromEntries(Object.entries(provideData).map(([key, value]) => [`$${key}`, value]));
153
- provideData.i18n = { ...provideData, ...$provideData };
154
- return {
155
- provide: {
156
- ...provideData,
157
- getI18nConfig: () => i18nConfig
149
+ if (import.meta.server && !i18nConfig.noScripts && !nuxtApp.ssrContext?.event?.context?._nitro?.routeRules?.noScripts) {
150
+ const hint = (key, href) => ({ key, rel: "preload", as: "fetch", crossorigin: "anonymous", href });
151
+ const link = [hint("i18n-preload", translationStorage.clientUrl(initialLocale, initialRouteName, loadOptions))];
152
+ if (i18nConfig.rootDelta) link.push(hint("i18n-preload-root", translationStorage.clientUrl(initialLocale, PREMERGED_ROOT_PAGE, loadOptions)));
153
+ useHead({ link });
158
154
  }
159
- };
155
+ const hydrated = shallowRef(false);
156
+ nuxtApp.hooks.hookOnce("app:suspense:resolve", async () => {
157
+ hydrated.value = true;
158
+ if (!import.meta.client || !i18nConfig.hashMode) return;
159
+ const hashLocale = i18nStrategy.getLocaleFromPath(router.currentRoute.value.path);
160
+ if (hashLocale && hashLocale !== localeState.value) await api.provide.switchLocale(hashLocale);
161
+ });
162
+ const api = createNuxtI18nPluginApi({
163
+ i18n,
164
+ loader,
165
+ i18nStrategy,
166
+ // $getLocales lists the locales of other domains too: link targets
167
+ i18nConfig: domains ? { ...i18nConfig, locales: i18nConfig.linkLocales ?? i18nConfig.locales } : i18nConfig,
168
+ router,
169
+ locale,
170
+ getCurrentLocale,
171
+ i18nRouteParams,
172
+ navigateTo,
173
+ setMissingHandler: (handler) => {
174
+ customMissingHandler.value = handler;
175
+ },
176
+ // a ref: a link rendered without the hash gets it back when hydration ends
177
+ isHydrating: () => !hydrated.value && nuxtApp.isHydrating === true
178
+ });
179
+ if (import.meta.server) rewriteSwitcherLinks(nuxtApp, api.provide, i18nRouteParams, (path) => router.resolve(path).href);
180
+ if (import.meta.dev && import.meta.client) {
181
+ setI18nDevtoolsBridge({
182
+ i18n,
183
+ i18nConfig,
184
+ localeState
185
+ });
186
+ }
187
+ const dropPageHead = createPageHeadDrop(pageHead);
188
+ setupI18nNavigation({
189
+ nuxtApp,
190
+ router,
191
+ i18n,
192
+ loader,
193
+ i18nConfig,
194
+ i18nStrategy,
195
+ locale,
196
+ api,
197
+ leavePage: () => {
198
+ i18nRouteParams.value = {};
199
+ dropPageHead();
200
+ }
201
+ });
202
+ const { provide, install } = createI18nProvide({ api, i18nConfig, router, i18nRouteParams });
203
+ install(nuxtApp);
204
+ return { provide };
205
+ }
160
206
  });