nuxt-i18n-micro 3.18.3 → 3.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +16 -16
  2. package/dist/client/200.html +1 -1
  3. package/dist/client/404.html +1 -1
  4. package/dist/client/_nuxt/{D6byAye8.js → BA2bGsrZ.js} +1 -1
  5. package/dist/client/_nuxt/{BNXusRXY.js → C_HK2snF.js} +1 -1
  6. package/dist/client/_nuxt/{BCTbvRLO.js → Dx6dmpFi.js} +1 -1
  7. package/dist/client/_nuxt/SDQMalAT.js +14 -0
  8. package/dist/client/_nuxt/builds/latest.json +1 -1
  9. package/dist/client/_nuxt/builds/meta/5a55317c-1ad8-41ca-a7f6-2fe58e16b2f6.json +1 -0
  10. package/dist/client/index.html +1 -1
  11. package/dist/module.d.mts +1 -0
  12. package/dist/module.json +1 -1
  13. package/dist/module.mjs +197 -231
  14. package/dist/runtime/components/i18n-group.vue +1 -4
  15. package/dist/runtime/components/i18n-link.vue +2 -12
  16. package/dist/runtime/components/i18n-switcher.vue +7 -30
  17. package/dist/runtime/composables/useI18n.js +1 -1
  18. package/dist/runtime/composables/useI18nLocale.js +5 -6
  19. package/dist/runtime/composables/useLocaleHead.js +2 -2
  20. package/dist/runtime/middleware/i18n-redirect.global.d.ts +6 -0
  21. package/dist/runtime/middleware/i18n-redirect.global.js +43 -0
  22. package/dist/runtime/plugins/01.plugin.d.ts +21 -21
  23. package/dist/runtime/plugins/01.plugin.js +69 -313
  24. package/dist/runtime/plugins/02.meta.js +2 -2
  25. package/dist/runtime/plugins/05.hooks.js +32 -32
  26. package/dist/runtime/plugins/06.redirect.d.ts +1 -0
  27. package/dist/runtime/plugins/06.redirect.js +15 -86
  28. package/dist/runtime/server/middleware/i18n.global.js +17 -5
  29. package/dist/runtime/server/plugins/watcher.dev.js +36 -66
  30. package/dist/runtime/server/routes/i18n.js +2 -2
  31. package/dist/runtime/server/utils/locale-detector.d.ts +9 -12
  32. package/dist/runtime/server/utils/locale-detector.js +25 -18
  33. package/dist/runtime/server/utils/locale-server-middleware.js +10 -8
  34. package/dist/runtime/server/utils/server-loader.d.ts +3 -3
  35. package/dist/runtime/server/utils/server-loader.js +40 -15
  36. package/dist/runtime/server/utils/translation-server-middleware.js +10 -8
  37. package/dist/runtime/utils/nuxt-i18n.d.ts +122 -0
  38. package/dist/runtime/utils/nuxt-i18n.js +335 -0
  39. package/dist/runtime/utils/storage.d.ts +11 -12
  40. package/dist/runtime/utils/storage.js +37 -21
  41. package/dist/types.d.mts +2 -0
  42. package/internals.d.mts +1 -0
  43. package/package.json +21 -15
  44. package/dist/client/_nuxt/B-cq5x_h.js +0 -14
  45. package/dist/client/_nuxt/builds/meta/c5d2004e-f981-4363-a9ec-4d72cf7f4a8f.json +0 -1
  46. package/dist/runtime/utils/accept-language.d.ts +0 -3
  47. package/dist/runtime/utils/accept-language.js +0 -21
  48. package/dist/runtime/utils/active-locales.d.ts +0 -4
  49. package/dist/runtime/utils/active-locales.js +0 -9
  50. package/dist/runtime/utils/cache-control.d.ts +0 -50
  51. package/dist/runtime/utils/cache-control.js +0 -88
  52. package/dist/runtime/utils/cookie.d.ts +0 -24
  53. package/dist/runtime/utils/cookie.js +0 -22
  54. package/dist/runtime/utils/deep-merge.d.ts +0 -15
  55. package/dist/runtime/utils/deep-merge.js +0 -14
  56. package/dist/runtime/utils/resolve-server-locale.d.ts +0 -21
  57. package/dist/runtime/utils/resolve-server-locale.js +0 -30
  58. package/dist/runtime/utils/route-utils.d.ts +0 -33
  59. package/dist/runtime/utils/route-utils.js +0 -63
  60. package/dist/runtime/utils/runtime-i18n-config.d.ts +0 -8
  61. package/dist/runtime/utils/runtime-i18n-config.js +0 -90
@@ -1 +1 @@
1
- {"id":"c5d2004e-f981-4363-a9ec-4d72cf7f4a8f","timestamp":1781272693310}
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:"c5d2004e-f981-4363-a9ec-4d72cf7f4a8f",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1781272696442,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.d.mts CHANGED
@@ -3,6 +3,7 @@ import { HookResult } from '@nuxt/schema';
3
3
  import { ModuleOptions } from '@i18n-micro/types';
4
4
  export { Getter, GlobalLocaleRoutes, Locale, LocaleCode, ModuleOptions, PluralFunc, Strategies } from '@i18n-micro/types';
5
5
  export { PluginsInjections } from '../dist/runtime/plugins/01.plugin.js';
6
+ export { TranslationPayloadMode, resolveTranslationPayloadMode, resolveTranslationPayloadOptions, resolveTranslationPayloadPublicDir } from '@i18n-micro/utils/payload-config';
6
7
 
7
8
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
8
9
 
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-i18n-micro",
3
3
  "configKey": "i18n",
4
- "version": "3.18.3",
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,13 +1,20 @@
1
1
  import * as fs from 'node:fs';
2
- import fs__default, { readFileSync, existsSync, mkdirSync, writeFileSync } 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';
6
6
  import { defaultPlural, isNoPrefixStrategy, withPrefixStrategy } from '@i18n-micro/core';
7
+ import { generateHmrPlugin } from '@i18n-micro/hmr/generate-plugin';
7
8
  import { RouteGenerator, isLocaleAllowedForUnlocalizedRoute, normalizePath, isInternalPath } from '@i18n-micro/route-strategy';
8
- import { useNuxt, defineNuxtModule, useLogger, createResolver, addTemplate, addImportsDir, addPlugin, addServerHandler, addComponentsDir, addTypeTemplate, addPrerenderRoutes } from '@nuxt/kit';
9
+ import { buildTranslationSourceLayers, preMergeLocales } from '@i18n-micro/utils/build';
10
+ import { resolveTranslationPayloadOptions, getTranslationPayloadMisconfigurationWarnings, getTranslationPayloadSizeWarning, resolveTranslationPayloadWarningThresholds, resolveTranslationPayloadPublicDir } from '@i18n-micro/utils/payload-config';
11
+ export { resolveTranslationPayloadMode, resolveTranslationPayloadOptions, resolveTranslationPayloadPublicDir } from '@i18n-micro/utils/payload-config';
12
+ import { scanTranslationPayloadDirectory } from '@i18n-micro/utils/payload-stats';
13
+ import { useNuxt, defineNuxtModule, useLogger, createResolver, addTemplate, addImportsDir, addPlugin, addRouteMiddleware, addServerImportsDir, addServerHandler, addComponentsDir, addTypeTemplate, addVitePlugin, addPrerenderRoutes } from '@nuxt/kit';
9
14
  import { globby } from 'globby';
10
15
  import { onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
16
+ import { createUnplugin } from 'unplugin';
17
+ import { parse } from '@vue/compiler-sfc';
11
18
 
12
19
  const DEVTOOLS_UI_PORT = 3030;
13
20
  const DEVTOOLS_UI_ROUTE = "/__nuxt-i18n-micro";
@@ -118,46 +125,6 @@ function setupDevToolsUI(options, resolve2, rootDirs) {
118
125
  });
119
126
  }
120
127
 
121
- function generateHmrPlugin(files) {
122
- const accepts = files.map((file) => {
123
- const isPage = /\/pages\//.test(file);
124
- let pageName = "";
125
- let locale = "";
126
- if (isPage) {
127
- const m = /\/pages\/(.+)\/([^/]+)\.json$/.exec(file);
128
- pageName = m?.[1] || "";
129
- locale = m?.[2] || "";
130
- } else {
131
- const m = /\/([^/]+)\.json$/.exec(file);
132
- locale = m?.[1] || "";
133
- }
134
- return `
135
- if (import.meta.hot) {
136
- import.meta.hot.accept('${file}', async (mod) => {
137
- const nuxtApp = useNuxtApp()
138
- const data = (mod && typeof mod === 'object' && Object.prototype.hasOwnProperty.call(mod, 'default'))
139
- ? mod.default
140
- : mod
141
- try {
142
- ${isPage ? `await nuxtApp.$loadPageTranslations('${locale}', '${pageName}', data)` : `await nuxtApp.$loadTranslations('${locale}', data)`}
143
- console.log('[i18n HMR] Translations reloaded:', '${isPage ? "page" : "global"}', '${locale}'${isPage ? `, '${pageName}'` : ""})
144
- }
145
- catch (e) {
146
- console.warn('[i18n HMR] Failed to reload translations for', '${file}', e)
147
- }
148
- })
149
- }
150
- `.trim();
151
- }).join("\n");
152
- return `
153
- import { defineNuxtPlugin, useNuxtApp } from '#imports'
154
-
155
- export default defineNuxtPlugin(() => {
156
- ${accepts}
157
- })
158
- `.trim();
159
- }
160
-
161
128
  function shouldLocalizeRouteRulePath(originalPath) {
162
129
  const routeRulePath = originalPath.trim();
163
130
  if (!routeRulePath) return true;
@@ -168,8 +135,11 @@ function shouldLocalizeRouteRulePath(originalPath) {
168
135
  }
169
136
 
170
137
  function extractScriptContent(content) {
171
- const scriptMatch = content.match(/<script[^>]*>([\s\S]*?)<\/script>/);
172
- 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;
173
143
  }
174
144
  function removeTypeScriptTypes(scriptContent) {
175
145
  return scriptContent.replace(/\((\w+):[^)]*\)/g, "($1)");
@@ -214,17 +184,10 @@ function findDefineI18nRouteConfig(scriptContent) {
214
184
  const scriptWithoutImports = scriptContent.split("\n").filter((line) => !line.trim().startsWith("import ")).join("\n");
215
185
  const cleanScript = removeTypeScriptTypes(scriptWithoutImports);
216
186
  const safeScript = `
217
- // Mock $defineI18nRoute to prevent errors
218
187
  const $defineI18nRoute = () => {}
219
188
  const defineI18nRoute = () => {}
220
-
221
- // Mock process.env for conditional logic
222
189
  const process = { env: { NODE_ENV: 'development' } }
223
-
224
- // Execute the script content without imports and TypeScript types
225
190
  ${cleanScript}
226
-
227
- // Return the config object
228
191
  return (${cleanConfigStr})
229
192
  `;
230
193
  const configObject = Function(`"use strict";${safeScript}`)();
@@ -242,6 +205,37 @@ function findDefineI18nRouteConfig(scriptContent) {
242
205
  return null;
243
206
  }
244
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
+ }
245
239
  function extractDefineI18nRouteData(content, _filePath) {
246
240
  try {
247
241
  const scriptContent = extractScriptContent(content);
@@ -252,131 +246,79 @@ function extractDefineI18nRouteData(content, _filePath) {
252
246
  if (!configObject) {
253
247
  return null;
254
248
  }
255
- if (configObject.locales && typeof configObject.locales === "object" && !Array.isArray(configObject.locales)) {
256
- const localesObj = configObject.locales;
257
- const normalizedLocales = [];
258
- const normalizedLocaleRoutes = {};
259
- for (const [locale, value] of Object.entries(localesObj)) {
260
- normalizedLocales.push(locale);
261
- if (value && typeof value === "object" && "path" in value && typeof value.path === "string") {
262
- normalizedLocaleRoutes[locale] = value.path;
263
- }
264
- }
265
- return {
266
- ...configObject,
267
- locales: normalizedLocales,
268
- localeRoutes: configObject.localeRoutes || Object.keys(normalizedLocaleRoutes).length > 0 ? { ...configObject.localeRoutes, ...normalizedLocaleRoutes } : void 0
269
- };
270
- }
271
- if (Array.isArray(configObject.locales) && configObject.locales.length > 0 && typeof configObject.locales[0] === "object") {
272
- const normalizedLocales = configObject.locales.map((item) => {
273
- if (item && typeof item === "object" && "code" in item) {
274
- return item.code;
275
- }
276
- return String(item);
277
- });
278
- return {
279
- ...configObject,
280
- locales: normalizedLocales
281
- };
282
- }
283
- return configObject;
249
+ return normalizeDefineConfig(configObject);
284
250
  } catch {
285
251
  return null;
286
252
  }
287
253
  }
288
-
289
- function deepMergeTranslations(target, source) {
290
- if (!target || Object.keys(target).length === 0) return { ...source };
291
- const output = { ...target };
292
- for (const key in source) {
293
- if (key === "__proto__" || key === "constructor") continue;
294
- const src = source[key];
295
- const dst = output[key];
296
- if (src && typeof src === "object" && !Array.isArray(src) && dst && typeof dst === "object" && !Array.isArray(dst)) {
297
- output[key] = deepMergeTranslations(dst, src);
298
- } else {
299
- output[key] = src;
300
- }
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 "/";
301
259
  }
302
- return output;
260
+ const raw = withoutPagesPrefix.replace(/[/\\]index\.vue$/, "").replace(/\.vue$/, "").replace(/[/\\]$/, "").replace(/\\/g, "/");
261
+ return raw === "" ? "/" : raw;
303
262
  }
304
- const DEFAULT_CANONICAL_QUERY_WHITELIST = ["page", "sort", "filter", "search", "q", "query", "tag"];
305
- async function preMergeLocales(rootDirs, translationDirName, outputDir, locales, globalFallbackLocale, disablePageLocales) {
306
- if (existsSync(outputDir)) fs__default.rmSync(outputDir, { recursive: true, force: true });
307
- mkdirSync(outputDir, { recursive: true });
308
- const layerPaths = rootDirs.map((dir) => join(dir, translationDirName));
309
- const allFiles = /* @__PURE__ */ new Set();
310
- for (const lp of layerPaths) {
311
- if (!existsSync(lp)) continue;
312
- const files = await globby("**/*.json", { cwd: lp });
313
- files.forEach((f) => allFiles.add(f));
314
- }
315
- const merged = /* @__PURE__ */ new Map();
316
- for (const file of allFiles) {
317
- let content = {};
318
- for (const lp of layerPaths) {
319
- const fp = join(lp, file);
320
- if (existsSync(fp)) {
321
- try {
322
- content = deepMergeTranslations(content, JSON.parse(readFileSync(fp, "utf-8")));
323
- } catch {
324
- }
325
- }
326
- }
327
- merged.set(file, content);
328
- }
329
- const rootMap = /* @__PURE__ */ new Map();
330
- const pageMap = /* @__PURE__ */ new Map();
331
- for (const [file, content] of merged) {
332
- const dir = dirname(file);
333
- const locale = file.slice(file.lastIndexOf("/") + 1).replace(".json", "");
334
- if (dir === ".") {
335
- rootMap.set(locale, content);
336
- } else {
337
- if (!pageMap.has(dir)) pageMap.set(dir, /* @__PURE__ */ new Map());
338
- pageMap.get(dir).set(locale, content);
339
- }
340
- }
341
- const knownCodes = new Set(locales.map((l) => l.code));
342
- const applyFallback = (map) => {
343
- for (const locale of locales) {
344
- const chain = [globalFallbackLocale, locale.fallbackLocale, locale.code].filter((l) => !!l && knownCodes.has(l)).filter((v, i, arr) => arr.indexOf(v) === i);
345
- if (chain.length <= 1) continue;
346
- let result = {};
347
- for (const code of chain) {
348
- const data = map.get(code);
349
- if (data) result = deepMergeTranslations(result, data);
350
- }
351
- map.set(locale.code, result);
352
- }
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));
353
276
  };
354
- applyFallback(rootMap);
355
- for (const localeMap of pageMap.values()) {
356
- applyFallback(localeMap);
357
- }
358
- if (disablePageLocales || pageMap.size === 0) {
359
- const indexMap = /* @__PURE__ */ new Map();
360
- for (const [locale, data] of rootMap) {
361
- indexMap.set(locale, { ...data });
362
- }
363
- pageMap.set("pages/index", indexMap);
364
- } else {
365
- for (const [, localeMap] of pageMap) {
366
- for (const [locale, rootData] of rootMap) {
367
- const pageData = localeMap.get(locale);
368
- localeMap.set(locale, pageData ? deepMergeTranslations(rootData, pageData) : { ...rootData });
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
+ });
369
294
  }
295
+ writeMetaFile();
296
+ return null;
297
+ },
298
+ buildEnd() {
299
+ writeMetaFile();
370
300
  }
371
- }
372
- for (const [context, localeMap] of pageMap) {
373
- for (const [locale, data] of localeMap) {
374
- const targetPath = join(outputDir, context, `${locale}.json`);
375
- mkdirSync(dirname(targetPath), { recursive: true });
376
- writeFileSync(targetPath, JSON.stringify(data));
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
314
+ });
315
+ } catch {
377
316
  }
378
317
  }
318
+ return entries;
379
319
  }
320
+
321
+ const DEFAULT_CANONICAL_QUERY_WHITELIST = ["page", "sort", "filter", "search", "q", "query", "tag"];
380
322
  function generateI18nTypes() {
381
323
  return `
382
324
  import type {PluginsInjections} from "nuxt-i18n-micro";
@@ -425,6 +367,13 @@ const module = defineNuxtModule({
425
367
  apiBaseUrl: "_locales",
426
368
  apiBaseClientHost: void 0,
427
369
  apiBaseServerHost: void 0,
370
+ translationPayloads: {
371
+ mode: "premerged",
372
+ serverAssets: true,
373
+ serverHandler: true,
374
+ publicAssets: true,
375
+ prerenderRoutes: true
376
+ },
428
377
  routesLocaleLinks: {},
429
378
  globalLocaleRoutes: {},
430
379
  canonicalQueryWhitelist: void 0,
@@ -438,7 +387,7 @@ const module = defineNuxtModule({
438
387
  },
439
388
  async setup(options, nuxt) {
440
389
  const defaultLocale = process.env.DEFAULT_LOCALE ?? options.defaultLocale ?? "en";
441
- const isSSG = nuxt.options.nitro.static ?? nuxt.options._generate ?? false;
390
+ const isSSG = Boolean(nuxt.options.nitro?.static);
442
391
  const logger = useLogger("nuxt-i18n-micro");
443
392
  if (options.strategy === "no_prefix" && !options.localeCookie) {
444
393
  options.localeCookie = "user-locale";
@@ -452,27 +401,42 @@ const module = defineNuxtModule({
452
401
  const resolver = createResolver(import.meta.url);
453
402
  const rootDirs = nuxt.options._layers.map((layer) => layer.config.rootDir).reverse();
454
403
  const mergedLocalesDir = resolve(nuxt.options.buildDir, "i18n-merged");
404
+ const sourceLocalesDir = resolve(nuxt.options.buildDir, "i18n-source");
455
405
  const translationDirName = options.translationDir || "locales";
406
+ const translationPayloads = resolveTranslationPayloadOptions(options);
407
+ const translationAssetsDir = translationPayloads.mode === "source" ? sourceLocalesDir : mergedLocalesDir;
408
+ for (const warning of getTranslationPayloadMisconfigurationWarnings({
409
+ translationPayloads,
410
+ apiBaseClientHost: options.apiBaseClientHost,
411
+ apiBaseServerHost: options.apiBaseServerHost
412
+ })) {
413
+ logger.warn(warning.replace("[nuxt-i18n-micro] ", ""));
414
+ }
456
415
  const localeInfos = (options.locales ?? []).map(
457
416
  (l) => typeof l === "string" ? { code: l } : { code: l.code, fallbackLocale: l.fallbackLocale }
458
417
  );
459
418
  nuxt.hook("build:before", async () => {
460
- await preMergeLocales(rootDirs, translationDirName, mergedLocalesDir, localeInfos, options.fallbackLocale, options.disablePageLocales);
461
- logger.info(`Pre-merged translations from ${rootDirs.length} layer(s) into ${mergedLocalesDir}`);
419
+ if (translationPayloads.mode === "source") {
420
+ await buildTranslationSourceLayers(rootDirs, translationDirName, sourceLocalesDir);
421
+ logger.info(`Built compact translation source from ${rootDirs.length} layer(s) into ${sourceLocalesDir}`);
422
+ } else {
423
+ await preMergeLocales(rootDirs, translationDirName, mergedLocalesDir, localeInfos, options.fallbackLocale, options.disablePageLocales);
424
+ logger.info(`Pre-merged translations from ${rootDirs.length} layer(s) into ${mergedLocalesDir}`);
425
+ }
426
+ const payloadStats = scanTranslationPayloadDirectory(translationAssetsDir);
427
+ const sizeWarning = getTranslationPayloadSizeWarning(payloadStats, resolveTranslationPayloadWarningThresholds(options.translationPayloads));
428
+ if (sizeWarning) {
429
+ logger.warn(sizeWarning.replace("[nuxt-i18n-micro] ", ""));
430
+ }
462
431
  });
463
432
  const routeLocales = { ...options.routeLocales ?? {} };
464
433
  const globalLocaleRoutes = {};
465
434
  const routeDisableMeta = {};
466
- const pageFiles = await globby(["pages/**/*.vue", "app/pages/**/*.vue"], { cwd: nuxt.options.rootDir });
467
- for (const pageFile of pageFiles) {
468
- 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)) {
469
438
  try {
470
- const fileContent = readFileSync(fullPath, "utf-8");
471
- const config = extractDefineI18nRouteData(fileContent, fullPath);
472
- if (!config) continue;
473
439
  const { locales: extractedLocales, localeRoutes, disableMeta } = config;
474
- const raw = pageFile.replace(/^(app\/)?pages\//, "").replace(/\/index\.vue$/, "").replace(/\.vue$/, "").replace(/\/$/, "");
475
- const routePath = raw === "" || raw === "index" ? "/" : raw;
476
440
  if (extractedLocales) {
477
441
  if (Array.isArray(extractedLocales)) {
478
442
  routeLocales[routePath] = extractedLocales;
@@ -552,7 +516,6 @@ const module = defineNuxtModule({
552
516
  hashMode: nuxt.options?.router?.options?.hashMode ?? false,
553
517
  apiBaseUrl,
554
518
  apiBaseClientHost,
555
- apiBaseServerHost,
556
519
  isSSG,
557
520
  disablePageLocales: options.disablePageLocales ?? false,
558
521
  canonicalQueryWhitelist: options.canonicalQueryWhitelist ?? DEFAULT_CANONICAL_QUERY_WHITELIST,
@@ -562,6 +525,7 @@ const module = defineNuxtModule({
562
525
  globalLocaleRoutes: mergedGlobalLocaleRoutes,
563
526
  missingWarn: options.missingWarn ?? true,
564
527
  redirects: options.redirects !== false,
528
+ hooks: options.hooks !== false,
565
529
  hmr: options.hmr ?? true,
566
530
  localizedRouteNamePrefix: options.localizedRouteNamePrefix ?? "localized-",
567
531
  routesLocaleLinks: options.routesLocaleLinks ?? {},
@@ -569,7 +533,8 @@ const module = defineNuxtModule({
569
533
  debug: options.debug ?? false,
570
534
  customRegexMatcher: options.customRegexMatcher instanceof RegExp ? options.customRegexMatcher.source : options.customRegexMatcher,
571
535
  cacheMaxSize: options.cacheMaxSize ?? 0,
572
- cacheTtl: options.cacheTtl ?? 0
536
+ cacheTtl: options.cacheTtl ?? 0,
537
+ translationPayloadMode: translationPayloads.mode
573
538
  };
574
539
  const fullConfigJson = JSON.stringify(fullConfig);
575
540
  const strategyTemplate = addTemplate({
@@ -633,7 +598,8 @@ export function createI18nStrategy(router) {
633
598
  routesLocaleLinks: options.routesLocaleLinks ?? {},
634
599
  apiBaseUrl,
635
600
  apiBaseClientHost,
636
- apiBaseServerHost
601
+ apiBaseServerHost,
602
+ serverTranslationPreload: options.serverTranslationPreload ?? false
637
603
  };
638
604
  const privateConfigJson = JSON.stringify(privateConfig);
639
605
  const configTemplate = addTemplate({
@@ -675,14 +641,24 @@ export function getI18nPrivateConfig() { return __privateConfig }
675
641
  }
676
642
  addPlugin({
677
643
  src: resolver.resolve("./runtime/plugins/06.redirect"),
678
- mode: "all",
644
+ mode: "server",
679
645
  name: "i18n-plugin-redirect",
680
646
  order: 10
681
647
  });
682
- addServerHandler({
683
- route: `/${apiBaseUrl}/:page/:locale/data.json`,
684
- handler: resolver.resolve("./runtime/server/routes/i18n")
685
- });
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"));
656
+ if (translationPayloads.serverHandler) {
657
+ addServerHandler({
658
+ route: `/${apiBaseUrl}/:page/:locale/data.json`,
659
+ handler: resolver.resolve("./runtime/server/routes/i18n")
660
+ });
661
+ }
686
662
  if (options.components !== false) {
687
663
  addComponentsDir({
688
664
  path: resolver.resolve("./runtime/components"),
@@ -758,6 +734,7 @@ declare module '#i18n-internal/plural' {
758
734
  }
759
735
  });
760
736
  const addDataRoutes = (pages = []) => {
737
+ if (!translationPayloads.prerenderRoutes) return;
761
738
  const pagesForDataRoutes = pages.filter((p) => p.name !== void 0 && (!options.routesLocaleLinks || !options.routesLocaleLinks[p.name]));
762
739
  const dataRoutes = routeGenerator.generateDataRoutes(pagesForDataRoutes, apiBaseUrl, !!options.disablePageLocales);
763
740
  addPrerenderRoutes(dataRoutes);
@@ -773,47 +750,38 @@ declare module '#i18n-internal/plural' {
773
750
  if (options.disablePageLocales) {
774
751
  nuxt.hook("build:before", () => addDataRoutes([]));
775
752
  }
776
- nuxt.hook("vite:extendConfig", (viteConfig) => {
777
- const resolve2 = viteConfig.resolve ?? {};
778
- viteConfig.resolve = resolve2;
779
- const alias = resolve2.alias || {};
780
- resolve2.alias = Array.isArray(alias) ? [
781
- ...alias,
782
- { find: "#i18n-internal/plural", replacement: pluralTemplate.dst },
783
- { find: "#i18n-internal/strategy", replacement: strategyTemplate.dst },
784
- { find: "#i18n-internal/config", replacement: configTemplate.dst }
785
- ] : {
786
- ...alias,
787
- "#i18n-internal/plural": pluralTemplate.dst,
788
- "#i18n-internal/strategy": strategyTemplate.dst,
789
- "#i18n-internal/config": configTemplate.dst
790
- };
791
- });
753
+ addVitePlugin(
754
+ createDefineI18nRoutePlugin({
755
+ buildDir: nuxt.options.buildDir,
756
+ rootDirs
757
+ }).vite()
758
+ );
792
759
  nuxt.hook("nitro:config", (nitroConfig) => {
793
760
  nitroConfig.alias = nitroConfig.alias || {};
794
761
  nitroConfig.alias["#i18n-internal/plural"] = pluralTemplate.dst;
795
762
  nitroConfig.alias["#i18n-internal/strategy"] = strategyTemplate.dst;
796
763
  nitroConfig.alias["#i18n-internal/config"] = configTemplate.dst;
797
- nitroConfig.serverAssets = nitroConfig.serverAssets || [];
798
- nitroConfig.serverAssets.push({
799
- baseName: "i18n",
800
- dir: mergedLocalesDir
801
- });
802
- if (nitroConfig.imports) {
803
- nitroConfig.imports.presets = nitroConfig.imports.presets || [];
804
- nitroConfig.imports.presets.push({
805
- from: resolver.resolve("./runtime/server/utils/translation-server-middleware"),
806
- imports: ["useTranslationServerMiddleware"]
807
- });
808
- nitroConfig.imports.presets.push({
809
- from: resolver.resolve("./runtime/server/utils/locale-server-middleware"),
810
- imports: ["useLocaleServerMiddleware"]
764
+ if (translationPayloads.serverAssets) {
765
+ nitroConfig.serverAssets = nitroConfig.serverAssets || [];
766
+ nitroConfig.serverAssets.push({
767
+ baseName: "i18n",
768
+ dir: translationAssetsDir
811
769
  });
812
770
  }
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
+ };
813
782
  const routeRules = nuxt.options.routeRules || {};
814
783
  const strategy = options.strategy;
815
784
  if (routeRules && Object.keys(routeRules).length && !isNoPrefixStrategy(strategy)) {
816
- nitroConfig.routeRules = nitroConfig.routeRules || {};
817
785
  for (const [originalPath, ruleValue] of Object.entries(routeRules)) {
818
786
  if (!shouldLocalizeRouteRulePath(originalPath)) continue;
819
787
  routeGenerator.locales.forEach((localeObj) => {
@@ -835,34 +803,32 @@ declare module '#i18n-internal/plural' {
835
803
  });
836
804
  }
837
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
+ });
838
815
  });
839
816
  nuxt.hook("nitro:build:public-assets", (nitro) => {
840
817
  const isProd = nuxt.options.dev === false;
841
- if (isProd) {
842
- const publicDir = path.join(nitro.options.output.publicDir ?? "./dist", options.translationDir ?? "locales");
818
+ if (isProd && translationPayloads.publicAssets) {
819
+ const publicDir = resolveTranslationPayloadPublicDir(nitro.options.output.publicDir, options);
843
820
  try {
844
- if (existsSync(mergedLocalesDir)) {
845
- fs__default.cpSync(mergedLocalesDir, publicDir, { recursive: true });
846
- logger.log(`Pre-merged translations copied to public directory`);
821
+ if (existsSync(translationAssetsDir)) {
822
+ fs__default.cpSync(translationAssetsDir, publicDir, { recursive: true });
823
+ logger.log(`Translation payloads copied to public directory`);
847
824
  } else {
848
- logger.warn(`Pre-merged translations directory not found: ${mergedLocalesDir}`);
825
+ logger.warn(`Translation assets directory not found: ${translationAssetsDir}`);
849
826
  }
850
827
  } catch (err) {
851
828
  logger.error("Error copying translations:", err);
852
829
  }
853
830
  }
854
831
  });
855
- nuxt.hook("nitro:config", (nitroConfig) => {
856
- nitroConfig.plugins = nitroConfig.plugins || [];
857
- if (nuxt.options.dev && (options.hmr ?? true)) {
858
- nitroConfig.plugins.push(resolver.resolve("./runtime/server/plugins/watcher.dev"));
859
- }
860
- nitroConfig.handlers = nitroConfig.handlers || [];
861
- nitroConfig.handlers.unshift({
862
- middleware: true,
863
- handler: resolver.resolve("./runtime/server/middleware/i18n.global")
864
- });
865
- });
866
832
  nuxt.hook("prerender:routes", async (prerenderRoutes) => {
867
833
  if (isNoPrefixStrategy(options.strategy)) {
868
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