nuxt-i18n-micro 3.22.0 → 3.24.1

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.
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js"}}</script><link rel="stylesheet" href="/__nuxt-i18n-micro/_nuxt/entry.CJGrD2VA.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js"><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DBXvRcU1.js"><script type="module" src="/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-500.BcWSpGYy.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/p3h4sA5-.js"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DbtvxgAm.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-i18n-micro",buildId:"1a94a94a-6da6-4d5d-a5e3-7102aa8132cd",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1785416283591,false]</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js"}}</script><link rel="stylesheet" href="/__nuxt-i18n-micro/_nuxt/entry.CJGrD2VA.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js"><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DBXvRcU1.js"><script type="module" src="/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-500.BcWSpGYy.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/p3h4sA5-.js"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DbtvxgAm.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-i18n-micro",buildId:"0b3f8860-da30-4026-a699-f73b6a5e2c02",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1785443243383,false]</script></body></html>
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js"}}</script><link rel="stylesheet" href="/__nuxt-i18n-micro/_nuxt/entry.CJGrD2VA.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js"><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DBXvRcU1.js"><script type="module" src="/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-500.BcWSpGYy.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/p3h4sA5-.js"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DbtvxgAm.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-i18n-micro",buildId:"1a94a94a-6da6-4d5d-a5e3-7102aa8132cd",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1785416283592,false]</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js"}}</script><link rel="stylesheet" href="/__nuxt-i18n-micro/_nuxt/entry.CJGrD2VA.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js"><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DBXvRcU1.js"><script type="module" src="/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-500.BcWSpGYy.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/p3h4sA5-.js"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DbtvxgAm.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-i18n-micro",buildId:"0b3f8860-da30-4026-a699-f73b6a5e2c02",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1785443243384,false]</script></body></html>
@@ -1 +1 @@
1
- {"id":"1a94a94a-6da6-4d5d-a5e3-7102aa8132cd","timestamp":1785416281191}
1
+ {"id":"0b3f8860-da30-4026-a699-f73b6a5e2c02","timestamp":1785443239297}
@@ -0,0 +1 @@
1
+ {"id":"0b3f8860-da30-4026-a699-f73b6a5e2c02","timestamp":1785443239297,"prerendered":[]}
@@ -1 +1 @@
1
- <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js"}}</script><link rel="stylesheet" href="/__nuxt-i18n-micro/_nuxt/entry.CJGrD2VA.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js"><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DBXvRcU1.js"><script type="module" src="/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-500.BcWSpGYy.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/p3h4sA5-.js"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DbtvxgAm.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-i18n-micro",buildId:"1a94a94a-6da6-4d5d-a5e3-7102aa8132cd",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1785416283592,false]</script></body></html>
1
+ <!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><script type="importmap">{"imports":{"#entry":"/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js"}}</script><link rel="stylesheet" href="/__nuxt-i18n-micro/_nuxt/entry.CJGrD2VA.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js"><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DBXvRcU1.js"><script type="module" src="/__nuxt-i18n-micro/_nuxt/Bwak6g3b.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-500.BcWSpGYy.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/p3h4sA5-.js"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DbtvxgAm.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-i18n-micro",buildId:"0b3f8860-da30-4026-a699-f73b6a5e2c02",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1785443243384,false]</script></body></html>
package/dist/module.d.mts CHANGED
@@ -3,7 +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
+ export { TranslationPayloadMode, resolveTranslationPayloadMode, resolveTranslationPayloadOptions, resolveTranslationPayloadPublicDir, resolveTranslationPayloadPublicRel, shouldCopyTranslationPayloadsToPublic, shouldRegisterNitroServerAssets } from '@i18n-micro/utils/payload-config';
7
7
 
8
8
  declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
9
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.22.0",
4
+ "version": "3.24.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -6,8 +6,8 @@ import { defaultPlural, isNoPrefixStrategy, withPrefixStrategy } from '@i18n-mic
6
6
  import { generateHmrPlugin } from '@i18n-micro/hmr/generate-plugin';
7
7
  import { RouteGenerator, isLocaleAllowedForUnlocalizedRoute, normalizePath, isInternalPath } from '@i18n-micro/route-strategy';
8
8
  import { buildTranslationSourceLayers, preMergeLocales } from '@i18n-micro/utils/build';
9
- import { resolveTranslationPayloadOptions, getTranslationPayloadMisconfigurationWarnings, getTranslationPayloadSizeWarning, resolveTranslationPayloadWarningThresholds, resolveTranslationPayloadPublicDir } from '@i18n-micro/utils/payload-config';
10
- export { resolveTranslationPayloadMode, resolveTranslationPayloadOptions, resolveTranslationPayloadPublicDir } from '@i18n-micro/utils/payload-config';
9
+ import { resolveTranslationPayloadOptions, getTranslationPayloadMisconfigurationWarnings, getTranslationPayloadSizeWarning, resolveTranslationPayloadWarningThresholds, resolveTranslationPayloadPublicRel, shouldRegisterNitroServerAssets, shouldCopyTranslationPayloadsToPublic, resolveTranslationPayloadPublicDir } from '@i18n-micro/utils/payload-config';
10
+ export { resolveTranslationPayloadMode, resolveTranslationPayloadOptions, resolveTranslationPayloadPublicDir, resolveTranslationPayloadPublicRel, shouldCopyTranslationPayloadsToPublic, shouldRegisterNitroServerAssets } from '@i18n-micro/utils/payload-config';
11
11
  import { hashTranslationSources, scanTranslationPayloadDirectory, compressTranslationPayloads } from '@i18n-micro/utils/payload-stats';
12
12
  import { buildTranslationPayloadCacheControl } from '@i18n-micro/utils/payload-url';
13
13
  import { useNuxt, addDevServerHandler, defineNuxtModule, useLogger, createResolver, addImportsDir, addPlugin, addRouteMiddleware, addServerImportsDir, addServerHandler, addComponentsDir, addTemplate, addTypeTemplate, addVitePlugin, resolvePath, addPrerenderRoutes } from '@nuxt/kit';
@@ -543,7 +543,7 @@ function buildFullConfig(params) {
543
543
  translationPayloadMode
544
544
  };
545
545
  }
546
- function buildPrivateConfig(options, nuxt, apiConfig, locales) {
546
+ function buildPrivateConfig(options, nuxt, apiConfig, locales, payloadFsDir, payloadPublicRel) {
547
547
  return {
548
548
  rootDir: nuxt.options.rootDir,
549
549
  debug: options.debug ?? false,
@@ -555,12 +555,13 @@ function buildPrivateConfig(options, nuxt, apiConfig, locales) {
555
555
  apiBaseUrl: apiConfig.apiBaseUrl,
556
556
  apiBaseClientHost: apiConfig.apiBaseClientHost,
557
557
  apiBaseServerHost: apiConfig.apiBaseServerHost,
558
- serverTranslationPreload: options.serverTranslationPreload ?? false
558
+ serverTranslationPreload: options.serverTranslationPreload ?? false,
559
+ payloadFsDir,
560
+ payloadPublicRel
559
561
  };
560
562
  }
561
563
  function registerI18nTemplates(options, resolvedStrategyPath, fullConfig, privateConfig) {
562
564
  let runtimeFullConfig = fullConfig;
563
- const privateConfigJson = JSON.stringify(privateConfig);
564
565
  const pluralTemplate = addTemplate({
565
566
  filename: "i18n.plural.mjs",
566
567
  write: true,
@@ -614,7 +615,7 @@ export function createI18nStrategy(router) {
614
615
  const configTemplate = addTemplate({
615
616
  filename: "i18n.config.mjs",
616
617
  write: true,
617
- getContents: () => `const __privateConfig = ${privateConfigJson}
618
+ getContents: () => `const __privateConfig = ${JSON.stringify(privateConfig)}
618
619
  export function getI18nPrivateConfig() { return __privateConfig }
619
620
  `
620
621
  });
@@ -659,8 +660,10 @@ const module$1 = defineNuxtModule({
659
660
  mode: "premerged",
660
661
  serverAssets: true,
661
662
  serverHandler: true,
662
- publicAssets: true,
663
- prerenderRoutes: true
663
+ // Do not default publicAssets here — Nuxt deep-merges nested defaults and would
664
+ // force publicAssets:true even for mode:'source' (resolveTranslationPayloadOptions
665
+ // sets publicAssets false in source mode unless explicitly opted in).
666
+ prerenderRoutes: false
664
667
  },
665
668
  routesLocaleLinks: {},
666
669
  globalLocaleRoutes: {},
@@ -694,7 +697,7 @@ const module$1 = defineNuxtModule({
694
697
  const translationDirName = options.translationDir || "locales";
695
698
  const translationsHash = hashTranslationSources(rootDirs, translationDirName);
696
699
  const translationPayloads = resolveTranslationPayloadOptions(options);
697
- const translationAssetsDir = translationPayloads.mode === "source" ? sourceLocalesDir : mergedLocalesDir;
700
+ const publicAssetsDir = translationPayloads.mode === "source" ? sourceLocalesDir : mergedLocalesDir;
698
701
  for (const warning of getTranslationPayloadMisconfigurationWarnings({
699
702
  translationPayloads,
700
703
  apiBaseClientHost: options.apiBaseClientHost,
@@ -702,18 +705,33 @@ const module$1 = defineNuxtModule({
702
705
  })) {
703
706
  logger.warn(warning.replace("[nuxt-i18n-micro] ", ""));
704
707
  }
705
- const localeInfos = (options.locales ?? []).map(
706
- (l) => typeof l === "string" ? { code: l } : { code: l.code, fallbackLocale: l.fallbackLocale }
707
- );
708
+ if (!translationPayloads.serverAssets && !translationPayloads.publicAssets && !(process.env.NUXT_I18N_APP_BASE_SERVER_HOST ?? options.apiBaseServerHost)) {
709
+ throw new Error(
710
+ "nuxt-i18n-micro: local SSR payloads are disabled (serverAssets/publicAssets false) and apiBaseServerHost is not set. SSR would load empty translations."
711
+ );
712
+ }
713
+ const localeInfosByCode = /* @__PURE__ */ new Map();
714
+ for (const locale of options.locales ?? []) {
715
+ if (typeof locale === "string") {
716
+ if (!localeInfosByCode.has(locale)) localeInfosByCode.set(locale, { code: locale });
717
+ continue;
718
+ }
719
+ const prev = localeInfosByCode.get(locale.code);
720
+ localeInfosByCode.set(locale.code, {
721
+ code: locale.code,
722
+ fallbackLocale: locale.fallbackLocale ?? prev?.fallbackLocale,
723
+ disabled: !!(prev?.disabled || locale.disabled)
724
+ });
725
+ }
726
+ const localeInfos = [...localeInfosByCode.values()].filter((locale) => !locale.disabled).map(({ code, fallbackLocale }) => ({ code, fallbackLocale }));
708
727
  nuxt.hook("build:before", async () => {
709
- if (translationPayloads.mode === "source") {
710
- await buildTranslationSourceLayers(rootDirs, translationDirName, sourceLocalesDir);
711
- logger.info(`Built compact translation source from ${rootDirs.length} layer(s) into ${sourceLocalesDir}`);
712
- } else {
728
+ await buildTranslationSourceLayers(rootDirs, translationDirName, sourceLocalesDir);
729
+ logger.info(`Built compact translation source from ${rootDirs.length} layer(s) into ${sourceLocalesDir}`);
730
+ if (translationPayloads.mode !== "source") {
713
731
  await preMergeLocales(rootDirs, translationDirName, mergedLocalesDir, localeInfos, options.fallbackLocale, options.disablePageLocales);
714
732
  logger.info(`Pre-merged translations from ${rootDirs.length} layer(s) into ${mergedLocalesDir}`);
715
733
  }
716
- const payloadStats = scanTranslationPayloadDirectory(translationAssetsDir);
734
+ const payloadStats = scanTranslationPayloadDirectory(publicAssetsDir);
717
735
  const sizeWarning = getTranslationPayloadSizeWarning(payloadStats, resolveTranslationPayloadWarningThresholds(options.translationPayloads));
718
736
  if (sizeWarning) {
719
737
  logger.warn(sizeWarning.replace("[nuxt-i18n-micro] ", ""));
@@ -765,6 +783,7 @@ const module$1 = defineNuxtModule({
765
783
  }
766
784
  const apiBaseUrl = rawUrl.replace(/^\/+|\/+$/g, "").replace(/\/{2,}/g, "/");
767
785
  const apiConfig = { apiBaseUrl, apiBaseClientHost, apiBaseServerHost };
786
+ const payloadPublicRel = resolveTranslationPayloadPublicRel(options, apiBaseUrl);
768
787
  const routeGenerator = new RouteGenerator({
769
788
  locales: options.locales ?? [],
770
789
  defaultLocaleCode: defaultLocale,
@@ -790,7 +809,7 @@ const module$1 = defineNuxtModule({
790
809
  locales: routeGenerator.locales ?? [],
791
810
  translationsHash
792
811
  });
793
- const privateConfig = buildPrivateConfig(options, nuxt, apiConfig, routeGenerator.locales ?? []);
812
+ const privateConfig = buildPrivateConfig(options, nuxt, apiConfig, routeGenerator.locales ?? [], publicAssetsDir, payloadPublicRel);
794
813
  const templates = registerI18nTemplates(options, resolvedStrategyPath, fullConfig, privateConfig);
795
814
  if (typeof options.customRegexMatcher !== "undefined") {
796
815
  const localeCodes = routeGenerator.locales.map((l) => l.code);
@@ -922,6 +941,10 @@ declare module '#i18n-internal/strategy' {
922
941
  declare module '#i18n-internal/plural' {
923
942
  export const plural: PluralFunc
924
943
  }
944
+
945
+ declare module '#i18n-internal/payload-source' {
946
+ export function readPayload(relPath: string): Promise<Record<string, unknown>>
947
+ }
925
948
  `;
926
949
  }
927
950
  });
@@ -954,12 +977,27 @@ declare module '#i18n-internal/plural' {
954
977
  nitroConfig.alias["#i18n-internal/plural"] = pluralTemplate.dst;
955
978
  nitroConfig.alias["#i18n-internal/strategy"] = strategyTemplate.dst;
956
979
  nitroConfig.alias["#i18n-internal/config"] = configTemplate.dst;
957
- if (translationPayloads.serverAssets) {
980
+ const isNode = nitroConfig.node !== false;
981
+ nitroConfig.alias["#i18n-internal/payload-source"] = isNode ? resolver.resolve("./runtime/server/payload-source.fs") : resolver.resolve("./runtime/server/payload-source.assets");
982
+ if (shouldRegisterNitroServerAssets(translationPayloads, isNode)) {
958
983
  nitroConfig.serverAssets = nitroConfig.serverAssets || [];
959
984
  nitroConfig.serverAssets.push({
960
985
  baseName: "i18n",
961
- dir: translationAssetsDir
986
+ // Same layout as public (`mode`): premerged matrix or compact source.
987
+ dir: publicAssetsDir
962
988
  });
989
+ if (translationPayloads.mode === "premerged") {
990
+ logger.warn(
991
+ `Edge target with translationPayloads.mode:"premerged" embeds the full page/locale matrix via Nitro serverAssets (Rollup raw:). Prefer mode:'source' for large catalogs.`
992
+ );
993
+ }
994
+ }
995
+ if (isNode && translationPayloads.serverAssets && !translationPayloads.publicAssets) {
996
+ logger.info("translationPayloads.serverAssets: on Node, payloads are served from public/ (publicAssets copy enabled for SSR).");
997
+ }
998
+ nitroConfig.plugins = nitroConfig.plugins || [];
999
+ if (nuxt.options.dev && (options.hmr ?? true)) {
1000
+ nitroConfig.plugins.push(resolver.resolve("./runtime/server/plugins/watcher.dev"));
963
1001
  }
964
1002
  nitroConfig.routeRules = nitroConfig.routeRules || {};
965
1003
  const httpCacheDuration = Math.floor(options.httpCacheDuration ?? 0);
@@ -993,10 +1031,6 @@ declare module '#i18n-internal/plural' {
993
1031
  });
994
1032
  }
995
1033
  }
996
- nitroConfig.plugins = nitroConfig.plugins || [];
997
- if (nuxt.options.dev && (options.hmr ?? true)) {
998
- nitroConfig.plugins.push(resolver.resolve("./runtime/server/plugins/watcher.dev"));
999
- }
1000
1034
  nitroConfig.handlers = nitroConfig.handlers || [];
1001
1035
  nitroConfig.handlers.unshift({
1002
1036
  middleware: true,
@@ -1005,18 +1039,20 @@ declare module '#i18n-internal/plural' {
1005
1039
  });
1006
1040
  nuxt.hook("nitro:build:public-assets", (nitro) => {
1007
1041
  const isProd = nuxt.options.dev === false;
1008
- if (isProd && translationPayloads.publicAssets) {
1009
- const publicDir = resolveTranslationPayloadPublicDir(nitro.options.output.publicDir, options);
1042
+ const isNode = nitro.options.node !== false;
1043
+ const copyPublicPayloads = shouldCopyTranslationPayloadsToPublic(translationPayloads, isNode);
1044
+ if (isProd && copyPublicPayloads) {
1045
+ const publicDir = resolveTranslationPayloadPublicDir(nitro.options.output.publicDir, options, apiBaseUrl);
1010
1046
  try {
1011
- if (existsSync(translationAssetsDir)) {
1012
- fs__default.cpSync(translationAssetsDir, publicDir, { recursive: true });
1047
+ if (existsSync(publicAssetsDir)) {
1048
+ fs__default.cpSync(publicAssetsDir, publicDir, { recursive: true });
1013
1049
  logger.log(`Translation payloads copied to public directory`);
1014
1050
  const compressed = compressTranslationPayloads(publicDir, nitro.options.compressPublicAssets);
1015
1051
  if (compressed > 0) {
1016
1052
  logger.log(`Compressed ${compressed} translation payload file(s)`);
1017
1053
  }
1018
1054
  } else {
1019
- logger.warn(`Translation assets directory not found: ${translationAssetsDir}`);
1055
+ logger.warn(`Translation assets directory not found: ${publicAssetsDir}`);
1020
1056
  }
1021
1057
  } catch (err) {
1022
1058
  logger.error("Error copying translations:", err);
@@ -25,11 +25,6 @@ export default defineNuxtPlugin(async (nuxtApp) => {
25
25
  const i18nRouteParams = useState("i18n-route-params", () => ({}));
26
26
  const { resetPageHead } = useI18nHead();
27
27
  const customMissingHandler = useState("i18n-missing-handler", () => null);
28
- const SSR_CHUNKS_KEY = "i18n-ssr-chunks";
29
- const readSsrChunks = () => nuxtApp.payload.data?.[SSR_CHUNKS_KEY] ?? {};
30
- if (import.meta.server) {
31
- nuxtApp.payload.data[SSR_CHUNKS_KEY] ??= {};
32
- }
33
28
  const i18n = new NuxtI18n({
34
29
  plural,
35
30
  missingWarn: i18nConfig.missingWarn ?? true,
@@ -62,22 +57,9 @@ export default defineNuxtPlugin(async (nuxtApp) => {
62
57
  dateBuild: i18nConfig.dateBuild,
63
58
  routesLocaleLinks: i18nConfig.routesLocaleLinks
64
59
  };
65
- if (import.meta.client) {
66
- const seeded = readSsrChunks();
67
- if (Object.keys(seeded).length > 0) {
68
- translationStorage.seedFromSsrChunks(seeded);
69
- i18n.seedChunks(seeded);
70
- }
71
- }
72
- const setSsrChunk = (cacheKey, data) => {
73
- if (!import.meta.server) return;
74
- const chunks = nuxtApp.payload.data[SSR_CHUNKS_KEY] ??= {};
75
- chunks[cacheKey] = data;
76
- };
77
60
  const loader = new NuxtTranslationLoader({
78
61
  i18n,
79
62
  loadOptions,
80
- setSsrChunk,
81
63
  isDev
82
64
  });
83
65
  const serverLocale = import.meta.server ? nuxtApp.ssrContext?.event?.context?.i18n?.locale : void 0;
@@ -0,0 +1 @@
1
+ export declare function readPayload(relPath: string): Promise<Record<string, unknown>>;
@@ -0,0 +1,15 @@
1
+ import { useStorage } from "nitropack/runtime";
2
+ export async function readPayload(relPath) {
3
+ const key = relPath.replace(/^\/+/, "").replace(/\\/g, "/").replace(/\//g, ":");
4
+ const value = await useStorage().getItem(`assets:i18n:${key}`);
5
+ if (value === null || value === void 0) return {};
6
+ if (typeof value === "string") {
7
+ try {
8
+ return JSON.parse(value);
9
+ } catch {
10
+ return {};
11
+ }
12
+ }
13
+ if (typeof value === "object") return value;
14
+ return {};
15
+ }
@@ -0,0 +1 @@
1
+ export declare function readPayload(relPath: string): Promise<Record<string, unknown>>;
@@ -0,0 +1,23 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { getI18nPrivateConfig } from "#i18n-internal/config";
5
+ function baseDir() {
6
+ const cfg = getI18nPrivateConfig();
7
+ if (import.meta.dev || import.meta.prerender) {
8
+ return cfg.payloadFsDir;
9
+ }
10
+ return join(dirname(fileURLToPath(import.meta.url)), "..", "public", cfg.payloadPublicRel);
11
+ }
12
+ export async function readPayload(relPath) {
13
+ const path = join(baseDir(), relPath.replace(/^\/+/, "").replace(/\\/g, "/"));
14
+ try {
15
+ return JSON.parse(await readFile(path, "utf8"));
16
+ } catch (error) {
17
+ const err = error;
18
+ if (err.code !== "ENOENT") {
19
+ console.error(`[nuxt-i18n-micro] cannot read ${relPath} from ${baseDir()}:`, error);
20
+ }
21
+ return {};
22
+ }
23
+ }
@@ -1,13 +1,10 @@
1
1
  /**
2
2
  * Server-side translation loader.
3
- * In `premerged` mode, layers/fallback/root+page are merged at build time and this loader
4
- * reads a single Nitro asset. In `source` mode, compact source files are merged at runtime.
3
+ * - Node: JSON from public locales dir (`#i18n-internal/payload-source` fs)
4
+ * - Edge: Nitro `serverAssets` (`assets:i18n`)
5
+ * `premerged` reads one file; `source` merges root+page+fallback at runtime.
5
6
  */
6
7
  import type { Translations } from '@i18n-micro/types';
7
- /**
8
- * Load translations for a given locale and page.
9
- * Returns merged translations and a pre-serialized JSON string for the API route.
10
- */
11
8
  export declare function loadTranslationsFromServer(locale: string, routeName: string): Promise<{
12
9
  data: Translations;
13
10
  json: string;
@@ -1,14 +1,15 @@
1
1
  import { SERVER_CC_KEY } from "@i18n-micro/hmr/cache-keys";
2
2
  import { isEnabledLocale } from "@i18n-micro/utils/active-locales";
3
3
  import { CacheControl } from "@i18n-micro/utils/cache-control";
4
- import { normalizeConfiguredLocales } from "@i18n-micro/utils/merge-source";
4
+ import { normalizeConfiguredLocales, toPremergedStorageKey } from "@i18n-micro/utils/merge-source";
5
5
  import { fetchTranslationPayloadFromHost } from "@i18n-micro/utils/payload-fetch";
6
6
  import { resolveTranslationPayloadPage } from "@i18n-micro/utils/payload-url";
7
7
  import { resolveI18nConfigWithRuntimeOverrides } from "@i18n-micro/utils/runtime-config";
8
8
  import { loadSourceTranslationsFromStorage } from "@i18n-micro/utils/source-loader";
9
- import { useStorage } from "nitropack/runtime";
10
- import { getI18nConfig } from "#i18n-internal/strategy";
9
+ import { toTranslations } from "@i18n-micro/utils/normalize";
10
+ import { readPayload } from "#i18n-internal/payload-source";
11
11
  import { getI18nPrivateConfig } from "#i18n-internal/config";
12
+ import { getI18nConfig } from "#i18n-internal/strategy";
12
13
  function getServerCacheControl() {
13
14
  const g = globalThis;
14
15
  if (!g[SERVER_CC_KEY]) {
@@ -17,8 +18,6 @@ function getServerCacheControl() {
17
18
  }
18
19
  return g[SERVER_CC_KEY];
19
20
  }
20
- const ASSETS_PREFIX = "assets:i18n";
21
- import { toTranslations } from "@i18n-micro/utils/normalize";
22
21
  export async function loadTranslationsFromServer(locale, routeName) {
23
22
  const cc = getServerCacheControl();
24
23
  const cacheKey = `${locale}:${routeName}`;
@@ -33,10 +32,8 @@ export async function loadTranslationsFromServer(locale, routeName) {
33
32
  cc.set(cacheKey, empty);
34
33
  return empty;
35
34
  }
36
- const storage = useStorage();
37
35
  const routesLocaleLinks = config.routesLocaleLinks || {};
38
36
  const resolvedPage = resolveTranslationPayloadPage(routeName, routesLocaleLinks);
39
- const normalizedPage = resolvedPage.replace(/\//g, ":");
40
37
  if (privateConfig.apiBaseServerHost) {
41
38
  const data2 = await fetchTranslationPayloadFromHost(
42
39
  { apiBaseUrl: config.apiBaseUrl, apiBaseServerHost: privateConfig.apiBaseServerHost, dateBuild: config.dateBuild },
@@ -49,22 +46,22 @@ export async function loadTranslationsFromServer(locale, routeName) {
49
46
  cc.set(cacheKey, entry2);
50
47
  return entry2;
51
48
  }
52
- if (config.translationPayloadMode === "source") {
53
- const data2 = await loadSourceTranslationsFromStorage(storage, {
54
- locale,
55
- pageName: resolvedPage,
56
- locales: normalizeConfiguredLocales(config.locales),
57
- globalFallbackLocale: config.fallbackLocale,
58
- disablePageLocales: config.disablePageLocales
59
- });
60
- const json2 = JSON.stringify(data2).replace(/</g, "\\u003c");
61
- const entry2 = { data: data2, json: json2 };
62
- cc.set(cacheKey, entry2);
63
- return entry2;
49
+ const mode = config.translationPayloadMode === "source" ? "source" : "premerged";
50
+ let data;
51
+ if (mode === "source") {
52
+ data = await loadSourceTranslationsFromStorage(
53
+ { getItem: (key) => readPayload(key) },
54
+ {
55
+ locale,
56
+ pageName: resolvedPage,
57
+ locales: normalizeConfiguredLocales(config.locales),
58
+ globalFallbackLocale: config.fallbackLocale,
59
+ disablePageLocales: config.disablePageLocales
60
+ }
61
+ );
62
+ } else {
63
+ data = toTranslations(await readPayload(toPremergedStorageKey(resolvedPage, locale)));
64
64
  }
65
- const key = `${ASSETS_PREFIX}:pages:${normalizedPage}:${locale}.json`;
66
- const loaded = await storage.getItem(key);
67
- const data = toTranslations(loaded);
68
65
  const json = JSON.stringify(data).replace(/</g, "\\u003c");
69
66
  const entry = { data, json };
70
67
  cc.set(cacheKey, entry);
@@ -92,7 +92,6 @@ export declare class NuxtI18n extends BaseI18n {
92
92
  export interface NuxtTranslationLoaderOptions {
93
93
  i18n: NuxtI18n;
94
94
  loadOptions: LoadOptions;
95
- setSsrChunk: (cacheKey: string, data: Record<string, unknown>) => void;
96
95
  isDev?: boolean;
97
96
  }
98
97
  export declare class NuxtTranslationLoader {
@@ -228,23 +228,19 @@ export class NuxtTranslationLoader {
228
228
  }
229
229
  pendingLoads = /* @__PURE__ */ new Map();
230
230
  loadFromCacheSync(locale, routeName) {
231
- const { i18n, setSsrChunk } = this.options;
232
- const cacheKey = i18n.getCacheKey(locale, routeName);
231
+ const { i18n } = this.options;
233
232
  if (i18n.hasChunk(locale, routeName)) {
234
- const data = i18n.getChunk(locale, routeName);
235
- setSsrChunk(cacheKey, data);
236
- return data;
233
+ return i18n.getChunk(locale, routeName);
237
234
  }
238
235
  const cached = translationStorage.getFromCache(locale, routeName);
239
236
  if (cached) {
240
237
  i18n.setChunk(locale, routeName, cached.data);
241
- setSsrChunk(cacheKey, cached.data);
242
238
  return cached.data;
243
239
  }
244
240
  return null;
245
241
  }
246
242
  loadAsync(locale, routeName) {
247
- const { i18n, loadOptions, setSsrChunk, isDev } = this.options;
243
+ const { i18n, loadOptions, isDev } = this.options;
248
244
  const cacheKey = i18n.getCacheKey(locale, routeName);
249
245
  const pending = this.pendingLoads.get(cacheKey);
250
246
  if (pending) return pending;
@@ -254,7 +250,6 @@ export class NuxtTranslationLoader {
254
250
  const existing = i18n.getChunk(locale, routeName);
255
251
  const mergedChunk = mergeTranslationChunk(existing, result.data, { preserveExisting: true });
256
252
  i18n.setChunk(locale, routeName, mergedChunk);
257
- setSsrChunk(cacheKey, mergedChunk);
258
253
  return mergedChunk;
259
254
  } catch (e) {
260
255
  if (isDev) console.error("[i18n] Load error:", e);
@@ -19,30 +19,13 @@ declare class TranslationStorage {
19
19
  */
20
20
  configure(options: CacheControlOptions): void;
21
21
  /**
22
- * Detach from Vue reactivity before freezing. Only the SSR payload needs this:
23
- * its chunks come back as reactive proxies, and freezing a proxy would freeze
24
- * the reactive object the app is still using.
25
- */
26
- /**
27
- * Freeze a chunk before it enters the cache, so a consumer cannot mutate what other
28
- * callers will read.
29
- *
30
- * The freeze is applied to a copy, never to the caller's object: the SSR seed arrives as
31
- * `nuxtApp.payload.data['i18n-ssr-chunks']`, which Nuxt still owns, and freezing that in
32
- * place would make the payload immutable for everything else on the page.
33
- *
34
- * A shallow copy, though — the nested values are shared, which is what makes this cheap.
35
- * Deep-cloning meant serialising and re-parsing the largest object on the page during
36
- * hydration, and it protected nothing: no caller mutates a chunk in place.
22
+ * Freeze a shallow copy before it enters the cache so a consumer cannot mutate
23
+ * what other callers will read. Nested values are shared on purpose cheap, and
24
+ * no caller mutates a chunk in place.
37
25
  */
38
26
  private freezeChunk;
39
27
  private getCacheKey;
40
28
  private fetchTranslations;
41
- /**
42
- * Seed translation cache from the SSR payload (`payload.data['i18n-ssr-chunks']`).
43
- * Called on the client before the first fetch.
44
- */
45
- seedFromSsrChunks(chunks: Record<string, Record<string, unknown>>): void;
46
29
  /**
47
30
  * Synchronous cache check and retrieval.
48
31
  * Returns data if cached (and not expired), otherwise null.
@@ -24,21 +24,9 @@ class TranslationStorage {
24
24
  // HELPERS
25
25
  // ==========================================================================
26
26
  /**
27
- * Detach from Vue reactivity before freezing. Only the SSR payload needs this:
28
- * its chunks come back as reactive proxies, and freezing a proxy would freeze
29
- * the reactive object the app is still using.
30
- */
31
- /**
32
- * Freeze a chunk before it enters the cache, so a consumer cannot mutate what other
33
- * callers will read.
34
- *
35
- * The freeze is applied to a copy, never to the caller's object: the SSR seed arrives as
36
- * `nuxtApp.payload.data['i18n-ssr-chunks']`, which Nuxt still owns, and freezing that in
37
- * place would make the payload immutable for everything else on the page.
38
- *
39
- * A shallow copy, though — the nested values are shared, which is what makes this cheap.
40
- * Deep-cloning meant serialising and re-parsing the largest object on the page during
41
- * hydration, and it protected nothing: no caller mutates a chunk in place.
27
+ * Freeze a shallow copy before it enters the cache so a consumer cannot mutate
28
+ * what other callers will read. Nested values are shared on purpose cheap, and
29
+ * no caller mutates a chunk in place.
42
30
  */
43
31
  freezeChunk(data) {
44
32
  return Object.freeze({ ...data });
@@ -69,15 +57,6 @@ class TranslationStorage {
69
57
  // ==========================================================================
70
58
  // PUBLIC API
71
59
  // ==========================================================================
72
- /**
73
- * Seed translation cache from the SSR payload (`payload.data['i18n-ssr-chunks']`).
74
- * Called on the client before the first fetch.
75
- */
76
- seedFromSsrChunks(chunks) {
77
- for (const [cacheKey, data] of Object.entries(chunks)) {
78
- this.cc.set(cacheKey, this.freezeChunk(data));
79
- }
80
- }
81
60
  /**
82
61
  * Synchronous cache check and retrieval.
83
62
  * Returns data if cached (and not expired), otherwise null.
package/dist/types.d.mts CHANGED
@@ -8,7 +8,7 @@ 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'
11
+ export { type TranslationPayloadMode, type resolveTranslationPayloadMode, type resolveTranslationPayloadOptions, type resolveTranslationPayloadPublicDir, type resolveTranslationPayloadPublicRel, type shouldCopyTranslationPayloadsToPublic, type shouldRegisterNitroServerAssets } from '@i18n-micro/utils/payload-config'
12
12
 
13
13
  export { default } from './module.mjs'
14
14
 
package/internals.d.mts CHANGED
@@ -14,6 +14,10 @@ interface I18nPrivateConfig {
14
14
  customRegexMatcher?: string
15
15
  routesLocaleLinks?: Record<string, string>
16
16
  serverTranslationPreload?: boolean
17
+ /** Absolute buildDir payload dir for Node fs reads in dev/prerender. */
18
+ payloadFsDir: string
19
+ /** Relative dir under Nitro `public/` for prod payload files. */
20
+ payloadPublicRel: string
17
21
  }
18
22
 
19
23
  declare module '#build/i18n.config.mjs' {
@@ -94,3 +98,7 @@ declare module '#i18n-internal/strategy' {
94
98
  export function getI18nConfig(): unknown
95
99
  export function createI18nStrategy(router: I18nStrategyRouterLike): I18nStrategyInstance
96
100
  }
101
+
102
+ declare module '#i18n-internal/payload-source' {
103
+ export function readPayload(relPath: string): Promise<Record<string, unknown>>
104
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-i18n-micro",
3
- "version": "3.22.0",
3
+ "version": "3.24.1",
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
  "keywords": [
6
6
  "i18n",
@@ -52,13 +52,17 @@
52
52
  ],
53
53
  "#i18n-internal/config": [
54
54
  "./internals.d.mts"
55
+ ],
56
+ "#i18n-internal/payload-source": [
57
+ "./internals.d.mts"
55
58
  ]
56
59
  }
57
60
  },
58
61
  "imports": {
59
62
  "#i18n-internal/plural": "./internals.d.mts",
60
63
  "#i18n-internal/strategy": "./internals.d.mts",
61
- "#i18n-internal/config": "./internals.d.mts"
64
+ "#i18n-internal/config": "./internals.d.mts",
65
+ "#i18n-internal/payload-source": "./internals.d.mts"
62
66
  },
63
67
  "exports": {
64
68
  ".": {
@@ -77,13 +81,13 @@
77
81
  "sirv": "^2.0.3",
78
82
  "ufo": "^1.5.4",
79
83
  "unplugin": "^1.16.1",
80
- "@i18n-micro/core": "1.3.8",
81
- "@i18n-micro/hmr": "1.0.4",
82
- "@i18n-micro/test-utils": "1.2.6",
83
- "@i18n-micro/path-strategy": "1.3.6",
84
- "@i18n-micro/types": "1.2.7",
85
- "@i18n-micro/route-strategy": "1.1.12",
86
- "@i18n-micro/utils": "1.0.8"
84
+ "@i18n-micro/core": "^1.3.9",
85
+ "@i18n-micro/path-strategy": "^1.3.7",
86
+ "@i18n-micro/test-utils": "^1.2.7",
87
+ "@i18n-micro/hmr": "^1.0.5",
88
+ "@i18n-micro/types": "^1.2.8",
89
+ "@i18n-micro/utils": "^1.0.11",
90
+ "@i18n-micro/route-strategy": "^1.1.13"
87
91
  },
88
92
  "devDependencies": {
89
93
  "@nuxt/devtools": "^3.3.1",
@@ -1 +0,0 @@
1
- {"id":"1a94a94a-6da6-4d5d-a5e3-7102aa8132cd","timestamp":1785416281191,"prerendered":[]}