nuxt-i18n-micro 3.30.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +2 -2
- package/dist/client/200.html +1 -1
- package/dist/client/404.html +1 -1
- package/dist/client/_nuxt/BVna_KKx.js +3 -0
- package/dist/client/_nuxt/Bw81RhAv.js +1 -0
- package/dist/client/_nuxt/CPz_jhsD.js +1 -0
- package/dist/client/_nuxt/CmvObVGq.js +1 -0
- package/dist/client/_nuxt/DKUOBR6F.js +5 -0
- package/dist/client/_nuxt/builds/latest.json +1 -1
- package/dist/client/_nuxt/builds/meta/f0c8d4bf-40d2-4915-8b4a-ebe46bde61c8.json +1 -0
- package/dist/client/_nuxt/{entry.BM-mexAv.css → entry.PSWtHhay.css} +1 -1
- package/dist/client/_nuxt/error-404.xs3fxhVJ.css +1 -0
- package/dist/client/_nuxt/error-500.C2S-4iLg.css +1 -0
- package/dist/client/index.html +1 -1
- package/dist/module.d.mts +14 -5
- package/dist/module.json +4 -1
- package/dist/module.mjs +850 -475
- package/dist/runtime/components/i18n-group.d.vue.ts +17 -9
- package/dist/runtime/components/i18n-group.vue +2 -4
- package/dist/runtime/components/i18n-group.vue.d.ts +17 -9
- package/dist/runtime/components/i18n-link.d.vue.ts +9 -6
- package/dist/runtime/components/i18n-link.vue +7 -28
- package/dist/runtime/components/i18n-link.vue.d.ts +9 -6
- package/dist/runtime/components/i18n-switcher.d.vue.ts +128 -54
- package/dist/runtime/components/i18n-switcher.vue +56 -156
- package/dist/runtime/components/i18n-switcher.vue.d.ts +128 -54
- package/dist/runtime/components/i18n-t.d.vue.ts +59 -116
- package/dist/runtime/components/i18n-t.vue +24 -71
- package/dist/runtime/components/i18n-t.vue.d.ts +59 -116
- package/dist/runtime/composables/useI18n.js +2 -38
- package/dist/runtime/composables/useI18nHead.js +21 -6
- package/dist/runtime/composables/useI18nLocale.d.ts +4 -1
- package/dist/runtime/composables/useI18nLocale.js +20 -22
- package/dist/runtime/composables/useLocaleHead.d.ts +26 -31
- package/dist/runtime/composables/useLocaleHead.js +47 -167
- package/dist/runtime/devtools/bridge.d.ts +2 -2
- package/dist/runtime/devtools/vue-devtools.d.ts +2 -2
- package/dist/runtime/devtools/vue-devtools.js +5 -5
- package/dist/runtime/middleware/i18n-redirect.global.d.ts +3 -2
- package/dist/runtime/middleware/i18n-redirect.global.js +30 -41
- package/dist/runtime/plugins/01.plugin.d.ts +156 -34
- package/dist/runtime/plugins/01.plugin.js +184 -138
- package/dist/runtime/plugins/02.meta.js +36 -43
- package/dist/runtime/plugins/04.vue-devtools.js +1 -1
- package/dist/runtime/plugins/06.redirect.d.ts +3 -3
- package/dist/runtime/plugins/06.redirect.js +65 -91
- package/dist/runtime/server/{utils → internal}/dev-live-translations.js +4 -3
- package/dist/runtime/server/internal/server-translation-cache.d.ts +9 -0
- package/dist/runtime/server/internal/server-translation-cache.js +10 -0
- package/dist/runtime/server/internal/shared-cache.d.ts +24 -0
- package/dist/runtime/server/internal/shared-cache.js +71 -0
- package/dist/runtime/server/internal/translation-watcher.d.ts +79 -0
- package/dist/runtime/server/internal/translation-watcher.js +105 -0
- package/dist/runtime/server/middleware/i18n.global.js +24 -37
- package/dist/runtime/server/payload-source.assets.d.ts +1 -0
- package/dist/runtime/server/payload-source.assets.js +3 -8
- package/dist/runtime/server/payload-source.fs.d.ts +1 -0
- package/dist/runtime/server/payload-source.fs.js +18 -7
- package/dist/runtime/server/plugins/failed-read.d.ts +7 -0
- package/dist/runtime/server/plugins/failed-read.js +23 -0
- package/dist/runtime/server/plugins/payload-cache.d.ts +6 -0
- package/dist/runtime/server/plugins/payload-cache.js +16 -0
- package/dist/runtime/server/plugins/watcher.dev.js +15 -79
- package/dist/runtime/server/routes/i18n.js +13 -14
- package/dist/runtime/server/utils/locale-detector.d.ts +32 -2
- package/dist/runtime/server/utils/locale-detector.js +48 -27
- package/dist/runtime/server/utils/locale-server-middleware.js +14 -33
- package/dist/runtime/server/utils/payload-cache.d.ts +4 -0
- package/dist/runtime/server/utils/payload-cache.js +13 -0
- package/dist/runtime/server/utils/server-loader.d.ts +17 -6
- package/dist/runtime/server/utils/server-loader.js +105 -51
- package/dist/runtime/server/utils/translation-server-middleware.js +18 -35
- package/dist/runtime/utils/cache-keys.d.ts +6 -0
- package/dist/runtime/utils/cache-keys.js +2 -0
- package/dist/runtime/utils/i18n-api.d.ts +185 -0
- package/dist/runtime/utils/i18n-api.js +196 -0
- package/dist/runtime/utils/i18n-config.d.ts +18 -0
- package/dist/runtime/utils/i18n-config.js +47 -0
- package/dist/runtime/utils/i18n-navigation.d.ts +26 -0
- package/dist/runtime/utils/i18n-navigation.js +96 -0
- package/dist/runtime/utils/nuxt-i18n-server.d.ts +26 -0
- package/dist/runtime/utils/nuxt-i18n-server.js +83 -0
- package/dist/runtime/utils/nuxt-i18n.d.ts +81 -96
- package/dist/runtime/utils/nuxt-i18n.js +180 -252
- package/dist/runtime/utils/page-head.d.ts +12 -0
- package/dist/runtime/utils/page-head.js +6 -0
- package/dist/runtime/utils/request-url.d.ts +6 -0
- package/dist/runtime/utils/request-url.js +11 -0
- package/dist/runtime/utils/storage.d.ts +31 -19
- package/dist/runtime/utils/storage.js +100 -49
- package/dist/runtime/utils/translation-entry.d.ts +6 -0
- package/dist/runtime/utils/translation-entry.js +16 -0
- package/dist/types.d.mts +4 -6
- package/internals.d.mts +9 -2
- package/package.json +78 -79
- package/dist/client/_nuxt/BOxWElQP.js +0 -21
- package/dist/client/_nuxt/CUWB4CwN.js +0 -3
- package/dist/client/_nuxt/CmUbe-uv.js +0 -1
- package/dist/client/_nuxt/DAzShkZF.js +0 -1
- package/dist/client/_nuxt/MokPP5zi.js +0 -1
- package/dist/client/_nuxt/builds/meta/f020fec8-462b-480b-82d8-6b0cb65617e8.json +0 -1
- package/dist/client/_nuxt/error-404.D9Svp6Hk.css +0 -1
- package/dist/client/_nuxt/error-500.B-mjI9_9.css +0 -1
- package/dist/runtime/plugins/03.define.d.ts +0 -7
- package/dist/runtime/plugins/03.define.js +0 -42
- package/dist/runtime/plugins/05.hooks.d.ts +0 -2
- package/dist/runtime/plugins/05.hooks.js +0 -47
- package/dist/runtime/server/utils/server-translation-cache.d.ts +0 -11
- package/dist/runtime/server/utils/server-translation-cache.js +0 -20
- /package/dist/runtime/server/{utils → internal}/dev-live-translations.d.ts +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyI18nRuntimeOverrides,
|
|
3
|
+
createI18nConfigResolver,
|
|
4
|
+
readI18nRuntimeOverrides
|
|
5
|
+
} from "@i18n-micro/utils/runtime-config";
|
|
6
|
+
import { useState } from "#app/composables/state";
|
|
7
|
+
import { configForHost } from "@i18n-micro/utils/domains";
|
|
8
|
+
import { domains, getI18nConfig } from "#build/i18n.strategy.mjs";
|
|
9
|
+
const configs = /* @__PURE__ */ new WeakMap();
|
|
10
|
+
const resolveOnServer = import.meta.server ? createI18nConfigResolver() : void 0;
|
|
11
|
+
const silent = () => {
|
|
12
|
+
};
|
|
13
|
+
function overridesOf(config) {
|
|
14
|
+
return {
|
|
15
|
+
defaultLocale: config.defaultLocale,
|
|
16
|
+
fallbackLocale: config.fallbackLocale,
|
|
17
|
+
disabledLocales: (config.locales ?? []).filter((locale) => locale.disabled).map((locale) => locale.code),
|
|
18
|
+
domain: config.domain,
|
|
19
|
+
dateBuild: String(config.dateBuild)
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function shippedOverrides(config, base) {
|
|
23
|
+
const shipped = overridesOf(config);
|
|
24
|
+
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);
|
|
26
|
+
return same ? null : shipped;
|
|
27
|
+
}
|
|
28
|
+
function resolve(nuxtApp) {
|
|
29
|
+
const base = getI18nConfig();
|
|
30
|
+
const shipped = useState("i18n-runtime-overrides", () => null);
|
|
31
|
+
if (import.meta.server) {
|
|
32
|
+
const config2 = nuxtApp.ssrContext?.event?.context?.i18n?.config ?? resolveOnServer(base, nuxtApp.$config.public);
|
|
33
|
+
shipped.value = shippedOverrides(config2, base);
|
|
34
|
+
return config2;
|
|
35
|
+
}
|
|
36
|
+
if (nuxtApp.payload.serverRendered) {
|
|
37
|
+
const config2 = shipped.value ? applyI18nRuntimeOverrides(base, shipped.value, silent) : base;
|
|
38
|
+
return domains && shipped.value?.domain ? configForHost(config2, shipped.value.domain) : config2;
|
|
39
|
+
}
|
|
40
|
+
const config = applyI18nRuntimeOverrides(base, readI18nRuntimeOverrides(nuxtApp.$config.public));
|
|
41
|
+
return domains ? configForHost(config, location.host) : config;
|
|
42
|
+
}
|
|
43
|
+
export function useResolvedI18nConfig(nuxtApp) {
|
|
44
|
+
let config = configs.get(nuxtApp);
|
|
45
|
+
if (!config) configs.set(nuxtApp, config = resolve(nuxtApp));
|
|
46
|
+
return config;
|
|
47
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PathStrategy } from '@i18n-micro/path-strategy';
|
|
2
|
+
import type { ResolvedI18nConfig } from '@i18n-micro/types';
|
|
3
|
+
import type { Router } from 'vue-router';
|
|
4
|
+
import type { NuxtApp } from 'nuxt/app';
|
|
5
|
+
import type { useI18nLocale } from '../composables/useI18nLocale.js';
|
|
6
|
+
import type { NuxtI18nPluginApi } from './i18n-api.js';
|
|
7
|
+
import type { NuxtI18n, NuxtTranslationLoader } from './nuxt-i18n.js';
|
|
8
|
+
export interface I18nNavigationDeps {
|
|
9
|
+
nuxtApp: NuxtApp;
|
|
10
|
+
router: Router;
|
|
11
|
+
i18n: NuxtI18n;
|
|
12
|
+
loader: NuxtTranslationLoader;
|
|
13
|
+
i18nConfig: ResolvedI18nConfig;
|
|
14
|
+
i18nStrategy: PathStrategy;
|
|
15
|
+
locale: ReturnType<typeof useI18nLocale>;
|
|
16
|
+
api: NuxtI18nPluginApi;
|
|
17
|
+
/** The page changed: what the last one set for itself (route params, head) goes. */
|
|
18
|
+
leavePage: () => void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Translations follow navigations: a guard loads what the target shows, and it is applied when the navigation commits.
|
|
22
|
+
* A guard is too early: a later guard can abort, and the router cancels a pending navigation for a duplicate of the
|
|
23
|
+
* current page without running one.
|
|
24
|
+
* ponytail: a file of its own, like `i18n-api`, keeps the plugin a flat list of steps for about 300 B gzip of property names. Inline them if a bundle budget needs it.
|
|
25
|
+
*/
|
|
26
|
+
export declare function setupI18nNavigation(deps: I18nNavigationDeps): void;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { hooks } from "#build/i18n.strategy.mjs";
|
|
2
|
+
function createOutgoingWatch(nuxtApp, i18n) {
|
|
3
|
+
let stopWaiting;
|
|
4
|
+
let noElement = null;
|
|
5
|
+
nuxtApp.hook("page:finish", () => {
|
|
6
|
+
i18n.finishTransition(noElement);
|
|
7
|
+
noElement = null;
|
|
8
|
+
});
|
|
9
|
+
return (page) => {
|
|
10
|
+
stopWaiting?.();
|
|
11
|
+
noElement = null;
|
|
12
|
+
const outgoing = i18n.outgoing;
|
|
13
|
+
const element = page?.$el;
|
|
14
|
+
if (!element?.isConnected) {
|
|
15
|
+
noElement = outgoing;
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (!outgoing) return;
|
|
19
|
+
const observer = new MutationObserver(() => {
|
|
20
|
+
if (element.isConnected) return;
|
|
21
|
+
stopWaiting?.();
|
|
22
|
+
i18n.finishTransition(outgoing);
|
|
23
|
+
});
|
|
24
|
+
observer.observe(document, { childList: true, subtree: true });
|
|
25
|
+
stopWaiting = () => {
|
|
26
|
+
observer.disconnect();
|
|
27
|
+
stopWaiting = void 0;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function createRegister(nuxtApp, { mergeTranslation }) {
|
|
32
|
+
const subscribed = () => {
|
|
33
|
+
const { _hooks: subscribers } = nuxtApp.hooks;
|
|
34
|
+
return !subscribers || Boolean(subscribers["i18n:register"]?.length);
|
|
35
|
+
};
|
|
36
|
+
return async (locale, routeName) => {
|
|
37
|
+
if (!subscribed()) return;
|
|
38
|
+
const merges = [];
|
|
39
|
+
const register = (translations, selectedLocale) => {
|
|
40
|
+
merges.push(mergeTranslation(selectedLocale ?? locale, routeName, translations));
|
|
41
|
+
};
|
|
42
|
+
await nuxtApp.callHook("i18n:register", register, locale);
|
|
43
|
+
await Promise.all(merges);
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function setupI18nNavigation(deps) {
|
|
47
|
+
const { nuxtApp, router, i18n, loader, i18nConfig, i18nStrategy, api, leavePage } = deps;
|
|
48
|
+
const noPrefix = i18nConfig.strategy === "no_prefix";
|
|
49
|
+
const { locale: localeState, getPreferredLocale, isValidLocale, setLocale } = deps.locale;
|
|
50
|
+
const finishWhenGone = createOutgoingWatch(nuxtApp, i18n);
|
|
51
|
+
const register = createRegister(nuxtApp, api.helper);
|
|
52
|
+
const targets = /* @__PURE__ */ new WeakMap();
|
|
53
|
+
if (hooks) nuxtApp.hooks.hookOnce("app:created", () => register(i18n.getLocale(), i18n.getCurrentRouteName()));
|
|
54
|
+
const loadTarget = async (to) => {
|
|
55
|
+
try {
|
|
56
|
+
const hashLocale = i18nConfig.hashMode ? i18nStrategy.getLocaleFromPath(to.path) : null;
|
|
57
|
+
const locale = hashLocale || api.provide.getLocale(to);
|
|
58
|
+
const routeName = i18nStrategy.getPluginRouteName(to, locale);
|
|
59
|
+
const changed = locale !== i18n.getLocale() || routeName !== i18n.getCurrentRouteName();
|
|
60
|
+
return { locale, routeName, changed, data: changed ? await loader.fetchContext(locale, routeName) : null };
|
|
61
|
+
} catch (e) {
|
|
62
|
+
if (process.env.NODE_ENV !== "production") console.error("[i18n] Navigation error:", e);
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
router.beforeEach(async (to, from) => {
|
|
67
|
+
if (to.path === from.path && !noPrefix) return;
|
|
68
|
+
const paths = noPrefix ? to.matched.at(-1)?.meta.i18n?.paths : void 0;
|
|
69
|
+
const customPath = paths?.[api.provide.getLocale(to)];
|
|
70
|
+
if (customPath) return { path: customPath, query: to.query, hash: to.hash };
|
|
71
|
+
loader.invalidatePendingSwitches();
|
|
72
|
+
const target = await loadTarget(to);
|
|
73
|
+
if (!target) return;
|
|
74
|
+
targets.set(to, target);
|
|
75
|
+
if (hooks) await register(target.locale, target.routeName);
|
|
76
|
+
});
|
|
77
|
+
router.afterEach((to, from, failure) => {
|
|
78
|
+
if (failure) return;
|
|
79
|
+
if (to.path !== from.path) leavePage();
|
|
80
|
+
const target = targets.get(to);
|
|
81
|
+
if (!target) return;
|
|
82
|
+
const { locale, routeName, changed, data } = target;
|
|
83
|
+
const applied = !changed || loader.applyContext(locale, routeName, data && (loader.loadFromCacheSync(locale, routeName) ?? data));
|
|
84
|
+
const left = from.matched.at(-1);
|
|
85
|
+
const stays = from.matched.some((record) => record.path === left?.path && to.matched.includes(record));
|
|
86
|
+
if (changed && applied && !stays) finishWhenGone(left?.instances.default);
|
|
87
|
+
const bound = noPrefix && locale !== (getPreferredLocale() ?? i18nConfig.defaultLocale);
|
|
88
|
+
if (!applied || bound || !locale || !isValidLocale(locale)) return;
|
|
89
|
+
if (!to.matched.length) {
|
|
90
|
+
localeState.value = locale;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const urlNamed = !noPrefix && !i18nConfig.hashMode && import.meta.client && !nuxtApp.isHydrating;
|
|
94
|
+
if (urlNamed || localeState.value !== locale) setLocale(locale);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { TranslationKey, Translations } from '@i18n-micro/types';
|
|
2
|
+
import { NuxtI18n } from './nuxt-i18n.js';
|
|
3
|
+
type Tree = Record<string, unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* The instance of a server request. Its chunks are the process's shared trees, and a patch (`i18n:register`,
|
|
6
|
+
* `$mergeTranslations`) is this request's alone: merging would copy the root of a shared tree on every request, and a new
|
|
7
|
+
* tree starts with a cold leaf cache. So a patch stays beside its chunk, and a lookup reads through it. A subscriber may
|
|
8
|
+
* register other keys per request: nothing is shared or cached between requests but the chunk, which no one writes to.
|
|
9
|
+
* ponytail: what needs the whole tree (`$resolveTranslations`, `$setTranslation`) merges once, at O(chunk) for that request.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ServerNuxtI18n extends NuxtI18n {
|
|
12
|
+
/** Patches of the dictionary on screen, not merged yet, in order. A chunk off screen is read by no one here: it merges at once. */
|
|
13
|
+
private overlay;
|
|
14
|
+
private keyOnScreen;
|
|
15
|
+
/** The merge the patches stood in for, once a caller needs the tree. */
|
|
16
|
+
private settle;
|
|
17
|
+
getChunk(locale: string, routeName?: string): Tree;
|
|
18
|
+
protected patch(locale: string, routeName: string, translations: Translations): void;
|
|
19
|
+
protected resolveLookup(key: TranslationKey, routeContext?: unknown): unknown | null;
|
|
20
|
+
applySwitchContext(locale: string, routeName: string | undefined, data: Tree | null): void;
|
|
21
|
+
resolveTranslations(routeContext?: unknown): Translations;
|
|
22
|
+
setTranslation(key: TranslationKey, value: unknown): void;
|
|
23
|
+
dropChunks(): void;
|
|
24
|
+
clearCache(): void;
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { mergeTranslationChunk } from "@i18n-micro/core";
|
|
2
|
+
import { NuxtI18n } from "./nuxt-i18n.js";
|
|
3
|
+
const isTree = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
4
|
+
function nodesAt(layers, key) {
|
|
5
|
+
const trees = [];
|
|
6
|
+
for (let index = layers.length - 1; index >= 0; index--) {
|
|
7
|
+
const layer = layers[index];
|
|
8
|
+
if (!Object.hasOwn(layer, key)) continue;
|
|
9
|
+
const value = layer[key];
|
|
10
|
+
if (!isTree(value)) return trees.length ? trees : [value];
|
|
11
|
+
trees.unshift(value);
|
|
12
|
+
}
|
|
13
|
+
return trees;
|
|
14
|
+
}
|
|
15
|
+
const valueOf = (nodes) => nodes.length < 2 ? nodes[0] : nodes.reduce((merged, node) => mergeTranslationChunk(merged, node));
|
|
16
|
+
function resolveLayered(layers, key) {
|
|
17
|
+
const flat = valueOf(nodesAt(layers, key));
|
|
18
|
+
if (flat !== void 0 && flat !== null || key.indexOf(".") === -1) return flat ?? null;
|
|
19
|
+
let nodes = layers;
|
|
20
|
+
for (const part of key.split(".")) {
|
|
21
|
+
const node = nodes[0];
|
|
22
|
+
if (node === null || typeof node !== "object" || part === "__proto__" || part === "length" && Array.isArray(node)) return null;
|
|
23
|
+
nodes = nodesAt(nodes, part);
|
|
24
|
+
}
|
|
25
|
+
return valueOf(nodes) ?? null;
|
|
26
|
+
}
|
|
27
|
+
function touches(fragments, key) {
|
|
28
|
+
const dot = key.indexOf(".");
|
|
29
|
+
const head = dot === -1 ? key : key.slice(0, dot);
|
|
30
|
+
return fragments.some((fragment) => Object.hasOwn(fragment, key) || Object.hasOwn(fragment, head));
|
|
31
|
+
}
|
|
32
|
+
export class ServerNuxtI18n extends NuxtI18n {
|
|
33
|
+
/** Patches of the dictionary on screen, not merged yet, in order. A chunk off screen is read by no one here: it merges at once. */
|
|
34
|
+
overlay;
|
|
35
|
+
keyOnScreen() {
|
|
36
|
+
return this.getCacheKey(this.getLocale(), this.getCurrentRouteName());
|
|
37
|
+
}
|
|
38
|
+
/** The merge the patches stood in for, once a caller needs the tree. */
|
|
39
|
+
settle() {
|
|
40
|
+
if (!this.overlay) return;
|
|
41
|
+
const merged = this.overlay.reduce((tree, fragment) => mergeTranslationChunk(tree, fragment), this.cachedTranslations);
|
|
42
|
+
this.overlay = void 0;
|
|
43
|
+
this.cachedTranslations = merged;
|
|
44
|
+
this.storePatched(this.keyOnScreen(), merged);
|
|
45
|
+
}
|
|
46
|
+
getChunk(locale, routeName) {
|
|
47
|
+
if (this.overlay && this.getCacheKey(locale, routeName) === this.keyOnScreen()) this.settle();
|
|
48
|
+
return super.getChunk(locale, routeName);
|
|
49
|
+
}
|
|
50
|
+
patch(locale, routeName, translations) {
|
|
51
|
+
const active = locale === this.getLocale() && (routeName || "index") === this.getRoute();
|
|
52
|
+
if (!active || this.storage.translations.get(this.keyOnScreen()) !== this.cachedTranslations) return super.patch(locale, routeName, translations);
|
|
53
|
+
(this.overlay ??= []).push(mergeTranslationChunk({}, translations));
|
|
54
|
+
this.notifyLoaded();
|
|
55
|
+
}
|
|
56
|
+
resolveLookup(key, routeContext) {
|
|
57
|
+
if (!this.overlay) return super.resolveLookup(key, routeContext);
|
|
58
|
+
const text = String(key);
|
|
59
|
+
const chunkKey = routeContext ? this.chunkKeyOf(routeContext) : void 0;
|
|
60
|
+
if (chunkKey !== void 0 && chunkKey !== this.keyOnScreen() || !touches(this.overlay, text)) return super.resolveLookup(key, routeContext);
|
|
61
|
+
return resolveLayered([this.cachedTranslations, ...this.overlay], text);
|
|
62
|
+
}
|
|
63
|
+
applySwitchContext(locale, routeName, data) {
|
|
64
|
+
this.settle();
|
|
65
|
+
super.applySwitchContext(locale, routeName, data);
|
|
66
|
+
}
|
|
67
|
+
resolveTranslations(routeContext) {
|
|
68
|
+
this.settle();
|
|
69
|
+
return super.resolveTranslations(routeContext);
|
|
70
|
+
}
|
|
71
|
+
setTranslation(key, value) {
|
|
72
|
+
this.settle();
|
|
73
|
+
super.setTranslation(key, value);
|
|
74
|
+
}
|
|
75
|
+
dropChunks() {
|
|
76
|
+
this.settle();
|
|
77
|
+
super.dropChunks();
|
|
78
|
+
}
|
|
79
|
+
clearCache() {
|
|
80
|
+
this.overlay = void 0;
|
|
81
|
+
super.clearCache();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { BaseI18n, type BaseI18nOptions, type TranslationStorage } from '@i18n-micro/core';
|
|
2
|
-
import type {
|
|
3
|
-
import
|
|
4
|
-
import type { RouteLocationNamedRaw, RouteLocationNormalizedLoaded, RouteLocationRaw, RouteLocationResolved, RouteLocationResolvedGeneric, Router } from 'vue-router';
|
|
5
|
-
import type { GetLocaleFromRoute } from '../composables/useI18nLocale.js';
|
|
1
|
+
import { BaseI18n, FormatService, type BaseI18nOptions, type TranslationStorage } from '@i18n-micro/core';
|
|
2
|
+
import type { CleanTranslation, ResolvedI18nConfig, Params, TranslationKey, Translations } from '@i18n-micro/types';
|
|
3
|
+
import { type ShallowRef } from 'vue';
|
|
6
4
|
import { type LoadOptions } from './storage.js';
|
|
7
5
|
export type TranslationFn<T extends CleanTranslation> = (key: TranslationKey, params?: Params, defaultValue?: string | null) => T;
|
|
8
6
|
export interface RouteTranslationContext {
|
|
@@ -21,11 +19,17 @@ export interface NuxtI18nOptions extends BaseI18nOptions {
|
|
|
21
19
|
*/
|
|
22
20
|
cacheMaxSize?: number;
|
|
23
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;
|
|
24
28
|
export declare class NuxtI18n extends BaseI18n {
|
|
25
29
|
readonly storage: TranslationStorage;
|
|
26
30
|
private readonly cacheMaxSize;
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
protected readonly contextSignal: ShallowRef<number>;
|
|
32
|
+
protected cachedTranslations: Record<string, unknown>;
|
|
29
33
|
/**
|
|
30
34
|
* Hot-reload buffer for page transitions: the leaving page's chunk, kept until it unmounts.
|
|
31
35
|
*
|
|
@@ -33,6 +37,10 @@ export declare class NuxtI18n extends BaseI18n {
|
|
|
33
37
|
* already installed. Cleared in `finishTransition`.
|
|
34
38
|
*/
|
|
35
39
|
private outgoingTranslations;
|
|
40
|
+
/** A lookup was answered from `outgoingTranslations`: only then does dropping it change a render. */
|
|
41
|
+
private outgoingServed;
|
|
42
|
+
/** Chunks that hold only what was patched in: no page's file has reached them, so to a loader they are not loaded. */
|
|
43
|
+
private readonly fragments;
|
|
36
44
|
private currentLocale;
|
|
37
45
|
private currentRouteName;
|
|
38
46
|
private resolveRouteContext?;
|
|
@@ -42,19 +50,16 @@ export declare class NuxtI18n extends BaseI18n {
|
|
|
42
50
|
setRouteContextResolver(resolver: GetRouteTranslationContext): void;
|
|
43
51
|
getCacheKey(locale: string, routeName?: string): string;
|
|
44
52
|
getChunk(locale: string, routeName?: string): Record<string, unknown>;
|
|
45
|
-
|
|
53
|
+
/** The chunk is loaded. What a patch alone stored is a fragment, see `installChunk`. */
|
|
54
|
+
hasChunk(locale: string, routeName?: string): boolean;
|
|
46
55
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* The loader only installs the chunk for the page being rendered, but SSR may
|
|
50
|
-
* load buckets for other `(locale, route)` pairs — `$tForRoute()` resolves against
|
|
51
|
-
* `getChunk()` directly. Without this they hydrate as missing until navigation.
|
|
52
|
-
*
|
|
53
|
-
* Existing entries win: anything already loaded is kept as-is.
|
|
56
|
+
* A chunk fresh from a load or the cache, under what was patched in before it came: patches win. Without any, it
|
|
57
|
+
* is stored as it is: a stored tree is never written to, so it needs no copy.
|
|
54
58
|
*/
|
|
55
|
-
|
|
56
|
-
hasChunk(locale: string, routeName?: string): boolean;
|
|
59
|
+
installChunk(locale: string, routeName: string | undefined, data: Record<string, unknown>): Record<string, unknown>;
|
|
57
60
|
mergeChunk(locale: string, routeName: string | undefined, data: Record<string, unknown>): Record<string, unknown>;
|
|
61
|
+
/** A tree a patch made: over no loaded chunk (a stand-in, an evicted one) it is a fragment, and the next visit still loads. */
|
|
62
|
+
protected storePatched(key: string, tree: Record<string, unknown>): void;
|
|
58
63
|
/**
|
|
59
64
|
* FIFO eviction for `storage.translations` when `cacheMaxSize` is set.
|
|
60
65
|
* Prefers keeping the just-written key and the active page chunk. When those
|
|
@@ -66,7 +71,6 @@ export declare class NuxtI18n extends BaseI18n {
|
|
|
66
71
|
getLocale(): string;
|
|
67
72
|
getFallbackLocale(): string;
|
|
68
73
|
getRoute(): string;
|
|
69
|
-
getCurrentLocale(): string;
|
|
70
74
|
getCurrentRouteName(): string;
|
|
71
75
|
onContextChange(listener: (reason: I18nContextChangeReason) => void): () => void;
|
|
72
76
|
onMissingKey(listener: (locale: string, key: string, routeName: string) => void): () => void;
|
|
@@ -81,17 +85,33 @@ export declare class NuxtI18n extends BaseI18n {
|
|
|
81
85
|
* a shallow merge loses nested keys. Two plain objects + fallthrough on miss is cheaper
|
|
82
86
|
* and correct for the short hot-reload window.
|
|
83
87
|
*/
|
|
84
|
-
applySwitchContext(locale: string, routeName: string | undefined, data: Record<string, unknown>): void;
|
|
85
|
-
/**
|
|
86
|
-
|
|
88
|
+
applySwitchContext(locale: string, routeName: string | undefined, data: Record<string, unknown> | null): void;
|
|
89
|
+
/**
|
|
90
|
+
* Swap the active dictionary for a fresh copy of the same page. Not a page change: a live
|
|
91
|
+
* transition keeps its outgoing layer, the leaving page is still on screen.
|
|
92
|
+
*/
|
|
93
|
+
replaceDictionary(data: Record<string, unknown>): void;
|
|
94
|
+
/** The leaving page's chunk while it may still render, or null. */
|
|
95
|
+
get outgoing(): Record<string, unknown> | null;
|
|
96
|
+
/**
|
|
97
|
+
* The outgoing page has unmounted, so its keys must stop resolving. `outgoing` names which
|
|
98
|
+
* page left: a transition that ends after the next navigation must not drop that one's chunk.
|
|
99
|
+
*/
|
|
100
|
+
finishTransition(outgoing?: Record<string, unknown> | null): void;
|
|
101
|
+
/** Merge a fragment into a chunk and, when that chunk is on screen, into the active dictionary. */
|
|
102
|
+
protected patch(locale: string, routeName: string, translations: Translations): void;
|
|
103
|
+
/** The active dictionary changed: what rendered from it renders again. */
|
|
104
|
+
protected notifyLoaded(): void;
|
|
87
105
|
mergeTranslations(newTranslations: Translations): void;
|
|
88
106
|
loadPageTranslations(locale: string, routeName: string, translations: Translations): Promise<void>;
|
|
89
107
|
mergeTranslationAsync(locale: string, routeName: string, newTranslations: Translations, loadIfMissing: LoadTranslationsChunk): Promise<void>;
|
|
90
108
|
tForRoute(route: unknown): TranslationFn<CleanTranslation>;
|
|
91
109
|
tsForRoute(route: unknown): TranslationFn<string>;
|
|
92
110
|
protected touch(): void;
|
|
111
|
+
/** The chunk a route reads from, when routes are known here. */
|
|
112
|
+
protected chunkKeyOf(routeContext: unknown): string | undefined;
|
|
93
113
|
protected resolveLookup(key: TranslationKey, routeContext?: unknown): unknown | null;
|
|
94
|
-
protected resolveHas(key: TranslationKey): boolean;
|
|
114
|
+
protected resolveHas(key: TranslationKey, routeContext?: unknown): boolean;
|
|
95
115
|
/**
|
|
96
116
|
* Dump what `$t` can resolve right now — including the hot-reload buffer while a transition
|
|
97
117
|
* is in flight. Outside that window this is just `cachedTranslations`.
|
|
@@ -110,100 +130,65 @@ export declare class NuxtI18n extends BaseI18n {
|
|
|
110
130
|
routeName: string;
|
|
111
131
|
};
|
|
112
132
|
protected warnMissing(key: TranslationKey, routeContext?: unknown): void;
|
|
133
|
+
/** Drop every stored chunk but keep the dictionary on screen until a reload replaces it. */
|
|
134
|
+
dropChunks(): void;
|
|
113
135
|
clearCache(): void;
|
|
114
136
|
}
|
|
115
137
|
export interface NuxtTranslationLoaderOptions {
|
|
116
138
|
i18n: NuxtI18n;
|
|
117
139
|
loadOptions: LoadOptions;
|
|
118
|
-
|
|
140
|
+
/** The render is a file: a first page with no dictionary fails, as raw keys would stay in it. */
|
|
141
|
+
prerender?: boolean;
|
|
142
|
+
/** Any chunk of this render failed to load, the first page's or one merged later. A 404 is no failure. */
|
|
143
|
+
onFailedRead?: () => void;
|
|
119
144
|
}
|
|
120
145
|
export declare class NuxtTranslationLoader {
|
|
121
146
|
private readonly options;
|
|
122
147
|
private readonly pendingLoads;
|
|
123
148
|
/**
|
|
124
|
-
* Monotonic token so a slower in-flight `switchContext`
|
|
125
|
-
*
|
|
149
|
+
* Monotonic token so a slower in-flight `switchContext` (`$switchLocale` without a URL to move)
|
|
150
|
+
* cannot overwrite a newer one. Navigations do not need it: they apply when they commit.
|
|
126
151
|
*/
|
|
127
152
|
private switchGeneration;
|
|
128
153
|
constructor(options: NuxtTranslationLoaderOptions);
|
|
129
154
|
/**
|
|
130
155
|
* Bump the switch generation so any in-flight `switchContext` becomes a no-op.
|
|
131
|
-
* Call on every navigation that may supersede a pending load
|
|
132
|
-
* target `(locale, routeName)` already matches the applied context (caller skips switch).
|
|
156
|
+
* Call on every navigation that may supersede a pending load.
|
|
133
157
|
*/
|
|
134
158
|
invalidatePendingSwitches(): void;
|
|
135
159
|
loadFromCacheSync(locale: string, routeName?: string): Record<string, unknown> | null;
|
|
160
|
+
/** The chunk, or `{}` when the load fails (a page without translations still renders). */
|
|
136
161
|
loadAsync(locale: string, routeName?: string): Promise<Record<string, unknown>>;
|
|
162
|
+
/** The loaded chunk, or null when the load failed (reported here): a reload must tell that from an empty chunk. */
|
|
163
|
+
private loadChunk;
|
|
137
164
|
/**
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
165
|
+
* Reload the current context's dictionary (after a cache clear, an HMR edit). Unlike
|
|
166
|
+
* {@link switchContext} it does not supersede an in-flight navigation: the fresh chunk is
|
|
167
|
+
* applied only if the context is still the one it was loaded for.
|
|
168
|
+
*/
|
|
169
|
+
refresh(): Promise<void>;
|
|
170
|
+
/**
|
|
171
|
+
* Drop every cached chunk (storage and instance) and reload the current page's dictionary.
|
|
172
|
+
* The page keeps its current dictionary until the fresh one is applied: no raw keys between.
|
|
173
|
+
*/
|
|
174
|
+
reload(): Promise<void>;
|
|
175
|
+
/** The chunk of a context, cached or loaded, or null when its load failed. */
|
|
176
|
+
fetchContext(locale: string, routeName?: string): Record<string, unknown> | Promise<Record<string, unknown> | null>;
|
|
177
|
+
/**
|
|
178
|
+
* Make a fetched chunk the active context. `false` when its load failed: that keeps what is on screen, as
|
|
179
|
+
* refresh() does, since an empty dictionary would turn the strings that still work (a layout's) into raw keys.
|
|
180
|
+
* Only a first render has nothing to keep. A prerender throws there instead: no next request would load it again.
|
|
181
|
+
*/
|
|
182
|
+
applyContext(locale: string, routeName: string | undefined, data: Record<string, unknown> | null): boolean;
|
|
183
|
+
/**
|
|
184
|
+
* Load and apply translations for a locale/route. Returns `false` when the switch did not
|
|
185
|
+
* apply — a newer navigation superseded it, or its chunk failed to load — and callers must
|
|
186
|
+
* skip post-await side effects (e.g. `setLocale`). The next navigation loads it again.
|
|
141
187
|
*/
|
|
142
188
|
switchContext(locale: string, routeName?: string): Promise<boolean>;
|
|
189
|
+
/**
|
|
190
|
+
* A first render whose chunk failed has nothing to keep, but its root dictionary can be at hand (the shared
|
|
191
|
+
* cache in a host outage): the layout's strings stay text. A prerender must fail instead, it would keep the file.
|
|
192
|
+
*/
|
|
193
|
+
private firstRenderStandIn;
|
|
143
194
|
}
|
|
144
|
-
export interface NuxtI18nPluginApiDeps {
|
|
145
|
-
i18n: NuxtI18n;
|
|
146
|
-
loader: NuxtTranslationLoader;
|
|
147
|
-
i18nStrategy: PathStrategy;
|
|
148
|
-
i18nConfig: ModuleOptionsExtend;
|
|
149
|
-
router: Router;
|
|
150
|
-
getCurrentLocale: (route?: ResolvedRouteLike) => string;
|
|
151
|
-
getEffectiveLocale: (route: unknown, getLocaleFromRoute: GetLocaleFromRoute) => string;
|
|
152
|
-
getPluginRouteName: (route: ResolvedRouteLike, locale: string) => string;
|
|
153
|
-
getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, locale?: string) => string;
|
|
154
|
-
i18nRouteParams: {
|
|
155
|
-
value: I18nRouteParams;
|
|
156
|
-
};
|
|
157
|
-
setLocale: (locale: string) => void;
|
|
158
|
-
isValidLocale: (locale: string) => boolean;
|
|
159
|
-
navigateTo: (to: string, options?: {
|
|
160
|
-
redirectCode?: number;
|
|
161
|
-
external?: boolean;
|
|
162
|
-
}) => unknown;
|
|
163
|
-
setMissingHandler: (handler: MissingHandler | null) => void;
|
|
164
|
-
}
|
|
165
|
-
export declare function createNuxtI18nPluginApi(deps: NuxtI18nPluginApiDeps): {
|
|
166
|
-
helper: {
|
|
167
|
-
mergeTranslation(locale: string, routeName: string, newTranslations: Translations, _force?: boolean): Promise<void>;
|
|
168
|
-
};
|
|
169
|
-
switchContext: (locale: string, routeName?: string) => Promise<boolean>;
|
|
170
|
-
provide: {
|
|
171
|
-
i18nStrategy: PathStrategy;
|
|
172
|
-
getLocale: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric) => string;
|
|
173
|
-
getLocaleName: () => string | null;
|
|
174
|
-
defaultLocale: () => string | undefined;
|
|
175
|
-
getLocales: () => import("@i18n-micro/types").Locale[];
|
|
176
|
-
getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, locale?: string) => string;
|
|
177
|
-
t: (key: TranslationKey, params?: Params, defaultValue?: string | null, routeContext?: unknown) => CleanTranslation;
|
|
178
|
-
ts: (key: TranslationKey, params?: Params, defaultValue?: string, route?: RouteLocationNormalizedLoaded) => string;
|
|
179
|
-
_t: (route: RouteLocationNormalizedLoaded) => TranslationFn<CleanTranslation>;
|
|
180
|
-
_ts: (route: RouteLocationNormalizedLoaded) => TranslationFn<string>;
|
|
181
|
-
tc: (key: TranslationKey, count: number | Params, defaultValue?: string) => string;
|
|
182
|
-
tn: {
|
|
183
|
-
(value: number, options?: Intl.NumberFormatOptions): string;
|
|
184
|
-
(value: number, key: string, overrides?: Intl.NumberFormatOptions): string;
|
|
185
|
-
(value: number, key: string, locale: string, overrides?: Intl.NumberFormatOptions): string;
|
|
186
|
-
};
|
|
187
|
-
td: {
|
|
188
|
-
(value: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
|
|
189
|
-
(value: Date | number | string, key: string, overrides?: Intl.DateTimeFormatOptions): string;
|
|
190
|
-
(value: Date | number | string, key: string, locale: string, overrides?: Intl.DateTimeFormatOptions): string;
|
|
191
|
-
};
|
|
192
|
-
tdr: (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions) => string;
|
|
193
|
-
has: (key: TranslationKey, routeContext?: unknown) => boolean;
|
|
194
|
-
resolveTranslations: (routeContext?: unknown) => Translations;
|
|
195
|
-
setTranslation: (key: TranslationKey, value: unknown) => void;
|
|
196
|
-
mergeTranslations: (newTranslations: Translations) => void;
|
|
197
|
-
switchLocaleRoute: (toLocale: string) => RouteLocationRaw;
|
|
198
|
-
clearCache: () => void;
|
|
199
|
-
switchLocalePath: (toLocale: string) => string;
|
|
200
|
-
switchLocale: (toLocale: string) => Promise<unknown>;
|
|
201
|
-
switchRoute: (route: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, toLocale?: string) => unknown;
|
|
202
|
-
localeRoute(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): RouteLocationResolved;
|
|
203
|
-
localePath(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): string;
|
|
204
|
-
setI18nRouteParams: (value: I18nRouteParams) => I18nRouteParams;
|
|
205
|
-
loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
|
|
206
|
-
setMissingHandler: (handler: MissingHandler | null) => void;
|
|
207
|
-
};
|
|
208
|
-
};
|
|
209
|
-
export type NuxtI18nPluginProvide = ReturnType<typeof createNuxtI18nPluginApi>['provide'];
|