nuxt-i18n-micro 4.0.1 → 4.1.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.
- package/dist/client/200.html +1 -1
- package/dist/client/404.html +1 -1
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/4cf3c3ff-2c34-458a-8031-474a528c7231.json +1 -0
- package/dist/client/index.html +1 -1
- package/dist/module.d.mts +4 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +90 -50
- package/dist/runtime/composables/useI18nLocale.js +1 -1
- package/dist/runtime/plugins/01.plugin.d.ts +11 -11
- package/dist/runtime/plugins/01.plugin.js +4 -2
- package/dist/runtime/plugins/06.redirect.js +4 -3
- package/dist/runtime/server/internal/dev-live-translations.js +4 -1
- package/dist/runtime/server/internal/translation-watcher.d.ts +2 -0
- package/dist/runtime/server/internal/translation-watcher.js +5 -9
- package/dist/runtime/server/utils/server-loader.js +5 -2
- package/dist/runtime/server/utils/translation-server-middleware.d.ts +7 -2
- package/dist/runtime/server/utils/translation-server-middleware.js +36 -3
- package/dist/runtime/server/utils/warn-once.d.ts +1 -0
- package/dist/runtime/server/utils/warn-once.js +6 -0
- package/dist/runtime/utils/formatter.d.ts +9 -0
- package/dist/runtime/utils/formatter.js +7 -0
- package/dist/runtime/utils/hooks.d.ts +8 -0
- package/dist/runtime/utils/hooks.js +10 -0
- package/dist/runtime/utils/i18n-api.d.ts +10 -7
- package/dist/runtime/utils/i18n-api.js +13 -4
- package/dist/runtime/utils/i18n-config.js +21 -4
- package/dist/runtime/utils/i18n-navigation.js +30 -9
- package/dist/runtime/utils/nuxt-i18n.d.ts +3 -9
- package/dist/runtime/utils/nuxt-i18n.js +0 -7
- package/dist/types.d.mts +1 -1
- package/internals.d.mts +6 -0
- package/package.json +6 -6
- package/dist/client/_nuxt/builds/meta/356b87e3-45dd-4f80-a40a-b649665c1844.json +0 -1
package/dist/client/200.html
CHANGED
|
@@ -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/DKUOBR6F.js"}}</script><link rel="stylesheet" href="/__nuxt-i18n-micro/_nuxt/entry.PSWtHhay.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DKUOBR6F.js"><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/BVna_KKx.js"><script type="module" src="/__nuxt-i18n-micro/_nuxt/DKUOBR6F.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-404.xs3fxhVJ.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/CmvObVGq.js"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/CPz_jhsD.js"><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-500.C2S-4iLg.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/Bw81RhAv.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-i18n-micro",buildId:"
|
|
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/DKUOBR6F.js"}}</script><link rel="stylesheet" href="/__nuxt-i18n-micro/_nuxt/entry.PSWtHhay.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DKUOBR6F.js"><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/BVna_KKx.js"><script type="module" src="/__nuxt-i18n-micro/_nuxt/DKUOBR6F.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-404.xs3fxhVJ.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/CmvObVGq.js"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/CPz_jhsD.js"><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-500.C2S-4iLg.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/Bw81RhAv.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-i18n-micro",buildId:"4cf3c3ff-2c34-458a-8031-474a528c7231",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1794130971663,false]</script></body></html>
|
package/dist/client/404.html
CHANGED
|
@@ -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/DKUOBR6F.js"}}</script><link rel="stylesheet" href="/__nuxt-i18n-micro/_nuxt/entry.PSWtHhay.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DKUOBR6F.js"><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/BVna_KKx.js"><script type="module" src="/__nuxt-i18n-micro/_nuxt/DKUOBR6F.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-404.xs3fxhVJ.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/CmvObVGq.js"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/CPz_jhsD.js"><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-500.C2S-4iLg.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/Bw81RhAv.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-i18n-micro",buildId:"
|
|
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/DKUOBR6F.js"}}</script><link rel="stylesheet" href="/__nuxt-i18n-micro/_nuxt/entry.PSWtHhay.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DKUOBR6F.js"><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/BVna_KKx.js"><script type="module" src="/__nuxt-i18n-micro/_nuxt/DKUOBR6F.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-404.xs3fxhVJ.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/CmvObVGq.js"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/CPz_jhsD.js"><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-500.C2S-4iLg.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/Bw81RhAv.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-i18n-micro",buildId:"4cf3c3ff-2c34-458a-8031-474a528c7231",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1794130971664,false]</script></body></html>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"
|
|
1
|
+
{"id":"4cf3c3ff-2c34-458a-8031-474a528c7231","timestamp":1794130968604}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"4cf3c3ff-2c34-458a-8031-474a528c7231","timestamp":1794130968604,"prerendered":[]}
|
package/dist/client/index.html
CHANGED
|
@@ -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/DKUOBR6F.js"}}</script><link rel="stylesheet" href="/__nuxt-i18n-micro/_nuxt/entry.PSWtHhay.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DKUOBR6F.js"><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/BVna_KKx.js"><script type="module" src="/__nuxt-i18n-micro/_nuxt/DKUOBR6F.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-404.xs3fxhVJ.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/CmvObVGq.js"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/CPz_jhsD.js"><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-500.C2S-4iLg.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/Bw81RhAv.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-i18n-micro",buildId:"
|
|
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/DKUOBR6F.js"}}</script><link rel="stylesheet" href="/__nuxt-i18n-micro/_nuxt/entry.PSWtHhay.css" crossorigin><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/DKUOBR6F.js"><link rel="modulepreload" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/BVna_KKx.js"><script type="module" src="/__nuxt-i18n-micro/_nuxt/DKUOBR6F.js" crossorigin></script><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-404.xs3fxhVJ.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/CmvObVGq.js"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/CPz_jhsD.js"><link rel="prefetch" as="style" crossorigin href="/__nuxt-i18n-micro/_nuxt/error-500.C2S-4iLg.css"><link rel="prefetch" as="script" crossorigin href="/__nuxt-i18n-micro/_nuxt/Bw81RhAv.js"></head><body><div id="__nuxt"></div><div id="teleports"></div><script>window.__NUXT__={};window.__NUXT__.config={public:{},app:{baseURL:"/__nuxt-i18n-micro",buildId:"4cf3c3ff-2c34-458a-8031-474a528c7231",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1794130971665,false]</script></body></html>
|
package/dist/module.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
import { HookResult } from '@nuxt/schema';
|
|
3
|
-
import { ModuleOptions } from '@i18n-micro/types';
|
|
4
|
-
export { Getter, GlobalLocaleRoutes, Locale, LocaleCode, ModuleOptions, PluralFunc, Strategies } from '@i18n-micro/types';
|
|
3
|
+
import { I18nBeforeLocaleSwitchContext, ModuleOptions } from '@i18n-micro/types';
|
|
4
|
+
export { Getter, GlobalLocaleRoutes, I18nBeforeLocaleSwitchContext, Locale, LocaleCode, ModuleOptions, PluralFunc, Strategies } from '@i18n-micro/types';
|
|
5
5
|
import { PluginsInjections } from '../dist/runtime/plugins/01.plugin.js';
|
|
6
6
|
export { PluginsInjections } from '../dist/runtime/plugins/01.plugin.js';
|
|
7
7
|
|
|
@@ -16,6 +16,8 @@ declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, fa
|
|
|
16
16
|
*/
|
|
17
17
|
interface ModuleRuntimeHooks {
|
|
18
18
|
'i18n:register': (registerModule: (translations: unknown, locale?: string) => void, locale: string) => HookResult;
|
|
19
|
+
/** Before the page switches to another locale: set `ctx.to` to switch to another one, `ctx.cancel = true` to stay. */
|
|
20
|
+
'i18n:beforeLocaleSwitch': (ctx: I18nBeforeLocaleSwitchContext) => HookResult;
|
|
19
21
|
}
|
|
20
22
|
declare module '#app' {
|
|
21
23
|
interface RuntimeNuxtHooks extends ModuleRuntimeHooks {
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import fs__default, { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, readFileSync } from 'node:fs';
|
|
3
|
-
import path, {
|
|
3
|
+
import path, { resolve, dirname, join, relative, basename, isAbsolute } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { defaultPlural, isNoPrefixStrategy } from '@i18n-micro/core';
|
|
6
6
|
import { forwardSlashes, trimLeadingSlashes, isInternalPath } from '@i18n-micro/utils/app-path';
|
|
7
|
-
import { readTranslationFile,
|
|
7
|
+
import { readTranslationFile, translationRelativePath, resolveLiveTranslationDirs } from '../dist/runtime/server/internal/translation-watcher.js';
|
|
8
8
|
import { isLocaleAllowedForUnlocalizedRoute, normalizePath, routeKey, nameOfPath, RouteGenerator, localesOutsideMatcher, pageNames } from '@i18n-micro/route-strategy';
|
|
9
9
|
import { configForHost } from '@i18n-micro/utils/domains';
|
|
10
10
|
import { createHash } from 'node:crypto';
|
|
11
11
|
import { globby } from 'globby';
|
|
12
12
|
import { deepMergeTranslationsRecursive } from '@i18n-micro/utils/deep-merge';
|
|
13
|
+
import { additionalTranslationRoots, isInsideDir, resolveTranslationFile } from '@i18n-micro/utils/load-dir';
|
|
13
14
|
import { PREMERGED_ROOT_PAGE, mergeSourceTranslations, PREMERGED_ROOT_MARK, mergeOverRoot, buildFallbackLocaleChain } from '@i18n-micro/utils/merge-source';
|
|
14
15
|
import { gzipSync, brotliCompressSync, constants } from 'node:zlib';
|
|
15
16
|
import { resolveTranslationPayloadCachePolicy } from '@i18n-micro/utils/payload-url';
|
|
16
17
|
import { useNuxt, addDevServerHandler, defineNuxtModule, useLogger, createResolver, addImportsDir, addPlugin, addRouteMiddleware, addPluginTemplate, addServerImports, addServerHandler, addComponentsDir, addVitePlugin, addTemplate, addTypeTemplate, resolvePath, addPrerenderRoutes } from '@nuxt/kit';
|
|
17
18
|
import sirv from 'sirv';
|
|
18
19
|
import { startSubprocess, addCustomTab, onDevToolsInitialized, extendServerRpc } from '@nuxt/devtools-kit';
|
|
19
|
-
import { isInsideDir, resolveTranslationFile } from '@i18n-micro/utils/load-dir';
|
|
20
20
|
import { fromNodeMiddleware, defineEventHandler, proxyRequest } from 'h3';
|
|
21
21
|
import { joinURL } from 'ufo';
|
|
22
22
|
import { DomainPathStrategy, createPathStrategy } from '@i18n-micro/path-strategy';
|
|
@@ -61,24 +61,14 @@ ${read}`;
|
|
|
61
61
|
function mergeAdditionalRootLocaleFiles(rootDirs, additionalDirNames, locale, primaryContent = {}) {
|
|
62
62
|
if (!additionalDirNames.length) return primaryContent;
|
|
63
63
|
let content = {};
|
|
64
|
-
for (const
|
|
65
|
-
|
|
66
|
-
content = deepMergeTranslationsRecursive(content, readTranslationFile(join(rootDir, dirName, `${locale}.json`)));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
64
|
+
for (const dir of additionalTranslationRoots(rootDirs, additionalDirNames))
|
|
65
|
+
content = deepMergeTranslationsRecursive(content, readTranslationFile(join(dir, `${locale}.json`)));
|
|
69
66
|
return deepMergeTranslationsRecursive(content, primaryContent);
|
|
70
67
|
}
|
|
71
68
|
function listAdditionalRootLocales(rootDirs, additionalDirNames) {
|
|
72
69
|
const locales = /* @__PURE__ */ new Set();
|
|
73
|
-
for (const
|
|
74
|
-
for (const
|
|
75
|
-
const dir = join(rootDir, dirName);
|
|
76
|
-
if (!existsSync(dir)) continue;
|
|
77
|
-
for (const name of readdirSync(dir)) {
|
|
78
|
-
if (!name.endsWith(".json")) continue;
|
|
79
|
-
locales.add(name.slice(0, -".json".length));
|
|
80
|
-
}
|
|
81
|
-
}
|
|
70
|
+
for (const dir of additionalTranslationRoots(rootDirs, additionalDirNames).filter((path) => existsSync(path))) {
|
|
71
|
+
for (const name of readdirSync(dir)) if (name.endsWith(".json")) locales.add(name.slice(0, -".json".length));
|
|
82
72
|
}
|
|
83
73
|
return [...locales];
|
|
84
74
|
}
|
|
@@ -130,7 +120,7 @@ async function namingDeepFile(rootDirs, dirNames, step) {
|
|
|
130
120
|
await step();
|
|
131
121
|
} catch (error) {
|
|
132
122
|
if (!(error instanceof RangeError) || !error.message.includes("call stack")) throw error;
|
|
133
|
-
const dirs = rootDirs.flatMap((root) => dirNames.map((name) =>
|
|
123
|
+
const dirs = rootDirs.flatMap((root) => dirNames.map((name) => resolve(root, name))).filter((dir) => existsSync(dir));
|
|
134
124
|
const files = (await Promise.all(dirs.map((cwd) => globby("**/*.json", { cwd, absolute: true })))).flat();
|
|
135
125
|
const [deepest] = files.map((file) => ({ file, depth: depthOf(readTranslationFile(file)) })).sort((a, b) => b.depth - a.depth);
|
|
136
126
|
if (!deepest) throw error;
|
|
@@ -142,7 +132,13 @@ async function preMergeLocales(options) {
|
|
|
142
132
|
}
|
|
143
133
|
async function writeChunks(options) {
|
|
144
134
|
const { rootDirs, translationDirName, outputDir, locales, globalFallbackLocale, disablePageLocales, additionalTranslationDirs } = options;
|
|
145
|
-
const { routeNames = [], rootDelta = false } = options;
|
|
135
|
+
const { routeNames = [], rootDelta = false, linkedMessages = false } = options;
|
|
136
|
+
const warned = /* @__PURE__ */ new Set();
|
|
137
|
+
const warn = (message) => {
|
|
138
|
+
if (warned.has(message)) return;
|
|
139
|
+
warned.add(message);
|
|
140
|
+
options.warn?.(message);
|
|
141
|
+
};
|
|
146
142
|
const merged = await mergedFiles(rootDirs, translationDirName, outputDir, additionalTranslationDirs);
|
|
147
143
|
const pages = /* @__PURE__ */ new Set([...[...merged.keys()].filter((f) => f.startsWith("pages/")).map((f) => dirname(f).slice("pages/".length)), ...routeNames]);
|
|
148
144
|
const targets = disablePageLocales || pages.size === 0 ? ["index"] : [...pages, ...pages.has("index") ? [] : ["index"]];
|
|
@@ -152,7 +148,10 @@ async function writeChunks(options) {
|
|
|
152
148
|
locales,
|
|
153
149
|
globalFallbackLocale,
|
|
154
150
|
disablePageLocales,
|
|
155
|
-
readLocaleFile: (relativePath) => merged.get(relativePath) ?? {}
|
|
151
|
+
readLocaleFile: (relativePath) => merged.get(relativePath) ?? {},
|
|
152
|
+
// the root and every page resolved alike: a delta is taken between the resolved trees
|
|
153
|
+
linkedMessages,
|
|
154
|
+
warn
|
|
156
155
|
});
|
|
157
156
|
const write = (page, locale, data) => {
|
|
158
157
|
const targetPath = join(outputDir, page, locale, "data.json");
|
|
@@ -184,6 +183,13 @@ async function buildTranslationSourceLayers(rootDirs, translationDirName, output
|
|
|
184
183
|
});
|
|
185
184
|
}
|
|
186
185
|
|
|
186
|
+
function resolvePayloadHostSources(options, env = process.env) {
|
|
187
|
+
return {
|
|
188
|
+
apiBaseClientHost: env.NUXT_I18N_APP_BASE_CLIENT_HOST || options.apiBaseClientHost || void 0,
|
|
189
|
+
apiBaseServerHost: env.NUXT_I18N_APP_BASE_SERVER_HOST || options.apiBaseServerHost || void 0,
|
|
190
|
+
apiBaseUrl: env.NUXT_I18N_APP_BASE_URL || options.apiBaseUrl || "_locales"
|
|
191
|
+
};
|
|
192
|
+
}
|
|
187
193
|
const DEFAULT_TRANSLATION_PAYLOAD_WARN_FILE_COUNT = 500;
|
|
188
194
|
const DEFAULT_TRANSLATION_PAYLOAD_WARN_SIZE_BYTES = 10 * 1024 * 1024;
|
|
189
195
|
function resolveTranslationPayloadOptions(options) {
|
|
@@ -275,6 +281,10 @@ function getTranslationPayloadSizeWarning(stats, thresholds) {
|
|
|
275
281
|
return `Generated translation payloads are large (${stats.fileCount} files, ${formatBytes(stats.totalBytes)}). Consider translationPayloads.mode: 'source' for public output, hosting payloads externally, or disabling unused public/prerender outputs.`;
|
|
276
282
|
}
|
|
277
283
|
|
|
284
|
+
function localesByLayerPriority(layers, merged = []) {
|
|
285
|
+
return [...layers.flatMap((layer) => layer.config.i18n?.locales ?? []), ...merged];
|
|
286
|
+
}
|
|
287
|
+
|
|
278
288
|
function collectJsonFiles(dir) {
|
|
279
289
|
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
280
290
|
const fullPath = join(dir, entry.name);
|
|
@@ -342,12 +352,14 @@ function hashTranslationSources(rootDirs, translationDirName, additionalTranslat
|
|
|
342
352
|
}
|
|
343
353
|
hashFiles(dir, files);
|
|
344
354
|
};
|
|
355
|
+
const relativeExtras = additionalTranslationDirs.filter((extra) => !isAbsolute(extra));
|
|
345
356
|
for (const rootDir of rootDirs) {
|
|
346
357
|
hashDir(join(rootDir, translationDirName));
|
|
347
|
-
for (const extra of
|
|
358
|
+
for (const extra of relativeExtras) {
|
|
348
359
|
hashAdditionalRootLocales(join(rootDir, extra));
|
|
349
360
|
}
|
|
350
361
|
}
|
|
362
|
+
for (const extra of new Set(additionalTranslationDirs.filter((dir) => isAbsolute(dir)))) hashAdditionalRootLocales(extra);
|
|
351
363
|
return seen > 0 ? hash.digest("hex").slice(0, 16) : null;
|
|
352
364
|
}
|
|
353
365
|
const NITRO_MIN_COMPRESS_BYTES = 1024;
|
|
@@ -451,7 +463,8 @@ function setupDevToolsUI(options, rootDirs) {
|
|
|
451
463
|
const extraDir = file.startsWith("additional:") ? (options.additionalTranslationDirs ?? []).find((dir) => file.startsWith(`additional:${dir}/`)) : void 0;
|
|
452
464
|
if (file.startsWith("additional:") && !extraDir) throw new Error(`Not a configured additional translation directory: ${file}`);
|
|
453
465
|
const relativeFile = extraDir ? file.slice(`additional:${extraDir}/`.length) : file;
|
|
454
|
-
const
|
|
466
|
+
const dirs = extraDir ? additionalTranslationRoots(rootDirs, [extraDir]) : rootDirs.map((rootDir) => path.join(rootDir, options.translationDir || "locales"));
|
|
467
|
+
const target = dirs.reverse().map((dir) => resolveTranslationFile(dir, relativeFile)).find((candidate) => candidate !== null);
|
|
455
468
|
if (!target) throw new Error(`File not found in the translation directories: ${file}`);
|
|
456
469
|
fs.writeFileSync(target, JSON.stringify(content, null, 2), "utf-8");
|
|
457
470
|
},
|
|
@@ -484,14 +497,12 @@ function setupDevToolsUI(options, rootDirs) {
|
|
|
484
497
|
const pagesDir = path.join(localesDir, "pages");
|
|
485
498
|
processDirectory(localesDir, localesDir);
|
|
486
499
|
processDirectory(pagesDir, localesDir);
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
500
|
+
}
|
|
501
|
+
for (const extra of options.additionalTranslationDirs ?? []) {
|
|
502
|
+
for (const extraDir of additionalTranslationRoots(rootDirs, [extra]).filter((dir) => fs.existsSync(dir))) {
|
|
490
503
|
for (const name of fs.readdirSync(extraDir)) {
|
|
491
|
-
if (!name.endsWith(".json")) continue;
|
|
492
504
|
const filePath = path.join(extraDir, name);
|
|
493
|
-
if (
|
|
494
|
-
readFile(`additional:${extra}/${name}`, filePath);
|
|
505
|
+
if (name.endsWith(".json") && fs.statSync(filePath).isFile()) readFile(`additional:${extra}/${name}`, filePath);
|
|
495
506
|
}
|
|
496
507
|
}
|
|
497
508
|
}
|
|
@@ -499,8 +510,10 @@ function setupDevToolsUI(options, rootDirs) {
|
|
|
499
510
|
}
|
|
500
511
|
};
|
|
501
512
|
const rpc = extendServerRpc(DEVTOOLS_RPC_NAMESPACE, functions);
|
|
502
|
-
const
|
|
503
|
-
|
|
513
|
+
const roots = [
|
|
514
|
+
...rootDirs.map((root) => resolve(root, options.translationDir || "locales")),
|
|
515
|
+
...additionalTranslationRoots(rootDirs, options.additionalTranslationDirs)
|
|
516
|
+
];
|
|
504
517
|
let timer;
|
|
505
518
|
nuxt.hook("builder:watch", (_event, watched) => {
|
|
506
519
|
const file = resolve(nuxt.options.srcDir, watched);
|
|
@@ -881,6 +894,8 @@ function extractDefineI18nRouteData(content, filePath) {
|
|
|
881
894
|
warnUnextractable(filePath, result.error);
|
|
882
895
|
return null;
|
|
883
896
|
}
|
|
897
|
+
if (result.config === false) return false;
|
|
898
|
+
if (!result.config || typeof result.config !== "object") return null;
|
|
884
899
|
return normalizeDefineConfig(result.config);
|
|
885
900
|
} catch {
|
|
886
901
|
return null;
|
|
@@ -919,6 +934,10 @@ function mergeDefineI18nRouteMeta(entries, config) {
|
|
|
919
934
|
const routeDisableMeta = { ...config.routeDisableMeta };
|
|
920
935
|
const fromPages = {};
|
|
921
936
|
for (const { routePath, config: page } of entries) {
|
|
937
|
+
if (page === false) {
|
|
938
|
+
fromPages[routePath] = false;
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
922
941
|
if (Array.isArray(page.locales)) routeLocales[routePath] = page.locales;
|
|
923
942
|
else if (page.locales) routeLocales[routePath] = Object.keys(page.locales);
|
|
924
943
|
if (page.localeRoutes) fromPages[routePath] = page.localeRoutes;
|
|
@@ -936,7 +955,7 @@ function collectDefineI18nRouteMetaFromFiles(pageFiles, rootDirs) {
|
|
|
936
955
|
if (!rootDir) continue;
|
|
937
956
|
try {
|
|
938
957
|
const config = extractDefineI18nRouteData(readFileSync(pageFile, "utf-8"), pageFile);
|
|
939
|
-
if (
|
|
958
|
+
if (config === null) continue;
|
|
940
959
|
entries.push({
|
|
941
960
|
routePath: pageFilePathToRoutePath(pageFile, rootDir),
|
|
942
961
|
config
|
|
@@ -967,12 +986,13 @@ declare module '#app' {
|
|
|
967
986
|
|
|
968
987
|
export {}`;
|
|
969
988
|
}
|
|
989
|
+
const REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
970
990
|
const DEFAULT_CANONICAL_QUERY_WHITELIST = ["page", "sort", "filter", "search", "q", "query", "tag"];
|
|
971
991
|
function isLiveDictionary(options, mode, serverHost) {
|
|
972
992
|
return mode === "source" || Boolean(options.cacheTtl) || Boolean(serverHost);
|
|
973
993
|
}
|
|
974
994
|
function buildFullConfig(params) {
|
|
975
|
-
const { options, nuxt, defaultLocale, apiConfig, translationPayloadMode, locales,
|
|
995
|
+
const { options, nuxt, defaultLocale, apiConfig, translationPayloadMode, locales, publicPayloads, rootDelta } = params;
|
|
976
996
|
const noScripts = nuxt.options.features.noScripts;
|
|
977
997
|
return {
|
|
978
998
|
locales: locales ?? [],
|
|
@@ -985,12 +1005,16 @@ function buildFullConfig(params) {
|
|
|
985
1005
|
defaultLocale,
|
|
986
1006
|
fallbackLocale: options.fallbackLocale ?? void 0,
|
|
987
1007
|
localeCookie: options.localeCookie ?? null,
|
|
1008
|
+
// set only when it is not the default: 06.redirect reads it
|
|
1009
|
+
redirectStatusCode: options.redirectStatusCode ?? void 0,
|
|
1010
|
+
// only a build that writes a cookie ships its attributes
|
|
1011
|
+
localeCookieOptions: options.localeCookie || nuxt.options?.router?.options?.hashMode ? options.localeCookieOptions : void 0,
|
|
988
1012
|
autoDetectLanguage: options.autoDetectLanguage,
|
|
989
1013
|
autoDetectPath: options.autoDetectPath || "/",
|
|
990
1014
|
strategy: options.strategy,
|
|
991
|
-
// the `?v=` of payload URLs: a fingerprint of the translations, as a timestamp would make
|
|
992
|
-
// what never changed. `options.dateBuild` wins even when it is `0`/`''`: that opts out of busting.
|
|
993
|
-
dateBuild: options.dateBuild ??
|
|
1015
|
+
// the `?v=` of payload URLs: a fingerprint of the translations (set at modules:done), as a timestamp would make
|
|
1016
|
+
// clients re-download what never changed. `options.dateBuild` wins even when it is `0`/`''`: that opts out of busting.
|
|
1017
|
+
dateBuild: options.dateBuild ?? Date.now(),
|
|
994
1018
|
hashMode: nuxt.options?.router?.options?.hashMode ?? false,
|
|
995
1019
|
trailingSlash: options.trailingSlash ?? nuxt.options.experimental?.defaults?.nuxtLink?.trailingSlash,
|
|
996
1020
|
apiBaseUrl: apiConfig.apiBaseUrl,
|
|
@@ -1027,6 +1051,8 @@ function buildPrivateConfig(options, nuxt, rootDirs, apiConfig, locales, payload
|
|
|
1027
1051
|
additionalTranslationDirs: options.additionalTranslationDirs ?? [],
|
|
1028
1052
|
apiBaseServerHost: apiConfig.apiBaseServerHost,
|
|
1029
1053
|
serverTranslationPreload: options.serverTranslationPreload ?? false,
|
|
1054
|
+
// read where a server merges source files (mode 'source', dev): set only when on
|
|
1055
|
+
linkedMessages: options.linkedMessages || void 0,
|
|
1030
1056
|
cacheStorage: options.cacheStorage,
|
|
1031
1057
|
cacheStorageBuild: nuxt.options.buildId,
|
|
1032
1058
|
payloadFsDir,
|
|
@@ -1145,6 +1171,7 @@ const module$1 = defineNuxtModule({
|
|
|
1145
1171
|
httpCacheDuration: 31536e3
|
|
1146
1172
|
},
|
|
1147
1173
|
async setup(options, nuxt) {
|
|
1174
|
+
options.locales = localesByLayerPriority(nuxt.options._layers, options.locales);
|
|
1148
1175
|
const defaultLocale = process.env.DEFAULT_LOCALE || options.defaultLocale || "en";
|
|
1149
1176
|
let isSSG = false;
|
|
1150
1177
|
const logger = useLogger("nuxt-i18n-micro");
|
|
@@ -1159,6 +1186,13 @@ const module$1 = defineNuxtModule({
|
|
|
1159
1186
|
options.localeCookie = "user-locale";
|
|
1160
1187
|
logger.info("Strategy 'no_prefix': localeCookie automatically set to 'user-locale' for locale persistence.");
|
|
1161
1188
|
}
|
|
1189
|
+
if (options.redirectStatusCode !== void 0 && !REDIRECT_STATUSES.has(options.redirectStatusCode)) {
|
|
1190
|
+
logger.warn(`redirectStatusCode ${options.redirectStatusCode} is none of 301, 302, 303, 307, 308; 302 is used.`);
|
|
1191
|
+
options.redirectStatusCode = void 0;
|
|
1192
|
+
}
|
|
1193
|
+
if (options.localeCookieOptions?.sameSite === "none" && !options.localeCookieOptions.secure) {
|
|
1194
|
+
logger.warn("localeCookieOptions: sameSite 'none' needs secure: true, or browsers reject the locale cookie.");
|
|
1195
|
+
}
|
|
1162
1196
|
if (options.strategy !== "no_prefix" && options.redirects !== false && !options.localeCookie) {
|
|
1163
1197
|
logger.info(
|
|
1164
1198
|
"Redirects are enabled but localeCookie is not set. Locale-based redirects will not remember user's locale preference across page reloads. Set `localeCookie: 'user-locale'` to enable cookie-based locale persistence for redirects."
|
|
@@ -1169,10 +1203,11 @@ const module$1 = defineNuxtModule({
|
|
|
1169
1203
|
const mergedLocalesDir = resolve(nuxt.options.buildDir, "i18n-merged");
|
|
1170
1204
|
const sourceLocalesDir = resolve(nuxt.options.buildDir, "i18n-source");
|
|
1171
1205
|
const translationDirName = options.translationDir || "locales";
|
|
1172
|
-
const
|
|
1206
|
+
const extraDirs = options.additionalTranslationDirs ??= [];
|
|
1207
|
+
const fingerprint = () => options.dateBuild === void 0 ? hashTranslationSources(
|
|
1173
1208
|
rootDirs,
|
|
1174
1209
|
translationDirName,
|
|
1175
|
-
|
|
1210
|
+
extraDirs,
|
|
1176
1211
|
JSON.stringify([options.fallbackLocale, options.disablePageLocales, (options.locales ?? []).map((l) => [l.code, l.fallbackLocale])])
|
|
1177
1212
|
) : null;
|
|
1178
1213
|
const translationPayloads = resolveTranslationPayloadOptions(options);
|
|
@@ -1184,7 +1219,8 @@ const module$1 = defineNuxtModule({
|
|
|
1184
1219
|
})) {
|
|
1185
1220
|
logger.warn(warning);
|
|
1186
1221
|
}
|
|
1187
|
-
const
|
|
1222
|
+
const payloadSources = resolvePayloadHostSources(options);
|
|
1223
|
+
const rawServerHost = payloadSources.apiBaseServerHost;
|
|
1188
1224
|
if (!translationPayloads.serverAssets && !translationPayloads.publicAssets && !rawServerHost) {
|
|
1189
1225
|
throw new Error(
|
|
1190
1226
|
"nuxt-i18n-micro: local SSR payloads are disabled (serverAssets/publicAssets false) and apiBaseServerHost is not set. SSR would load empty translations."
|
|
@@ -1200,10 +1236,11 @@ const module$1 = defineNuxtModule({
|
|
|
1200
1236
|
localeInfosByCode.set(locale.code, {
|
|
1201
1237
|
code: locale.code,
|
|
1202
1238
|
fallbackLocale: prev?.fallbackLocale ?? locale.fallbackLocale,
|
|
1239
|
+
iso: prev?.iso ?? locale.iso,
|
|
1203
1240
|
disabled: prev?.disabled ?? locale.disabled
|
|
1204
1241
|
});
|
|
1205
1242
|
}
|
|
1206
|
-
const localeInfos = [...localeInfosByCode.values()].filter((locale) => !locale.disabled).map(({ code, fallbackLocale }) => ({ code, fallbackLocale }));
|
|
1243
|
+
const localeInfos = [...localeInfosByCode.values()].filter((locale) => !locale.disabled).map(({ code, fallbackLocale, iso }) => ({ code, fallbackLocale, iso }));
|
|
1207
1244
|
let routeNames = [];
|
|
1208
1245
|
nuxt.hook("build:before", async () => {
|
|
1209
1246
|
if (translationPayloads.mode === "source") {
|
|
@@ -1220,7 +1257,9 @@ const module$1 = defineNuxtModule({
|
|
|
1220
1257
|
additionalTranslationDirs: options.additionalTranslationDirs,
|
|
1221
1258
|
routeNames,
|
|
1222
1259
|
// dev reads live files, and a prerender merges what it reads like any server
|
|
1223
|
-
rootDelta: !nuxt.options.dev && translationPayloads.rootDelta
|
|
1260
|
+
rootDelta: !nuxt.options.dev && translationPayloads.rootDelta,
|
|
1261
|
+
linkedMessages: options.linkedMessages,
|
|
1262
|
+
warn: (message) => logger.warn(message)
|
|
1224
1263
|
});
|
|
1225
1264
|
logger.info(`Pre-merged translations from ${rootDirs.length} layer(s) into ${mergedLocalesDir}`);
|
|
1226
1265
|
}
|
|
@@ -1236,12 +1275,12 @@ const module$1 = defineNuxtModule({
|
|
|
1236
1275
|
} = mergeDefineI18nRouteMeta(collectDefineI18nRouteMetaFromFiles(pageFiles, rootDirs), options);
|
|
1237
1276
|
const resolvedStrategyPath = await resolveEntry("@i18n-micro/path-strategy");
|
|
1238
1277
|
const resolvedHelpersPath = await resolveEntry("@i18n-micro/core/helpers");
|
|
1239
|
-
let apiBaseClientHost =
|
|
1278
|
+
let apiBaseClientHost = payloadSources.apiBaseClientHost;
|
|
1240
1279
|
if (apiBaseClientHost?.endsWith("/")) {
|
|
1241
1280
|
apiBaseClientHost = apiBaseClientHost.slice(0, -1);
|
|
1242
1281
|
}
|
|
1243
1282
|
const apiBaseServerHost = rawServerHost?.endsWith("/") ? rawServerHost.slice(0, -1) : rawServerHost;
|
|
1244
|
-
const rawUrl =
|
|
1283
|
+
const rawUrl = payloadSources.apiBaseUrl;
|
|
1245
1284
|
if (rawUrl.startsWith("http://") || rawUrl.startsWith("https://")) {
|
|
1246
1285
|
throw new Error("Nuxt-i18n-micro: Please use NUXT_I18N_APP_BASE_CLIENT_HOST or NUXT_I18N_APP_BASE_SERVER_HOST instead.");
|
|
1247
1286
|
}
|
|
@@ -1271,8 +1310,12 @@ const module$1 = defineNuxtModule({
|
|
|
1271
1310
|
translationPayloadMode: translationPayloads.mode,
|
|
1272
1311
|
publicPayloads: translationPayloads.mode === "premerged" && translationPayloads.publicAssets,
|
|
1273
1312
|
rootDelta: !nuxt.options.dev && translationPayloads.mode === "premerged" && translationPayloads.rootDelta && !options.disablePageLocales && (translationPayloads.publicAssets || Boolean(options.apiBaseClientHost)),
|
|
1274
|
-
locales: routeGenerator.locales ?? []
|
|
1275
|
-
|
|
1313
|
+
locales: routeGenerator.locales ?? []
|
|
1314
|
+
});
|
|
1315
|
+
nuxt.hook("modules:done", () => {
|
|
1316
|
+
for (const dir of nuxt.options.i18n?.additionalTranslationDirs ?? [])
|
|
1317
|
+
if (!extraDirs.includes(dir)) extraDirs.push(dir);
|
|
1318
|
+
fullConfig.dateBuild = fingerprint() ?? fullConfig.dateBuild;
|
|
1276
1319
|
});
|
|
1277
1320
|
const generateDomain = process.env.NUXT_I18N_DOMAIN;
|
|
1278
1321
|
const buildConfig = generateDomain ? configForHost(fullConfig, generateDomain) : fullConfig;
|
|
@@ -1366,16 +1409,13 @@ export default defineNuxtPlugin(() => ({ provide: { defineI18nRoute: async () =>
|
|
|
1366
1409
|
});
|
|
1367
1410
|
}
|
|
1368
1411
|
if (nuxt.options.dev && (options.hmr ?? true)) {
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
translationDir: translationDirName,
|
|
1372
|
-
additionalTranslationDirs: options.additionalTranslationDirs
|
|
1373
|
-
});
|
|
1412
|
+
let translationDirs;
|
|
1413
|
+
const liveDirs = () => translationDirs ??= resolveLiveTranslationDirs({ rootDirs, translationDir: translationDirName, additionalTranslationDirs: extraDirs });
|
|
1374
1414
|
addVitePlugin(
|
|
1375
1415
|
{
|
|
1376
1416
|
name: "i18n-micro:translations-hmr",
|
|
1377
1417
|
hotUpdate({ file }) {
|
|
1378
|
-
if (this.environment.name !== "client" || !translationRelativePath(file,
|
|
1418
|
+
if (this.environment.name !== "client" || !translationRelativePath(file, liveDirs())) return;
|
|
1379
1419
|
this.environment.hot.send({ type: "custom", event: TRANSLATIONS_HMR_EVENT });
|
|
1380
1420
|
return [];
|
|
1381
1421
|
}
|
|
@@ -13,7 +13,7 @@ export function useI18nLocale() {
|
|
|
13
13
|
const localeState = useState("i18n-locale", () => null);
|
|
14
14
|
const localeCookieName = getLocaleCookieName(i18nConfig);
|
|
15
15
|
const hashCookieName = getHashCookieName(i18nConfig);
|
|
16
|
-
const cookieRef = (name) => cookies && name ? useCookie(name, getLocaleCookieOptions()) : { value: null };
|
|
16
|
+
const cookieRef = (name) => cookies && name ? useCookie(name, getLocaleCookieOptions(i18nConfig.localeCookieOptions)) : { value: null };
|
|
17
17
|
const localeCookie = cookieRef(localeCookieName);
|
|
18
18
|
const hashCookie = cookieRef(hashCookieName);
|
|
19
19
|
const syncLocale = (locale) => {
|
|
@@ -17,8 +17,8 @@ declare const _default: import("nuxt/app").Plugin<{
|
|
|
17
17
|
defaultLocale: () => string;
|
|
18
18
|
getLocales: () => Locale[];
|
|
19
19
|
getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
|
|
20
|
-
t: (key: TranslationKey, params?:
|
|
21
|
-
ts: (key: TranslationKey, params?:
|
|
20
|
+
t: (key: TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string | null, routeContext?: unknown) => CleanTranslation;
|
|
21
|
+
ts: (key: TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string, routeContext?: unknown) => string;
|
|
22
22
|
_t: (route: RouteLocationNormalizedLoaded) => TranslationFn<CleanTranslation>;
|
|
23
23
|
_ts: (route: RouteLocationNormalizedLoaded) => TranslationFn<string>;
|
|
24
24
|
tc: (key: TranslationKey, count: number | Params, defaultValue?: string) => string;
|
|
@@ -49,7 +49,7 @@ declare const _default: import("nuxt/app").Plugin<{
|
|
|
49
49
|
loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
|
|
50
50
|
setMissingHandler: (handler: MissingHandler | null) => void;
|
|
51
51
|
};
|
|
52
|
-
defineI18nRoute: ReturnType<({ getLocale, mergeTranslations }: Pick<NuxtI18nPluginProvide, "getLocale" | "mergeTranslations">) => (
|
|
52
|
+
defineI18nRoute: ReturnType<({ getLocale, mergeTranslations }: Pick<NuxtI18nPluginProvide, "getLocale" | "mergeTranslations">) => (config: DefineI18nRouteConfig | false) => Promise<void>>;
|
|
53
53
|
helper: {
|
|
54
54
|
mergeTranslation(locale: string, routeName: string, newTranslations: Translations): Promise<void>;
|
|
55
55
|
};
|
|
@@ -61,8 +61,8 @@ declare const _default: import("nuxt/app").Plugin<{
|
|
|
61
61
|
defaultLocale: () => string;
|
|
62
62
|
getLocales: () => Locale[];
|
|
63
63
|
getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
|
|
64
|
-
t: (key: TranslationKey, params?:
|
|
65
|
-
ts: (key: TranslationKey, params?:
|
|
64
|
+
t: (key: TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string | null, routeContext?: unknown) => CleanTranslation;
|
|
65
|
+
ts: (key: TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string, routeContext?: unknown) => string;
|
|
66
66
|
_t: (route: RouteLocationNormalizedLoaded) => TranslationFn<CleanTranslation>;
|
|
67
67
|
_ts: (route: RouteLocationNormalizedLoaded) => TranslationFn<string>;
|
|
68
68
|
tc: (key: TranslationKey, count: number | Params, defaultValue?: string) => string;
|
|
@@ -105,8 +105,8 @@ declare const _default: import("nuxt/app").Plugin<{
|
|
|
105
105
|
defaultLocale: () => string;
|
|
106
106
|
getLocales: () => Locale[];
|
|
107
107
|
getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
|
|
108
|
-
t: (key: TranslationKey, params?:
|
|
109
|
-
ts: (key: TranslationKey, params?:
|
|
108
|
+
t: (key: TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string | null, routeContext?: unknown) => CleanTranslation;
|
|
109
|
+
ts: (key: TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string, routeContext?: unknown) => string;
|
|
110
110
|
_t: (route: RouteLocationNormalizedLoaded) => TranslationFn<CleanTranslation>;
|
|
111
111
|
_ts: (route: RouteLocationNormalizedLoaded) => TranslationFn<string>;
|
|
112
112
|
tc: (key: TranslationKey, count: number | Params, defaultValue?: string) => string;
|
|
@@ -137,7 +137,7 @@ declare const _default: import("nuxt/app").Plugin<{
|
|
|
137
137
|
loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
|
|
138
138
|
setMissingHandler: (handler: MissingHandler | null) => void;
|
|
139
139
|
};
|
|
140
|
-
defineI18nRoute: ReturnType<({ getLocale, mergeTranslations }: Pick<NuxtI18nPluginProvide, "getLocale" | "mergeTranslations">) => (
|
|
140
|
+
defineI18nRoute: ReturnType<({ getLocale, mergeTranslations }: Pick<NuxtI18nPluginProvide, "getLocale" | "mergeTranslations">) => (config: DefineI18nRouteConfig | false) => Promise<void>>;
|
|
141
141
|
helper: {
|
|
142
142
|
mergeTranslation(locale: string, routeName: string, newTranslations: Translations): Promise<void>;
|
|
143
143
|
};
|
|
@@ -149,8 +149,8 @@ declare const _default: import("nuxt/app").Plugin<{
|
|
|
149
149
|
defaultLocale: () => string;
|
|
150
150
|
getLocales: () => Locale[];
|
|
151
151
|
getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
|
|
152
|
-
t: (key: TranslationKey, params?:
|
|
153
|
-
ts: (key: TranslationKey, params?:
|
|
152
|
+
t: (key: TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string | null, routeContext?: unknown) => CleanTranslation;
|
|
153
|
+
ts: (key: TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string, routeContext?: unknown) => string;
|
|
154
154
|
_t: (route: RouteLocationNormalizedLoaded) => TranslationFn<CleanTranslation>;
|
|
155
155
|
_ts: (route: RouteLocationNormalizedLoaded) => TranslationFn<string>;
|
|
156
156
|
tc: (key: TranslationKey, count: number | Params, defaultValue?: string) => string;
|
|
@@ -323,5 +323,5 @@ export interface NuxtAppOnlyInjections {
|
|
|
323
323
|
* Register per-page locale configuration and translations from inside a component.
|
|
324
324
|
* Merges into the active locale and re-applies when the locale changes.
|
|
325
325
|
*/
|
|
326
|
-
$defineI18nRoute: (routeDefinition: DefineI18nRouteConfig) => Promise<void>;
|
|
326
|
+
$defineI18nRoute: (routeDefinition: DefineI18nRouteConfig | false) => Promise<void>;
|
|
327
327
|
}
|
|
@@ -12,7 +12,8 @@ import { useI18nLocale } from "../composables/useI18nLocale.js";
|
|
|
12
12
|
import { createI18nProvide, createNuxtI18nPluginApi } from "../utils/i18n-api.js";
|
|
13
13
|
import { useResolvedI18nConfig } from "../utils/i18n-config.js";
|
|
14
14
|
import { setupI18nNavigation } from "../utils/i18n-navigation.js";
|
|
15
|
-
import { formatterFor
|
|
15
|
+
import { formatterFor } from "../utils/formatter.js";
|
|
16
|
+
import { NuxtI18n, NuxtTranslationLoader } from "../utils/nuxt-i18n.js";
|
|
16
17
|
import { ServerNuxtI18n } from "../utils/nuxt-i18n-server.js";
|
|
17
18
|
import { setI18nDevtoolsBridge } from "../devtools/bridge.js";
|
|
18
19
|
import { DEFAULT_SERVER_CACHE_SIZE } from "../utils/cache-keys.js";
|
|
@@ -174,7 +175,8 @@ export default defineNuxtPlugin({
|
|
|
174
175
|
customMissingHandler.value = handler;
|
|
175
176
|
},
|
|
176
177
|
// a ref: a link rendered without the hash gets it back when hydration ends
|
|
177
|
-
isHydrating: () => !hydrated.value && nuxtApp.isHydrating === true
|
|
178
|
+
isHydrating: () => !hydrated.value && nuxtApp.isHydrating === true,
|
|
179
|
+
nuxtApp
|
|
178
180
|
});
|
|
179
181
|
if (import.meta.server) rewriteSwitcherLinks(nuxtApp, api.provide, i18nRouteParams, (path) => router.resolve(path).href);
|
|
180
182
|
if (import.meta.dev && import.meta.client) {
|
|
@@ -41,7 +41,7 @@ export default defineNuxtPlugin({
|
|
|
41
41
|
const urlLocale = i18nStrategy.getLocaleFromPath(path);
|
|
42
42
|
const writeCookie = (locale) => {
|
|
43
43
|
if (!cookieName) return;
|
|
44
|
-
const { watch: _watch, decode: _decode, ...options } = getLocaleCookieOptions();
|
|
44
|
+
const { watch: _watch, decode: _decode, ...options } = getLocaleCookieOptions(i18nConfig.localeCookieOptions);
|
|
45
45
|
setCookie(event, cookieName, locale, options);
|
|
46
46
|
};
|
|
47
47
|
const redirectable = redirects && !urlless && !i18nConfig.hashMode && (urlLocale || i18nConfig.strategy !== "prefix_and_default");
|
|
@@ -49,8 +49,9 @@ export default defineNuxtPlugin({
|
|
|
49
49
|
const router = useRouter();
|
|
50
50
|
const served = i18nConfig.strategy !== "no_prefix" && (router.getRoutes().length === 0 || router.currentRoute.value.matched.length > 0);
|
|
51
51
|
if (urlLocale && served && !(redirects && anyPath)) writeCookie(urlLocale);
|
|
52
|
+
const redirectCode = i18nConfig.redirectStatusCode ?? 302;
|
|
52
53
|
if (i18nConfig.hashMode && alwaysPrefixed)
|
|
53
|
-
return navigateTo(`/#${applyTrailingSlash(path, i18nConfig.trailingSlash)}${url.search}`, { redirectCode
|
|
54
|
+
return navigateTo(`/#${applyTrailingSlash(path, i18nConfig.trailingSlash)}${url.search}`, { redirectCode });
|
|
54
55
|
if (!redirects || i18nConfig.hashMode) return;
|
|
55
56
|
const preferred = redirectLocale({
|
|
56
57
|
path,
|
|
@@ -69,7 +70,7 @@ export default defineNuxtPlugin({
|
|
|
69
70
|
if (!target) return;
|
|
70
71
|
if (anyPath && urlLocale) writeCookie(preferred);
|
|
71
72
|
if (DEBUG) console.error("[i18n-redirect] REDIRECT", { path, target, preferred });
|
|
72
|
-
return navigateTo(applyTrailingSlash(target, i18nConfig.trailingSlash) + url.search + url.hash, { redirectCode
|
|
73
|
+
return navigateTo(applyTrailingSlash(target, i18nConfig.trailingSlash) + url.search + url.hash, { redirectCode });
|
|
73
74
|
}
|
|
74
75
|
});
|
|
75
76
|
function chosenState(state, urlLocale) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createTranslationDirReader, mergeDevTranslations, resolveLiveTranslationDirs } from "./translation-watcher.js";
|
|
2
2
|
import { toTranslations } from "@i18n-micro/utils/normalize";
|
|
3
3
|
import { getI18nPrivateConfig } from "#i18n-internal/config";
|
|
4
|
+
import { warnOnce } from "../utils/warn-once.js";
|
|
4
5
|
export async function loadLiveDevTranslations(locale, pageName, config) {
|
|
5
6
|
const privateConfig = getI18nPrivateConfig();
|
|
6
7
|
const { translationRoots, additionalRoots } = resolveLiveTranslationDirs({
|
|
@@ -16,7 +17,9 @@ export async function loadLiveDevTranslations(locale, pageName, config) {
|
|
|
16
17
|
locales: config.linkLocales ?? config.locales,
|
|
17
18
|
fallbackLocale: config.fallbackLocale,
|
|
18
19
|
disablePageLocales: config.disablePageLocales,
|
|
19
|
-
readLocaleFile
|
|
20
|
+
readLocaleFile,
|
|
21
|
+
linkedMessages: privateConfig.linkedMessages,
|
|
22
|
+
warn: warnOnce
|
|
20
23
|
});
|
|
21
24
|
return toTranslations(merged);
|
|
22
25
|
}
|
|
@@ -11,6 +11,8 @@ export interface DevTranslationMergeInput {
|
|
|
11
11
|
fallbackLocale?: string;
|
|
12
12
|
disablePageLocales?: boolean;
|
|
13
13
|
readLocaleFile: (relativePath: string) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
14
|
+
linkedMessages?: boolean;
|
|
15
|
+
warn?: (message: string) => void;
|
|
14
16
|
}
|
|
15
17
|
export interface HandleTranslationWatchChangeInput {
|
|
16
18
|
relativePath: string;
|
|
@@ -3,7 +3,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
4
4
|
import { forwardSlashes } from "@i18n-micro/utils/app-path";
|
|
5
5
|
import { deepMergeTranslationsRecursive } from "@i18n-micro/utils/deep-merge";
|
|
6
|
-
import { parseJsonFile } from "@i18n-micro/utils/load-dir";
|
|
6
|
+
import { additionalTranslationRoots, parseJsonFile } from "@i18n-micro/utils/load-dir";
|
|
7
7
|
import { mergeSourceTranslations, normalizeConfiguredLocales } from "@i18n-micro/utils/merge-source";
|
|
8
8
|
import { parseTranslationRelativePath } from "@i18n-micro/utils/parse-path";
|
|
9
9
|
export function readTranslationFile(filePath) {
|
|
@@ -25,13 +25,7 @@ function skip(filePath, reason) {
|
|
|
25
25
|
const WARNED = /* @__PURE__ */ new Set();
|
|
26
26
|
export function resolveLiveTranslationDirs(input) {
|
|
27
27
|
const translationRoots = input.rootDirs.map((root) => resolve(root, input.translationDir));
|
|
28
|
-
|
|
29
|
-
for (const dir of input.additionalTranslationDirs ?? []) {
|
|
30
|
-
for (const root of input.rootDirs) {
|
|
31
|
-
additionalRoots.push(resolve(root, dir));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return { translationRoots, additionalRoots };
|
|
28
|
+
return { translationRoots, additionalRoots: additionalTranslationRoots(input.rootDirs, input.additionalTranslationDirs) };
|
|
35
29
|
}
|
|
36
30
|
export function translationRelativePath(filePath, dirs) {
|
|
37
31
|
const inside = (root) => {
|
|
@@ -73,7 +67,9 @@ export async function mergeDevTranslations(input) {
|
|
|
73
67
|
locales: effectiveLocales,
|
|
74
68
|
globalFallbackLocale: input.fallbackLocale,
|
|
75
69
|
disablePageLocales: input.disablePageLocales,
|
|
76
|
-
readLocaleFile: input.readLocaleFile
|
|
70
|
+
readLocaleFile: input.readLocaleFile,
|
|
71
|
+
linkedMessages: input.linkedMessages,
|
|
72
|
+
warn: input.warn
|
|
77
73
|
});
|
|
78
74
|
}
|
|
79
75
|
export async function handleTranslationWatchChange(input) {
|
|
@@ -11,13 +11,14 @@ import { getI18nServerConfig } from "./locale-detector.js";
|
|
|
11
11
|
import { getOrCreateServerTranslationCache } from "../internal/server-translation-cache.js";
|
|
12
12
|
import { readSharedChunk, sharedChunkKey, writeSharedChunk } from "../internal/shared-cache.js";
|
|
13
13
|
import { takeRootMark, toEntry } from "../../utils/translation-entry.js";
|
|
14
|
+
import { warnOnce } from "./warn-once.js";
|
|
14
15
|
const EMPTY = { data: {}, json: "{}" };
|
|
15
16
|
export const NO_FILE = { data: {}, json: "{}" };
|
|
16
17
|
const pending = /* @__PURE__ */ new Map();
|
|
17
18
|
const requestBoundIo = typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers";
|
|
18
19
|
function runtimeHost() {
|
|
19
20
|
try {
|
|
20
|
-
return process.env.NUXT_I18N_APP_BASE_SERVER_HOST;
|
|
21
|
+
return process.env.NUXT_I18N_APP_BASE_SERVER_HOST || void 0;
|
|
21
22
|
} catch {
|
|
22
23
|
return void 0;
|
|
23
24
|
}
|
|
@@ -95,7 +96,9 @@ async function load(locale, page, config) {
|
|
|
95
96
|
// a fallback may live on another domain: its file is still this build's
|
|
96
97
|
locales: normalizeConfiguredLocales(config.linkLocales ?? config.locales),
|
|
97
98
|
globalFallbackLocale: config.fallbackLocale,
|
|
98
|
-
disablePageLocales: config.disablePageLocales
|
|
99
|
+
disablePageLocales: config.disablePageLocales,
|
|
100
|
+
linkedMessages: getI18nPrivateConfig().linkedMessages,
|
|
101
|
+
warn: warnOnce
|
|
99
102
|
}
|
|
100
103
|
);
|
|
101
104
|
return { entry: toEntry(data) };
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { BaseI18n } from '@i18n-micro/core';
|
|
2
|
+
import type { TranslateParams } from '@i18n-micro/types';
|
|
2
3
|
import type { H3Event } from 'h3';
|
|
3
|
-
|
|
4
|
+
type Helpers = Pick<BaseI18n, 'tc' | 'tn' | 'td' | 'tdr' | 'has'>;
|
|
5
|
+
export declare const useTranslationServerMiddleware: (event: H3Event, defaultLocale?: string, currentLocale?: string) => Promise<((key: string, params?: TranslateParams, defaultValue?: string) => string) & Helpers>;
|
|
6
|
+
/** `t` of `useTranslationServerMiddleware`, with the page's `tc`, `tn`, `td`, `tdr` and `has`. */
|
|
7
|
+
export type ServerTranslate = Awaited<ReturnType<typeof useTranslationServerMiddleware>>;
|
|
8
|
+
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { interpolate, resolveTranslation } from "@i18n-micro/core";
|
|
1
|
+
import { BaseI18n, interpolate, localeIsoMap, resolveTranslation, translationCacheKey } from "@i18n-micro/core";
|
|
2
|
+
import { plural } from "#i18n-internal/plural";
|
|
3
|
+
import { formatterFor } from "../../utils/formatter.js";
|
|
2
4
|
import { getI18nServerConfig, getRequestLocale } from "./locale-detector.js";
|
|
3
5
|
import { loadTranslationsFromServer } from "./server-loader.js";
|
|
4
6
|
const I18N_CONTEXT_KEY = "__i18n_translations__";
|
|
@@ -16,8 +18,30 @@ async function dictionaryOf(event, locale) {
|
|
|
16
18
|
}
|
|
17
19
|
return loaded[locale] = chunk.data;
|
|
18
20
|
}
|
|
21
|
+
class DictionaryI18n extends BaseI18n {
|
|
22
|
+
constructor(locale, translations, config) {
|
|
23
|
+
super({
|
|
24
|
+
storage: { translations: /* @__PURE__ */ new Map([[translationCacheKey(locale, "index"), translations]]) },
|
|
25
|
+
plural,
|
|
26
|
+
formatter: formatterFor(config),
|
|
27
|
+
missingWarn: false,
|
|
28
|
+
localeIso: localeIsoMap(config.linkLocales ?? config.locales)
|
|
29
|
+
});
|
|
30
|
+
this.locale = locale;
|
|
31
|
+
}
|
|
32
|
+
getLocale() {
|
|
33
|
+
return this.locale;
|
|
34
|
+
}
|
|
35
|
+
getFallbackLocale() {
|
|
36
|
+
return this.locale;
|
|
37
|
+
}
|
|
38
|
+
getRoute() {
|
|
39
|
+
return "index";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
19
42
|
export const useTranslationServerMiddleware = async (event, defaultLocale, currentLocale) => {
|
|
20
|
-
const
|
|
43
|
+
const config = getI18nServerConfig(event);
|
|
44
|
+
const locale = currentLocale || getRequestLocale(event, config, defaultLocale);
|
|
21
45
|
const translations = await dictionaryOf(event, locale);
|
|
22
46
|
function t(key, params, defaultValue) {
|
|
23
47
|
const value = resolveTranslation(translations, key);
|
|
@@ -25,5 +49,14 @@ export const useTranslationServerMiddleware = async (event, defaultLocale, curre
|
|
|
25
49
|
const str = typeof value === "string" ? value : String(value);
|
|
26
50
|
return params ? interpolate(str, params) : str;
|
|
27
51
|
}
|
|
28
|
-
|
|
52
|
+
let i18n;
|
|
53
|
+
const own = () => i18n ??= new DictionaryI18n(locale, translations, config);
|
|
54
|
+
const helpers = {
|
|
55
|
+
tc: (...args) => own().tc(...args),
|
|
56
|
+
tn: ((...args) => own().tn(...args)),
|
|
57
|
+
td: ((...args) => own().td(...args)),
|
|
58
|
+
tdr: (...args) => own().tdr(...args),
|
|
59
|
+
has: (...args) => own().has(...args)
|
|
60
|
+
};
|
|
61
|
+
return Object.assign(t, helpers);
|
|
29
62
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function warnOnce(message: string): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { FormatService } from '@i18n-micro/core';
|
|
2
|
+
import type { ResolvedI18nConfig } from '@i18n-micro/types';
|
|
3
|
+
/**
|
|
4
|
+
* The server's formatters. `Intl` objects are costly and depend only on what the service is built
|
|
5
|
+
* from, so requests share them: its input is this read-only config, and no request reaches it
|
|
6
|
+
* (`$tn` and `$td` pass the locale per call). One per resolved config: runtime overrides and hosts.
|
|
7
|
+
* No vue here: the page plugin and the Nitro helpers share it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatterFor(config: ResolvedI18nConfig): FormatService;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FormatService } from "@i18n-micro/core";
|
|
2
|
+
const formatters = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
export function formatterFor(config) {
|
|
4
|
+
let formatter = formatters.get(config);
|
|
5
|
+
if (!formatter) formatters.set(config, formatter = new FormatService(config));
|
|
6
|
+
return formatter;
|
|
7
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { I18nBeforeLocaleSwitchContext } from '@i18n-micro/types';
|
|
2
|
+
import type { NuxtApp } from 'nuxt/app';
|
|
3
|
+
/** What of the NuxtApp a hook needs. */
|
|
4
|
+
export type HookHost = Pick<NuxtApp, 'hooks' | 'callHook'>;
|
|
5
|
+
/** Has `name` a subscriber? `_hooks` is hookable's own, so without it the hook just runs. */
|
|
6
|
+
export declare function hasSubscriber(nuxtApp: HookHost, name: string): boolean;
|
|
7
|
+
/** `i18n:beforeLocaleSwitch`: what the subscribers decided, or null when nobody listens (no context is made). */
|
|
8
|
+
export declare function beforeLocaleSwitch(nuxtApp: HookHost, from: string, to: string): Promise<I18nBeforeLocaleSwitchContext | null>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function hasSubscriber(nuxtApp, name) {
|
|
2
|
+
const { _hooks: subscribers } = nuxtApp.hooks;
|
|
3
|
+
return !subscribers || Boolean(subscribers[name]?.length);
|
|
4
|
+
}
|
|
5
|
+
export async function beforeLocaleSwitch(nuxtApp, from, to) {
|
|
6
|
+
if (!hasSubscriber(nuxtApp, "i18n:beforeLocaleSwitch")) return null;
|
|
7
|
+
const ctx = { from, to, cancel: false };
|
|
8
|
+
await nuxtApp.callHook("i18n:beforeLocaleSwitch", ctx);
|
|
9
|
+
return ctx;
|
|
10
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { DefineI18nRouteConfig, I18nRouteParams, MissingHandler, ResolvedI1
|
|
|
3
3
|
import type { RouteLocationNamedRaw, RouteLocationNormalizedLoaded, RouteLocationResolved, RouteLocationResolvedGeneric, Router } from 'vue-router';
|
|
4
4
|
import type { useI18nLocale } from '../composables/useI18nLocale.js';
|
|
5
5
|
import type { NuxtI18n, NuxtTranslationLoader } from './nuxt-i18n.js';
|
|
6
|
+
import { type HookHost } from './hooks.js';
|
|
6
7
|
/** URL of a switch result: the strategy hands out a URL string or a resolved route (fullPath set). */
|
|
7
8
|
export declare function switchedPath(switched: RouteLike | string): string;
|
|
8
9
|
export interface NuxtI18nPluginApiDeps {
|
|
@@ -22,6 +23,8 @@ export interface NuxtI18nPluginApiDeps {
|
|
|
22
23
|
}) => unknown;
|
|
23
24
|
setMissingHandler: (handler: MissingHandler | null) => void;
|
|
24
25
|
isHydrating: () => boolean;
|
|
26
|
+
/** For `i18n:beforeLocaleSwitch`. */
|
|
27
|
+
nuxtApp: HookHost;
|
|
25
28
|
}
|
|
26
29
|
export declare function createNuxtI18nPluginApi(deps: NuxtI18nPluginApiDeps): {
|
|
27
30
|
helper: {
|
|
@@ -34,8 +37,8 @@ export declare function createNuxtI18nPluginApi(deps: NuxtI18nPluginApiDeps): {
|
|
|
34
37
|
defaultLocale: () => string;
|
|
35
38
|
getLocales: () => import("@i18n-micro/types").Locale[];
|
|
36
39
|
getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
|
|
37
|
-
t: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").
|
|
38
|
-
ts: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").
|
|
40
|
+
t: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string | null, routeContext?: unknown) => import("@i18n-micro/types").CleanTranslation;
|
|
41
|
+
ts: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string, routeContext?: unknown) => string;
|
|
39
42
|
_t: (route: RouteLocationNormalizedLoaded) => import("./nuxt-i18n.js").TranslationFn<import("@i18n-micro/types").CleanTranslation>;
|
|
40
43
|
_ts: (route: RouteLocationNormalizedLoaded) => import("./nuxt-i18n.js").TranslationFn<string>;
|
|
41
44
|
tc: (key: import("@i18n-micro/types").TranslationKey, count: number | import("@i18n-micro/types").Params, defaultValue?: string) => string;
|
|
@@ -71,7 +74,7 @@ export declare function createNuxtI18nPluginApi(deps: NuxtI18nPluginApiDeps): {
|
|
|
71
74
|
export type NuxtI18nPluginApi = ReturnType<typeof createNuxtI18nPluginApi>;
|
|
72
75
|
export type NuxtI18nPluginProvide = NuxtI18nPluginApi['provide'];
|
|
73
76
|
/** The page's own translations: on the active locale, and again on each switch while the page lives. */
|
|
74
|
-
declare function createDefineI18nRoute({ getLocale, mergeTranslations }: Pick<NuxtI18nPluginProvide, 'getLocale' | 'mergeTranslations'>): (
|
|
77
|
+
declare function createDefineI18nRoute({ getLocale, mergeTranslations }: Pick<NuxtI18nPluginProvide, 'getLocale' | 'mergeTranslations'>): (config: DefineI18nRouteConfig | false) => Promise<void>;
|
|
75
78
|
export interface I18nProvideDeps {
|
|
76
79
|
api: NuxtI18nPluginApi;
|
|
77
80
|
i18nConfig: ResolvedI18nConfig;
|
|
@@ -98,8 +101,8 @@ export declare function createI18nProvide({ api: { helper, provide }, i18nConfig
|
|
|
98
101
|
defaultLocale: () => string;
|
|
99
102
|
getLocales: () => import("@i18n-micro/types").Locale[];
|
|
100
103
|
getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
|
|
101
|
-
t: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").
|
|
102
|
-
ts: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").
|
|
104
|
+
t: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string | null, routeContext?: unknown) => import("@i18n-micro/types").CleanTranslation;
|
|
105
|
+
ts: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string, routeContext?: unknown) => string;
|
|
103
106
|
_t: (route: RouteLocationNormalizedLoaded) => import("./nuxt-i18n.js").TranslationFn<import("@i18n-micro/types").CleanTranslation>;
|
|
104
107
|
_ts: (route: RouteLocationNormalizedLoaded) => import("./nuxt-i18n.js").TranslationFn<string>;
|
|
105
108
|
tc: (key: import("@i18n-micro/types").TranslationKey, count: number | import("@i18n-micro/types").Params, defaultValue?: string) => string;
|
|
@@ -142,8 +145,8 @@ export declare function createI18nProvide({ api: { helper, provide }, i18nConfig
|
|
|
142
145
|
defaultLocale: () => string;
|
|
143
146
|
getLocales: () => import("@i18n-micro/types").Locale[];
|
|
144
147
|
getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
|
|
145
|
-
t: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").
|
|
146
|
-
ts: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").
|
|
148
|
+
t: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string | null, routeContext?: unknown) => import("@i18n-micro/types").CleanTranslation;
|
|
149
|
+
ts: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").TranslateParams, defaultValue?: string, routeContext?: unknown) => string;
|
|
147
150
|
_t: (route: RouteLocationNormalizedLoaded) => import("./nuxt-i18n.js").TranslationFn<import("@i18n-micro/types").CleanTranslation>;
|
|
148
151
|
_ts: (route: RouteLocationNormalizedLoaded) => import("./nuxt-i18n.js").TranslationFn<string>;
|
|
149
152
|
tc: (key: import("@i18n-micro/types").TranslationKey, count: number | import("@i18n-micro/types").Params, defaultValue?: string) => string;
|
|
@@ -2,7 +2,8 @@ import { isNoPrefixStrategy, urlPath } from "@i18n-micro/core";
|
|
|
2
2
|
import { trimTrailingSlashes } from "@i18n-micro/utils/app-path";
|
|
3
3
|
import { hasProtocol } from "ufo";
|
|
4
4
|
import { getCurrentInstance, onActivated, watch } from "vue";
|
|
5
|
-
import { domains, defineRoute, serverPathFallback } from "#build/i18n.strategy.mjs";
|
|
5
|
+
import { domains, defineRoute, hooks, serverPathFallback } from "#build/i18n.strategy.mjs";
|
|
6
|
+
import { beforeLocaleSwitch } from "./hooks.js";
|
|
6
7
|
import { usePageRoute } from "./page-head.js";
|
|
7
8
|
import { applyTrailingSlash } from "./trailing-slash.js";
|
|
8
9
|
function toRouteLocationResolved(result) {
|
|
@@ -34,7 +35,8 @@ export function createNuxtI18nPluginApi(deps) {
|
|
|
34
35
|
i18nRouteParams,
|
|
35
36
|
navigateTo,
|
|
36
37
|
setMissingHandler,
|
|
37
|
-
isHydrating
|
|
38
|
+
isHydrating,
|
|
39
|
+
nuxtApp
|
|
38
40
|
} = deps;
|
|
39
41
|
const { getEffectiveLocale, setLocale, isValidLocale } = localeState;
|
|
40
42
|
const setLocaleState = (code) => {
|
|
@@ -116,6 +118,12 @@ export function createNuxtI18nPluginApi(deps) {
|
|
|
116
118
|
return false;
|
|
117
119
|
}
|
|
118
120
|
const urlless = isNoPrefix || i18nConfig.hashMode;
|
|
121
|
+
const from = i18n.getLocale();
|
|
122
|
+
if (hooks && urlless && toLocale !== i18n.getLocale()) {
|
|
123
|
+
const ctx = await beforeLocaleSwitch(nuxtApp, i18n.getLocale(), toLocale);
|
|
124
|
+
if (ctx?.cancel) return false;
|
|
125
|
+
if (ctx && isValidLocale(ctx.to)) toLocale = ctx.to;
|
|
126
|
+
}
|
|
119
127
|
const retry = getCurrentLocale() === toLocale && i18n.getLocale() !== toLocale;
|
|
120
128
|
const route = router.currentRoute.value;
|
|
121
129
|
const loaded = urlless || retry ? await loader.switchContext(toLocale, i18nStrategy.getPluginRouteName(route, toLocale)) : null;
|
|
@@ -129,7 +137,7 @@ export function createNuxtI18nPluginApi(deps) {
|
|
|
129
137
|
if (!urlless) setLocaleState(i18n.getLocale());
|
|
130
138
|
throw error;
|
|
131
139
|
}
|
|
132
|
-
return loaded ?? i18n.getLocale() === toLocale;
|
|
140
|
+
return loaded ?? (i18n.getLocale() === toLocale || i18n.getLocale() !== from);
|
|
133
141
|
},
|
|
134
142
|
switchRoute: async (route, toLocale) => {
|
|
135
143
|
return switchLocaleLogic(toLocale ?? getCurrentLocale(), i18nRouteParams.value, route);
|
|
@@ -158,7 +166,8 @@ export function createNuxtI18nPluginApi(deps) {
|
|
|
158
166
|
return { helper, provide };
|
|
159
167
|
}
|
|
160
168
|
function createDefineI18nRoute({ getLocale, mergeTranslations }) {
|
|
161
|
-
return async (
|
|
169
|
+
return async (config) => {
|
|
170
|
+
const locales = config ? config.locales : void 0;
|
|
162
171
|
if (!locales || Array.isArray(locales)) return;
|
|
163
172
|
const apply = () => {
|
|
164
173
|
const translations = locales[getLocale()];
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applyI18nRuntimeOverrides,
|
|
3
|
+
applyLocaleBaseUrls,
|
|
4
|
+
readLocaleBaseUrls,
|
|
3
5
|
createI18nConfigResolver,
|
|
4
6
|
readI18nRuntimeOverrides
|
|
5
7
|
} from "@i18n-micro/utils/runtime-config";
|
|
@@ -20,11 +22,22 @@ function overridesOf(config) {
|
|
|
20
22
|
};
|
|
21
23
|
}
|
|
22
24
|
export function shippedOverrides(config, base) {
|
|
23
|
-
|
|
25
|
+
if (config === base) return null;
|
|
26
|
+
const shipped = Object.assign(overridesOf(config), originsOf(config, base));
|
|
24
27
|
const built = overridesOf(base);
|
|
25
|
-
const same = shipped.defaultLocale === built.defaultLocale && shipped.fallbackLocale === built.fallbackLocale && shipped.domain === built.domain && shipped.dateBuild === built.dateBuild && String(shipped.disabledLocales) === String(built.disabledLocales);
|
|
28
|
+
const same = shipped.defaultLocale === built.defaultLocale && shipped.fallbackLocale === built.fallbackLocale && shipped.domain === built.domain && shipped.dateBuild === built.dateBuild && String(shipped.disabledLocales) === String(built.disabledLocales) && !shipped.metaBaseUrl && !shipped.localeBaseUrls;
|
|
26
29
|
return same ? null : shipped;
|
|
27
30
|
}
|
|
31
|
+
function originsOf(config, base) {
|
|
32
|
+
const origins = {};
|
|
33
|
+
if (!config.domain && config.metaBaseUrl !== base.metaBaseUrl && config.metaBaseUrl) origins.metaBaseUrl = config.metaBaseUrl;
|
|
34
|
+
const built = base.locales ?? [];
|
|
35
|
+
const moved = (config.linkLocales ?? config.locales ?? []).filter(
|
|
36
|
+
(locale) => locale.baseUrl && locale.baseUrl !== built.find((entry) => entry.code === locale.code)?.baseUrl
|
|
37
|
+
);
|
|
38
|
+
if (moved.length) origins.localeBaseUrls = Object.fromEntries(moved.map((locale) => [locale.code, locale.baseUrl]));
|
|
39
|
+
return origins;
|
|
40
|
+
}
|
|
28
41
|
function resolve(nuxtApp) {
|
|
29
42
|
const base = getI18nConfig();
|
|
30
43
|
const shipped = useState("i18n-runtime-overrides", () => null);
|
|
@@ -34,10 +47,14 @@ function resolve(nuxtApp) {
|
|
|
34
47
|
return config2;
|
|
35
48
|
}
|
|
36
49
|
if (nuxtApp.payload.serverRendered) {
|
|
37
|
-
const
|
|
50
|
+
const applied2 = shipped.value ? applyI18nRuntimeOverrides(base, shipped.value, silent) : base;
|
|
51
|
+
const config2 = domains && shipped.value ? applyLocaleBaseUrls(applied2, shipped.value, silent) : applied2;
|
|
38
52
|
return domains && shipped.value?.domain ? configForHost(config2, shipped.value.domain) : config2;
|
|
39
53
|
}
|
|
40
|
-
const
|
|
54
|
+
const runtimePublic = nuxtApp.$config.public;
|
|
55
|
+
const overrides = readI18nRuntimeOverrides(runtimePublic);
|
|
56
|
+
const applied = applyI18nRuntimeOverrides(base, overrides);
|
|
57
|
+
const config = domains ? applyLocaleBaseUrls(applied, { ...overrides, localeBaseUrls: readLocaleBaseUrls(runtimePublic) }) : applied;
|
|
41
58
|
return domains ? configForHost(config, location.host) : config;
|
|
42
59
|
}
|
|
43
60
|
export function useResolvedI18nConfig(nuxtApp) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hooks } from "#build/i18n.strategy.mjs";
|
|
2
|
+
import { beforeLocaleSwitch, hasSubscriber } from "./hooks.js";
|
|
2
3
|
function createOutgoingWatch(nuxtApp, i18n) {
|
|
3
4
|
let stopWaiting;
|
|
4
5
|
let noElement = null;
|
|
@@ -29,12 +30,8 @@ function createOutgoingWatch(nuxtApp, i18n) {
|
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
function createRegister(nuxtApp, { mergeTranslation }) {
|
|
32
|
-
const subscribed = () => {
|
|
33
|
-
const { _hooks: subscribers } = nuxtApp.hooks;
|
|
34
|
-
return !subscribers || Boolean(subscribers["i18n:register"]?.length);
|
|
35
|
-
};
|
|
36
33
|
return async (locale, routeName) => {
|
|
37
|
-
if (!
|
|
34
|
+
if (!hasSubscriber(nuxtApp, "i18n:register")) return;
|
|
38
35
|
const merges = [];
|
|
39
36
|
const register = (translations, selectedLocale) => {
|
|
40
37
|
merges.push(mergeTranslation(selectedLocale ?? locale, routeName, translations));
|
|
@@ -43,6 +40,21 @@ function createRegister(nuxtApp, { mergeTranslation }) {
|
|
|
43
40
|
await Promise.all(merges);
|
|
44
41
|
};
|
|
45
42
|
}
|
|
43
|
+
function createSwitchVerdict({ nuxtApp, i18n, api, locale: { isValidLocale } }) {
|
|
44
|
+
let redirectedTo = null;
|
|
45
|
+
return async (to, locale) => {
|
|
46
|
+
const from = i18n.getLocale();
|
|
47
|
+
const redirected = redirectedTo === locale;
|
|
48
|
+
redirectedTo = null;
|
|
49
|
+
if (!import.meta.client || nuxtApp.isHydrating || !from || locale === from || redirected) return void 0;
|
|
50
|
+
const ctx = await beforeLocaleSwitch(nuxtApp, from, locale);
|
|
51
|
+
if (!ctx) return void 0;
|
|
52
|
+
if (ctx.cancel) return false;
|
|
53
|
+
if (ctx.to === locale || !isValidLocale(ctx.to)) return void 0;
|
|
54
|
+
redirectedTo = ctx.to;
|
|
55
|
+
return api.provide.localeRoute(to, ctx.to);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
46
58
|
export function setupI18nNavigation(deps) {
|
|
47
59
|
const { nuxtApp, router, i18n, loader, i18nConfig, i18nStrategy, api, leavePage } = deps;
|
|
48
60
|
const noPrefix = i18nConfig.strategy === "no_prefix";
|
|
@@ -51,10 +63,10 @@ export function setupI18nNavigation(deps) {
|
|
|
51
63
|
const register = createRegister(nuxtApp, api.helper);
|
|
52
64
|
const targets = /* @__PURE__ */ new WeakMap();
|
|
53
65
|
if (hooks) nuxtApp.hooks.hookOnce("app:created", () => register(i18n.getLocale(), i18n.getCurrentRouteName()));
|
|
54
|
-
const
|
|
66
|
+
const targetLocale = (to) => (i18nConfig.hashMode ? i18nStrategy.getLocaleFromPath(to.path) : null) || api.provide.getLocale(to);
|
|
67
|
+
const switchVerdict = hooks ? createSwitchVerdict(deps) : void 0;
|
|
68
|
+
const loadTarget = async (to, locale) => {
|
|
55
69
|
try {
|
|
56
|
-
const hashLocale = i18nConfig.hashMode ? i18nStrategy.getLocaleFromPath(to.path) : null;
|
|
57
|
-
const locale = hashLocale || api.provide.getLocale(to);
|
|
58
70
|
const routeName = i18nStrategy.getPluginRouteName(to, locale);
|
|
59
71
|
const changed = locale !== i18n.getLocale() || routeName !== i18n.getCurrentRouteName();
|
|
60
72
|
return { locale, routeName, changed, data: changed ? await loader.fetchContext(locale, routeName) : null };
|
|
@@ -68,8 +80,17 @@ export function setupI18nNavigation(deps) {
|
|
|
68
80
|
const paths = noPrefix ? to.matched.at(-1)?.meta.i18n?.paths : void 0;
|
|
69
81
|
const customPath = paths?.[api.provide.getLocale(to)];
|
|
70
82
|
if (customPath) return { path: customPath, query: to.query, hash: to.hash };
|
|
83
|
+
let locale;
|
|
84
|
+
try {
|
|
85
|
+
locale = targetLocale(to);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
if (process.env.NODE_ENV !== "production") console.error("[i18n] Navigation error:", e);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const verdict = switchVerdict && await switchVerdict(to, locale);
|
|
91
|
+
if (verdict !== void 0) return verdict;
|
|
71
92
|
loader.invalidatePendingSwitches();
|
|
72
|
-
const target = await loadTarget(to);
|
|
93
|
+
const target = await loadTarget(to, locale);
|
|
73
94
|
if (!target) return;
|
|
74
95
|
targets.set(to, target);
|
|
75
96
|
if (hooks) await register(target.locale, target.routeName);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { BaseI18n,
|
|
2
|
-
import type { CleanTranslation,
|
|
1
|
+
import { BaseI18n, type BaseI18nOptions, type TranslationStorage } from '@i18n-micro/core';
|
|
2
|
+
import type { CleanTranslation, TranslateParams, TranslationKey, Translations } from '@i18n-micro/types';
|
|
3
3
|
import { type ShallowRef } from 'vue';
|
|
4
4
|
import { type LoadOptions } from './storage.js';
|
|
5
|
-
export type TranslationFn<T extends CleanTranslation> = (key: TranslationKey, params?:
|
|
5
|
+
export type TranslationFn<T extends CleanTranslation> = (key: TranslationKey, params?: TranslateParams, defaultValue?: string | null) => T;
|
|
6
6
|
export interface RouteTranslationContext {
|
|
7
7
|
locale: string;
|
|
8
8
|
routeName: string;
|
|
@@ -19,12 +19,6 @@ export interface NuxtI18nOptions extends BaseI18nOptions {
|
|
|
19
19
|
*/
|
|
20
20
|
cacheMaxSize?: number;
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
23
|
-
* The server's formatters. `Intl` objects are costly and depend only on what the service is built
|
|
24
|
-
* from, so requests share them: its input is this read-only config, and no request reaches it
|
|
25
|
-
* (`$tn` and `$td` pass the locale per call). One per resolved config: runtime overrides and hosts.
|
|
26
|
-
*/
|
|
27
|
-
export declare function formatterFor(config: ResolvedI18nConfig): FormatService;
|
|
28
22
|
export declare class NuxtI18n extends BaseI18n {
|
|
29
23
|
readonly storage: TranslationStorage;
|
|
30
24
|
private readonly cacheMaxSize;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BaseI18n,
|
|
3
|
-
FormatService,
|
|
4
3
|
mergeTranslationChunk,
|
|
5
4
|
resolveCachedTranslation,
|
|
6
5
|
setTranslationAtKey,
|
|
@@ -9,12 +8,6 @@ import {
|
|
|
9
8
|
import { PREMERGED_ROOT_PAGE } from "@i18n-micro/utils/merge-source";
|
|
10
9
|
import { shallowRef, triggerRef } from "vue";
|
|
11
10
|
import { translationStorage } from "./storage.js";
|
|
12
|
-
const formatters = /* @__PURE__ */ new WeakMap();
|
|
13
|
-
export function formatterFor(config) {
|
|
14
|
-
let formatter = formatters.get(config);
|
|
15
|
-
if (!formatter) formatters.set(config, formatter = new FormatService(config));
|
|
16
|
-
return formatter;
|
|
17
|
-
}
|
|
18
11
|
export class NuxtI18n extends BaseI18n {
|
|
19
12
|
storage;
|
|
20
13
|
cacheMaxSize;
|
package/dist/types.d.mts
CHANGED
|
@@ -4,7 +4,7 @@ declare module '#app' {
|
|
|
4
4
|
interface RuntimeNuxtHooks extends ModuleRuntimeHooks {}
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export { type Getter, type GlobalLocaleRoutes, type Locale, type LocaleCode, type ModuleOptions, type PluralFunc, type Strategies } from '@i18n-micro/types'
|
|
7
|
+
export { type Getter, type GlobalLocaleRoutes, type I18nBeforeLocaleSwitchContext, type Locale, type LocaleCode, type ModuleOptions, type PluralFunc, type Strategies } from '@i18n-micro/types'
|
|
8
8
|
|
|
9
9
|
export { type PluginsInjections } from '../dist/runtime/plugins/01.plugin.js'
|
|
10
10
|
|
package/internals.d.mts
CHANGED
|
@@ -12,6 +12,8 @@ interface I18nPrivateConfig {
|
|
|
12
12
|
debug: boolean
|
|
13
13
|
translationDir: string
|
|
14
14
|
additionalTranslationDirs?: string[]
|
|
15
|
+
/** `linkedMessages`, for servers that merge source files. */
|
|
16
|
+
linkedMessages?: boolean
|
|
15
17
|
fallbackLocale?: string
|
|
16
18
|
apiBaseUrl: string
|
|
17
19
|
apiBaseClientHost?: string
|
|
@@ -108,6 +110,10 @@ declare module '#i18n-internal/strategy' {
|
|
|
108
110
|
export function createI18nStrategy(router: I18nStrategyRouterLike): I18nStrategyInstance
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
declare module '#i18n-internal/plural' {
|
|
114
|
+
export const plural: import('@i18n-micro/types').PluralFunc
|
|
115
|
+
}
|
|
116
|
+
|
|
111
117
|
declare module '#i18n-internal/payload-source' {
|
|
112
118
|
export const embeddedPayloads: boolean
|
|
113
119
|
export function readPayload(relPath: string): Promise<Record<string, unknown>>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-i18n-micro",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "Fast, lightweight i18n for Nuxt with strategy-based routing and minimal overhead.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"i18n",
|
|
@@ -78,11 +78,11 @@
|
|
|
78
78
|
"h3": "^1.15.11",
|
|
79
79
|
"sirv": "^3.0.2",
|
|
80
80
|
"ufo": "^1.6.4",
|
|
81
|
-
"@i18n-micro/core": "^2.0
|
|
82
|
-
"@i18n-micro/
|
|
83
|
-
"@i18n-micro/
|
|
84
|
-
"@i18n-micro/
|
|
85
|
-
"@i18n-micro/
|
|
81
|
+
"@i18n-micro/core": "^2.1.0",
|
|
82
|
+
"@i18n-micro/path-strategy": "^2.1.0",
|
|
83
|
+
"@i18n-micro/route-strategy": "^2.1.1",
|
|
84
|
+
"@i18n-micro/types": "^2.1.0",
|
|
85
|
+
"@i18n-micro/utils": "^2.1.0"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
88
|
"@nuxt/devtools": "^3.4.2",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"id":"356b87e3-45dd-4f80-a40a-b649665c1844","timestamp":1793785771623,"prerendered":[]}
|