nuxt-i18n-micro 3.18.3 → 3.20.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 (61) hide show
  1. package/README.md +16 -16
  2. package/dist/client/200.html +1 -1
  3. package/dist/client/404.html +1 -1
  4. package/dist/client/_nuxt/{D6byAye8.js → BA2bGsrZ.js} +1 -1
  5. package/dist/client/_nuxt/{BNXusRXY.js → C_HK2snF.js} +1 -1
  6. package/dist/client/_nuxt/{BCTbvRLO.js → Dx6dmpFi.js} +1 -1
  7. package/dist/client/_nuxt/SDQMalAT.js +14 -0
  8. package/dist/client/_nuxt/builds/latest.json +1 -1
  9. package/dist/client/_nuxt/builds/meta/5a55317c-1ad8-41ca-a7f6-2fe58e16b2f6.json +1 -0
  10. package/dist/client/index.html +1 -1
  11. package/dist/module.d.mts +1 -0
  12. package/dist/module.json +1 -1
  13. package/dist/module.mjs +197 -231
  14. package/dist/runtime/components/i18n-group.vue +1 -4
  15. package/dist/runtime/components/i18n-link.vue +2 -12
  16. package/dist/runtime/components/i18n-switcher.vue +7 -30
  17. package/dist/runtime/composables/useI18n.js +1 -1
  18. package/dist/runtime/composables/useI18nLocale.js +5 -6
  19. package/dist/runtime/composables/useLocaleHead.js +2 -2
  20. package/dist/runtime/middleware/i18n-redirect.global.d.ts +6 -0
  21. package/dist/runtime/middleware/i18n-redirect.global.js +43 -0
  22. package/dist/runtime/plugins/01.plugin.d.ts +21 -21
  23. package/dist/runtime/plugins/01.plugin.js +69 -313
  24. package/dist/runtime/plugins/02.meta.js +2 -2
  25. package/dist/runtime/plugins/05.hooks.js +32 -32
  26. package/dist/runtime/plugins/06.redirect.d.ts +1 -0
  27. package/dist/runtime/plugins/06.redirect.js +15 -86
  28. package/dist/runtime/server/middleware/i18n.global.js +17 -5
  29. package/dist/runtime/server/plugins/watcher.dev.js +36 -66
  30. package/dist/runtime/server/routes/i18n.js +2 -2
  31. package/dist/runtime/server/utils/locale-detector.d.ts +9 -12
  32. package/dist/runtime/server/utils/locale-detector.js +25 -18
  33. package/dist/runtime/server/utils/locale-server-middleware.js +10 -8
  34. package/dist/runtime/server/utils/server-loader.d.ts +3 -3
  35. package/dist/runtime/server/utils/server-loader.js +40 -15
  36. package/dist/runtime/server/utils/translation-server-middleware.js +10 -8
  37. package/dist/runtime/utils/nuxt-i18n.d.ts +122 -0
  38. package/dist/runtime/utils/nuxt-i18n.js +335 -0
  39. package/dist/runtime/utils/storage.d.ts +11 -12
  40. package/dist/runtime/utils/storage.js +37 -21
  41. package/dist/types.d.mts +2 -0
  42. package/internals.d.mts +1 -0
  43. package/package.json +21 -15
  44. package/dist/client/_nuxt/B-cq5x_h.js +0 -14
  45. package/dist/client/_nuxt/builds/meta/c5d2004e-f981-4363-a9ec-4d72cf7f4a8f.json +0 -1
  46. package/dist/runtime/utils/accept-language.d.ts +0 -3
  47. package/dist/runtime/utils/accept-language.js +0 -21
  48. package/dist/runtime/utils/active-locales.d.ts +0 -4
  49. package/dist/runtime/utils/active-locales.js +0 -9
  50. package/dist/runtime/utils/cache-control.d.ts +0 -50
  51. package/dist/runtime/utils/cache-control.js +0 -88
  52. package/dist/runtime/utils/cookie.d.ts +0 -24
  53. package/dist/runtime/utils/cookie.js +0 -22
  54. package/dist/runtime/utils/deep-merge.d.ts +0 -15
  55. package/dist/runtime/utils/deep-merge.js +0 -14
  56. package/dist/runtime/utils/resolve-server-locale.d.ts +0 -21
  57. package/dist/runtime/utils/resolve-server-locale.js +0 -30
  58. package/dist/runtime/utils/route-utils.d.ts +0 -33
  59. package/dist/runtime/utils/route-utils.js +0 -63
  60. package/dist/runtime/utils/runtime-i18n-config.d.ts +0 -8
  61. package/dist/runtime/utils/runtime-i18n-config.js +0 -90
@@ -1,25 +1,13 @@
1
- import { FormatService, isNoPrefixStrategy } from "@i18n-micro/core";
2
- import { shallowRef, triggerRef, unref } from "vue";
1
+ import { isNoPrefixStrategy } from "@i18n-micro/core";
2
+ import { resolveI18nConfigWithRuntimeOverrides } from "@i18n-micro/utils/runtime-config";
3
3
  import { useState } from "#app";
4
4
  import { plural } from "#build/i18n.plural.mjs";
5
5
  import { createI18nStrategy, getI18nConfig } from "#build/i18n.strategy.mjs";
6
- import { createError, defineNuxtPlugin, navigateTo, useHead, useRouter, useRuntimeConfig } from "#imports";
6
+ import { createError, defineNuxtPlugin, navigateTo, useRouter, useRuntimeConfig } from "#imports";
7
7
  import { useI18nLocale } from "../composables/useI18nLocale.js";
8
- import { deepMergeTranslations } from "../utils/deep-merge.js";
9
- import { resolveI18nConfigWithRuntimeOverrides } from "../utils/runtime-i18n-config.js";
8
+ import { createNuxtI18nPluginApi, NuxtI18n, NuxtTranslationLoader } from "../utils/nuxt-i18n.js";
10
9
  import { translationStorage } from "../utils/storage.js";
11
10
  const isDev = process.env.NODE_ENV !== "production";
12
- const RE_TOKEN = /\{(\w+)\}/g;
13
- function getByPath(obj, path) {
14
- if (obj[path] !== void 0) return obj[path];
15
- const parts = path.split(".");
16
- let v = obj;
17
- for (const p of parts) {
18
- if (v == null || typeof v !== "object") return void 0;
19
- v = v[p];
20
- }
21
- return v;
22
- }
23
11
  export default defineNuxtPlugin(async (nuxtApp) => {
24
12
  const router = useRouter();
25
13
  const i18nStrategy = createI18nStrategy(router);
@@ -32,14 +20,15 @@ export default defineNuxtPlugin(async (nuxtApp) => {
32
20
  maxSize: i18nConfig.cacheMaxSize ?? 0,
33
21
  ttl: i18nConfig.cacheTtl ?? 0
34
22
  });
35
- const loadedChunks = /* @__PURE__ */ new Map();
36
- let currentLocale = "";
37
- let currentRouteName = "";
38
- let cachedTranslations = {};
39
- let pendingCleanState = null;
40
- const contextSignal = shallowRef(0);
41
23
  const { locale: localeState, setLocale, getLocale, getEffectiveLocale, resolveInitialLocale, isValidLocale } = useI18nLocale();
42
- const translationService = new FormatService();
24
+ const i18nRouteParams = useState("i18n-route-params", () => ({}));
25
+ const customMissingHandler = useState("i18n-missing-handler", () => null);
26
+ const ssrChunks = useState("i18n-ssr-chunks", () => ({}));
27
+ const i18n = new NuxtI18n({
28
+ plural,
29
+ missingWarn: i18nConfig.missingWarn ?? true,
30
+ getCustomMissingHandler: () => customMissingHandler.value
31
+ });
43
32
  const getCurrentLocale = (route) => {
44
33
  const r = route ?? router.currentRoute.value;
45
34
  return i18nStrategy.getCurrentLocale(r, getLocale() ?? null);
@@ -47,100 +36,36 @@ export default defineNuxtPlugin(async (nuxtApp) => {
47
36
  const getPluginRouteName = (route, locale) => {
48
37
  return i18nStrategy.getPluginRouteName(route, locale);
49
38
  };
50
- const switchLocaleLogic = (toLocale, i18nParams, to) => {
51
- const fromLocale = getCurrentLocale();
52
- const currentRoute = router.currentRoute.value;
53
- let resolvedTarget;
54
- if (typeof to === "string") {
55
- const formattedPath = i18nStrategy.formatPathForResolve(to, fromLocale, toLocale);
56
- resolvedTarget = router.resolve(formattedPath);
57
- } else {
58
- resolvedTarget = to ?? currentRoute;
59
- }
60
- const switchedRoute = i18nStrategy.switchLocaleRoute(fromLocale, toLocale, resolvedTarget, {
61
- i18nRouteParams: i18nParams
62
- });
63
- if (typeof switchedRoute === "string" && (switchedRoute.startsWith("http://") || switchedRoute.startsWith("https://"))) {
64
- return navigateTo(switchedRoute, { redirectCode: 200, external: true });
65
- }
66
- if (isNoPrefixStrategy(i18nConfig.strategy)) {
67
- ;
68
- switchedRoute.force = true;
69
- }
70
- return router.push(switchedRoute);
71
- };
39
+ i18n.setRouteContextResolver((route) => {
40
+ const resolvedRoute = route ?? router.currentRoute.value;
41
+ const locale = getCurrentLocale(resolvedRoute);
42
+ return {
43
+ locale,
44
+ routeName: getPluginRouteName(resolvedRoute, locale)
45
+ };
46
+ });
72
47
  nuxtApp.hook("page:transition:finish", () => {
73
- if (pendingCleanState) {
74
- cachedTranslations = pendingCleanState;
75
- pendingCleanState = null;
76
- }
48
+ i18n.finishTransition();
77
49
  });
78
- const i18nRouteParams = useState("i18n-route-params", () => ({}));
79
- const customMissingHandler = useState("i18n-missing-handler", () => null);
80
- const missingWarn = i18nConfig.missingWarn ?? true;
81
50
  const loadOptions = {
82
51
  apiBaseUrl: i18nConfig.apiBaseUrl ?? "_locales",
83
52
  baseURL: runtimeConfig.app.baseURL,
84
- dateBuild: i18nConfig.dateBuild
85
- };
86
- const getCacheKey = (locale, routeName) => {
87
- return `${locale}:${routeName || "index"}`;
88
- };
89
- const loadFromCacheSync = (locale, routeName) => {
90
- const cacheKey = getCacheKey(locale, routeName);
91
- if (loadedChunks.has(cacheKey)) {
92
- return loadedChunks.get(cacheKey);
93
- }
94
- const cached = translationStorage.getFromCache(locale, routeName);
95
- if (cached) {
96
- loadedChunks.set(cacheKey, cached.data);
97
- return cached.data;
98
- }
99
- return null;
100
- };
101
- const pendingLoads = /* @__PURE__ */ new Map();
102
- const loadAsync = (locale, routeName) => {
103
- const cacheKey = getCacheKey(locale, routeName);
104
- const pending = pendingLoads.get(cacheKey);
105
- if (pending) return pending;
106
- const promise = (async () => {
107
- try {
108
- const result = await translationStorage.load(locale, routeName, loadOptions);
109
- const existing = loadedChunks.get(cacheKey);
110
- const mergedChunk = existing ? { ...result.data, ...existing } : result.data;
111
- loadedChunks.set(cacheKey, mergedChunk);
112
- if (import.meta.server && result.json) {
113
- const ctx = nuxtApp.ssrContext.event.context;
114
- if (!ctx._i18n) ctx._i18n = {};
115
- ctx._i18n[cacheKey] = result.json;
116
- }
117
- return mergedChunk;
118
- } catch (e) {
119
- if (isDev) console.error("[i18n] Load error:", e);
120
- return {};
121
- } finally {
122
- pendingLoads.delete(cacheKey);
123
- }
124
- })();
125
- pendingLoads.set(cacheKey, promise);
126
- return promise;
127
- };
128
- const switchContext = async (locale, routeName) => {
129
- let data = loadFromCacheSync(locale, routeName);
130
- if (data === null) {
131
- data = await loadAsync(locale, routeName);
132
- }
133
- if (currentLocale === locale) {
134
- cachedTranslations = deepMergeTranslations(cachedTranslations, data);
135
- pendingCleanState = data;
136
- } else {
137
- cachedTranslations = deepMergeTranslations(cachedTranslations, data);
138
- pendingCleanState = null;
139
- }
140
- currentLocale = locale;
141
- currentRouteName = routeName || "";
142
- triggerRef(contextSignal);
53
+ apiBaseClientHost: i18nConfig.apiBaseClientHost,
54
+ dateBuild: i18nConfig.dateBuild,
55
+ routesLocaleLinks: i18nConfig.routesLocaleLinks
143
56
  };
57
+ if (import.meta.client && Object.keys(ssrChunks.value).length > 0) {
58
+ translationStorage.seedFromSsrChunks(ssrChunks.value);
59
+ }
60
+ const loader = new NuxtTranslationLoader({
61
+ i18n,
62
+ loadOptions,
63
+ getSsrChunks: () => ssrChunks.value,
64
+ setSsrChunk: (cacheKey, data) => {
65
+ ssrChunks.value = { ...ssrChunks.value, [cacheKey]: data };
66
+ },
67
+ isDev
68
+ });
144
69
  const serverLocale = import.meta.server ? nuxtApp.ssrContext?.event?.context?.i18n?.locale : void 0;
145
70
  const initialLocale = resolveInitialLocale({
146
71
  route: router.currentRoute.value,
@@ -149,35 +74,50 @@ export default defineNuxtPlugin(async (nuxtApp) => {
149
74
  });
150
75
  const initialRouteName = getPluginRouteName(router.currentRoute.value, initialLocale);
151
76
  try {
152
- await switchContext(initialLocale, initialRouteName);
77
+ await loader.switchContext(initialLocale, initialRouteName);
153
78
  } catch (e) {
154
79
  if (isDev) console.error("[i18n] Initial load error:", e);
155
80
  throw createError({ statusCode: 404, statusMessage: "Page Not Found" });
156
81
  }
157
- if (import.meta.server) {
158
- const ctx = nuxtApp.ssrContext?.event.context;
159
- if (ctx?._i18n && Object.keys(ctx._i18n).length > 0) {
160
- let script = "window.__I18N__={};";
161
- for (const [key, value] of Object.entries(ctx._i18n)) {
162
- const safeKey = JSON.stringify(key);
163
- script += `window.__I18N__[${safeKey}]=${value};`;
164
- }
165
- useHead({
166
- script: [{ innerHTML: script, type: "text/javascript", tagPosition: "bodyClose" }]
167
- });
82
+ const getRouteName = (route, locale) => {
83
+ const selectedRoute = route ?? router.currentRoute.value;
84
+ const selectedLocale = locale ?? getCurrentLocale(selectedRoute);
85
+ return i18nStrategy.getRouteBaseName(selectedRoute, selectedLocale) ?? "";
86
+ };
87
+ const {
88
+ helper,
89
+ switchContext,
90
+ provide: provideApi
91
+ } = createNuxtI18nPluginApi({
92
+ i18n,
93
+ loader,
94
+ i18nStrategy,
95
+ i18nConfig,
96
+ router,
97
+ getCurrentLocale,
98
+ getEffectiveLocale,
99
+ getPluginRouteName,
100
+ getRouteName,
101
+ i18nRouteParams,
102
+ setLocale,
103
+ isValidLocale,
104
+ navigateTo,
105
+ setMissingHandler: (handler) => {
106
+ customMissingHandler.value = handler;
168
107
  }
169
- }
108
+ });
170
109
  router.beforeEach(async (to, from) => {
171
110
  if (to.name !== from.name) {
172
111
  i18nRouteParams.value = {};
173
112
  }
174
- if (to.path === from.path && !isNoPrefixStrategy(i18nConfig.strategy)) {
113
+ const shouldSwitchContext = to.path !== from.path || isNoPrefixStrategy(i18nConfig.strategy);
114
+ if (!shouldSwitchContext) {
175
115
  return;
176
116
  }
177
117
  try {
178
118
  const targetLocale = getEffectiveLocale(to, (r) => getCurrentLocale(r));
179
119
  const targetRouteName = getPluginRouteName(to, targetLocale);
180
- if (targetLocale !== currentLocale || targetRouteName !== currentRouteName) {
120
+ if (targetLocale !== i18n.getCurrentLocale() || targetRouteName !== i18n.getCurrentRouteName()) {
181
121
  await switchContext(targetLocale, targetRouteName);
182
122
  }
183
123
  if (targetLocale && isValidLocale(targetLocale) && localeState.value !== targetLocale) {
@@ -188,195 +128,11 @@ export default defineNuxtPlugin(async (nuxtApp) => {
188
128
  }
189
129
  return;
190
130
  });
191
- const tFast = (key, params, defaultValue, route) => {
192
- if (!key) return "";
193
- contextSignal.value;
194
- const translations = route ? loadedChunks.get(
195
- getCacheKey(
196
- getCurrentLocale(route),
197
- getPluginRouteName(route, getCurrentLocale(route))
198
- )
199
- ) || {} : cachedTranslations;
200
- let val = translations[key];
201
- if (val === void 0 && key.includes(".")) {
202
- val = getByPath(translations, key);
203
- }
204
- if (val === void 0) {
205
- if (customMissingHandler.value) {
206
- customMissingHandler.value(currentLocale, key, currentRouteName);
207
- } else if (missingWarn && isDev && import.meta.client) {
208
- console.warn(`[i18n] Missing key '${key}' in '${currentLocale}' for route '${currentRouteName}'`);
209
- }
210
- return defaultValue === void 0 ? key : defaultValue;
211
- }
212
- if (typeof val !== "string") return val;
213
- if (!params) return val;
214
- return val.replace(RE_TOKEN, (_, k) => {
215
- return params[k] !== void 0 ? String(params[k]) : `{${k}}`;
216
- });
217
- };
218
- const getRouteName = (route, locale) => {
219
- const selectedRoute = route ?? router.currentRoute.value;
220
- const selectedLocale = locale ?? getCurrentLocale(selectedRoute);
221
- return i18nStrategy.getRouteBaseName(selectedRoute, selectedLocale) ?? "";
222
- };
223
- const hasTranslation = (key) => {
224
- contextSignal.value;
225
- if (cachedTranslations[key] !== void 0) return true;
226
- if (key.includes(".") && getByPath(cachedTranslations, key) !== void 0) return true;
227
- return false;
228
- };
229
- const mergeTranslations = (newTranslations) => {
230
- const cacheKey = getCacheKey(currentLocale, currentRouteName);
231
- const current = loadedChunks.get(cacheKey) || {};
232
- const merged = { ...current, ...newTranslations };
233
- loadedChunks.set(cacheKey, merged);
234
- cachedTranslations = deepMergeTranslations(cachedTranslations, merged);
235
- if (pendingCleanState) pendingCleanState = merged;
236
- triggerRef(contextSignal);
237
- };
238
- const helper = {
239
- async mergeTranslation(locale, routeName, newTranslations, _force = false) {
240
- const cacheKey = getCacheKey(locale, routeName);
241
- if (!loadedChunks.has(cacheKey)) {
242
- await loadAsync(locale, routeName || void 0);
243
- }
244
- const existing = loadedChunks.get(cacheKey) || {};
245
- const mergedChunk = { ...existing, ...newTranslations };
246
- loadedChunks.set(cacheKey, mergedChunk);
247
- if (locale === currentLocale && routeName === currentRouteName) {
248
- cachedTranslations = deepMergeTranslations(cachedTranslations, mergedChunk);
249
- if (pendingCleanState) pendingCleanState = mergedChunk;
250
- triggerRef(contextSignal);
251
- }
252
- }
253
- };
254
131
  const provideData = {
255
- i18n: void 0,
256
- __micro: true,
132
+ ...provideApi,
257
133
  helper,
258
- i18nStrategy,
259
- getLocale: (route) => getEffectiveLocale(route, (r) => getCurrentLocale(r)),
260
- getLocaleName: () => i18nStrategy.getCurrentLocaleName(router.currentRoute.value, getLocale() ?? null),
261
- defaultLocale: () => i18nConfig.defaultLocale,
262
- getLocales: () => i18nConfig.locales || [],
263
- getRouteName,
264
- t: tFast,
265
- ts: (key, params, defaultValue, route) => {
266
- const value = tFast(key, params, defaultValue, route);
267
- return value?.toString() ?? defaultValue ?? key;
268
- },
269
- _t: (route) => {
270
- return (key, params, defaultValue) => {
271
- return tFast(key, params, defaultValue, route);
272
- };
273
- },
274
- _ts: (route) => {
275
- return (key, params, defaultValue) => {
276
- const value = tFast(key, params, defaultValue, route);
277
- return value?.toString() ?? defaultValue ?? key;
278
- };
279
- },
280
- tc: (key, params, defaultValue) => {
281
- contextSignal.value;
282
- const { count, ..._params } = typeof params === "number" ? { count: params } : params;
283
- if (count === void 0) return defaultValue ?? key;
284
- return plural(key, Number.parseInt(count.toString(), 10), _params, currentLocale, tFast) ?? defaultValue ?? key;
285
- },
286
- tn: (value, options) => {
287
- contextSignal.value;
288
- return translationService.formatNumber(value, currentLocale, options);
289
- },
290
- td: (value, options) => {
291
- contextSignal.value;
292
- return translationService.formatDate(value, currentLocale, options);
293
- },
294
- tdr: (value, options) => {
295
- contextSignal.value;
296
- return translationService.formatRelativeTime(value, currentLocale, options);
297
- },
298
- has: hasTranslation,
299
- mergeTranslations,
300
- switchLocaleRoute: (toLocale) => {
301
- const route = router.currentRoute.value;
302
- const fromLocale = getCurrentLocale(route);
303
- return i18nStrategy.switchLocaleRoute(fromLocale, toLocale, route, { i18nRouteParams: unref(i18nRouteParams.value) });
304
- },
305
- clearCache: () => {
306
- translationStorage.clear();
307
- loadedChunks.clear();
308
- cachedTranslations = {};
309
- triggerRef(contextSignal);
310
- },
311
- switchLocalePath: (toLocale) => {
312
- const route = router.currentRoute.value;
313
- const fromLocale = getCurrentLocale(route);
314
- const localeRoute = i18nStrategy.switchLocaleRoute(fromLocale, toLocale, route, { i18nRouteParams: unref(i18nRouteParams.value) });
315
- if (!localeRoute || typeof localeRoute !== "object") return String(localeRoute ?? "");
316
- if ("fullPath" in localeRoute && localeRoute.fullPath) return localeRoute.fullPath;
317
- if ("path" in localeRoute && localeRoute.path) return localeRoute.path;
318
- if ("name" in localeRoute && localeRoute.name && router.hasRoute(String(localeRoute.name))) {
319
- return router.resolve(localeRoute).fullPath;
320
- }
321
- return "";
322
- },
323
- switchLocale: async (toLocale) => {
324
- if (!isValidLocale(toLocale)) {
325
- if (isDev) {
326
- console.warn(`[i18n] Invalid locale '${toLocale}'`);
327
- }
328
- return;
329
- }
330
- setLocale(toLocale);
331
- if (isNoPrefixStrategy(i18nConfig.strategy) || i18nConfig.hashMode) {
332
- const route = router.currentRoute.value;
333
- const routeName = getPluginRouteName(route, toLocale);
334
- await switchContext(toLocale, routeName);
335
- }
336
- return switchLocaleLogic(toLocale, unref(i18nRouteParams.value));
337
- },
338
- switchRoute: (route, toLocale) => {
339
- return switchLocaleLogic(
340
- toLocale ?? getCurrentLocale(),
341
- unref(i18nRouteParams.value),
342
- route
343
- );
344
- },
345
- localeRoute(to, locale) {
346
- const targetLocale = locale !== void 0 && locale !== "" ? String(locale) : getCurrentLocale();
347
- const currentRoute = router.currentRoute.value;
348
- const result = i18nStrategy.localeRoute(targetLocale, to, currentRoute);
349
- const fullPath = result.fullPath ?? result.path ?? "";
350
- const path = result.path ?? fullPath.split("?")[0]?.split("#")[0] ?? fullPath;
351
- const out = { path, fullPath, href: fullPath };
352
- if (result.query && Object.keys(result.query).length) out.query = result.query;
353
- if (result.hash) out.hash = result.hash;
354
- return out;
355
- },
356
- localePath(to, locale) {
357
- const targetLocale = locale !== void 0 && locale !== "" ? String(locale) : getCurrentLocale();
358
- const currentRoute = router.currentRoute.value;
359
- const result = i18nStrategy.localeRoute(targetLocale, to, currentRoute);
360
- return result.fullPath ?? result.path ?? "";
361
- },
362
- setI18nRouteParams: (value) => {
363
- i18nRouteParams.value = value;
364
- return i18nRouteParams.value;
365
- },
366
- loadPageTranslations: async (locale, routeName, translations) => {
367
- const cacheKey = getCacheKey(locale, routeName);
368
- const current = loadedChunks.get(cacheKey) || {};
369
- const mergedChunk = { ...current, ...translations };
370
- loadedChunks.set(cacheKey, mergedChunk);
371
- if (locale === currentLocale && routeName === currentRouteName) {
372
- cachedTranslations = deepMergeTranslations(cachedTranslations, mergedChunk);
373
- if (pendingCleanState) pendingCleanState = mergedChunk;
374
- triggerRef(contextSignal);
375
- }
376
- },
377
- setMissingHandler: (handler) => {
378
- customMissingHandler.value = handler;
379
- }
134
+ i18n: void 0,
135
+ __micro: true
380
136
  };
381
137
  const $provideData = Object.fromEntries(Object.entries(provideData).map(([key, value]) => [`$${key}`, value]));
382
138
  provideData.i18n = { ...provideData, ...$provideData };
@@ -1,9 +1,9 @@
1
+ import { isMetaDisabledForRoute } from "@i18n-micro/utils/route";
2
+ import { resolveI18nConfigWithRuntimeOverrides } from "@i18n-micro/utils/runtime-config";
1
3
  import { watch } from "vue";
2
4
  import { getI18nConfig } from "#build/i18n.strategy.mjs";
3
5
  import { defineNuxtPlugin, useHead, useRequestURL, useRoute } from "#imports";
4
6
  import { useLocaleHead } from "../composables/useLocaleHead.js";
5
- import { isMetaDisabledForRoute } from "../utils/route-utils.js";
6
- import { resolveI18nConfigWithRuntimeOverrides } from "../utils/runtime-i18n-config.js";
7
7
  export default defineNuxtPlugin((nuxtApp) => {
8
8
  const route = useRoute();
9
9
  const getRuntimeConfig = nuxtApp.$getI18nConfig;
@@ -1,45 +1,45 @@
1
1
  import { isNoPrefixStrategy } from "@i18n-micro/core";
2
+ import { resolveI18nConfigWithRuntimeOverrides } from "@i18n-micro/utils/runtime-config";
2
3
  import { getI18nConfig } from "#build/i18n.strategy.mjs";
3
4
  import { defineNuxtPlugin, useNuxtApp, useRouter } from "#imports";
4
- import { resolveI18nConfigWithRuntimeOverrides } from "../utils/runtime-i18n-config.js";
5
5
  const isDev = process.env.NODE_ENV !== "production";
6
- export default defineNuxtPlugin(async (nuxtApp) => {
7
- const getRuntimeConfig = nuxtApp.$getI18nConfig;
8
- const i18nConfig = resolveI18nConfigWithRuntimeOverrides(
9
- typeof getRuntimeConfig === "function" ? getRuntimeConfig() : getI18nConfig()
10
- );
11
- const router = useRouter();
12
- const { $getLocale, $getRouteName } = useNuxtApp();
13
- const i18nHelper = nuxtApp.$i18n.helper;
14
- if (!i18nHelper) {
15
- if (isDev) {
16
- console.warn("[i18n] Helper is not available. Skipping hooks plugin.");
6
+ export default defineNuxtPlugin({
7
+ name: "i18n-plugin-hooks",
8
+ dependsOn: ["i18n-plugin-loader"],
9
+ async setup(nuxtApp) {
10
+ const getRuntimeConfig = nuxtApp.$getI18nConfig;
11
+ const i18nConfig = resolveI18nConfigWithRuntimeOverrides(
12
+ typeof getRuntimeConfig === "function" ? getRuntimeConfig() : getI18nConfig()
13
+ );
14
+ const router = useRouter();
15
+ const { $getLocale, $getRouteName } = useNuxtApp();
16
+ const i18nHelper = nuxtApp.$i18n?.helper;
17
+ if (!i18nHelper) {
18
+ if (isDev) {
19
+ console.warn("[i18n] Helper is not available. Skipping hooks plugin.");
20
+ }
21
+ return;
17
22
  }
18
- return;
19
- }
20
- const locale = $getLocale();
21
- const routeName = $getRouteName();
22
- await nuxtApp.callHook(
23
- // @ts-expect-error i18n:register is custom hook
24
- "i18n:register",
25
- (translations, selectedLocale) => {
26
- i18nHelper.mergeTranslation(selectedLocale ?? locale, routeName, translations, true);
27
- },
28
- locale
29
- );
30
- router.beforeEach(async (to, from) => {
31
- if (to.path !== from.path || isNoPrefixStrategy(i18nConfig.strategy)) {
32
- const locale2 = $getLocale(to);
33
- const routeName2 = $getRouteName(to);
23
+ const callRegister = async (route) => {
24
+ const locale = $getLocale(route);
25
+ const routeName = $getRouteName(route);
34
26
  await nuxtApp.callHook(
35
27
  // @ts-expect-error i18n:register is custom hook
36
28
  "i18n:register",
37
29
  (translations, selectedLocale) => {
38
- i18nHelper.mergeTranslation(selectedLocale ?? locale2, routeName2, translations, true);
30
+ void i18nHelper.mergeTranslation(selectedLocale ?? locale, routeName, translations, true);
39
31
  },
40
- locale2
32
+ locale
41
33
  );
34
+ };
35
+ if (i18nConfig.hooks !== false) {
36
+ await callRegister();
42
37
  }
43
- return;
44
- });
38
+ router.beforeEach(async (to, from) => {
39
+ if (i18nConfig.hooks === false) return;
40
+ if (to.path !== from.path || isNoPrefixStrategy(i18nConfig.strategy)) {
41
+ await callRegister(to);
42
+ }
43
+ });
44
+ }
45
45
  });
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Universal redirect plugin for i18n routes (works on both server and client).
3
3
  * Handles locale detection, 404 checks, and redirects.
4
+ * Client SPA redirects are handled by i18n-redirect route middleware.
4
5
  */
5
6
  declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
6
7
  export default _default;
@@ -1,58 +1,26 @@
1
+ import { isInternalPath } from "@i18n-micro/route-strategy";
2
+ import { getEnabledLocaleCodes } from "@i18n-micro/utils/active-locales";
3
+ import { getLocaleCookieName, getLocaleCookieOptions } from "@i18n-micro/utils/cookie";
4
+ import { resolvePreferredLocale } from "@i18n-micro/utils/resolve-locale";
1
5
  import { getCookie, getHeader, getRequestURL, setCookie } from "h3";
2
- import { createI18nStrategy, getI18nConfig } from "#build/i18n.strategy.mjs";
3
- import { createError, defineNuxtPlugin, navigateTo, useRequestEvent, useRoute, useRouter, useState } from "#imports";
4
- import { useI18nLocale } from "../composables/useI18nLocale.js";
5
- import { getEnabledLocaleCodes } from "../utils/active-locales.js";
6
- import { getLocaleCookieName, getLocaleCookieOptions } from "../utils/cookie.js";
7
- import { resolvePreferredLocale } from "../utils/resolve-server-locale.js";
8
- import { resolveI18nConfigWithRuntimeOverrides } from "../utils/runtime-i18n-config.js";
6
+ import { createError, defineNuxtPlugin, navigateTo, useRequestEvent, useState } from "#imports";
9
7
  const DEBUG = process.env.NUXT_I18N_DEBUG_REDIRECT === "1";
10
- const DEFAULT_STATIC_PATTERNS = [
11
- /^\/sitemap.*\.xml$/,
12
- /^\/sitemap\.xml$/,
13
- /^\/robots\.txt$/,
14
- /^\/favicon\.ico$/,
15
- /^\/apple-touch-icon.*\.png$/,
16
- /^\/manifest\.json$/,
17
- /^\/sw\.js$/,
18
- /^\/workbox-.*\.js$/,
19
- /\.(xml|txt|ico|json|js|css|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/
20
- ];
21
- function isInternalPath(path, excludePatterns) {
22
- if (/(?:^|\/)__[^/]+/.test(path)) {
23
- return true;
8
+ function resolveI18nStrategy(nuxtApp) {
9
+ if (nuxtApp.$i18nStrategy) {
10
+ return nuxtApp.$i18nStrategy;
24
11
  }
25
- for (const pattern of DEFAULT_STATIC_PATTERNS) {
26
- if (pattern.test(path)) {
27
- return true;
28
- }
29
- }
30
- if (excludePatterns) {
31
- for (const pattern of excludePatterns) {
32
- if (typeof pattern === "string") {
33
- if (pattern.includes("*") || pattern.includes("?")) {
34
- const regex = new RegExp(pattern.replace(/\*/g, ".*").replace(/\?/g, "."));
35
- if (regex.test(path)) return true;
36
- } else if (path === pattern || path.startsWith(pattern)) {
37
- return true;
38
- }
39
- } else if (pattern instanceof RegExp) {
40
- if (pattern.test(path)) return true;
41
- }
42
- }
43
- }
44
- return false;
12
+ throw new Error("[nuxt-i18n-micro] i18n redirect plugin requires the main i18n plugin ($i18nStrategy).");
45
13
  }
46
14
  export default defineNuxtPlugin({
47
15
  name: "i18n-redirect",
48
- enforce: "pre",
16
+ dependsOn: ["i18n-plugin-loader"],
49
17
  setup(nuxtApp) {
50
- const router = useRouter();
51
- const i18nStrategy = createI18nStrategy(router);
18
+ const i18nStrategy = resolveI18nStrategy(nuxtApp);
52
19
  const getRuntimeConfig = nuxtApp.$getI18nConfig;
53
- const i18nConfig = resolveI18nConfigWithRuntimeOverrides(
54
- typeof getRuntimeConfig === "function" ? getRuntimeConfig() : getI18nConfig()
55
- );
20
+ if (typeof getRuntimeConfig !== "function") {
21
+ throw new Error("[nuxt-i18n-micro] i18n redirect plugin requires $getI18nConfig from the main i18n plugin.");
22
+ }
23
+ const i18nConfig = getRuntimeConfig();
56
24
  const validLocales = getEnabledLocaleCodes(i18nConfig.locales);
57
25
  const defaultLocale = i18nConfig.defaultLocale || "en";
58
26
  const autoDetectPath = i18nConfig.autoDetectPath || "/";
@@ -137,44 +105,5 @@ export default defineNuxtPlugin({
137
105
  }
138
106
  }
139
107
  }
140
- if (import.meta.client && i18nConfig.redirects !== false) {
141
- const runRedirect = () => {
142
- const { getPreferredLocale } = useI18nLocale();
143
- let preferredLocale = getPreferredLocale();
144
- if (!preferredLocale) return;
145
- const route = useRoute();
146
- const path = route.path || "/";
147
- const pathSegments = path.replace(/^\//, "").split("/").filter(Boolean);
148
- const firstSegment = pathSegments[0];
149
- const hasLocalePrefix = Boolean(firstSegment && validLocales.includes(firstSegment));
150
- if (autoDetectPath === "*" && !hasLocalePrefix) {
151
- preferredLocale = defaultLocale;
152
- }
153
- if (autoDetectPath === "*" && hasLocalePrefix && firstSegment !== preferredLocale) {
154
- const rest = pathSegments.slice(1).join("/");
155
- let targetPath;
156
- if (preferredLocale === defaultLocale && i18nConfig.strategy === "prefix_except_default") {
157
- targetPath = rest ? `/${rest}` : "/";
158
- } else {
159
- targetPath = rest ? `/${preferredLocale}/${rest}` : `/${preferredLocale}`;
160
- }
161
- navigateTo(targetPath, { replace: true, redirectCode: 302 });
162
- return;
163
- }
164
- const redirectPath = i18nStrategy.getClientRedirect(path, preferredLocale);
165
- if (redirectPath) {
166
- navigateTo(redirectPath, { replace: true, redirectCode: 302 });
167
- }
168
- };
169
- nuxtApp.hook("app:mounted", () => {
170
- runRedirect();
171
- });
172
- router.afterEach((to, from) => {
173
- if (to.path === from.path) return;
174
- setTimeout(() => {
175
- runRedirect();
176
- }, 0);
177
- });
178
- }
179
108
  }
180
109
  });