nuxt-i18n-micro 1.101.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +21 -27
  2. package/dist/client/200.html +8 -8
  3. package/dist/client/404.html +8 -8
  4. package/dist/client/_nuxt/{DTIY11lm.js → BYEpoBUk.js} +1 -1
  5. package/dist/client/_nuxt/{T7szCJDf.js → CBxwnKtU.js} +1 -1
  6. package/dist/client/_nuxt/{Df3aIBNA.js → CQupjgaS.js} +1 -1
  7. package/dist/client/_nuxt/{s7GTWWQo.js → XZXfxmri.js} +33 -33
  8. package/dist/client/_nuxt/builds/latest.json +1 -1
  9. package/dist/client/_nuxt/builds/meta/9577efa7-2d15-4fca-9d0d-3cd305cdd931.json +1 -0
  10. package/dist/client/_nuxt/error-404.Ba2SLv0l.css +1 -0
  11. package/dist/client/_nuxt/error-500.BqKd8Zt-.css +1 -0
  12. package/dist/client/index.html +8 -8
  13. package/dist/module.json +1 -1
  14. package/dist/module.mjs +96 -55
  15. package/dist/runtime/composables/useLocaleHead.d.ts +36 -33
  16. package/dist/runtime/composables/useLocaleHead.js +2 -13
  17. package/dist/runtime/plugins/01.plugin.d.ts +30 -0
  18. package/dist/runtime/plugins/01.plugin.js +52 -61
  19. package/dist/runtime/plugins/02.meta.js +19 -4
  20. package/dist/runtime/plugins/03.define.d.ts +3 -10
  21. package/dist/runtime/plugins/05.hooks.js +15 -7
  22. package/dist/runtime/server/plugins/watcher.dev.d.ts +2 -0
  23. package/dist/runtime/server/plugins/watcher.dev.js +60 -0
  24. package/dist/runtime/server/routes/get.js +62 -37
  25. package/dist/runtime/translation-server-middleware.js +11 -1
  26. package/dist/runtime/utils/route-utils.d.ts +9 -0
  27. package/dist/runtime/utils/route-utils.js +33 -0
  28. package/package.json +4 -4
  29. package/dist/client/_nuxt/builds/meta/48c4fe22-ef0c-4134-a6f0-f5d3d316c514.json +0 -1
  30. package/dist/client/_nuxt/error-404.B2VbLYbY.css +0 -1
  31. package/dist/client/_nuxt/error-500.DGwSTbEi.css +0 -1
@@ -1 +1 @@
1
- {"id":"48c4fe22-ef0c-4134-a6f0-f5d3d316c514","timestamp":1759992972613}
1
+ {"id":"9577efa7-2d15-4fca-9d0d-3cd305cdd931","timestamp":1761831387055}
@@ -0,0 +1 @@
1
+ {"id":"9577efa7-2d15-4fca-9d0d-3cd305cdd931","timestamp":1761831387055,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
@@ -0,0 +1 @@
1
+ .spotlight[data-v-f75a8935]{background:linear-gradient(45deg,#00dc82,#36e4da 50%,#0047e1);bottom:-30vh;filter:blur(20vh);height:40vh}.gradient-border[data-v-f75a8935]{-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border-radius:.5rem;position:relative}@media (prefers-color-scheme:light){.gradient-border[data-v-f75a8935]{background-color:#ffffff4d}.gradient-border[data-v-f75a8935]:before{background:linear-gradient(90deg,#e2e2e2,#e2e2e2 25%,#00dc82,#36e4da 75%,#0047e1)}}@media (prefers-color-scheme:dark){.gradient-border[data-v-f75a8935]{background-color:#1414144d}.gradient-border[data-v-f75a8935]:before{background:linear-gradient(90deg,#303030,#303030 25%,#00dc82,#36e4da 75%,#0047e1)}}.gradient-border[data-v-f75a8935]:before{background-size:400% auto;border-radius:.5rem;content:"";inset:0;-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:.5;padding:2px;position:absolute;transition:background-position .3s ease-in-out,opacity .2s ease-in-out;width:100%}.gradient-border[data-v-f75a8935]:hover:before{background-position:-50% 0;opacity:1}.fixed[data-v-f75a8935]{position:fixed}.left-0[data-v-f75a8935]{left:0}.right-0[data-v-f75a8935]{right:0}.z-10[data-v-f75a8935]{z-index:10}.z-20[data-v-f75a8935]{z-index:20}.grid[data-v-f75a8935]{display:grid}.mb-16[data-v-f75a8935]{margin-bottom:4rem}.mb-8[data-v-f75a8935]{margin-bottom:2rem}.max-w-520px[data-v-f75a8935]{max-width:520px}.min-h-screen[data-v-f75a8935]{min-height:100vh}.w-full[data-v-f75a8935]{width:100%}.flex[data-v-f75a8935]{display:flex}.cursor-pointer[data-v-f75a8935]{cursor:pointer}.place-content-center[data-v-f75a8935]{place-content:center}.items-center[data-v-f75a8935]{align-items:center}.justify-center[data-v-f75a8935]{justify-content:center}.overflow-hidden[data-v-f75a8935]{overflow:hidden}.bg-white[data-v-f75a8935]{--un-bg-opacity:1;background-color:rgb(255 255 255/var(--un-bg-opacity))}.px-4[data-v-f75a8935]{padding-left:1rem;padding-right:1rem}.px-8[data-v-f75a8935]{padding-left:2rem;padding-right:2rem}.py-2[data-v-f75a8935]{padding-bottom:.5rem;padding-top:.5rem}.text-center[data-v-f75a8935]{text-align:center}.text-8xl[data-v-f75a8935]{font-size:6rem;line-height:1}.text-xl[data-v-f75a8935]{font-size:1.25rem;line-height:1.75rem}.text-black[data-v-f75a8935]{--un-text-opacity:1;color:rgb(0 0 0/var(--un-text-opacity))}.font-light[data-v-f75a8935]{font-weight:300}.font-medium[data-v-f75a8935]{font-weight:500}.leading-tight[data-v-f75a8935]{line-height:1.25}.font-sans[data-v-f75a8935]{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.antialiased[data-v-f75a8935]{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (prefers-color-scheme:dark){.dark\:bg-black[data-v-f75a8935]{--un-bg-opacity:1;background-color:rgb(0 0 0/var(--un-bg-opacity))}.dark\:text-white[data-v-f75a8935]{--un-text-opacity:1;color:rgb(255 255 255/var(--un-text-opacity))}}@media (min-width:640px){.sm\:px-0[data-v-f75a8935]{padding-left:0;padding-right:0}.sm\:px-6[data-v-f75a8935]{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-3[data-v-f75a8935]{padding-bottom:.75rem;padding-top:.75rem}.sm\:text-4xl[data-v-f75a8935]{font-size:2.25rem;line-height:2.5rem}.sm\:text-xl[data-v-f75a8935]{font-size:1.25rem;line-height:1.75rem}}
@@ -0,0 +1 @@
1
+ .spotlight[data-v-8bb04cde]{background:linear-gradient(45deg,#00dc82,#36e4da 50%,#0047e1);filter:blur(20vh)}.fixed[data-v-8bb04cde]{position:fixed}.-bottom-1\/2[data-v-8bb04cde]{bottom:-50%}.left-0[data-v-8bb04cde]{left:0}.right-0[data-v-8bb04cde]{right:0}.grid[data-v-8bb04cde]{display:grid}.mb-16[data-v-8bb04cde]{margin-bottom:4rem}.mb-8[data-v-8bb04cde]{margin-bottom:2rem}.h-1\/2[data-v-8bb04cde]{height:50%}.max-w-520px[data-v-8bb04cde]{max-width:520px}.min-h-screen[data-v-8bb04cde]{min-height:100vh}.place-content-center[data-v-8bb04cde]{place-content:center}.overflow-hidden[data-v-8bb04cde]{overflow:hidden}.bg-white[data-v-8bb04cde]{--un-bg-opacity:1;background-color:rgb(255 255 255/var(--un-bg-opacity))}.px-8[data-v-8bb04cde]{padding-left:2rem;padding-right:2rem}.text-center[data-v-8bb04cde]{text-align:center}.text-8xl[data-v-8bb04cde]{font-size:6rem;line-height:1}.text-xl[data-v-8bb04cde]{font-size:1.25rem;line-height:1.75rem}.text-black[data-v-8bb04cde]{--un-text-opacity:1;color:rgb(0 0 0/var(--un-text-opacity))}.font-light[data-v-8bb04cde]{font-weight:300}.font-medium[data-v-8bb04cde]{font-weight:500}.leading-tight[data-v-8bb04cde]{line-height:1.25}.font-sans[data-v-8bb04cde]{font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji}.antialiased[data-v-8bb04cde]{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (prefers-color-scheme:dark){.dark\:bg-black[data-v-8bb04cde]{--un-bg-opacity:1;background-color:rgb(0 0 0/var(--un-bg-opacity))}.dark\:text-white[data-v-8bb04cde]{--un-text-opacity:1;color:rgb(255 255 255/var(--un-text-opacity))}}@media (min-width:640px){.sm\:px-0[data-v-8bb04cde]{padding-left:0;padding-right:0}.sm\:text-4xl[data-v-8bb04cde]{font-size:2.25rem;line-height:2.5rem}}
@@ -1,12 +1,12 @@
1
1
  <!DOCTYPE html><html><head><meta charset="utf-8">
2
2
  <meta name="viewport" content="width=device-width, initial-scale=1">
3
3
  <link rel="stylesheet" href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/entry.Cq9tULRb.css" crossorigin>
4
- <link rel="modulepreload" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/s7GTWWQo.js">
4
+ <link rel="modulepreload" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/XZXfxmri.js">
5
5
  <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/l5rcX3cq.js">
6
- <link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-404.B2VbLYbY.css">
7
- <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/Df3aIBNA.js">
8
- <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/T7szCJDf.js">
9
- <link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-500.DGwSTbEi.css">
10
- <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/DTIY11lm.js">
11
- <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/s7GTWWQo.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1759992978390,false]</script>
12
- <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"48c4fe22-ef0c-4134-a6f0-f5d3d316c514",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
6
+ <link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-404.Ba2SLv0l.css">
7
+ <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/CQupjgaS.js">
8
+ <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/CBxwnKtU.js">
9
+ <link rel="prefetch" as="style" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/error-500.BqKd8Zt-.css">
10
+ <link rel="prefetch" as="script" crossorigin href="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/BYEpoBUk.js">
11
+ <script type="module" src="/__NUXT_DEVTOOLS_I18N_BASE__/_nuxt/XZXfxmri.js" crossorigin></script></head><body><div id="__nuxt"></div><div id="teleports"></div><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1761831392702,false]</script>
12
+ <script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__NUXT_DEVTOOLS_I18N_BASE__/",buildId:"9577efa7-2d15-4fca-9d0d-3cd305cdd931",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script></body></html>
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-i18n-micro",
3
3
  "configKey": "i18n",
4
- "version": "1.101.2",
4
+ "version": "2.0.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -2,7 +2,6 @@ import path, { resolve, join } from 'node:path';
2
2
  import * as fs from 'node:fs';
3
3
  import fs__default, { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
4
4
  import { useNuxt, defineNuxtModule, useLogger, createResolver, addTemplate, addImportsDir, addPlugin, addServerHandler, addComponentsDir, addTypeTemplate, addPrerenderRoutes } from '@nuxt/kit';
5
- import { watch } from 'chokidar';
6
5
  import { isPrefixAndDefaultStrategy, isPrefixStrategy, isNoPrefixStrategy, isPrefixExceptDefaultStrategy, withPrefixStrategy } from 'nuxt-i18n-micro-core';
7
6
  import { fileURLToPath } from 'node:url';
8
7
  import { onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
@@ -189,39 +188,15 @@ function extractDefineI18nRouteData(content, _filePath) {
189
188
  try {
190
189
  const scriptContent = extractScriptContent(content);
191
190
  if (!scriptContent) {
192
- return { locales: null, localeRoutes: null };
191
+ return null;
193
192
  }
194
193
  const configObject = findDefineI18nRouteConfig(scriptContent);
195
194
  if (!configObject) {
196
- return { locales: null, localeRoutes: null };
197
- }
198
- let locales = null;
199
- if (configObject.locales) {
200
- if (Array.isArray(configObject.locales)) {
201
- locales = configObject.locales.map((item) => {
202
- if (typeof item === "string") {
203
- return item;
204
- } else if (typeof item === "object" && item !== null && item.code) {
205
- return item.code;
206
- }
207
- return null;
208
- }).filter((item) => item !== null);
209
- } else if (typeof configObject.locales === "object") {
210
- locales = Object.keys(configObject.locales).filter(
211
- (key) => key !== "meta" && key !== "path" && key !== "title" && key !== "description"
212
- );
213
- }
214
- }
215
- let localeRoutes = null;
216
- if (configObject.localeRoutes && typeof configObject.localeRoutes === "object") {
217
- const isValid = Object.values(configObject.localeRoutes).every((value) => typeof value === "string");
218
- if (isValid) {
219
- localeRoutes = configObject.localeRoutes;
220
- }
195
+ return null;
221
196
  }
222
- return { locales, localeRoutes };
197
+ return configObject;
223
198
  } catch {
224
- return { locales: null, localeRoutes: null };
199
+ return null;
225
200
  }
226
201
  }
227
202
  const normalizePath = (routePath) => {
@@ -600,7 +575,14 @@ class PageManager {
600
575
  ...page,
601
576
  children: this.createLocalizedChildren(originalChildren, page.path, localeCodes, true, false, parentLocale, { [firstLocale]: customPath }),
602
577
  path: routePath,
603
- name: routeName
578
+ name: routeName,
579
+ alias: [],
580
+ // remove alias to prevent infinite recursion
581
+ meta: {
582
+ ...page.meta,
583
+ alias: []
584
+ // remove alias to prevent infinite recursion
585
+ }
604
586
  };
605
587
  }
606
588
  buildLocalizedRouteName(baseName, locale, modifyName, forceLocaleSuffixOrCustom = false) {
@@ -709,6 +691,7 @@ const module = defineNuxtModule({
709
691
  disablePageLocales: false,
710
692
  disableWatcher: false,
711
693
  disableUpdater: false,
694
+ // experimental kept in runtimeConfig only to avoid type drift here
712
695
  noPrefixRedirect: false,
713
696
  includeDefaultLocaleRoute: void 0,
714
697
  fallbackLocale: void 0,
@@ -749,19 +732,29 @@ const module = defineNuxtModule({
749
732
  const localeManager = new LocaleManager(options, rootDirs);
750
733
  const routeLocales = {};
751
734
  const globalLocaleRoutes = {};
735
+ const routeDisableMeta = {};
752
736
  const pageFiles = await globby("pages/**/*.vue", { cwd: nuxt.options.rootDir });
753
737
  for (const pageFile of pageFiles) {
754
738
  const fullPath = join(nuxt.options.rootDir, pageFile);
755
739
  try {
756
740
  const fileContent = readFileSync(fullPath, "utf-8");
757
- const { locales: extractedLocales, localeRoutes } = extractDefineI18nRouteData(fileContent, fullPath);
741
+ const config = extractDefineI18nRouteData(fileContent, fullPath);
742
+ if (!config) continue;
743
+ const { locales: extractedLocales, localeRoutes, disableMeta } = config;
758
744
  const routePath = pageFile.replace(/^pages\//, "/").replace(/\/index\.vue$/, "").replace(/\.vue$/, "").replace(/\/$/, "") || "/";
759
745
  if (extractedLocales) {
760
- routeLocales[routePath] = extractedLocales;
746
+ if (Array.isArray(extractedLocales)) {
747
+ routeLocales[routePath] = extractedLocales;
748
+ } else if (typeof extractedLocales === "object") {
749
+ routeLocales[routePath] = Object.keys(extractedLocales);
750
+ }
761
751
  }
762
752
  if (localeRoutes) {
763
753
  globalLocaleRoutes[routePath] = localeRoutes;
764
754
  }
755
+ if (disableMeta !== void 0) {
756
+ routeDisableMeta[routePath] = disableMeta;
757
+ }
765
758
  } catch {
766
759
  }
767
760
  }
@@ -782,9 +775,6 @@ const module = defineNuxtModule({
782
775
  localeCookie: options.localeCookie ?? "user-locale",
783
776
  autoDetectPath: options.autoDetectPath ?? "/",
784
777
  strategy: options.strategy ?? "prefix_except_default",
785
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
786
- // @ts-ignore
787
- routesLocaleLinks: options.routesLocaleLinks ?? {},
788
778
  dateBuild: Date.now(),
789
779
  hashMode: nuxt.options?.router?.options?.hashMode ?? false,
790
780
  apiBaseUrl,
@@ -795,8 +785,14 @@ const module = defineNuxtModule({
795
785
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
796
786
  // @ts-ignore
797
787
  routeLocales,
788
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
789
+ // @ts-ignore
790
+ routeDisableMeta,
798
791
  experimental: {
799
- i18nPreviousPageFallback: options.experimental?.i18nPreviousPageFallback ?? false
792
+ i18nPreviousPageFallback: options.experimental?.i18nPreviousPageFallback ?? false,
793
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
794
+ // @ts-ignore
795
+ hmr: options.experimental?.hmr ?? true
800
796
  }
801
797
  };
802
798
  if (typeof options.customRegexMatcher !== "undefined") {
@@ -813,7 +809,10 @@ const module = defineNuxtModule({
813
809
  // @ts-ignore
814
810
  fallbackLocale: options.fallbackLocale ?? void 0,
815
811
  translationDir: options.translationDir ?? "locales",
816
- customRegexMatcher: options.customRegexMatcher
812
+ customRegexMatcher: options.customRegexMatcher,
813
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
814
+ // @ts-ignore
815
+ routesLocaleLinks: options.routesLocaleLinks ?? {}
817
816
  };
818
817
  addImportsDir(resolver.resolve("./runtime/composables"));
819
818
  if (process.env && process.env.TEST) {
@@ -873,12 +872,66 @@ const module = defineNuxtModule({
873
872
  pathPrefix: false,
874
873
  extensions: ["vue"]
875
874
  });
875
+ if (nuxt.options.dev && (options.experimental?.hmr ?? true)) {
876
+ const translationsDir = join(nuxt.options.rootDir, options.translationDir || "locales");
877
+ const files = await globby(["**/*.json"], { cwd: translationsDir, absolute: true });
878
+ const tpl = addTemplate({
879
+ filename: "i18n-hmr-plugin.mjs",
880
+ write: true,
881
+ getContents: () => generateHmrPlugin(files.map((f) => f.replace(/\\/g, "/")))
882
+ });
883
+ addPlugin({
884
+ src: tpl.dst,
885
+ mode: "client",
886
+ name: "i18n-hmr-plugin",
887
+ order: 10
888
+ });
889
+ }
876
890
  if (options.types) {
877
891
  addTypeTemplate({
878
892
  filename: "types/i18n-plugin.d.ts",
879
893
  getContents: () => generateI18nTypes()
880
894
  });
881
895
  }
896
+ function generateHmrPlugin(files) {
897
+ const accepts = files.map((file) => {
898
+ const isPage = /\/pages\//.test(file);
899
+ let pageName = "";
900
+ let locale = "";
901
+ if (isPage) {
902
+ const m = /\/pages\/([^/]+)\/([^/]+)\.json$/.exec(file);
903
+ pageName = m?.[1] || "";
904
+ locale = m?.[2] || "";
905
+ } else {
906
+ const m = /\/([^/]+)\.json$/.exec(file);
907
+ locale = m?.[1] || "";
908
+ }
909
+ return `
910
+ if (import.meta.hot) {
911
+ import.meta.hot.accept('${file}', async (mod) => {
912
+ const nuxtApp = useNuxtApp()
913
+ const data = (mod && typeof mod === 'object' && Object.prototype.hasOwnProperty.call(mod, 'default'))
914
+ ? mod.default
915
+ : mod
916
+ try {
917
+ ${isPage ? `await nuxtApp.$loadPageTranslations('${locale}', '${pageName}', data)` : `await nuxtApp.$loadTranslations('${locale}', data)`}
918
+ console.log('[i18n HMR] Translations reloaded:', '${isPage ? "page" : "global"}', '${locale}'${isPage ? `, '${pageName}'` : ""})
919
+ }
920
+ catch (e) {
921
+ console.warn('[i18n HMR] Failed to reload translations for', '${file}', e)
922
+ }
923
+ })
924
+ }
925
+ `.trim();
926
+ }).join("\n");
927
+ return `
928
+ import { defineNuxtPlugin, useNuxtApp } from '#imports'
929
+
930
+ export default defineNuxtPlugin(() => {
931
+ ${accepts}
932
+ })
933
+ `.trim();
934
+ }
882
935
  nuxt.hook("pages:resolved", (pages) => {
883
936
  const prerenderRoutes = [];
884
937
  const routeRules = nuxt.options.routeRules || {};
@@ -1035,24 +1088,12 @@ const module = defineNuxtModule({
1035
1088
  }
1036
1089
  }
1037
1090
  });
1038
- if (!options.disableUpdater) {
1039
- nuxt.hook("nitro:build:before", async (_nitro) => {
1040
- const isProd = nuxt.options.dev === false;
1041
- if (!isProd) {
1042
- const translationPath = path.resolve(nuxt.options.rootDir, options.translationDir);
1043
- logger.log("\u2139 add file watcher: " + translationPath);
1044
- const watcherEvent = async (path2) => {
1045
- await watcher.close();
1046
- logger.log("\u21BB update store item: " + path2);
1047
- nuxt.callHook("restart");
1048
- };
1049
- const watcher = watch(translationPath, { depth: 2, persistent: true }).on("change", watcherEvent);
1050
- nuxt.hook("close", () => {
1051
- watcher.close();
1052
- });
1053
- }
1054
- });
1055
- }
1091
+ nuxt.hook("nitro:config", (nitroConfig) => {
1092
+ if (nuxt.options.dev && (options.experimental?.hmr ?? true) && !options.disableUpdater) {
1093
+ nitroConfig.plugins = nitroConfig.plugins || [];
1094
+ nitroConfig.plugins.push(resolver.resolve("./runtime/server/plugins/watcher.dev"));
1095
+ }
1096
+ });
1056
1097
  nuxt.hook("prerender:routes", async (prerenderRoutes) => {
1057
1098
  if (isNoPrefixStrategy(options.strategy)) {
1058
1099
  return;
@@ -22,37 +22,40 @@ export declare const useLocaleHead: ({ addDirAttribute, identifierAttribute, add
22
22
  identifierAttribute?: string | undefined;
23
23
  addSeoAttributes?: boolean | undefined;
24
24
  baseUrl?: string | undefined;
25
- }) => import("vue").Ref<{
26
- htmlAttrs: {
27
- lang?: string | undefined;
28
- dir?: "ltr" | "rtl" | "auto" | undefined;
29
- };
30
- link: {
31
- [x: string]: string | undefined;
32
- rel: string;
33
- href: string;
34
- hreflang?: string | undefined;
35
- }[];
36
- meta: {
37
- [x: string]: string;
38
- property: string;
39
- content: string;
40
- }[];
41
- }, MetaObject | {
42
- htmlAttrs: {
43
- lang?: string | undefined;
44
- dir?: "ltr" | "rtl" | "auto" | undefined;
45
- };
46
- link: {
47
- [x: string]: string | undefined;
48
- rel: string;
49
- href: string;
50
- hreflang?: string | undefined;
51
- }[];
52
- meta: {
53
- [x: string]: string;
54
- property: string;
55
- content: string;
56
- }[];
57
- }>;
25
+ }) => {
26
+ metaObject: import("vue").Ref<{
27
+ htmlAttrs: {
28
+ lang?: string | undefined;
29
+ dir?: "ltr" | "rtl" | "auto" | undefined;
30
+ };
31
+ link: {
32
+ [x: string]: string | undefined;
33
+ rel: string;
34
+ href: string;
35
+ hreflang?: string | undefined;
36
+ }[];
37
+ meta: {
38
+ [x: string]: string;
39
+ property: string;
40
+ content: string;
41
+ }[];
42
+ }, MetaObject | {
43
+ htmlAttrs: {
44
+ lang?: string | undefined;
45
+ dir?: "ltr" | "rtl" | "auto" | undefined;
46
+ };
47
+ link: {
48
+ [x: string]: string | undefined;
49
+ rel: string;
50
+ href: string;
51
+ hreflang?: string | undefined;
52
+ }[];
53
+ meta: {
54
+ [x: string]: string;
55
+ property: string;
56
+ content: string;
57
+ }[];
58
+ }>;
59
+ updateMeta: () => void;
60
+ };
58
61
  export {};
@@ -1,6 +1,6 @@
1
1
  import { joinURL, parseURL, withQuery } from "ufo";
2
2
  import { isPrefixExceptDefaultStrategy, isNoPrefixStrategy } from "nuxt-i18n-micro-core";
3
- import { unref, useRoute, useRuntimeConfig, watch, onUnmounted, ref, useNuxtApp } from "#imports";
3
+ import { unref, useRoute, useRuntimeConfig, ref, useNuxtApp } from "#imports";
4
4
  import { findAllowedLocalesForRoute } from "../utils/route-utils.js";
5
5
  export const useLocaleHead = ({ addDirAttribute = true, identifierAttribute = "id", addSeoAttributes = true, baseUrl = "/" } = {}) => {
6
6
  const metaObject = ref({
@@ -101,16 +101,5 @@ export const useLocaleHead = ({ addDirAttribute = true, identifierAttribute = "i
101
101
  metaObject.value.meta = [ogLocaleMeta, ogUrlMeta, ...alternateOgLocalesMeta];
102
102
  metaObject.value.link = [canonicalLink, ...alternateLinks];
103
103
  }
104
- if (import.meta.client) {
105
- const route = useRoute();
106
- const stop = watch(
107
- () => route.fullPath,
108
- () => updateMeta(),
109
- { immediate: true }
110
- );
111
- onUnmounted(() => stop());
112
- } else {
113
- updateMeta();
114
- }
115
- return metaObject;
104
+ return { metaObject, updateMeta };
116
105
  };
@@ -1,4 +1,5 @@
1
1
  import type { RouteLocationNormalizedLoaded, RouteLocationRaw, RouteLocationResolved, RouteLocationResolvedGeneric, RouteLocationNamedRaw } from 'vue-router';
2
+ import { useTranslationHelper } from 'nuxt-i18n-micro-core';
2
3
  import type { Locale, I18nRouteParams, Params, Translations, CleanTranslation } from 'nuxt-i18n-micro-types';
3
4
  declare const _default: import("nuxt/app").Plugin<{
4
5
  i18n: undefined;
@@ -28,6 +29,20 @@ declare const _default: import("nuxt/app").Plugin<{
28
29
  localePath: (to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string) => string;
29
30
  setI18nRouteParams: (value: I18nRouteParams) => I18nRouteParams;
30
31
  loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
32
+ helper: {
33
+ hasCache(locale: string, page: string): boolean;
34
+ getCache(locale: string, routeName: string): Map<string, unknown> | undefined;
35
+ setCache(locale: string, routeName: string, cache: Map<string, Translations | unknown>): void;
36
+ mergeTranslation(locale: string, routeName: string, newTranslations: Translations, force?: boolean): void;
37
+ mergeGlobalTranslation(locale: string, newTranslations: Translations, force?: boolean): void;
38
+ hasGeneralTranslation(locale: string): boolean;
39
+ hasPageTranslation(locale: string, routeName: string): boolean;
40
+ hasTranslation: (locale: string, key: string) => boolean;
41
+ getTranslation: <T = unknown>(locale: string, routeName: string, key: string) => T | null;
42
+ loadPageTranslations(locale: string, routeName: string, translations: Translations): Promise<void>;
43
+ loadTranslations(locale: string, translations: Translations): Promise<void>;
44
+ clearCache(): void;
45
+ };
31
46
  }> & import("nuxt/app").ObjectPlugin<{
32
47
  i18n: undefined;
33
48
  __micro: boolean;
@@ -56,6 +71,20 @@ declare const _default: import("nuxt/app").Plugin<{
56
71
  localePath: (to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string) => string;
57
72
  setI18nRouteParams: (value: I18nRouteParams) => I18nRouteParams;
58
73
  loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
74
+ helper: {
75
+ hasCache(locale: string, page: string): boolean;
76
+ getCache(locale: string, routeName: string): Map<string, unknown> | undefined;
77
+ setCache(locale: string, routeName: string, cache: Map<string, Translations | unknown>): void;
78
+ mergeTranslation(locale: string, routeName: string, newTranslations: Translations, force?: boolean): void;
79
+ mergeGlobalTranslation(locale: string, newTranslations: Translations, force?: boolean): void;
80
+ hasGeneralTranslation(locale: string): boolean;
81
+ hasPageTranslation(locale: string, routeName: string): boolean;
82
+ hasTranslation: (locale: string, key: string) => boolean;
83
+ getTranslation: <T = unknown>(locale: string, routeName: string, key: string) => T | null;
84
+ loadPageTranslations(locale: string, routeName: string, translations: Translations): Promise<void>;
85
+ loadTranslations(locale: string, translations: Translations): Promise<void>;
86
+ clearCache(): void;
87
+ };
59
88
  }>;
60
89
  export default _default;
61
90
  export interface PluginsInjections {
@@ -83,4 +112,5 @@ export interface PluginsInjections {
83
112
  $localePath: (to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string) => string;
84
113
  $setI18nRouteParams: (value: I18nRouteParams) => I18nRouteParams;
85
114
  $loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
115
+ helper: ReturnType<typeof useTranslationHelper>;
86
116
  }
@@ -1,7 +1,7 @@
1
1
  import { useTranslationHelper, interpolate, isNoPrefixStrategy, RouteService, FormatService } from "nuxt-i18n-micro-core";
2
- import { useRouter, useCookie, useState, unref, navigateTo, defineNuxtPlugin, useRuntimeConfig } from "#imports";
2
+ import { useRouter, useCookie, unref, navigateTo, defineNuxtPlugin, useRuntimeConfig, createError } from "#imports";
3
+ import { useState } from "#app";
3
4
  import { plural } from "#build/i18n.plural.mjs";
4
- const i18nHelper = useTranslationHelper();
5
5
  const isDev = process.env.NODE_ENV !== "production";
6
6
  export default defineNuxtPlugin(async (nuxtApp) => {
7
7
  const config = useRuntimeConfig();
@@ -9,6 +9,17 @@ export default defineNuxtPlugin(async (nuxtApp) => {
9
9
  const apiBaseUrl = i18nConfig.apiBaseUrl ?? "_locales";
10
10
  const router = useRouter();
11
11
  const runtimeConfig = useRuntimeConfig();
12
+ const generalLocaleCache = useState("i18n-general-cache", () => ({}));
13
+ const routeLocaleCache = useState("i18n-route-cache", () => ({}));
14
+ const dynamicTranslationsCaches = useState("i18n-dynamic-caches", () => []);
15
+ const serverTranslationCache = useState("i18n-server-cache", () => ({}));
16
+ const translationCaches = {
17
+ generalLocaleCache,
18
+ routeLocaleCache,
19
+ dynamicTranslationsCaches,
20
+ serverTranslationCache
21
+ };
22
+ const i18nHelper = useTranslationHelper(translationCaches);
12
23
  let hashLocaleDefault = null;
13
24
  let noPrefixDefault = null;
14
25
  if (i18nConfig.hashMode) {
@@ -39,75 +50,56 @@ export default defineNuxtPlugin(async (nuxtApp) => {
39
50
  previousPageInfo.value = null;
40
51
  }
41
52
  });
42
- const loadTranslationsIfNeeded = async (locale, routeName, path) => {
43
- try {
44
- if (!i18nHelper.hasPageTranslation(locale, routeName)) {
45
- let fRouteName = routeName;
46
- if (i18nConfig.routesLocaleLinks && fRouteName && i18nConfig.routesLocaleLinks[fRouteName]) {
47
- const newRouteName = i18nConfig.routesLocaleLinks[fRouteName];
48
- if (newRouteName) {
49
- fRouteName = newRouteName;
50
- }
51
- }
52
- if (!fRouteName || fRouteName === "") {
53
- console.warn(`[nuxt-i18n-next] The page name is missing in the path: ${path}. Please ensure that definePageMeta({ name: 'pageName' }) is set.`);
54
- return;
55
- }
56
- const url = `/${apiBaseUrl}/${fRouteName}/${locale}/data.json`.replace(/\/{2,}/g, "/");
57
- const data = await $fetch(url, {
58
- baseURL: runtimeConfig.app.baseURL,
59
- params: {
60
- v: i18nConfig.dateBuild
61
- }
62
- });
63
- await i18nHelper.loadPageTranslations(locale, routeName, data ?? {});
64
- }
65
- } catch (_error) {
66
- }
67
- };
68
- async function loadGlobalTranslations(to) {
53
+ async function loadPageAndGlobalTranslations(to) {
69
54
  let locale = routeService.getCurrentLocale(to);
70
55
  if (i18nConfig.hashMode) {
71
- locale = await nuxtApp.runWithContext(() => {
72
- return useCookie("hash-locale", { default: () => locale }).value;
73
- });
56
+ locale = await nuxtApp.runWithContext(() => useCookie("hash-locale", { default: () => locale }).value);
74
57
  }
75
58
  if (isNoPrefixStrategy(i18nConfig.strategy)) {
76
- locale = await nuxtApp.runWithContext(() => {
77
- return useCookie("no-prefix-locale", { default: () => locale }).value;
78
- });
59
+ locale = await nuxtApp.runWithContext(() => useCookie("no-prefix-locale", { default: () => locale }).value);
60
+ }
61
+ const routeName = routeService.getRouteName(to, locale);
62
+ if (!routeName) {
63
+ return;
64
+ }
65
+ if (i18nHelper.hasPageTranslation(locale, routeName)) {
66
+ if (isDev) {
67
+ console.log(`[DEBUG] Cache HIT for '${locale}:${routeName}'. Skipping fetch.`);
68
+ }
69
+ return;
79
70
  }
80
- if (!i18nHelper.hasGeneralTranslation(locale)) {
81
- const url = `/${apiBaseUrl}/general/${locale}/data.json`.replace(/\/{2,}/g, "/");
71
+ const url = `/${apiBaseUrl}/${routeName}/${locale}/data.json`.replace(/\/{2,}/g, "/");
72
+ try {
82
73
  const data = await $fetch(url, {
83
74
  baseURL: runtimeConfig.app.baseURL,
84
- params: {
85
- v: i18nConfig.dateBuild
86
- }
75
+ params: { v: i18nConfig.dateBuild }
87
76
  });
88
- await i18nHelper.loadTranslations(locale, data ?? {});
89
- }
90
- if (!i18nConfig.disablePageLocales) {
91
- const locale2 = routeService.getCurrentLocale(to);
92
- const routeName = routeService.getRouteName(to, locale2);
93
- await loadTranslationsIfNeeded(locale2, routeName, to.fullPath);
77
+ await i18nHelper.loadPageTranslations(locale, routeName, data ?? {});
78
+ } catch (e) {
79
+ if (isDev) {
80
+ console.error(`[i18n] Failed to load translations for ${routeName}/${locale}`, e);
81
+ }
82
+ throw createError({ statusCode: 404, statusMessage: "Page Not Found" });
94
83
  }
95
84
  }
96
85
  router.beforeEach(async (to, from, next) => {
97
- if (to.path !== from.path || isNoPrefixStrategy(i18nConfig.strategy)) {
98
- if (import.meta.client && from.path !== to.path && enablePreviousPageFallback) {
99
- const fromLocale = routeService.getCurrentLocale(from);
100
- const fromRouteName = routeService.getRouteName(from, fromLocale);
101
- previousPageInfo.value = { locale: fromLocale, routeName: fromRouteName };
102
- console.log(`Saved previous page info for cleanup: ${fromRouteName}`);
103
- }
104
- await loadGlobalTranslations(to);
86
+ if (to.path === from.path && !isNoPrefixStrategy(i18nConfig.strategy)) {
87
+ if (next) next();
88
+ return;
105
89
  }
106
- if (next) {
107
- next();
90
+ if (import.meta.client && enablePreviousPageFallback) {
91
+ const fromLocale = routeService.getCurrentLocale(from);
92
+ const fromRouteName = routeService.getRouteName(from, fromLocale);
93
+ previousPageInfo.value = { locale: fromLocale, routeName: fromRouteName };
108
94
  }
95
+ try {
96
+ await loadPageAndGlobalTranslations(to);
97
+ } catch (e) {
98
+ console.error("[i18n] Error loading translations:", e);
99
+ }
100
+ if (next) next();
109
101
  });
110
- await loadGlobalTranslations(router.currentRoute.value);
102
+ await loadPageAndGlobalTranslations(router.currentRoute.value);
111
103
  const provideData = {
112
104
  i18n: void 0,
113
105
  __micro: true,
@@ -134,9 +126,6 @@ export default defineNuxtPlugin(async (nuxtApp) => {
134
126
  console.log(`Using fallback translation from previous route: ${prev.routeName} -> ${key}`);
135
127
  }
136
128
  }
137
- if (!value) {
138
- value = i18nHelper.getTranslation(locale, "", key);
139
- }
140
129
  if (!value) {
141
130
  if (isDev && import.meta.client) {
142
131
  console.warn(`Not found '${key}' key in '${locale}' locale messages for route '${routeName}'.`);
@@ -243,7 +232,9 @@ export default defineNuxtPlugin(async (nuxtApp) => {
243
232
  },
244
233
  loadPageTranslations: async (locale, routeName, translations) => {
245
234
  await i18nHelper.loadPageTranslations(locale, routeName, translations);
246
- }
235
+ },
236
+ helper: i18nHelper
237
+ // Оставляем helper, он может быть полезен для продвинутых пользователей
247
238
  };
248
239
  const $provideData = Object.fromEntries(
249
240
  Object.entries(provideData).map(([key, value]) => [`$${key}`, value])
@@ -1,19 +1,34 @@
1
1
  import { useLocaleHead } from "../composables/useLocaleHead.js";
2
- import { useRequestURL, useHead, defineNuxtPlugin, useRuntimeConfig } from "#imports";
2
+ import { useRequestURL, useHead, defineNuxtPlugin, useRuntimeConfig, useRoute, watch } from "#imports";
3
+ import { isMetaDisabledForRoute } from "../utils/route-utils.js";
3
4
  const host = process.env.HOST ?? "localhost";
4
5
  const port = process.env.PORT ?? "host";
5
- export default defineNuxtPlugin((_nuxtApp) => {
6
+ export default defineNuxtPlugin((nuxtApp) => {
6
7
  const config = useRuntimeConfig();
8
+ const route = useRoute();
7
9
  const i18nConfig = config.public.i18nConfig;
10
+ const currentLocale = nuxtApp.$getLocale?.();
11
+ if (isMetaDisabledForRoute(route, i18nConfig.routeDisableMeta, currentLocale)) {
12
+ return;
13
+ }
8
14
  const schema = port === "443" ? "https" : "http";
9
15
  const defaultUrl = port === "80" || port === "443" ? `${schema}://${host}` : `${schema}://${host}:${port}`;
10
16
  const url = useRequestURL();
11
17
  const baseUrl = (i18nConfig.metaBaseUrl || url.origin || defaultUrl).toString();
12
- const head = useLocaleHead({
18
+ const { metaObject, updateMeta } = useLocaleHead({
13
19
  addDirAttribute: true,
14
20
  identifierAttribute: "id",
15
21
  addSeoAttributes: true,
16
22
  baseUrl
17
23
  });
18
- useHead(head);
24
+ useHead(metaObject);
25
+ if (import.meta.server) {
26
+ updateMeta();
27
+ } else if (import.meta.client) {
28
+ watch(
29
+ () => useRoute().fullPath,
30
+ () => updateMeta(),
31
+ { immediate: true }
32
+ );
33
+ }
19
34
  });