nuxt-i18n-micro 3.30.2 → 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/f020fec8-462b-480b-82d8-6b0cb65617e8.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,10 +1,11 @@
1
1
  import { isNoPrefixStrategy } from "@i18n-micro/core";
2
- import { resolveHreflangAlternates } from "@i18n-micro/utils/resolve-hreflang";
3
- import { findAllowedLocalesForRoute, isMetaDisabledForRoute } from "@i18n-micro/utils/route";
4
- import { resolveOgLocale, warnUnresolvedOgLocale } from "@i18n-micro/utils/resolve-og-locale";
5
- import { joinURL, parseURL, withQuery } from "ufo";
2
+ import { buildLocaleHead, filterQuery } from "@i18n-micro/utils/locale-head";
3
+ import { joinURL } from "ufo";
6
4
  import { ref, unref, watch } from "vue";
7
- import { useNuxtApp, useRoute, useState } from "#app";
5
+ import { useError } from "#app/composables/error";
6
+ import { useRoute } from "#app/composables/router";
7
+ import { useState } from "#app/composables/state";
8
+ import { useNuxtApp } from "#app/nuxt";
8
9
  import { applyTrailingSlash } from "../utils/trailing-slash.js";
9
10
  export const useLocaleHead = ({
10
11
  addDirAttribute = true,
@@ -15,181 +16,60 @@ export const useLocaleHead = ({
15
16
  } = {}) => {
16
17
  const nuxtApp = useNuxtApp();
17
18
  const route = useRoute();
18
- const metaObject = ref({
19
- htmlAttrs: {},
20
- link: [],
21
- meta: []
22
- });
23
- function filterQuery(fullPath, whitelist) {
24
- const { pathname, search } = parseURL(fullPath);
25
- const params = new URLSearchParams(search);
26
- const filtered = {};
27
- for (const key of whitelist) {
28
- if (params.has(key)) {
29
- filtered[key] = params.get(key);
30
- }
31
- }
32
- return withQuery(pathname, filtered);
33
- }
34
- function filterLocalizedHref(pathOrUrl, whitelist) {
35
- if (!pathOrUrl) return "";
36
- const parsed = parseURL(pathOrUrl);
37
- const filteredPath = filterQuery(pathOrUrl, whitelist);
38
- if (parsed.protocol && parsed.host) {
39
- return `${parsed.protocol}//${parsed.host}${filteredPath}`;
40
- }
41
- return filteredPath;
42
- }
43
- function clearMeta() {
44
- metaObject.value = { htmlAttrs: {}, link: [], meta: [] };
45
- }
46
- function updateMeta() {
47
- if (route.matched.length === 0 || route.matched.some((record) => record.name === "custom-fallback-route")) {
48
- clearMeta();
49
- return;
50
- }
19
+ const error = useError();
20
+ const empty = () => ({ htmlAttrs: {}, link: [], meta: [] });
21
+ const metaObject = ref(empty());
22
+ function build() {
51
23
  const i18nConfig = nuxtApp.$getI18nConfig();
52
- const { canonicalQueryWhitelist, routeLocales, localizedRouteNamePrefix } = i18nConfig;
53
- const strategy = i18nConfig.strategy;
54
- const localizedRouteNamePrefixResolved = localizedRouteNamePrefix || "localized-";
55
- const { $getLocales, $getLocale, $switchLocalePath } = nuxtApp;
56
- const allLocales = ($getLocales ? unref($getLocales()) : i18nConfig.locales) ?? [];
57
- const firstSegment = route.path.replace(/^\//, "").split("/").filter(Boolean)[0];
58
- const fallbackLocale = allLocales.find((loc) => loc.code === firstSegment)?.code || i18nConfig.defaultLocale || "en";
59
- const locale = ($getLocale ? unref($getLocale()) : fallbackLocale) || fallbackLocale;
24
+ const { canonicalQueryWhitelist, strategy, trailingSlash } = i18nConfig;
25
+ const { $getLocale, $switchLocalePath } = nuxtApp;
26
+ const i18nStrategy = nuxtApp.$i18nStrategy;
27
+ const allLocales = nuxtApp.$getLocales();
28
+ const locale = $getLocale() || i18nConfig.defaultLocale;
60
29
  const currentLocale = allLocales.find((loc) => loc.code === locale);
61
- if (!currentLocale || isMetaDisabledForRoute(route, i18nConfig.routeDisableMeta, locale, localizedRouteNamePrefixResolved)) {
62
- clearMeta();
63
- return;
64
- }
65
- const switchLocalePath = $switchLocalePath || (() => "");
66
- const routeName = (route.name ?? "").toString();
67
- const currentRouteLocales = findAllowedLocalesForRoute(route, routeLocales, localizedRouteNamePrefixResolved);
30
+ if (!currentLocale) return empty();
31
+ const metaDisabled = i18nStrategy.isMetaDisabled(route, locale);
32
+ const pageLocales = i18nStrategy.pageLocales(route);
68
33
  const enabledLocales = allLocales.filter((loc) => !loc.disabled);
69
- const locales = currentRouteLocales ? enabledLocales.filter((loc) => currentRouteLocales.includes(loc.code)) : enabledLocales;
70
- const currentIso = currentLocale.iso || locale;
71
- const currentOg = resolveOgLocale(currentLocale);
72
- const missingWarn = i18nConfig.missingWarn ?? true;
73
- const currentDir = currentLocale.dir || "auto";
74
- let fullPath = unref(route.fullPath);
75
- if (!fullPath.startsWith("/")) {
76
- fullPath = `/${fullPath}`;
77
- }
78
- const whitelist = canonicalQueryWhitelist ?? [];
79
- const trailingSlash = i18nConfig.trailingSlash;
34
+ const locales = pageLocales ? enabledLocales.filter((loc) => pageLocales.includes(loc.code)) : [];
80
35
  const toSeoHref = (pathOrUrl) => {
81
36
  if (!pathOrUrl) return "";
82
- let href;
83
- if (pathOrUrl.startsWith("http://") || pathOrUrl.startsWith("https://")) {
84
- href = filterLocalizedHref(pathOrUrl, whitelist);
85
- } else {
86
- const filteredPath = filterLocalizedHref(pathOrUrl, whitelist);
87
- href = joinURL(unref(baseUrl), filteredPath.startsWith("/") ? filteredPath : `/${filteredPath}`);
88
- }
89
- return applyTrailingSlash(href, trailingSlash);
90
- };
91
- const currentSwitchedPath = switchLocalePath(locale);
92
- let ogUrl;
93
- if (currentSwitchedPath) {
94
- ogUrl = toSeoHref(currentSwitchedPath);
95
- } else {
96
- const matchedLocale = [...locales].sort((a, b) => b.code.length - a.code.length).find((localeItem) => fullPath.startsWith(`/${localeItem.code}`));
97
- let localizedPath = fullPath;
98
- if (routeName.startsWith(localizedRouteNamePrefixResolved) && matchedLocale) {
99
- localizedPath = fullPath.slice(matchedLocale.code.length + 1);
100
- ogUrl = applyTrailingSlash(joinURL(unref(baseUrl), locale, filterQuery(localizedPath, whitelist)), trailingSlash);
101
- } else {
102
- ogUrl = applyTrailingSlash(joinURL(unref(baseUrl), filterQuery(fullPath, whitelist)), trailingSlash);
103
- }
104
- }
105
- const htmlAttrs = {
106
- lang: currentIso,
107
- ...addDirAttribute ? { dir: currentDir } : {}
37
+ const filtered = filterQuery(pathOrUrl, canonicalQueryWhitelist);
38
+ const absolute = filtered.startsWith("http://") || filtered.startsWith("https://");
39
+ return applyTrailingSlash(absolute ? filtered : joinURL(unref(baseUrl), filtered.startsWith("/") ? filtered : `/${filtered}`), trailingSlash);
108
40
  };
109
- if (!addSeoAttributes) {
110
- metaObject.value = { htmlAttrs, link: [], meta: [] };
111
- return;
112
- }
113
- const localesForSeo = locales.filter((loc) => loc.seo !== false);
114
- if (!currentOg) {
115
- warnUnresolvedOgLocale(currentLocale, { missingWarn, tag: "og:locale" });
116
- }
117
- const ogLocaleMeta = currentOg ? {
118
- [identifierAttribute]: "i18n-og",
119
- property: "og:locale",
120
- content: currentOg
121
- } : null;
122
- const ogUrlMeta = {
123
- [identifierAttribute]: "i18n-og-url",
124
- property: "og:url",
125
- content: ogUrl
126
- };
127
- const alternateOgLocalesMeta = localesForSeo.filter((loc) => loc.code !== locale).map((loc) => {
128
- const ogAlt = resolveOgLocale(loc);
129
- if (!ogAlt) {
130
- warnUnresolvedOgLocale(loc, { missingWarn, tag: "og:locale:alternate" });
131
- return null;
132
- }
133
- return {
134
- [identifierAttribute]: `i18n-og-alt-${ogAlt}`,
135
- property: "og:locale:alternate",
136
- content: ogAlt
137
- };
138
- }).filter((meta) => meta !== null);
139
- const canonicalLink = {
140
- [identifierAttribute]: "i18n-can",
141
- rel: "canonical",
142
- href: ogUrl
143
- };
144
- const defaultLocale = i18nConfig.defaultLocale || "en";
41
+ const hrefOf = (code) => toSeoHref($switchLocalePath(code));
42
+ const defaultLocale = i18nConfig.linkDefaultLocale ?? i18nConfig.defaultLocale;
145
43
  const defaultLocaleObj = allLocales.find((loc) => loc.code === defaultLocale);
146
- const alternateLinks = isNoPrefixStrategy(strategy) ? [] : (() => {
147
- const hrefByCode = /* @__PURE__ */ new Map();
148
- for (const loc of localesForSeo) {
149
- const switchedPath = switchLocalePath(loc.code);
150
- if (!switchedPath) continue;
151
- hrefByCode.set(String(loc.code), toSeoHref(switchedPath));
152
- }
153
- return resolveHreflangAlternates(localesForSeo, {
154
- hreflangBaseLanguage: i18nConfig.hreflangBaseLanguage ?? false
155
- }).flatMap(({ hreflang, localeCode }) => {
156
- const href = hrefByCode.get(localeCode);
157
- if (!href) return [];
158
- return [
159
- {
160
- [identifierAttribute]: `i18n-alternate-${hreflang}`,
161
- rel: "alternate",
162
- href,
163
- hreflang
164
- }
165
- ];
166
- });
167
- })();
168
- let xDefaultLink = null;
169
- if (!isNoPrefixStrategy(strategy) && defaultLocaleObj?.seo !== false) {
170
- const defaultSwitchedPath = switchLocalePath(defaultLocale);
171
- if (defaultSwitchedPath) {
172
- xDefaultLink = {
173
- [identifierAttribute]: "i18n-xd",
174
- rel: "alternate",
175
- href: toSeoHref(defaultSwitchedPath),
176
- hreflang: "x-default"
177
- };
178
- }
179
- }
180
- metaObject.value = {
181
- htmlAttrs,
182
- meta: [...ogLocaleMeta ? [ogLocaleMeta] : [], ogUrlMeta, ...alternateOgLocalesMeta],
183
- link: [canonicalLink, ...alternateLinks, ...xDefaultLink ? [xDefaultLink] : []]
184
- };
44
+ const noPrefix = isNoPrefixStrategy(strategy);
45
+ const hasXDefault = !noPrefix && defaultLocaleObj?.seo !== false && pageLocales?.includes(defaultLocale);
46
+ return buildLocaleHead({
47
+ locale: currentLocale,
48
+ // omit `seo: false`
49
+ seoLocales: locales.filter((loc) => loc.seo !== false),
50
+ hrefOf,
51
+ // canonical / og:url share `$switchLocalePath` with hreflang (incl. NuxtLink trailingSlash)
52
+ canonical: toSeoHref($switchLocalePath(locale) || route.fullPath),
53
+ xDefault: hasXDefault ? hrefOf(defaultLocale) : void 0,
54
+ alternates: !noPrefix,
55
+ addDirAttribute,
56
+ // 404 routes and disableMeta keep the language, but must not emit SEO tags: a 404 the module answers has a matched record
57
+ addSeoAttributes: addSeoAttributes && !metaDisabled && route.matched.length > 0 && !error.value,
58
+ identifierAttribute,
59
+ hreflangBaseLanguage: i18nConfig.hreflangBaseLanguage,
60
+ missingWarn: i18nConfig.missingWarn
61
+ });
62
+ }
63
+ function updateMeta() {
64
+ metaObject.value = build();
185
65
  }
186
66
  if (autoUpdate) {
187
67
  const localeState = useState("i18n-locale", () => null);
188
68
  watch(
189
- () => [route.fullPath, route.name, route.matched.length, localeState.value],
69
+ () => [route.fullPath, route.name, route.matched.length, localeState.value, error.value],
190
70
  () => updateMeta(),
191
71
  { immediate: true }
192
72
  );
193
73
  }
194
- return { metaObject, updateMeta };
74
+ return { metaObject, updateMeta, build };
195
75
  };
@@ -1,10 +1,10 @@
1
- import type { ModuleOptionsExtend } from '@i18n-micro/types';
1
+ import type { ResolvedI18nConfig } from '@i18n-micro/types';
2
2
  import type { Ref } from 'vue';
3
3
  import type { NuxtI18n } from '../utils/nuxt-i18n.js';
4
4
  export declare const I18N_DEVTOOLS_BRIDGE_KEY = "i18n-micro-devtools-bridge";
5
5
  export interface I18nDevtoolsBridge {
6
6
  i18n: NuxtI18n;
7
- i18nConfig: ModuleOptionsExtend;
7
+ i18nConfig: ResolvedI18nConfig;
8
8
  localeState: Ref<string | null>;
9
9
  }
10
10
  export declare function setI18nDevtoolsBridge(value: I18nDevtoolsBridge | null): void;
@@ -1,10 +1,10 @@
1
- import type { ModuleOptionsExtend } from '@i18n-micro/types';
1
+ import type { ResolvedI18nConfig } from '@i18n-micro/types';
2
2
  import type { App } from 'vue';
3
3
  import type { I18nContextChangeReason, NuxtI18n } from '../utils/nuxt-i18n.js';
4
4
  export interface VueI18nDevtoolsContext {
5
5
  app: App;
6
6
  i18n: NuxtI18n;
7
- i18nConfig: ModuleOptionsExtend;
7
+ i18nConfig: ResolvedI18nConfig;
8
8
  onContextChange: (listener: (reason: I18nContextChangeReason) => void) => () => void;
9
9
  onMissingKey: (listener: (locale: string, key: string, routeName: string) => void) => () => void;
10
10
  }
@@ -22,7 +22,7 @@ function toInspectorState(snapshot) {
22
22
  }
23
23
  function createSnapshot(i18n, i18nConfig) {
24
24
  return {
25
- locale: i18n.getCurrentLocale(),
25
+ locale: i18n.getLocale(),
26
26
  routeName: i18n.getCurrentRouteName() || "index",
27
27
  strategy: i18nConfig.strategy,
28
28
  defaultLocale: i18nConfig.defaultLocale,
@@ -61,7 +61,7 @@ function buildNodeState(i18n, i18nConfig, nodeId) {
61
61
  { key: "code", value: locale },
62
62
  { key: "displayName", value: configured?.displayName ?? locale },
63
63
  { key: "disabled", value: Boolean(configured?.disabled) },
64
- { key: "isActive", value: i18n.getCurrentLocale() === locale },
64
+ { key: "isActive", value: i18n.getLocale() === locale },
65
65
  { key: "loadedChunks", value: chunks.length }
66
66
  ],
67
67
  Chunks: chunks.length ? chunks : [{ key: "\u2014", value: "No translation chunks loaded for this locale yet" }]
@@ -86,7 +86,7 @@ function buildNodeState(i18n, i18nConfig, nodeId) {
86
86
  { key: "keys", value: countTranslationKeys(chunk) },
87
87
  {
88
88
  key: "isActive",
89
- value: i18n.getCurrentLocale() === parsed.locale && (i18n.getCurrentRouteName() || "index") === routeName
89
+ value: i18n.getLocale() === parsed.locale && (i18n.getCurrentRouteName() || "index") === routeName
90
90
  }
91
91
  ],
92
92
  Translations: entries.length ? entries : [{ key: "\u2014", value: "Chunk is loaded but empty" }]
@@ -96,7 +96,7 @@ function buildNodeState(i18n, i18nConfig, nodeId) {
96
96
  const routeName = parsed.routeName || "index";
97
97
  const segments = parsed.segments ?? [];
98
98
  const chunk = i18n.storage.translations.get(`${parsed.locale}:${routeName}`);
99
- const isActive = i18n.getCurrentLocale() === parsed.locale && (i18n.getCurrentRouteName() || "index") === routeName;
99
+ const isActive = i18n.getLocale() === parsed.locale && (i18n.getCurrentRouteName() || "index") === routeName;
100
100
  const source = isActive ? i18n.resolveTranslations() : chunk ?? {};
101
101
  const value = segments.length ? getByInspectorPath(source, segments) : source;
102
102
  if (isPlainObject(value)) {
@@ -186,7 +186,7 @@ export async function setupVueI18nDevtools(ctx) {
186
186
  nodeId: "root",
187
187
  storage: ctx.i18n.storage,
188
188
  configuredLocales: ctx.i18nConfig.locales ?? [],
189
- activeLocale: ctx.i18n.getCurrentLocale(),
189
+ activeLocale: ctx.i18n.getLocale(),
190
190
  activeRouteName: ctx.i18n.getCurrentRouteName() || "index",
191
191
  activeTranslations: ctx.i18n.resolveTranslations()
192
192
  });
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Client-side locale redirect middleware.
3
- * Replaces setTimeout-based afterEach redirects from the legacy redirect plugin.
2
+ * Client-side locale redirects (SPA navigations). The decision is the strategy's
3
+ * (`getClientRedirect`); preferences are read from the locale state and the cookie the main
4
+ * plugin keeps, without creating cookie refs per navigation.
4
5
  */
5
6
  declare const _default: import("nuxt/app").RouteMiddleware;
6
7
  export default _default;
@@ -1,50 +1,39 @@
1
1
  import { getEnabledLocaleCodes } from "@i18n-micro/utils/active-locales";
2
2
  import { isInternalPath } from "@i18n-micro/utils/app-path";
3
- import { shouldAttemptLocaleRedirect } from "@i18n-micro/utils/auto-detect-path";
4
- import { defineNuxtRouteMiddleware, navigateTo, useNuxtApp } from "#imports";
5
- import { useI18nLocale } from "../composables/useI18nLocale.js";
6
- function redirectTo(path, to) {
7
- return navigateTo({ path, query: to.query, hash: to.hash }, { replace: true, redirectCode: 302 });
3
+ import { decodeCookieValue, getPreferenceCookieName } from "@i18n-micro/utils/cookie";
4
+ import { redirectLocale } from "@i18n-micro/utils/resolve-locale";
5
+ import { applyTrailingSlash } from "../utils/trailing-slash.js";
6
+ import { defineNuxtRouteMiddleware, navigateTo, useNuxtApp, useState } from "#imports";
7
+ function readCookie(name) {
8
+ if (!name) return null;
9
+ const prefix = `${encodeURIComponent(name)}=`;
10
+ const entry = document.cookie.split("; ").find((part) => part.startsWith(prefix));
11
+ return entry ? decodeCookieValue(entry.slice(prefix.length)) : null;
8
12
  }
9
13
  export default defineNuxtRouteMiddleware((to, from) => {
10
14
  if (import.meta.server) return;
11
15
  const nuxtApp = useNuxtApp();
12
- const getRuntimeConfig = nuxtApp.$getI18nConfig;
13
- const i18nStrategy = nuxtApp.$i18nStrategy;
14
- if (typeof getRuntimeConfig !== "function" || !i18nStrategy) return;
16
+ const { $getI18nConfig: getRuntimeConfig, $i18nStrategy: i18nStrategy } = nuxtApp;
15
17
  const i18nConfig = getRuntimeConfig();
16
- if (i18nConfig.redirects === false) return;
17
- if (to.path === from.path) return;
18
- if (isInternalPath(to.path, i18nConfig.excludePatterns)) return;
19
- const validLocales = getEnabledLocaleCodes(i18nConfig.locales);
20
- const defaultLocale = i18nConfig.defaultLocale || "en";
21
- const autoDetectPath = i18nConfig.autoDetectPath || "/";
22
- const { getPreferredLocale } = useI18nLocale();
23
- const routeLocale = i18nStrategy.getCurrentLocale(to, getPreferredLocale());
24
- let preferredLocale = routeLocale || getPreferredLocale() || defaultLocale;
18
+ const samePage = to.path === from.path && nuxtApp.payload.serverRendered;
19
+ if (i18nConfig.redirects === false || samePage || isInternalPath(to.path, i18nConfig.excludePatterns, i18nConfig.apiBaseUrl)) return;
20
+ const defaultLocale = i18nConfig.defaultLocale;
21
+ const autoDetectPath = i18nConfig.autoDetectPath;
22
+ const anyPath = autoDetectPath === "*";
25
23
  const path = to.path || "/";
26
- const pathSegments = path.replace(/^\//, "").split("/").filter(Boolean);
27
- const firstSegment = pathSegments[0];
28
- const hasLocalePrefix = Boolean(firstSegment && validLocales.includes(firstSegment));
29
- const allowPreferenceRedirect = shouldAttemptLocaleRedirect(path, { autoDetectPath, hasLocalePrefix });
30
- if (!hasLocalePrefix && !allowPreferenceRedirect) {
31
- preferredLocale = defaultLocale;
32
- }
33
- if (autoDetectPath === "*" && !hasLocalePrefix) {
34
- preferredLocale = defaultLocale;
35
- }
36
- if (autoDetectPath === "*" && hasLocalePrefix && firstSegment !== preferredLocale) {
37
- const rest = pathSegments.slice(1).join("/");
38
- let targetPath;
39
- if (preferredLocale === defaultLocale && i18nConfig.strategy === "prefix_except_default") {
40
- targetPath = rest ? `/${rest}` : "/";
41
- } else {
42
- targetPath = rest ? `/${preferredLocale}/${rest}` : `/${preferredLocale}`;
43
- }
44
- return redirectTo(targetPath, to);
45
- }
46
- const redirectPath = i18nStrategy.getClientRedirect(path, preferredLocale);
47
- if (redirectPath) {
48
- return redirectTo(redirectPath, to);
49
- }
24
+ const urlLocale = i18nStrategy.getLocaleFromPath(path);
25
+ if (urlLocale && !anyPath) return;
26
+ const preference = redirectLocale({
27
+ path,
28
+ urlLocale,
29
+ autoDetectPath,
30
+ alwaysPrefixed: i18nConfig.strategy === "prefix",
31
+ defaultLocale,
32
+ validLocales: getEnabledLocaleCodes(i18nConfig.locales),
33
+ state: useState("i18n-locale", () => null).value,
34
+ cookie: readCookie(getPreferenceCookieName(i18nConfig))
35
+ });
36
+ const preferred = i18nStrategy.getCurrentLocale(to, preference);
37
+ const target = i18nStrategy.getClientRedirect(path, preferred, { switchPrefixed: anyPath });
38
+ if (target) return navigateTo({ path: applyTrailingSlash(target, i18nConfig.trailingSlash), query: to.query, hash: to.hash }, { redirectCode: 302 });
50
39
  });