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
@@ -0,0 +1,335 @@
1
+ import {
2
+ BaseI18n,
3
+ isNoPrefixStrategy,
4
+ mergeTranslationChunk,
5
+ resolveTranslation,
6
+ translationCacheKey
7
+ } from "@i18n-micro/core";
8
+ import { deepMergeTranslations } from "@i18n-micro/utils/deep-merge";
9
+ import { shallowRef, triggerRef, unref } from "vue";
10
+ import { translationStorage } from "./storage.js";
11
+ function toRouteLocationResolved(result) {
12
+ const fullPath = result.fullPath ?? result.path ?? "";
13
+ const path = result.path ?? fullPath.split("?")[0]?.split("#")[0] ?? fullPath;
14
+ const out = {
15
+ path,
16
+ fullPath,
17
+ href: fullPath
18
+ };
19
+ if (result.query && Object.keys(result.query).length) out.query = result.query;
20
+ if (result.hash) out.hash = result.hash;
21
+ return out;
22
+ }
23
+ function extractSwitchLocalePath(localeRoute, resolveRouter) {
24
+ if (!localeRoute || typeof localeRoute !== "object") return String(localeRoute ?? "");
25
+ if ("fullPath" in localeRoute && localeRoute.fullPath) return localeRoute.fullPath;
26
+ if ("path" in localeRoute && localeRoute.path) return localeRoute.path;
27
+ if ("name" in localeRoute && localeRoute.name && resolveRouter.hasRoute(String(localeRoute.name))) {
28
+ return resolveRouter.resolve(localeRoute).fullPath;
29
+ }
30
+ return "";
31
+ }
32
+ export class NuxtI18n extends BaseI18n {
33
+ storage;
34
+ contextSignal;
35
+ cachedTranslations = {};
36
+ pendingCleanState = null;
37
+ currentLocale = "";
38
+ currentRouteName = "";
39
+ resolveRouteContext;
40
+ constructor(options = {}) {
41
+ const storage = {
42
+ translations: /* @__PURE__ */ new Map()
43
+ };
44
+ super({ ...options, storage });
45
+ this.storage = storage;
46
+ this.contextSignal = shallowRef(0);
47
+ }
48
+ setRouteContextResolver(resolver) {
49
+ this.resolveRouteContext = resolver;
50
+ }
51
+ getCacheKey(locale, routeName) {
52
+ return translationCacheKey(locale, routeName);
53
+ }
54
+ getChunk(locale, routeName) {
55
+ return this.storage.translations.get(this.getCacheKey(locale, routeName)) ?? {};
56
+ }
57
+ setChunk(locale, routeName, data) {
58
+ this.storage.translations.set(this.getCacheKey(locale, routeName), data);
59
+ }
60
+ hasChunk(locale, routeName) {
61
+ return this.storage.translations.has(this.getCacheKey(locale, routeName));
62
+ }
63
+ mergeChunk(locale, routeName, data) {
64
+ const cacheKey = this.getCacheKey(locale, routeName);
65
+ const existing = this.storage.translations.get(cacheKey) ?? {};
66
+ const merged = mergeTranslationChunk(existing, data);
67
+ this.storage.translations.set(cacheKey, merged);
68
+ return merged;
69
+ }
70
+ getLocale() {
71
+ return this.currentLocale;
72
+ }
73
+ getFallbackLocale() {
74
+ return this.currentLocale;
75
+ }
76
+ getRoute() {
77
+ return this.currentRouteName || "index";
78
+ }
79
+ getCurrentLocale() {
80
+ return this.currentLocale;
81
+ }
82
+ getCurrentRouteName() {
83
+ return this.currentRouteName;
84
+ }
85
+ applySwitchContext(locale, routeName, data) {
86
+ this.setChunk(locale, routeName, data);
87
+ const sameLocale = this.currentLocale === locale;
88
+ if (sameLocale) {
89
+ this.cachedTranslations = deepMergeTranslations(this.cachedTranslations, data);
90
+ this.pendingCleanState = data;
91
+ } else {
92
+ this.cachedTranslations = data;
93
+ this.pendingCleanState = null;
94
+ }
95
+ this.currentLocale = locale;
96
+ this.currentRouteName = routeName || "";
97
+ triggerRef(this.contextSignal);
98
+ }
99
+ finishTransition() {
100
+ if (this.pendingCleanState) {
101
+ this.cachedTranslations = this.pendingCleanState;
102
+ this.pendingCleanState = null;
103
+ }
104
+ }
105
+ mergeTranslations(newTranslations) {
106
+ const merged = this.mergeChunk(this.currentLocale, this.currentRouteName, newTranslations);
107
+ this.cachedTranslations = deepMergeTranslations(this.cachedTranslations, merged);
108
+ if (this.pendingCleanState) this.pendingCleanState = merged;
109
+ triggerRef(this.contextSignal);
110
+ }
111
+ async loadPageTranslations(locale, routeName, translations) {
112
+ const mergedChunk = this.mergeChunk(locale, routeName, translations);
113
+ if (locale === this.currentLocale && routeName === this.currentRouteName) {
114
+ this.cachedTranslations = deepMergeTranslations(this.cachedTranslations, mergedChunk);
115
+ if (this.pendingCleanState) this.pendingCleanState = mergedChunk;
116
+ triggerRef(this.contextSignal);
117
+ }
118
+ }
119
+ async mergeTranslationAsync(locale, routeName, newTranslations, loadIfMissing) {
120
+ if (!this.hasChunk(locale, routeName)) {
121
+ const loaded = await loadIfMissing(locale, routeName || void 0);
122
+ this.setChunk(locale, routeName, loaded);
123
+ }
124
+ const mergedChunk = this.mergeChunk(locale, routeName, newTranslations);
125
+ if (locale === this.currentLocale && routeName === this.currentRouteName) {
126
+ this.cachedTranslations = deepMergeTranslations(this.cachedTranslations, mergedChunk);
127
+ if (this.pendingCleanState) this.pendingCleanState = mergedChunk;
128
+ triggerRef(this.contextSignal);
129
+ }
130
+ }
131
+ tForRoute(route) {
132
+ return (key, params, defaultValue) => this.t(key, params, defaultValue, route);
133
+ }
134
+ tsForRoute(route) {
135
+ return (key, params, defaultValue) => this.ts(key, params, defaultValue, route);
136
+ }
137
+ touch() {
138
+ this.contextSignal.value;
139
+ }
140
+ resolveLookup(key, routeContext) {
141
+ const translations = routeContext && this.resolveRouteContext ? (() => {
142
+ const { locale, routeName } = this.resolveRouteContext(routeContext);
143
+ return this.getChunk(locale, routeName);
144
+ })() : this.cachedTranslations;
145
+ return resolveTranslation(translations, String(key));
146
+ }
147
+ resolveHas(key) {
148
+ return resolveTranslation(this.cachedTranslations, String(key)) !== null;
149
+ }
150
+ getMissingContext(_routeContext) {
151
+ return { locale: this.currentLocale, routeName: this.currentRouteName };
152
+ }
153
+ warnMissing(key) {
154
+ const customHandler = this.getCustomMissingHandler?.();
155
+ if (customHandler) {
156
+ customHandler(this.currentLocale, String(key), this.currentRouteName);
157
+ return;
158
+ }
159
+ if (this.missingWarn && process.env.NODE_ENV !== "production" && import.meta.client) {
160
+ console.warn(`[i18n] Missing key '${key}' in '${this.currentLocale}' for route '${this.currentRouteName}'`);
161
+ }
162
+ }
163
+ clearCache() {
164
+ super.clearCache();
165
+ this.cachedTranslations = {};
166
+ this.pendingCleanState = null;
167
+ triggerRef(this.contextSignal);
168
+ }
169
+ }
170
+ export class NuxtTranslationLoader {
171
+ constructor(options) {
172
+ this.options = options;
173
+ }
174
+ pendingLoads = /* @__PURE__ */ new Map();
175
+ loadFromCacheSync(locale, routeName) {
176
+ const { i18n } = this.options;
177
+ if (i18n.hasChunk(locale, routeName)) {
178
+ return i18n.getChunk(locale, routeName);
179
+ }
180
+ const cached = translationStorage.getFromCache(locale, routeName);
181
+ if (cached) {
182
+ i18n.setChunk(locale, routeName, cached.data);
183
+ return cached.data;
184
+ }
185
+ return null;
186
+ }
187
+ loadAsync(locale, routeName) {
188
+ const { i18n, loadOptions, setSsrChunk, isDev } = this.options;
189
+ const cacheKey = i18n.getCacheKey(locale, routeName);
190
+ const pending = this.pendingLoads.get(cacheKey);
191
+ if (pending) return pending;
192
+ const promise = (async () => {
193
+ try {
194
+ const result = await translationStorage.load(locale, routeName, loadOptions);
195
+ const existing = i18n.getChunk(locale, routeName);
196
+ const mergedChunk = mergeTranslationChunk(existing, result.data, { preserveExisting: true });
197
+ i18n.setChunk(locale, routeName, mergedChunk);
198
+ if (import.meta.server) {
199
+ setSsrChunk(cacheKey, mergedChunk);
200
+ }
201
+ return mergedChunk;
202
+ } catch (e) {
203
+ if (isDev) console.error("[i18n] Load error:", e);
204
+ return {};
205
+ } finally {
206
+ this.pendingLoads.delete(cacheKey);
207
+ }
208
+ })();
209
+ this.pendingLoads.set(cacheKey, promise);
210
+ return promise;
211
+ }
212
+ async switchContext(locale, routeName) {
213
+ let data = this.loadFromCacheSync(locale, routeName);
214
+ if (data === null) {
215
+ data = await this.loadAsync(locale, routeName);
216
+ }
217
+ this.options.i18n.applySwitchContext(locale, routeName, data);
218
+ }
219
+ }
220
+ export function createNuxtI18nPluginApi(deps) {
221
+ const {
222
+ i18n,
223
+ loader,
224
+ i18nStrategy,
225
+ i18nConfig,
226
+ router,
227
+ getCurrentLocale,
228
+ getEffectiveLocale,
229
+ getPluginRouteName,
230
+ getRouteName,
231
+ i18nRouteParams,
232
+ setLocale,
233
+ isValidLocale,
234
+ navigateTo,
235
+ setMissingHandler
236
+ } = deps;
237
+ const isDev = process.env.NODE_ENV !== "production";
238
+ const isNoPrefix = isNoPrefixStrategy(i18nConfig.strategy);
239
+ const switchLocaleLogic = (toLocale, i18nParams, to) => {
240
+ const fromLocale = getCurrentLocale();
241
+ const currentRoute = router.currentRoute.value;
242
+ let resolvedTarget;
243
+ if (typeof to === "string") {
244
+ resolvedTarget = router.resolve(i18nStrategy.formatPathForResolve(to, fromLocale, toLocale));
245
+ } else {
246
+ resolvedTarget = to ?? currentRoute;
247
+ }
248
+ const switchedRoute = i18nStrategy.switchLocaleRoute(fromLocale, toLocale, resolvedTarget, {
249
+ i18nRouteParams: i18nParams
250
+ });
251
+ if (typeof switchedRoute === "string" && (switchedRoute.startsWith("http://") || switchedRoute.startsWith("https://"))) {
252
+ return navigateTo(switchedRoute, { redirectCode: 200, external: true });
253
+ }
254
+ if (isNoPrefix) {
255
+ ;
256
+ switchedRoute.force = true;
257
+ }
258
+ return router.push(switchedRoute);
259
+ };
260
+ const helper = {
261
+ async mergeTranslation(locale, routeName, newTranslations, _force = false) {
262
+ await i18n.mergeTranslationAsync(locale, routeName, newTranslations, (l, r) => loader.loadAsync(l, r));
263
+ }
264
+ };
265
+ const switchContext = (locale, routeName) => loader.switchContext(locale, routeName);
266
+ const provide = {
267
+ i18nStrategy,
268
+ getLocale: (route) => getEffectiveLocale(route, (r) => getCurrentLocale(r)),
269
+ getLocaleName: () => i18nStrategy.getCurrentLocaleName(router.currentRoute.value, getCurrentLocale() ?? null),
270
+ defaultLocale: () => i18nConfig.defaultLocale,
271
+ getLocales: () => i18nConfig.locales || [],
272
+ getRouteName,
273
+ t: i18n.t.bind(i18n),
274
+ ts: (key, params, defaultValue, route) => {
275
+ const value = route ? i18n.t(key, params, defaultValue, route) : i18n.ts(key, params, defaultValue);
276
+ return value?.toString() ?? defaultValue ?? key;
277
+ },
278
+ _t: (route) => i18n.tForRoute(route),
279
+ _ts: (route) => i18n.tsForRoute(route),
280
+ tc: i18n.tc.bind(i18n),
281
+ tn: i18n.tn.bind(i18n),
282
+ td: i18n.td.bind(i18n),
283
+ tdr: i18n.tdr.bind(i18n),
284
+ has: i18n.has.bind(i18n),
285
+ mergeTranslations: i18n.mergeTranslations.bind(i18n),
286
+ switchLocaleRoute: (toLocale) => {
287
+ const route = router.currentRoute.value;
288
+ const fromLocale = getCurrentLocale(route);
289
+ return i18nStrategy.switchLocaleRoute(fromLocale, toLocale, route, { i18nRouteParams: unref(i18nRouteParams.value) });
290
+ },
291
+ clearCache: () => {
292
+ translationStorage.clear();
293
+ i18n.clearCache();
294
+ },
295
+ switchLocalePath: (toLocale) => {
296
+ const route = router.currentRoute.value;
297
+ const fromLocale = getCurrentLocale(route);
298
+ const switched = i18nStrategy.switchLocaleRoute(fromLocale, toLocale, route, { i18nRouteParams: unref(i18nRouteParams.value) });
299
+ return extractSwitchLocalePath(switched, router);
300
+ },
301
+ switchLocale: async (toLocale) => {
302
+ if (!isValidLocale(toLocale)) {
303
+ if (isDev) console.warn(`[i18n] Invalid locale '${toLocale}'`);
304
+ return;
305
+ }
306
+ setLocale(toLocale);
307
+ if (isNoPrefixStrategy(i18nConfig.strategy) || i18nConfig.hashMode) {
308
+ const route = router.currentRoute.value;
309
+ const routeName = getPluginRouteName(route, toLocale);
310
+ await loader.switchContext(toLocale, routeName);
311
+ }
312
+ return switchLocaleLogic(toLocale, unref(i18nRouteParams.value));
313
+ },
314
+ switchRoute: (route, toLocale) => {
315
+ return switchLocaleLogic(toLocale ?? getCurrentLocale(), unref(i18nRouteParams.value), route);
316
+ },
317
+ localeRoute(to, locale) {
318
+ const targetLocale = locale !== void 0 && locale !== "" ? String(locale) : getCurrentLocale();
319
+ const result = i18nStrategy.localeRoute(targetLocale, to, router.currentRoute.value);
320
+ return toRouteLocationResolved(result);
321
+ },
322
+ localePath(to, locale) {
323
+ const targetLocale = locale !== void 0 && locale !== "" ? String(locale) : getCurrentLocale();
324
+ const result = i18nStrategy.localeRoute(targetLocale, to, router.currentRoute.value);
325
+ return result.fullPath ?? result.path ?? "";
326
+ },
327
+ setI18nRouteParams: (value) => {
328
+ i18nRouteParams.value = value;
329
+ return i18nRouteParams.value;
330
+ },
331
+ loadPageTranslations: i18n.loadPageTranslations.bind(i18n),
332
+ setMissingHandler
333
+ };
334
+ return { helper, switchContext, provide };
335
+ }
@@ -1,23 +1,15 @@
1
- /**
2
- * Translation Storage
3
- * Unified translation storage for client and server.
4
- * Cache control (TTL, maxSize) delegated to CacheControl.
5
- */
6
- import { type CacheControlOptions } from './cache-control.js';
7
- declare global {
8
- interface Window {
9
- __I18N__?: Record<string, unknown>;
10
- }
11
- }
1
+ import { type CacheControlOptions } from '@i18n-micro/utils/cache-control';
12
2
  export interface LoadOptions {
13
3
  apiBaseUrl: string;
14
4
  baseURL: string;
5
+ apiBaseClientHost?: string;
6
+ apiBaseServerHost?: string;
15
7
  dateBuild?: string | number;
8
+ routesLocaleLinks?: Record<string, string>;
16
9
  }
17
10
  export interface LoadResult {
18
11
  data: Record<string, unknown>;
19
12
  cacheKey: string;
20
- json?: string;
21
13
  }
22
14
  declare class TranslationStorage {
23
15
  private cc;
@@ -26,8 +18,15 @@ declare class TranslationStorage {
26
18
  * Configure cache limits. Call once from plugin with config values.
27
19
  */
28
20
  configure(options: CacheControlOptions): void;
21
+ /** Plain clone before freeze — SSR payload chunks may be Vue reactive proxies. */
22
+ private freezePlainClone;
29
23
  private getCacheKey;
30
24
  private fetchTranslations;
25
+ /**
26
+ * Seed translation cache from SSR payload (`useState('i18n-ssr-chunks')`).
27
+ * Called on client before the first fetch.
28
+ */
29
+ seedFromSsrChunks(chunks: Record<string, Record<string, unknown>>): void;
31
30
  /**
32
31
  * Synchronous cache check and retrieval.
33
32
  * Returns data if cached (and not expired), otherwise null.
@@ -1,11 +1,13 @@
1
- import { CacheControl } from "./cache-control.js";
2
- const CC_KEY = Symbol.for("__NUXT_I18N_STORAGE_CC__");
1
+ import { translationCacheKey } from "@i18n-micro/core/helpers";
2
+ import { STORAGE_CC_KEY } from "@i18n-micro/hmr/cache-keys";
3
+ import { CacheControl } from "@i18n-micro/utils/cache-control";
4
+ import { buildTranslationPayloadFetchRequest } from "@i18n-micro/utils/payload-url";
3
5
  function getStorageCacheControl() {
4
6
  const g = globalThis;
5
- if (!g[CC_KEY]) {
6
- g[CC_KEY] = new CacheControl();
7
+ if (!g[STORAGE_CC_KEY]) {
8
+ g[STORAGE_CC_KEY] = new CacheControl();
7
9
  }
8
- return g[CC_KEY];
10
+ return g[STORAGE_CC_KEY];
9
11
  }
10
12
  class TranslationStorage {
11
13
  cc;
@@ -21,24 +23,45 @@ class TranslationStorage {
21
23
  // ==========================================================================
22
24
  // HELPERS
23
25
  // ==========================================================================
26
+ /** Plain clone before freeze — SSR payload chunks may be Vue reactive proxies. */
27
+ freezePlainClone(data) {
28
+ return Object.freeze(JSON.parse(JSON.stringify(data)));
29
+ }
24
30
  getCacheKey(locale, routeName) {
25
- return `${locale}:${routeName || "index"}`;
31
+ return translationCacheKey(locale, routeName);
26
32
  }
27
33
  // ==========================================================================
28
34
  // FETCH LOADER
29
35
  // ==========================================================================
30
36
  async fetchTranslations(locale, routeName, options) {
31
- const { apiBaseUrl, baseURL, dateBuild } = options;
32
- const page = routeName || "index";
33
- const path = `/${apiBaseUrl}/${page}/${locale}/data.json`;
34
- return await $fetch(path.replace(/\/{2,}/g, "/"), {
35
- baseURL,
36
- params: dateBuild ? { v: dateBuild } : void 0
37
+ const request = buildTranslationPayloadFetchRequest({
38
+ apiBaseUrl: options.apiBaseUrl,
39
+ routeName,
40
+ locale,
41
+ isServer: import.meta.server,
42
+ baseURL: options.baseURL,
43
+ apiBaseClientHost: options.apiBaseClientHost,
44
+ apiBaseServerHost: options.apiBaseServerHost ?? (import.meta.server ? process.env.NUXT_I18N_APP_BASE_SERVER_HOST : void 0),
45
+ dateBuild: options.dateBuild,
46
+ routesLocaleLinks: options.routesLocaleLinks
47
+ });
48
+ return await $fetch(request.path, {
49
+ baseURL: request.baseURL,
50
+ params: request.params
37
51
  });
38
52
  }
39
53
  // ==========================================================================
40
54
  // PUBLIC API
41
55
  // ==========================================================================
56
+ /**
57
+ * Seed translation cache from SSR payload (`useState('i18n-ssr-chunks')`).
58
+ * Called on client before the first fetch.
59
+ */
60
+ seedFromSsrChunks(chunks) {
61
+ for (const [cacheKey, data] of Object.entries(chunks)) {
62
+ this.cc.set(cacheKey, this.freezePlainClone(data));
63
+ }
64
+ }
42
65
  /**
43
66
  * Synchronous cache check and retrieval.
44
67
  * Returns data if cached (and not expired), otherwise null.
@@ -49,12 +72,6 @@ class TranslationStorage {
49
72
  if (cached) {
50
73
  return { data: cached, cacheKey };
51
74
  }
52
- if (import.meta.client && typeof window !== "undefined" && window.__I18N__?.[cacheKey]) {
53
- const data = window.__I18N__[cacheKey];
54
- delete window.__I18N__[cacheKey];
55
- this.cc.set(cacheKey, Object.freeze(data));
56
- return { data: this.cc.get(cacheKey), cacheKey };
57
- }
58
75
  return null;
59
76
  }
60
77
  /**
@@ -66,9 +83,8 @@ class TranslationStorage {
66
83
  if (cached) return cached;
67
84
  const cacheKey = this.getCacheKey(locale, routeName);
68
85
  const data = await this.fetchTranslations(locale, routeName, options);
69
- this.cc.set(cacheKey, Object.freeze(data));
70
- const json = import.meta.server ? JSON.stringify(data).replace(/</g, "\\u003c") : void 0;
71
- return { data: this.cc.get(cacheKey), cacheKey, json };
86
+ this.cc.set(cacheKey, this.freezePlainClone(data));
87
+ return { data: this.cc.get(cacheKey), cacheKey };
72
88
  }
73
89
  /**
74
90
  * Clear cache and metadata.
package/dist/types.d.mts CHANGED
@@ -8,6 +8,8 @@ export { type Getter, type GlobalLocaleRoutes, type Locale, type LocaleCode, typ
8
8
 
9
9
  export { type PluginsInjections } from '../dist/runtime/plugins/01.plugin.js'
10
10
 
11
+ export { type TranslationPayloadMode, type resolveTranslationPayloadMode, type resolveTranslationPayloadOptions, type resolveTranslationPayloadPublicDir } from '@i18n-micro/utils/payload-config'
12
+
11
13
  export { default } from './module.mjs'
12
14
 
13
15
  export { type ModuleHooks } from './module.mjs'
package/internals.d.mts CHANGED
@@ -13,6 +13,7 @@ interface I18nPrivateConfig {
13
13
  apiBaseServerHost?: string
14
14
  customRegexMatcher?: string
15
15
  routesLocaleLinks?: Record<string, string>
16
+ serverTranslationPreload?: boolean
16
17
  }
17
18
 
18
19
  declare module '#build/i18n.config.mjs' {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-i18n-micro",
3
- "version": "3.18.3",
3
+ "version": "3.20.0",
4
4
  "description": "Nuxt I18n Micro is a lightweight, high-performance internationalization module for Nuxt, designed to handle multi-language support with minimal overhead, fast build times, and efficient runtime performance.",
5
5
  "repository": "s00d/nuxt-i18n-micro",
6
6
  "license": "MIT",
@@ -74,14 +74,19 @@
74
74
  "chokidar": "^3.6.0",
75
75
  "globby": "^14.1.0",
76
76
  "ufo": "^1.5.4",
77
- "@i18n-micro/core": "1.3.1",
78
- "@i18n-micro/route-strategy": "1.1.7",
79
- "@i18n-micro/test-utils": "1.2.1",
80
- "@i18n-micro/path-strategy": "1.3.2",
81
- "@i18n-micro/types": "1.2.2"
77
+ "unplugin": "^1.16.1",
78
+ "@vue/compiler-sfc": "^3.5.25",
79
+ "@i18n-micro/hmr": "1.0.1",
80
+ "@i18n-micro/core": "1.3.2",
81
+ "@i18n-micro/path-strategy": "1.3.3",
82
+ "@i18n-micro/types": "1.2.4",
83
+ "@i18n-micro/test-utils": "1.2.2",
84
+ "@i18n-micro/route-strategy": "1.1.8",
85
+ "@i18n-micro/utils": "1.0.1"
82
86
  },
83
87
  "devDependencies": {
84
- "@biomejs/biome": "^2.3.14",
88
+ "oxfmt": "^0.43.0",
89
+ "oxlint": "^1.58.0",
85
90
  "@nuxt/devtools": "^2.6.3",
86
91
  "@nuxt/devtools-ui-kit": "^2.6.3",
87
92
  "@nuxt/module-builder": "^1.0.0",
@@ -122,13 +127,14 @@
122
127
  "dev:generate": "nuxi generate playground",
123
128
  "release": "pnpm run release:check && pnpm run release:patch",
124
129
  "release:check": "pnpm run format && pnpm run lint && pnpm run typecheck && pnpm run test:types && pnpm run test && pnpm run test:vitest && pnpm run test:workspaces",
125
- "release:patch": "pnpm run prepack && node scripts/run-changelogen-release.mjs patch && pnpm publish -r && git push --follow-tags",
126
- "release:minor": "pnpm run prepack && node scripts/run-changelogen-release.mjs minor && pnpm publish -r && git push --follow-tags",
127
- "release:major": "pnpm run prepack && node scripts/run-changelogen-release.mjs major && pnpm publish -r && git push --follow-tags",
128
- "lint": "biome check .",
129
- "lint:fix": "biome check --write .",
130
- "format": "biome format --write .",
131
- "format:check": "biome format .",
130
+ "release:auth": "node scripts/ensure-npm-auth.mjs",
131
+ "release:patch": "pnpm run release:auth && pnpm run prepack && node scripts/run-changelogen-release.mjs patch && pnpm publish -r && git push --follow-tags",
132
+ "release:minor": "pnpm run release:auth && pnpm run prepack && node scripts/run-changelogen-release.mjs minor && pnpm publish -r && git push --follow-tags",
133
+ "release:major": "pnpm run release:auth && pnpm run prepack && node scripts/run-changelogen-release.mjs major && pnpm publish -r && git push --follow-tags",
134
+ "lint": "oxlint src packages test client playground build.config.ts",
135
+ "lint:fix": "oxlint src packages test client playground build.config.ts --fix",
136
+ "format": "oxfmt --write src packages test client playground build.config.ts",
137
+ "format:check": "oxfmt --check src packages test client playground build.config.ts",
132
138
  "test": "playwright test",
133
139
  "test:vitest": "vitest run",
134
140
  "test:performance": "vitest run --config vitest.performance.config.ts",
@@ -144,7 +150,7 @@
144
150
  "verify:packages": "node scripts/verify-packages.mjs",
145
151
  "verify:packages:publint": "node scripts/verify-packages.mjs --publint",
146
152
  "compare:published": "node scripts/compare-published-dist.mjs",
147
- "test:dist:packages": "pnpm --filter \"@i18n-micro/core\" --filter \"@i18n-micro/vue\" --filter \"@i18n-micro/path-strategy\" --filter \"@i18n-micro/route-strategy\" --filter \"@i18n-micro/devtools-ui\" --filter \"@i18n-micro/astro\" run test:dist",
153
+ "test:dist:packages": "pnpm --filter \"@i18n-micro/core\" --filter \"@i18n-micro/utils\" --filter \"@i18n-micro/hmr\" --filter \"@i18n-micro/vue\" --filter \"@i18n-micro/path-strategy\" --filter \"@i18n-micro/route-strategy\" --filter \"@i18n-micro/devtools-ui\" --filter \"@i18n-micro/astro\" run test:dist",
148
154
  "typecheck": "tsc --noEmit",
149
155
  "typecheck:nuxt": "nuxt typecheck --no-emit",
150
156
  "docs:dev": "vitepress dev docs",