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.
- package/README.md +16 -16
- package/dist/client/200.html +1 -1
- package/dist/client/404.html +1 -1
- package/dist/client/_nuxt/{D6byAye8.js → BA2bGsrZ.js} +1 -1
- package/dist/client/_nuxt/{BNXusRXY.js → C_HK2snF.js} +1 -1
- package/dist/client/_nuxt/{BCTbvRLO.js → Dx6dmpFi.js} +1 -1
- package/dist/client/_nuxt/SDQMalAT.js +14 -0
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/5a55317c-1ad8-41ca-a7f6-2fe58e16b2f6.json +1 -0
- package/dist/client/index.html +1 -1
- package/dist/module.d.mts +1 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +197 -231
- package/dist/runtime/components/i18n-group.vue +1 -4
- package/dist/runtime/components/i18n-link.vue +2 -12
- package/dist/runtime/components/i18n-switcher.vue +7 -30
- package/dist/runtime/composables/useI18n.js +1 -1
- package/dist/runtime/composables/useI18nLocale.js +5 -6
- package/dist/runtime/composables/useLocaleHead.js +2 -2
- package/dist/runtime/middleware/i18n-redirect.global.d.ts +6 -0
- package/dist/runtime/middleware/i18n-redirect.global.js +43 -0
- package/dist/runtime/plugins/01.plugin.d.ts +21 -21
- package/dist/runtime/plugins/01.plugin.js +69 -313
- package/dist/runtime/plugins/02.meta.js +2 -2
- package/dist/runtime/plugins/05.hooks.js +32 -32
- package/dist/runtime/plugins/06.redirect.d.ts +1 -0
- package/dist/runtime/plugins/06.redirect.js +15 -86
- package/dist/runtime/server/middleware/i18n.global.js +17 -5
- package/dist/runtime/server/plugins/watcher.dev.js +36 -66
- package/dist/runtime/server/routes/i18n.js +2 -2
- package/dist/runtime/server/utils/locale-detector.d.ts +9 -12
- package/dist/runtime/server/utils/locale-detector.js +25 -18
- package/dist/runtime/server/utils/locale-server-middleware.js +10 -8
- package/dist/runtime/server/utils/server-loader.d.ts +3 -3
- package/dist/runtime/server/utils/server-loader.js +40 -15
- package/dist/runtime/server/utils/translation-server-middleware.js +10 -8
- package/dist/runtime/utils/nuxt-i18n.d.ts +122 -0
- package/dist/runtime/utils/nuxt-i18n.js +335 -0
- package/dist/runtime/utils/storage.d.ts +11 -12
- package/dist/runtime/utils/storage.js +37 -21
- package/dist/types.d.mts +2 -0
- package/internals.d.mts +1 -0
- package/package.json +21 -15
- package/dist/client/_nuxt/B-cq5x_h.js +0 -14
- package/dist/client/_nuxt/builds/meta/c5d2004e-f981-4363-a9ec-4d72cf7f4a8f.json +0 -1
- package/dist/runtime/utils/accept-language.d.ts +0 -3
- package/dist/runtime/utils/accept-language.js +0 -21
- package/dist/runtime/utils/active-locales.d.ts +0 -4
- package/dist/runtime/utils/active-locales.js +0 -9
- package/dist/runtime/utils/cache-control.d.ts +0 -50
- package/dist/runtime/utils/cache-control.js +0 -88
- package/dist/runtime/utils/cookie.d.ts +0 -24
- package/dist/runtime/utils/cookie.js +0 -22
- package/dist/runtime/utils/deep-merge.d.ts +0 -15
- package/dist/runtime/utils/deep-merge.js +0 -14
- package/dist/runtime/utils/resolve-server-locale.d.ts +0 -21
- package/dist/runtime/utils/resolve-server-locale.js +0 -30
- package/dist/runtime/utils/route-utils.d.ts +0 -33
- package/dist/runtime/utils/route-utils.js +0 -63
- package/dist/runtime/utils/runtime-i18n-config.d.ts +0 -8
- package/dist/runtime/utils/runtime-i18n-config.js +0 -90
|
@@ -1,25 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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,
|
|
6
|
+
import { createError, defineNuxtPlugin, navigateTo, useRouter, useRuntimeConfig } from "#imports";
|
|
7
7
|
import { useI18nLocale } from "../composables/useI18nLocale.js";
|
|
8
|
-
import {
|
|
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
|
|
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
|
-
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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 !==
|
|
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
|
-
|
|
256
|
-
__micro: true,
|
|
132
|
+
...provideApi,
|
|
257
133
|
helper,
|
|
258
|
-
|
|
259
|
-
|
|
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(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 ??
|
|
30
|
+
void i18nHelper.mergeTranslation(selectedLocale ?? locale, routeName, translations, true);
|
|
39
31
|
},
|
|
40
|
-
|
|
32
|
+
locale
|
|
41
33
|
);
|
|
34
|
+
};
|
|
35
|
+
if (i18nConfig.hooks !== false) {
|
|
36
|
+
await callRegister();
|
|
42
37
|
}
|
|
43
|
-
|
|
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 {
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16
|
+
dependsOn: ["i18n-plugin-loader"],
|
|
49
17
|
setup(nuxtApp) {
|
|
50
|
-
const
|
|
51
|
-
const i18nStrategy = createI18nStrategy(router);
|
|
18
|
+
const i18nStrategy = resolveI18nStrategy(nuxtApp);
|
|
52
19
|
const getRuntimeConfig = nuxtApp.$getI18nConfig;
|
|
53
|
-
|
|
54
|
-
|
|
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
|
});
|