nuxt-i18n-micro 3.19.0 → 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 (39) 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.json +1 -1
  12. package/dist/module.mjs +147 -89
  13. package/dist/runtime/components/i18n-group.vue +1 -4
  14. package/dist/runtime/components/i18n-link.vue +2 -12
  15. package/dist/runtime/components/i18n-switcher.vue +7 -30
  16. package/dist/runtime/composables/useI18n.js +1 -1
  17. package/dist/runtime/composables/useI18nLocale.js +2 -3
  18. package/dist/runtime/composables/useLocaleHead.js +1 -1
  19. package/dist/runtime/middleware/i18n-redirect.global.d.ts +6 -0
  20. package/dist/runtime/middleware/i18n-redirect.global.js +43 -0
  21. package/dist/runtime/plugins/01.plugin.d.ts +21 -21
  22. package/dist/runtime/plugins/01.plugin.js +65 -312
  23. package/dist/runtime/plugins/05.hooks.js +31 -31
  24. package/dist/runtime/plugins/06.redirect.d.ts +1 -0
  25. package/dist/runtime/plugins/06.redirect.js +12 -83
  26. package/dist/runtime/server/middleware/i18n.global.js +13 -1
  27. package/dist/runtime/server/utils/locale-detector.d.ts +9 -12
  28. package/dist/runtime/server/utils/locale-detector.js +25 -28
  29. package/dist/runtime/server/utils/locale-server-middleware.js +8 -6
  30. package/dist/runtime/server/utils/server-loader.js +10 -9
  31. package/dist/runtime/server/utils/translation-server-middleware.js +8 -6
  32. package/dist/runtime/utils/nuxt-i18n.d.ts +122 -0
  33. package/dist/runtime/utils/nuxt-i18n.js +335 -0
  34. package/dist/runtime/utils/storage.d.ts +7 -6
  35. package/dist/runtime/utils/storage.js +18 -11
  36. package/internals.d.mts +1 -0
  37. package/package.json +16 -13
  38. package/dist/client/_nuxt/B-cq5x_h.js +0 -14
  39. package/dist/client/_nuxt/builds/meta/bbe443af-4371-4ec3-b41c-7a5e9deb9b9b.json +0 -1
@@ -1 +1 @@
1
- {"id":"bbe443af-4371-4ec3-b41c-7a5e9deb9b9b","timestamp":1781425540376}
1
+ {"id":"5a55317c-1ad8-41ca-a7f6-2fe58e16b2f6","timestamp":1781512935536}
@@ -0,0 +1 @@
1
+ {"id":"5a55317c-1ad8-41ca-a7f6-2fe58e16b2f6","timestamp":1781512935536,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/entry.Kj_DYE7z.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/B-cq5x_h.js"><script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/B-cq5x_h.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-404.CyBDSRXN.css"><link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/BCTbvRLO.js"><link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/D6byAye8.js"><link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-500.DJtCwW7w.css"><link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/BNXusRXY.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"bbe443af-4371-4ec3-b41c-7a5e9deb9b9b",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1781425543266,false]</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/entry.Kj_DYE7z.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/SDQMalAT.js"><script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/SDQMalAT.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-404.CyBDSRXN.css"><link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/Dx6dmpFi.js"><link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/BA2bGsrZ.js"><link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-500.DJtCwW7w.css"><link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/C_HK2snF.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"5a55317c-1ad8-41ca-a7f6-2fe58e16b2f6",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1781512938774,false]</script></body></html>
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-i18n-micro",
3
3
  "configKey": "i18n",
4
- "version": "3.19.0",
4
+ "version": "3.20.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as fs from 'node:fs';
2
- import fs__default, { readFileSync, existsSync } from 'node:fs';
2
+ import fs__default, { readFileSync, mkdirSync, writeFileSync, existsSync } from 'node:fs';
3
3
  import { createRequire } from 'node:module';
4
4
  import path, { resolve, join, dirname } from 'node:path';
5
5
  import { fileURLToPath, pathToFileURL } from 'node:url';
@@ -10,9 +10,11 @@ import { buildTranslationSourceLayers, preMergeLocales } from '@i18n-micro/utils
10
10
  import { resolveTranslationPayloadOptions, getTranslationPayloadMisconfigurationWarnings, getTranslationPayloadSizeWarning, resolveTranslationPayloadWarningThresholds, resolveTranslationPayloadPublicDir } from '@i18n-micro/utils/payload-config';
11
11
  export { resolveTranslationPayloadMode, resolveTranslationPayloadOptions, resolveTranslationPayloadPublicDir } from '@i18n-micro/utils/payload-config';
12
12
  import { scanTranslationPayloadDirectory } from '@i18n-micro/utils/payload-stats';
13
- import { useNuxt, defineNuxtModule, useLogger, createResolver, addTemplate, addImportsDir, addPlugin, addServerHandler, addComponentsDir, addTypeTemplate, addPrerenderRoutes } from '@nuxt/kit';
13
+ import { useNuxt, defineNuxtModule, useLogger, createResolver, addTemplate, addImportsDir, addPlugin, addRouteMiddleware, addServerImportsDir, addServerHandler, addComponentsDir, addTypeTemplate, addVitePlugin, addPrerenderRoutes } from '@nuxt/kit';
14
14
  import { globby } from 'globby';
15
15
  import { onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
16
+ import { createUnplugin } from 'unplugin';
17
+ import { parse } from '@vue/compiler-sfc';
16
18
 
17
19
  const DEVTOOLS_UI_PORT = 3030;
18
20
  const DEVTOOLS_UI_ROUTE = "/__nuxt-i18n-micro";
@@ -133,8 +135,11 @@ function shouldLocalizeRouteRulePath(originalPath) {
133
135
  }
134
136
 
135
137
  function extractScriptContent(content) {
136
- const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
137
- return scriptMatch?.[1] ? scriptMatch[1] : null;
138
+ const { descriptor } = parse(content, { pad: false });
139
+ const parts = [];
140
+ if (descriptor.script?.content) parts.push(descriptor.script.content);
141
+ if (descriptor.scriptSetup?.content) parts.push(descriptor.scriptSetup.content);
142
+ return parts.length > 0 ? parts.join("\n") : null;
138
143
  }
139
144
  function removeTypeScriptTypes(scriptContent) {
140
145
  return scriptContent.replace(/\((\w+):[^)]*\)/g, "($1)");
@@ -179,17 +184,10 @@ function findDefineI18nRouteConfig(scriptContent) {
179
184
  const scriptWithoutImports = scriptContent.split("\n").filter((line) => !line.trim().startsWith("import ")).join("\n");
180
185
  const cleanScript = removeTypeScriptTypes(scriptWithoutImports);
181
186
  const safeScript = `
182
- // Mock $defineI18nRoute to prevent errors
183
187
  const $defineI18nRoute = () => {}
184
188
  const defineI18nRoute = () => {}
185
-
186
- // Mock process.env for conditional logic
187
189
  const process = { env: { NODE_ENV: 'development' } }
188
-
189
- // Execute the script content without imports and TypeScript types
190
190
  ${cleanScript}
191
-
192
- // Return the config object
193
191
  return (${cleanConfigStr})
194
192
  `;
195
193
  const configObject = Function(`"use strict";${safeScript}`)();
@@ -207,6 +205,37 @@ function findDefineI18nRouteConfig(scriptContent) {
207
205
  return null;
208
206
  }
209
207
  }
208
+ function normalizeDefineConfig(configObject) {
209
+ if (configObject.locales && typeof configObject.locales === "object" && !Array.isArray(configObject.locales)) {
210
+ const localesObj = configObject.locales;
211
+ const normalizedLocales = [];
212
+ const normalizedLocaleRoutes = {};
213
+ for (const [locale, value] of Object.entries(localesObj)) {
214
+ normalizedLocales.push(locale);
215
+ if (value && typeof value === "object" && "path" in value && typeof value.path === "string") {
216
+ normalizedLocaleRoutes[locale] = value.path;
217
+ }
218
+ }
219
+ return {
220
+ ...configObject,
221
+ locales: normalizedLocales,
222
+ localeRoutes: configObject.localeRoutes || Object.keys(normalizedLocaleRoutes).length > 0 ? { ...configObject.localeRoutes, ...normalizedLocaleRoutes } : void 0
223
+ };
224
+ }
225
+ if (Array.isArray(configObject.locales) && configObject.locales.length > 0 && typeof configObject.locales[0] === "object") {
226
+ const normalizedLocales = configObject.locales.map((item) => {
227
+ if (item && typeof item === "object" && "code" in item) {
228
+ return item.code;
229
+ }
230
+ return String(item);
231
+ });
232
+ return {
233
+ ...configObject,
234
+ locales: normalizedLocales
235
+ };
236
+ }
237
+ return configObject;
238
+ }
210
239
  function extractDefineI18nRouteData(content, _filePath) {
211
240
  try {
212
241
  const scriptContent = extractScriptContent(content);
@@ -217,38 +246,76 @@ function extractDefineI18nRouteData(content, _filePath) {
217
246
  if (!configObject) {
218
247
  return null;
219
248
  }
220
- if (configObject.locales && typeof configObject.locales === "object" && !Array.isArray(configObject.locales)) {
221
- const localesObj = configObject.locales;
222
- const normalizedLocales = [];
223
- const normalizedLocaleRoutes = {};
224
- for (const [locale, value] of Object.entries(localesObj)) {
225
- normalizedLocales.push(locale);
226
- if (value && typeof value === "object" && "path" in value && typeof value.path === "string") {
227
- normalizedLocaleRoutes[locale] = value.path;
228
- }
249
+ return normalizeDefineConfig(configObject);
250
+ } catch {
251
+ return null;
252
+ }
253
+ }
254
+ function pageFilePathToRoutePath(pageFile, rootDir) {
255
+ const relative = pageFile.replace(rootDir, "").replace(/^[/\\]+/, "");
256
+ const withoutPagesPrefix = relative.replace(/^(app[/\\])?pages[/\\]/, "");
257
+ if (withoutPagesPrefix === "index.vue") {
258
+ return "/";
259
+ }
260
+ const raw = withoutPagesPrefix.replace(/[/\\]index\.vue$/, "").replace(/\.vue$/, "").replace(/[/\\]$/, "").replace(/\\/g, "/");
261
+ return raw === "" ? "/" : raw;
262
+ }
263
+
264
+ function resolveRootDir(filePath, rootDirs) {
265
+ const sorted = [...rootDirs].sort((a, b) => b.length - a.length);
266
+ return sorted.find((root) => filePath.startsWith(root));
267
+ }
268
+ function createDefineI18nRoutePlugin(options) {
269
+ const metaByFile = /* @__PURE__ */ new Map();
270
+ const writeMetaFile = () => {
271
+ const entries = [...metaByFile.values()].filter((entry) => entry !== null);
272
+ options.onMetaUpdate?.(entries);
273
+ const metaPath = join(options.buildDir, "i18n-route-meta.json");
274
+ mkdirSync(dirname(metaPath), { recursive: true });
275
+ writeFileSync(metaPath, JSON.stringify(entries, null, 2));
276
+ };
277
+ return createUnplugin(() => ({
278
+ name: "nuxt-i18n-micro-define-route",
279
+ enforce: "pre",
280
+ transformInclude(id) {
281
+ return id.endsWith(".vue") && !id.includes("node_modules");
282
+ },
283
+ transform(code, id) {
284
+ const rootDir = resolveRootDir(id, options.rootDirs);
285
+ if (!rootDir) return null;
286
+ const config = extractDefineI18nRouteData(code);
287
+ if (!config) {
288
+ metaByFile.delete(id);
289
+ } else {
290
+ metaByFile.set(id, {
291
+ routePath: pageFilePathToRoutePath(id, rootDir),
292
+ config
293
+ });
229
294
  }
230
- return {
231
- ...configObject,
232
- locales: normalizedLocales,
233
- localeRoutes: configObject.localeRoutes || Object.keys(normalizedLocaleRoutes).length > 0 ? { ...configObject.localeRoutes, ...normalizedLocaleRoutes } : void 0
234
- };
295
+ writeMetaFile();
296
+ return null;
297
+ },
298
+ buildEnd() {
299
+ writeMetaFile();
235
300
  }
236
- if (Array.isArray(configObject.locales) && configObject.locales.length > 0 && typeof configObject.locales[0] === "object") {
237
- const normalizedLocales = configObject.locales.map((item) => {
238
- if (item && typeof item === "object" && "code" in item) {
239
- return item.code;
240
- }
241
- return String(item);
301
+ }));
302
+ }
303
+ function collectDefineI18nRouteMetaFromFiles(pageFiles, rootDirs) {
304
+ const entries = [];
305
+ for (const pageFile of pageFiles) {
306
+ const rootDir = resolveRootDir(pageFile, rootDirs);
307
+ if (!rootDir) continue;
308
+ try {
309
+ const config = extractDefineI18nRouteData(readFileSync(pageFile, "utf-8"), pageFile);
310
+ if (!config) continue;
311
+ entries.push({
312
+ routePath: pageFilePathToRoutePath(pageFile, rootDir),
313
+ config
242
314
  });
243
- return {
244
- ...configObject,
245
- locales: normalizedLocales
246
- };
315
+ } catch {
247
316
  }
248
- return configObject;
249
- } catch {
250
- return null;
251
317
  }
318
+ return entries;
252
319
  }
253
320
 
254
321
  const DEFAULT_CANONICAL_QUERY_WHITELIST = ["page", "sort", "filter", "search", "q", "query", "tag"];
@@ -320,7 +387,7 @@ const module = defineNuxtModule({
320
387
  },
321
388
  async setup(options, nuxt) {
322
389
  const defaultLocale = process.env.DEFAULT_LOCALE ?? options.defaultLocale ?? "en";
323
- const isSSG = nuxt.options.nitro.static ?? nuxt.options._generate ?? false;
390
+ const isSSG = Boolean(nuxt.options.nitro?.static);
324
391
  const logger = useLogger("nuxt-i18n-micro");
325
392
  if (options.strategy === "no_prefix" && !options.localeCookie) {
326
393
  options.localeCookie = "user-locale";
@@ -365,16 +432,11 @@ const module = defineNuxtModule({
365
432
  const routeLocales = { ...options.routeLocales ?? {} };
366
433
  const globalLocaleRoutes = {};
367
434
  const routeDisableMeta = {};
368
- const pageFiles = await globby(["pages/**/*.vue", "app/pages/**/*.vue"], { cwd: nuxt.options.rootDir });
369
- for (const pageFile of pageFiles) {
370
- const fullPath = join(nuxt.options.rootDir, pageFile);
435
+ const pageGlobs = rootDirs.flatMap((root) => [join(root, "pages/**/*.vue"), join(root, "app/pages/**/*.vue")]);
436
+ const pageFiles = await globby(pageGlobs, { absolute: true });
437
+ for (const { routePath, config } of collectDefineI18nRouteMetaFromFiles(pageFiles, rootDirs)) {
371
438
  try {
372
- const fileContent = readFileSync(fullPath, "utf-8");
373
- const config = extractDefineI18nRouteData(fileContent, fullPath);
374
- if (!config) continue;
375
439
  const { locales: extractedLocales, localeRoutes, disableMeta } = config;
376
- const raw = pageFile.replace(/^(app\/)?pages\//, "").replace(/\/index\.vue$/, "").replace(/\.vue$/, "").replace(/\/$/, "");
377
- const routePath = raw === "" || raw === "index" ? "/" : raw;
378
440
  if (extractedLocales) {
379
441
  if (Array.isArray(extractedLocales)) {
380
442
  routeLocales[routePath] = extractedLocales;
@@ -454,7 +516,6 @@ const module = defineNuxtModule({
454
516
  hashMode: nuxt.options?.router?.options?.hashMode ?? false,
455
517
  apiBaseUrl,
456
518
  apiBaseClientHost,
457
- apiBaseServerHost,
458
519
  isSSG,
459
520
  disablePageLocales: options.disablePageLocales ?? false,
460
521
  canonicalQueryWhitelist: options.canonicalQueryWhitelist ?? DEFAULT_CANONICAL_QUERY_WHITELIST,
@@ -464,6 +525,7 @@ const module = defineNuxtModule({
464
525
  globalLocaleRoutes: mergedGlobalLocaleRoutes,
465
526
  missingWarn: options.missingWarn ?? true,
466
527
  redirects: options.redirects !== false,
528
+ hooks: options.hooks !== false,
467
529
  hmr: options.hmr ?? true,
468
530
  localizedRouteNamePrefix: options.localizedRouteNamePrefix ?? "localized-",
469
531
  routesLocaleLinks: options.routesLocaleLinks ?? {},
@@ -536,7 +598,8 @@ export function createI18nStrategy(router) {
536
598
  routesLocaleLinks: options.routesLocaleLinks ?? {},
537
599
  apiBaseUrl,
538
600
  apiBaseClientHost,
539
- apiBaseServerHost
601
+ apiBaseServerHost,
602
+ serverTranslationPreload: options.serverTranslationPreload ?? false
540
603
  };
541
604
  const privateConfigJson = JSON.stringify(privateConfig);
542
605
  const configTemplate = addTemplate({
@@ -578,10 +641,18 @@ export function getI18nPrivateConfig() { return __privateConfig }
578
641
  }
579
642
  addPlugin({
580
643
  src: resolver.resolve("./runtime/plugins/06.redirect"),
581
- mode: "all",
644
+ mode: "server",
582
645
  name: "i18n-plugin-redirect",
583
646
  order: 10
584
647
  });
648
+ if (options.redirects !== false && options.plugin !== false) {
649
+ addRouteMiddleware({
650
+ name: "i18n-redirect",
651
+ path: resolver.resolve("./runtime/middleware/i18n-redirect.global"),
652
+ global: true
653
+ });
654
+ }
655
+ addServerImportsDir(resolver.resolve("./runtime/server/utils"));
585
656
  if (translationPayloads.serverHandler) {
586
657
  addServerHandler({
587
658
  route: `/${apiBaseUrl}/:page/:locale/data.json`,
@@ -679,22 +750,12 @@ declare module '#i18n-internal/plural' {
679
750
  if (options.disablePageLocales) {
680
751
  nuxt.hook("build:before", () => addDataRoutes([]));
681
752
  }
682
- nuxt.hook("vite:extendConfig", (viteConfig) => {
683
- const resolve2 = viteConfig.resolve ?? {};
684
- viteConfig.resolve = resolve2;
685
- const alias = resolve2.alias || {};
686
- resolve2.alias = Array.isArray(alias) ? [
687
- ...alias,
688
- { find: "#i18n-internal/plural", replacement: pluralTemplate.dst },
689
- { find: "#i18n-internal/strategy", replacement: strategyTemplate.dst },
690
- { find: "#i18n-internal/config", replacement: configTemplate.dst }
691
- ] : {
692
- ...alias,
693
- "#i18n-internal/plural": pluralTemplate.dst,
694
- "#i18n-internal/strategy": strategyTemplate.dst,
695
- "#i18n-internal/config": configTemplate.dst
696
- };
697
- });
753
+ addVitePlugin(
754
+ createDefineI18nRoutePlugin({
755
+ buildDir: nuxt.options.buildDir,
756
+ rootDirs
757
+ }).vite()
758
+ );
698
759
  nuxt.hook("nitro:config", (nitroConfig) => {
699
760
  nitroConfig.alias = nitroConfig.alias || {};
700
761
  nitroConfig.alias["#i18n-internal/plural"] = pluralTemplate.dst;
@@ -707,21 +768,20 @@ declare module '#i18n-internal/plural' {
707
768
  dir: translationAssetsDir
708
769
  });
709
770
  }
710
- if (nitroConfig.imports) {
711
- nitroConfig.imports.presets = nitroConfig.imports.presets || [];
712
- nitroConfig.imports.presets.push({
713
- from: resolver.resolve("./runtime/server/utils/translation-server-middleware"),
714
- imports: ["useTranslationServerMiddleware"]
715
- });
716
- nitroConfig.imports.presets.push({
717
- from: resolver.resolve("./runtime/server/utils/locale-server-middleware"),
718
- imports: ["useLocaleServerMiddleware"]
719
- });
720
- }
771
+ nitroConfig.routeRules = nitroConfig.routeRules || {};
772
+ nitroConfig.routeRules[`/${apiBaseUrl}/**`] = {
773
+ ...nitroConfig.routeRules[`/${apiBaseUrl}/**`] || {},
774
+ cors: true,
775
+ ...nuxt.options.dev ? {} : {
776
+ cache: {
777
+ maxAge: 60,
778
+ swr: true
779
+ }
780
+ }
781
+ };
721
782
  const routeRules = nuxt.options.routeRules || {};
722
783
  const strategy = options.strategy;
723
784
  if (routeRules && Object.keys(routeRules).length && !isNoPrefixStrategy(strategy)) {
724
- nitroConfig.routeRules = nitroConfig.routeRules || {};
725
785
  for (const [originalPath, ruleValue] of Object.entries(routeRules)) {
726
786
  if (!shouldLocalizeRouteRulePath(originalPath)) continue;
727
787
  routeGenerator.locales.forEach((localeObj) => {
@@ -743,6 +803,15 @@ declare module '#i18n-internal/plural' {
743
803
  });
744
804
  }
745
805
  }
806
+ nitroConfig.plugins = nitroConfig.plugins || [];
807
+ if (nuxt.options.dev && (options.hmr ?? true)) {
808
+ nitroConfig.plugins.push(resolver.resolve("./runtime/server/plugins/watcher.dev"));
809
+ }
810
+ nitroConfig.handlers = nitroConfig.handlers || [];
811
+ nitroConfig.handlers.unshift({
812
+ middleware: true,
813
+ handler: resolver.resolve("./runtime/server/middleware/i18n.global")
814
+ });
746
815
  });
747
816
  nuxt.hook("nitro:build:public-assets", (nitro) => {
748
817
  const isProd = nuxt.options.dev === false;
@@ -760,17 +829,6 @@ declare module '#i18n-internal/plural' {
760
829
  }
761
830
  }
762
831
  });
763
- nuxt.hook("nitro:config", (nitroConfig) => {
764
- nitroConfig.plugins = nitroConfig.plugins || [];
765
- if (nuxt.options.dev && (options.hmr ?? true)) {
766
- nitroConfig.plugins.push(resolver.resolve("./runtime/server/plugins/watcher.dev"));
767
- }
768
- nitroConfig.handlers = nitroConfig.handlers || [];
769
- nitroConfig.handlers.unshift({
770
- middleware: true,
771
- handler: resolver.resolve("./runtime/server/middleware/i18n.global")
772
- });
773
- });
774
832
  nuxt.hook("prerender:routes", async (prerenderRoutes) => {
775
833
  if (isNoPrefixStrategy(options.strategy)) {
776
834
  return;
@@ -1,9 +1,6 @@
1
1
  <template>
2
2
  <div :class="['i18n-group', groupClass]">
3
- <slot
4
- :prefix="prefix"
5
- :t="translate"
6
- />
3
+ <slot :prefix="prefix" :t="translate" />
7
4
  </div>
8
5
  </template>
9
6
 
@@ -1,19 +1,9 @@
1
1
  <template>
2
- <a
3
- v-if="isExternalLink"
4
- :href="externalHref"
5
- :style="computedStyle"
6
- target="_blank"
7
- rel="noopener noreferrer"
8
- >
2
+ <a v-if="isExternalLink" :href="externalHref" :style="computedStyle" target="_blank" rel="noopener noreferrer">
9
3
  <slot />
10
4
  </a>
11
5
 
12
- <NuxtLink
13
- v-else
14
- :to="$localePath(to)"
15
- :style="computedStyle"
16
- >
6
+ <NuxtLink v-else :to="$localePath(to)" :style="computedStyle">
17
7
  <slot />
18
8
  </NuxtLink>
19
9
  </template>
@@ -2,11 +2,7 @@
2
2
  <div :style="[wrapperStyle, customWrapperStyle]">
3
3
  <slot name="before-button" />
4
4
 
5
- <button
6
- class="language-switcher"
7
- :style="[buttonStyle, customButtonStyle]"
8
- @click="toggleDropdown"
9
- >
5
+ <button class="language-switcher" :style="[buttonStyle, customButtonStyle]" @click="toggleDropdown">
10
6
  <slot name="before-selected-locale" />
11
7
  {{ currentLocaleLabel }}
12
8
  <slot name="after-selected-locale" />
@@ -15,21 +11,11 @@
15
11
 
16
12
  <slot name="before-dropdown" />
17
13
 
18
- <ul
19
- v-show="dropdownOpen"
20
- :style="[dropdownStyle, customDropdownStyle]"
21
- >
14
+ <ul v-show="dropdownOpen" :style="[dropdownStyle, customDropdownStyle]">
22
15
  <slot name="before-dropdown-items" />
23
16
 
24
- <li
25
- v-for="locale in locales"
26
- :key="locale.code"
27
- :style="[itemStyle, customItemStyle]"
28
- >
29
- <slot
30
- name="before-item"
31
- :locale="locale"
32
- />
17
+ <li v-for="locale in locales" :key="locale.code" :style="[itemStyle, customItemStyle]">
18
+ <slot name="before-item" :locale="locale" />
33
19
 
34
20
  <NuxtLink
35
21
  :class="`switcher-locale-${locale.code}`"
@@ -43,21 +29,12 @@
43
29
  :hreflang="locale.iso || locale.code"
44
30
  @click="switchLocale(locale.code)"
45
31
  >
46
- <slot
47
- name="before-link-content"
48
- :locale="locale"
49
- />
32
+ <slot name="before-link-content" :locale="locale" />
50
33
  {{ localeLabel(locale) }}
51
- <slot
52
- name="after-link-content"
53
- :locale="locale"
54
- />
34
+ <slot name="after-link-content" :locale="locale" />
55
35
  </NuxtLink>
56
36
 
57
- <slot
58
- name="after-item"
59
- :locale="locale"
60
- />
37
+ <slot name="after-item" :locale="locale" />
61
38
  </li>
62
39
 
63
40
  <slot name="after-dropdown-items" />
@@ -1,4 +1,4 @@
1
- import { useNuxtApp } from "#imports";
1
+ import { useNuxtApp } from "#app";
2
2
  export function useI18n() {
3
3
  const nuxtApp = useNuxtApp();
4
4
  const injections = {
@@ -1,9 +1,8 @@
1
1
  import { getEnabledLocaleCodes } from "@i18n-micro/utils/active-locales";
2
2
  import { getHashCookieName, getLocaleCookieName, getLocaleCookieOptions } from "@i18n-micro/utils/cookie";
3
3
  import { resolveI18nConfigWithRuntimeOverrides } from "@i18n-micro/utils/runtime-config";
4
- import { useNuxtApp, useState } from "#app";
4
+ import { useCookie, useNuxtApp, useState } from "#app";
5
5
  import { getI18nConfig } from "#build/i18n.strategy.mjs";
6
- import { useCookie } from "#imports";
7
6
  export function useI18nLocale() {
8
7
  const nuxtApp = useNuxtApp();
9
8
  const i18nConfig = resolveI18nConfigWithRuntimeOverrides(nuxtApp.$getI18nConfig?.() ?? getI18nConfig());
@@ -35,7 +34,7 @@ export function useI18nLocale() {
35
34
  return localeState.value ?? localeCookie.value ?? serverLocale ?? null;
36
35
  };
37
36
  const getEffectiveLocale = (route, getLocaleFromRoute) => {
38
- if (i18nConfig.hashMode && localeState.value != null) return localeState.value;
37
+ if (i18nConfig.hashMode && localeState.value !== null && localeState.value !== void 0) return localeState.value;
39
38
  return getLocaleFromRoute(route);
40
39
  };
41
40
  const resolveInitialLocale = (options) => {
@@ -2,7 +2,7 @@ import { isNoPrefixStrategy } from "@i18n-micro/core";
2
2
  import { findAllowedLocalesForRoute } from "@i18n-micro/utils/route";
3
3
  import { joinURL, parseURL, withQuery } from "ufo";
4
4
  import { ref, unref, watch } from "vue";
5
- import { useNuxtApp, useRoute } from "#imports";
5
+ import { useNuxtApp, useRoute } from "#app";
6
6
  export const useLocaleHead = ({
7
7
  addDirAttribute = true,
8
8
  identifierAttribute = "id",
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Client-side locale redirect middleware.
3
+ * Replaces setTimeout-based afterEach redirects from the legacy redirect plugin.
4
+ */
5
+ declare const _default: import("nuxt/app").RouteMiddleware;
6
+ export default _default;
@@ -0,0 +1,43 @@
1
+ import { getEnabledLocaleCodes } from "@i18n-micro/utils/active-locales";
2
+ import { defineNuxtRouteMiddleware, navigateTo, useNuxtApp } from "#imports";
3
+ import { useI18nLocale } from "../composables/useI18nLocale.js";
4
+ function redirectTo(path, to) {
5
+ return navigateTo({ path, query: to.query, hash: to.hash }, { replace: true, redirectCode: 302 });
6
+ }
7
+ export default defineNuxtRouteMiddleware((to, from) => {
8
+ if (import.meta.server) return;
9
+ const nuxtApp = useNuxtApp();
10
+ const getRuntimeConfig = nuxtApp.$getI18nConfig;
11
+ const i18nStrategy = nuxtApp.$i18nStrategy;
12
+ if (typeof getRuntimeConfig !== "function" || !i18nStrategy) return;
13
+ const i18nConfig = getRuntimeConfig();
14
+ if (i18nConfig.redirects === false) return;
15
+ if (to.path === from.path) return;
16
+ const validLocales = getEnabledLocaleCodes(i18nConfig.locales);
17
+ const defaultLocale = i18nConfig.defaultLocale || "en";
18
+ const autoDetectPath = i18nConfig.autoDetectPath || "/";
19
+ const { getPreferredLocale } = useI18nLocale();
20
+ const routeLocale = i18nStrategy.getCurrentLocale(to, getPreferredLocale());
21
+ let preferredLocale = routeLocale || getPreferredLocale() || defaultLocale;
22
+ const path = to.path || "/";
23
+ const pathSegments = path.replace(/^\//, "").split("/").filter(Boolean);
24
+ const firstSegment = pathSegments[0];
25
+ const hasLocalePrefix = Boolean(firstSegment && validLocales.includes(firstSegment));
26
+ if (autoDetectPath === "*" && !hasLocalePrefix) {
27
+ preferredLocale = defaultLocale;
28
+ }
29
+ if (autoDetectPath === "*" && hasLocalePrefix && firstSegment !== preferredLocale) {
30
+ const rest = pathSegments.slice(1).join("/");
31
+ let targetPath;
32
+ if (preferredLocale === defaultLocale && i18nConfig.strategy === "prefix_except_default") {
33
+ targetPath = rest ? `/${rest}` : "/";
34
+ } else {
35
+ targetPath = rest ? `/${preferredLocale}/${rest}` : `/${preferredLocale}`;
36
+ }
37
+ return redirectTo(targetPath, to);
38
+ }
39
+ const redirectPath = i18nStrategy.getClientRedirect(path, preferredLocale);
40
+ if (redirectPath) {
41
+ return redirectTo(redirectPath, to);
42
+ }
43
+ });