nuxt-i18n-micro 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/54fb212e-6673-4dcd-9032-ab7a5f971d8f.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 +74 -47
- 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 +4 -1
- 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:"54fb212e-6673-4dcd-9032-ab7a5f971d8f",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1793807641063,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:"54fb212e-6673-4dcd-9032-ab7a5f971d8f",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1793807641063,false]</script></body></html>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"
|
|
1
|
+
{"id":"54fb212e-6673-4dcd-9032-ab7a5f971d8f","timestamp":1793807638616}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"id":"54fb212e-6673-4dcd-9032-ab7a5f971d8f","timestamp":1793807638616,"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:"54fb212e-6673-4dcd-9032-ab7a5f971d8f",buildAssetsDir:"/_nuxt/",cdnURL:""}}</script><script type="application/json" data-nuxt-data="nuxt-app" data-ssr="false" id="__NUXT_DATA__">[{"prerenderedAt":1,"serverRendered":2},1793807641063,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");
|
|
@@ -342,12 +341,14 @@ function hashTranslationSources(rootDirs, translationDirName, additionalTranslat
|
|
|
342
341
|
}
|
|
343
342
|
hashFiles(dir, files);
|
|
344
343
|
};
|
|
344
|
+
const relativeExtras = additionalTranslationDirs.filter((extra) => !isAbsolute(extra));
|
|
345
345
|
for (const rootDir of rootDirs) {
|
|
346
346
|
hashDir(join(rootDir, translationDirName));
|
|
347
|
-
for (const extra of
|
|
347
|
+
for (const extra of relativeExtras) {
|
|
348
348
|
hashAdditionalRootLocales(join(rootDir, extra));
|
|
349
349
|
}
|
|
350
350
|
}
|
|
351
|
+
for (const extra of new Set(additionalTranslationDirs.filter((dir) => isAbsolute(dir)))) hashAdditionalRootLocales(extra);
|
|
351
352
|
return seen > 0 ? hash.digest("hex").slice(0, 16) : null;
|
|
352
353
|
}
|
|
353
354
|
const NITRO_MIN_COMPRESS_BYTES = 1024;
|
|
@@ -451,7 +452,8 @@ function setupDevToolsUI(options, rootDirs) {
|
|
|
451
452
|
const extraDir = file.startsWith("additional:") ? (options.additionalTranslationDirs ?? []).find((dir) => file.startsWith(`additional:${dir}/`)) : void 0;
|
|
452
453
|
if (file.startsWith("additional:") && !extraDir) throw new Error(`Not a configured additional translation directory: ${file}`);
|
|
453
454
|
const relativeFile = extraDir ? file.slice(`additional:${extraDir}/`.length) : file;
|
|
454
|
-
const
|
|
455
|
+
const dirs = extraDir ? additionalTranslationRoots(rootDirs, [extraDir]) : rootDirs.map((rootDir) => path.join(rootDir, options.translationDir || "locales"));
|
|
456
|
+
const target = dirs.reverse().map((dir) => resolveTranslationFile(dir, relativeFile)).find((candidate) => candidate !== null);
|
|
455
457
|
if (!target) throw new Error(`File not found in the translation directories: ${file}`);
|
|
456
458
|
fs.writeFileSync(target, JSON.stringify(content, null, 2), "utf-8");
|
|
457
459
|
},
|
|
@@ -484,14 +486,12 @@ function setupDevToolsUI(options, rootDirs) {
|
|
|
484
486
|
const pagesDir = path.join(localesDir, "pages");
|
|
485
487
|
processDirectory(localesDir, localesDir);
|
|
486
488
|
processDirectory(pagesDir, localesDir);
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
489
|
+
}
|
|
490
|
+
for (const extra of options.additionalTranslationDirs ?? []) {
|
|
491
|
+
for (const extraDir of additionalTranslationRoots(rootDirs, [extra]).filter((dir) => fs.existsSync(dir))) {
|
|
490
492
|
for (const name of fs.readdirSync(extraDir)) {
|
|
491
|
-
if (!name.endsWith(".json")) continue;
|
|
492
493
|
const filePath = path.join(extraDir, name);
|
|
493
|
-
if (
|
|
494
|
-
readFile(`additional:${extra}/${name}`, filePath);
|
|
494
|
+
if (name.endsWith(".json") && fs.statSync(filePath).isFile()) readFile(`additional:${extra}/${name}`, filePath);
|
|
495
495
|
}
|
|
496
496
|
}
|
|
497
497
|
}
|
|
@@ -499,8 +499,10 @@ function setupDevToolsUI(options, rootDirs) {
|
|
|
499
499
|
}
|
|
500
500
|
};
|
|
501
501
|
const rpc = extendServerRpc(DEVTOOLS_RPC_NAMESPACE, functions);
|
|
502
|
-
const
|
|
503
|
-
|
|
502
|
+
const roots = [
|
|
503
|
+
...rootDirs.map((root) => resolve(root, options.translationDir || "locales")),
|
|
504
|
+
...additionalTranslationRoots(rootDirs, options.additionalTranslationDirs)
|
|
505
|
+
];
|
|
504
506
|
let timer;
|
|
505
507
|
nuxt.hook("builder:watch", (_event, watched) => {
|
|
506
508
|
const file = resolve(nuxt.options.srcDir, watched);
|
|
@@ -881,6 +883,8 @@ function extractDefineI18nRouteData(content, filePath) {
|
|
|
881
883
|
warnUnextractable(filePath, result.error);
|
|
882
884
|
return null;
|
|
883
885
|
}
|
|
886
|
+
if (result.config === false) return false;
|
|
887
|
+
if (!result.config || typeof result.config !== "object") return null;
|
|
884
888
|
return normalizeDefineConfig(result.config);
|
|
885
889
|
} catch {
|
|
886
890
|
return null;
|
|
@@ -919,6 +923,10 @@ function mergeDefineI18nRouteMeta(entries, config) {
|
|
|
919
923
|
const routeDisableMeta = { ...config.routeDisableMeta };
|
|
920
924
|
const fromPages = {};
|
|
921
925
|
for (const { routePath, config: page } of entries) {
|
|
926
|
+
if (page === false) {
|
|
927
|
+
fromPages[routePath] = false;
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
922
930
|
if (Array.isArray(page.locales)) routeLocales[routePath] = page.locales;
|
|
923
931
|
else if (page.locales) routeLocales[routePath] = Object.keys(page.locales);
|
|
924
932
|
if (page.localeRoutes) fromPages[routePath] = page.localeRoutes;
|
|
@@ -936,7 +944,7 @@ function collectDefineI18nRouteMetaFromFiles(pageFiles, rootDirs) {
|
|
|
936
944
|
if (!rootDir) continue;
|
|
937
945
|
try {
|
|
938
946
|
const config = extractDefineI18nRouteData(readFileSync(pageFile, "utf-8"), pageFile);
|
|
939
|
-
if (
|
|
947
|
+
if (config === null) continue;
|
|
940
948
|
entries.push({
|
|
941
949
|
routePath: pageFilePathToRoutePath(pageFile, rootDir),
|
|
942
950
|
config
|
|
@@ -967,12 +975,13 @@ declare module '#app' {
|
|
|
967
975
|
|
|
968
976
|
export {}`;
|
|
969
977
|
}
|
|
978
|
+
const REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
970
979
|
const DEFAULT_CANONICAL_QUERY_WHITELIST = ["page", "sort", "filter", "search", "q", "query", "tag"];
|
|
971
980
|
function isLiveDictionary(options, mode, serverHost) {
|
|
972
981
|
return mode === "source" || Boolean(options.cacheTtl) || Boolean(serverHost);
|
|
973
982
|
}
|
|
974
983
|
function buildFullConfig(params) {
|
|
975
|
-
const { options, nuxt, defaultLocale, apiConfig, translationPayloadMode, locales,
|
|
984
|
+
const { options, nuxt, defaultLocale, apiConfig, translationPayloadMode, locales, publicPayloads, rootDelta } = params;
|
|
976
985
|
const noScripts = nuxt.options.features.noScripts;
|
|
977
986
|
return {
|
|
978
987
|
locales: locales ?? [],
|
|
@@ -985,12 +994,16 @@ function buildFullConfig(params) {
|
|
|
985
994
|
defaultLocale,
|
|
986
995
|
fallbackLocale: options.fallbackLocale ?? void 0,
|
|
987
996
|
localeCookie: options.localeCookie ?? null,
|
|
997
|
+
// set only when it is not the default: 06.redirect reads it
|
|
998
|
+
redirectStatusCode: options.redirectStatusCode ?? void 0,
|
|
999
|
+
// only a build that writes a cookie ships its attributes
|
|
1000
|
+
localeCookieOptions: options.localeCookie || nuxt.options?.router?.options?.hashMode ? options.localeCookieOptions : void 0,
|
|
988
1001
|
autoDetectLanguage: options.autoDetectLanguage,
|
|
989
1002
|
autoDetectPath: options.autoDetectPath || "/",
|
|
990
1003
|
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 ??
|
|
1004
|
+
// the `?v=` of payload URLs: a fingerprint of the translations (set at modules:done), as a timestamp would make
|
|
1005
|
+
// clients re-download what never changed. `options.dateBuild` wins even when it is `0`/`''`: that opts out of busting.
|
|
1006
|
+
dateBuild: options.dateBuild ?? Date.now(),
|
|
994
1007
|
hashMode: nuxt.options?.router?.options?.hashMode ?? false,
|
|
995
1008
|
trailingSlash: options.trailingSlash ?? nuxt.options.experimental?.defaults?.nuxtLink?.trailingSlash,
|
|
996
1009
|
apiBaseUrl: apiConfig.apiBaseUrl,
|
|
@@ -1027,6 +1040,8 @@ function buildPrivateConfig(options, nuxt, rootDirs, apiConfig, locales, payload
|
|
|
1027
1040
|
additionalTranslationDirs: options.additionalTranslationDirs ?? [],
|
|
1028
1041
|
apiBaseServerHost: apiConfig.apiBaseServerHost,
|
|
1029
1042
|
serverTranslationPreload: options.serverTranslationPreload ?? false,
|
|
1043
|
+
// read where a server merges source files (mode 'source', dev): set only when on
|
|
1044
|
+
linkedMessages: options.linkedMessages || void 0,
|
|
1030
1045
|
cacheStorage: options.cacheStorage,
|
|
1031
1046
|
cacheStorageBuild: nuxt.options.buildId,
|
|
1032
1047
|
payloadFsDir,
|
|
@@ -1159,6 +1174,13 @@ const module$1 = defineNuxtModule({
|
|
|
1159
1174
|
options.localeCookie = "user-locale";
|
|
1160
1175
|
logger.info("Strategy 'no_prefix': localeCookie automatically set to 'user-locale' for locale persistence.");
|
|
1161
1176
|
}
|
|
1177
|
+
if (options.redirectStatusCode !== void 0 && !REDIRECT_STATUSES.has(options.redirectStatusCode)) {
|
|
1178
|
+
logger.warn(`redirectStatusCode ${options.redirectStatusCode} is none of 301, 302, 303, 307, 308; 302 is used.`);
|
|
1179
|
+
options.redirectStatusCode = void 0;
|
|
1180
|
+
}
|
|
1181
|
+
if (options.localeCookieOptions?.sameSite === "none" && !options.localeCookieOptions.secure) {
|
|
1182
|
+
logger.warn("localeCookieOptions: sameSite 'none' needs secure: true, or browsers reject the locale cookie.");
|
|
1183
|
+
}
|
|
1162
1184
|
if (options.strategy !== "no_prefix" && options.redirects !== false && !options.localeCookie) {
|
|
1163
1185
|
logger.info(
|
|
1164
1186
|
"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 +1191,11 @@ const module$1 = defineNuxtModule({
|
|
|
1169
1191
|
const mergedLocalesDir = resolve(nuxt.options.buildDir, "i18n-merged");
|
|
1170
1192
|
const sourceLocalesDir = resolve(nuxt.options.buildDir, "i18n-source");
|
|
1171
1193
|
const translationDirName = options.translationDir || "locales";
|
|
1172
|
-
const
|
|
1194
|
+
const extraDirs = options.additionalTranslationDirs ??= [];
|
|
1195
|
+
const fingerprint = () => options.dateBuild === void 0 ? hashTranslationSources(
|
|
1173
1196
|
rootDirs,
|
|
1174
1197
|
translationDirName,
|
|
1175
|
-
|
|
1198
|
+
extraDirs,
|
|
1176
1199
|
JSON.stringify([options.fallbackLocale, options.disablePageLocales, (options.locales ?? []).map((l) => [l.code, l.fallbackLocale])])
|
|
1177
1200
|
) : null;
|
|
1178
1201
|
const translationPayloads = resolveTranslationPayloadOptions(options);
|
|
@@ -1200,10 +1223,11 @@ const module$1 = defineNuxtModule({
|
|
|
1200
1223
|
localeInfosByCode.set(locale.code, {
|
|
1201
1224
|
code: locale.code,
|
|
1202
1225
|
fallbackLocale: prev?.fallbackLocale ?? locale.fallbackLocale,
|
|
1226
|
+
iso: prev?.iso ?? locale.iso,
|
|
1203
1227
|
disabled: prev?.disabled ?? locale.disabled
|
|
1204
1228
|
});
|
|
1205
1229
|
}
|
|
1206
|
-
const localeInfos = [...localeInfosByCode.values()].filter((locale) => !locale.disabled).map(({ code, fallbackLocale }) => ({ code, fallbackLocale }));
|
|
1230
|
+
const localeInfos = [...localeInfosByCode.values()].filter((locale) => !locale.disabled).map(({ code, fallbackLocale, iso }) => ({ code, fallbackLocale, iso }));
|
|
1207
1231
|
let routeNames = [];
|
|
1208
1232
|
nuxt.hook("build:before", async () => {
|
|
1209
1233
|
if (translationPayloads.mode === "source") {
|
|
@@ -1220,7 +1244,9 @@ const module$1 = defineNuxtModule({
|
|
|
1220
1244
|
additionalTranslationDirs: options.additionalTranslationDirs,
|
|
1221
1245
|
routeNames,
|
|
1222
1246
|
// dev reads live files, and a prerender merges what it reads like any server
|
|
1223
|
-
rootDelta: !nuxt.options.dev && translationPayloads.rootDelta
|
|
1247
|
+
rootDelta: !nuxt.options.dev && translationPayloads.rootDelta,
|
|
1248
|
+
linkedMessages: options.linkedMessages,
|
|
1249
|
+
warn: (message) => logger.warn(message)
|
|
1224
1250
|
});
|
|
1225
1251
|
logger.info(`Pre-merged translations from ${rootDirs.length} layer(s) into ${mergedLocalesDir}`);
|
|
1226
1252
|
}
|
|
@@ -1271,8 +1297,12 @@ const module$1 = defineNuxtModule({
|
|
|
1271
1297
|
translationPayloadMode: translationPayloads.mode,
|
|
1272
1298
|
publicPayloads: translationPayloads.mode === "premerged" && translationPayloads.publicAssets,
|
|
1273
1299
|
rootDelta: !nuxt.options.dev && translationPayloads.mode === "premerged" && translationPayloads.rootDelta && !options.disablePageLocales && (translationPayloads.publicAssets || Boolean(options.apiBaseClientHost)),
|
|
1274
|
-
locales: routeGenerator.locales ?? []
|
|
1275
|
-
|
|
1300
|
+
locales: routeGenerator.locales ?? []
|
|
1301
|
+
});
|
|
1302
|
+
nuxt.hook("modules:done", () => {
|
|
1303
|
+
for (const dir of nuxt.options.i18n?.additionalTranslationDirs ?? [])
|
|
1304
|
+
if (!extraDirs.includes(dir)) extraDirs.push(dir);
|
|
1305
|
+
fullConfig.dateBuild = fingerprint() ?? fullConfig.dateBuild;
|
|
1276
1306
|
});
|
|
1277
1307
|
const generateDomain = process.env.NUXT_I18N_DOMAIN;
|
|
1278
1308
|
const buildConfig = generateDomain ? configForHost(fullConfig, generateDomain) : fullConfig;
|
|
@@ -1366,16 +1396,13 @@ export default defineNuxtPlugin(() => ({ provide: { defineI18nRoute: async () =>
|
|
|
1366
1396
|
});
|
|
1367
1397
|
}
|
|
1368
1398
|
if (nuxt.options.dev && (options.hmr ?? true)) {
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
translationDir: translationDirName,
|
|
1372
|
-
additionalTranslationDirs: options.additionalTranslationDirs
|
|
1373
|
-
});
|
|
1399
|
+
let translationDirs;
|
|
1400
|
+
const liveDirs = () => translationDirs ??= resolveLiveTranslationDirs({ rootDirs, translationDir: translationDirName, additionalTranslationDirs: extraDirs });
|
|
1374
1401
|
addVitePlugin(
|
|
1375
1402
|
{
|
|
1376
1403
|
name: "i18n-micro:translations-hmr",
|
|
1377
1404
|
hotUpdate({ file }) {
|
|
1378
|
-
if (this.environment.name !== "client" || !translationRelativePath(file,
|
|
1405
|
+
if (this.environment.name !== "client" || !translationRelativePath(file, liveDirs())) return;
|
|
1379
1406
|
this.environment.hot.send({ type: "custom", event: TRANSLATIONS_HMR_EVENT });
|
|
1380
1407
|
return [];
|
|
1381
1408
|
}
|
|
@@ -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,6 +11,7 @@ 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();
|
|
@@ -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.0
|
|
3
|
+
"version": "4.1.0",
|
|
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.0",
|
|
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":[]}
|