nuxt-i18n-micro 3.30.3 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +2 -2
  3. package/dist/client/200.html +1 -1
  4. package/dist/client/404.html +1 -1
  5. package/dist/client/_nuxt/BVna_KKx.js +3 -0
  6. package/dist/client/_nuxt/Bw81RhAv.js +1 -0
  7. package/dist/client/_nuxt/CPz_jhsD.js +1 -0
  8. package/dist/client/_nuxt/CmvObVGq.js +1 -0
  9. package/dist/client/_nuxt/DKUOBR6F.js +5 -0
  10. package/dist/client/_nuxt/builds/latest.json +1 -1
  11. package/dist/client/_nuxt/builds/meta/356b87e3-45dd-4f80-a40a-b649665c1844.json +1 -0
  12. package/dist/client/_nuxt/{entry.BM-mexAv.css → entry.PSWtHhay.css} +1 -1
  13. package/dist/client/_nuxt/error-404.xs3fxhVJ.css +1 -0
  14. package/dist/client/_nuxt/error-500.C2S-4iLg.css +1 -0
  15. package/dist/client/index.html +1 -1
  16. package/dist/module.d.mts +14 -5
  17. package/dist/module.json +4 -1
  18. package/dist/module.mjs +857 -476
  19. package/dist/runtime/components/i18n-group.d.vue.ts +17 -9
  20. package/dist/runtime/components/i18n-group.vue +2 -4
  21. package/dist/runtime/components/i18n-group.vue.d.ts +17 -9
  22. package/dist/runtime/components/i18n-link.d.vue.ts +9 -6
  23. package/dist/runtime/components/i18n-link.vue +7 -28
  24. package/dist/runtime/components/i18n-link.vue.d.ts +9 -6
  25. package/dist/runtime/components/i18n-switcher.d.vue.ts +128 -54
  26. package/dist/runtime/components/i18n-switcher.vue +56 -156
  27. package/dist/runtime/components/i18n-switcher.vue.d.ts +128 -54
  28. package/dist/runtime/components/i18n-t.d.vue.ts +59 -116
  29. package/dist/runtime/components/i18n-t.vue +24 -71
  30. package/dist/runtime/components/i18n-t.vue.d.ts +59 -116
  31. package/dist/runtime/composables/useI18n.js +2 -38
  32. package/dist/runtime/composables/useI18nHead.js +21 -6
  33. package/dist/runtime/composables/useI18nLocale.d.ts +4 -1
  34. package/dist/runtime/composables/useI18nLocale.js +20 -22
  35. package/dist/runtime/composables/useLocaleHead.d.ts +26 -31
  36. package/dist/runtime/composables/useLocaleHead.js +47 -167
  37. package/dist/runtime/devtools/bridge.d.ts +2 -2
  38. package/dist/runtime/devtools/vue-devtools.d.ts +2 -2
  39. package/dist/runtime/devtools/vue-devtools.js +5 -5
  40. package/dist/runtime/middleware/i18n-redirect.global.d.ts +3 -2
  41. package/dist/runtime/middleware/i18n-redirect.global.js +30 -41
  42. package/dist/runtime/plugins/01.plugin.d.ts +156 -34
  43. package/dist/runtime/plugins/01.plugin.js +184 -138
  44. package/dist/runtime/plugins/02.meta.js +36 -43
  45. package/dist/runtime/plugins/04.vue-devtools.js +1 -1
  46. package/dist/runtime/plugins/06.redirect.d.ts +3 -3
  47. package/dist/runtime/plugins/06.redirect.js +65 -91
  48. package/dist/runtime/server/{utils → internal}/dev-live-translations.js +4 -3
  49. package/dist/runtime/server/internal/server-translation-cache.d.ts +9 -0
  50. package/dist/runtime/server/internal/server-translation-cache.js +10 -0
  51. package/dist/runtime/server/internal/shared-cache.d.ts +24 -0
  52. package/dist/runtime/server/internal/shared-cache.js +71 -0
  53. package/dist/runtime/server/internal/translation-watcher.d.ts +79 -0
  54. package/dist/runtime/server/internal/translation-watcher.js +105 -0
  55. package/dist/runtime/server/middleware/i18n.global.js +24 -37
  56. package/dist/runtime/server/payload-source.assets.d.ts +1 -0
  57. package/dist/runtime/server/payload-source.assets.js +3 -8
  58. package/dist/runtime/server/payload-source.fs.d.ts +1 -0
  59. package/dist/runtime/server/payload-source.fs.js +18 -7
  60. package/dist/runtime/server/plugins/failed-read.d.ts +7 -0
  61. package/dist/runtime/server/plugins/failed-read.js +23 -0
  62. package/dist/runtime/server/plugins/payload-cache.d.ts +6 -0
  63. package/dist/runtime/server/plugins/payload-cache.js +16 -0
  64. package/dist/runtime/server/plugins/watcher.dev.js +15 -79
  65. package/dist/runtime/server/routes/i18n.js +13 -14
  66. package/dist/runtime/server/utils/locale-detector.d.ts +32 -2
  67. package/dist/runtime/server/utils/locale-detector.js +48 -27
  68. package/dist/runtime/server/utils/locale-server-middleware.js +14 -33
  69. package/dist/runtime/server/utils/payload-cache.d.ts +4 -0
  70. package/dist/runtime/server/utils/payload-cache.js +13 -0
  71. package/dist/runtime/server/utils/server-loader.d.ts +17 -6
  72. package/dist/runtime/server/utils/server-loader.js +105 -51
  73. package/dist/runtime/server/utils/translation-server-middleware.js +18 -35
  74. package/dist/runtime/utils/cache-keys.d.ts +6 -0
  75. package/dist/runtime/utils/cache-keys.js +2 -0
  76. package/dist/runtime/utils/i18n-api.d.ts +185 -0
  77. package/dist/runtime/utils/i18n-api.js +196 -0
  78. package/dist/runtime/utils/i18n-config.d.ts +18 -0
  79. package/dist/runtime/utils/i18n-config.js +47 -0
  80. package/dist/runtime/utils/i18n-navigation.d.ts +26 -0
  81. package/dist/runtime/utils/i18n-navigation.js +96 -0
  82. package/dist/runtime/utils/nuxt-i18n-server.d.ts +26 -0
  83. package/dist/runtime/utils/nuxt-i18n-server.js +83 -0
  84. package/dist/runtime/utils/nuxt-i18n.d.ts +81 -96
  85. package/dist/runtime/utils/nuxt-i18n.js +180 -252
  86. package/dist/runtime/utils/page-head.d.ts +12 -0
  87. package/dist/runtime/utils/page-head.js +6 -0
  88. package/dist/runtime/utils/request-url.d.ts +6 -0
  89. package/dist/runtime/utils/request-url.js +11 -0
  90. package/dist/runtime/utils/storage.d.ts +31 -19
  91. package/dist/runtime/utils/storage.js +100 -49
  92. package/dist/runtime/utils/translation-entry.d.ts +6 -0
  93. package/dist/runtime/utils/translation-entry.js +16 -0
  94. package/dist/types.d.mts +4 -6
  95. package/internals.d.mts +9 -2
  96. package/package.json +78 -79
  97. package/dist/client/_nuxt/BOxWElQP.js +0 -21
  98. package/dist/client/_nuxt/CUWB4CwN.js +0 -3
  99. package/dist/client/_nuxt/CmUbe-uv.js +0 -1
  100. package/dist/client/_nuxt/DAzShkZF.js +0 -1
  101. package/dist/client/_nuxt/MokPP5zi.js +0 -1
  102. package/dist/client/_nuxt/builds/meta/60974e48-d00e-4f9b-9467-9d4b322519da.json +0 -1
  103. package/dist/client/_nuxt/error-404.D9Svp6Hk.css +0 -1
  104. package/dist/client/_nuxt/error-500.B-mjI9_9.css +0 -1
  105. package/dist/runtime/plugins/03.define.d.ts +0 -7
  106. package/dist/runtime/plugins/03.define.js +0 -42
  107. package/dist/runtime/plugins/05.hooks.d.ts +0 -2
  108. package/dist/runtime/plugins/05.hooks.js +0 -47
  109. package/dist/runtime/server/utils/server-translation-cache.d.ts +0 -11
  110. package/dist/runtime/server/utils/server-translation-cache.js +0 -20
  111. /package/dist/runtime/server/{utils → internal}/dev-live-translations.d.ts +0 -0
@@ -1,65 +1,119 @@
1
1
  import { isEnabledLocale } from "@i18n-micro/utils/active-locales";
2
- import { normalizeConfiguredLocales, toPremergedStorageKey } from "@i18n-micro/utils/merge-source";
2
+ import { mergeOverRoot, normalizeConfiguredLocales, PREMERGED_ROOT_PAGE, toPremergedStorageKey } from "@i18n-micro/utils/merge-source";
3
3
  import { fetchTranslationPayloadFromHost } from "@i18n-micro/utils/payload-fetch";
4
4
  import { resolveTranslationPayloadPage } from "@i18n-micro/utils/payload-url";
5
- import { resolveI18nConfigWithRuntimeOverrides } from "@i18n-micro/utils/runtime-config";
6
5
  import { loadSourceTranslationsFromStorage } from "@i18n-micro/utils/source-loader";
7
6
  import { toTranslations } from "@i18n-micro/utils/normalize";
8
- import { readPayload } from "#i18n-internal/payload-source";
7
+ import { embeddedPayloads, readPayload } from "#i18n-internal/payload-source";
9
8
  import { getI18nPrivateConfig } from "#i18n-internal/config";
10
- import { getI18nConfig } from "#i18n-internal/strategy";
11
- import { loadLiveDevTranslations } from "./dev-live-translations.js";
12
- import { getOrCreateServerTranslationCache } from "./server-translation-cache.js";
13
- export async function loadTranslationsFromServer(locale, routeName) {
14
- const cc = getOrCreateServerTranslationCache();
15
- const cacheKey = `${locale}:${routeName}`;
16
- const cached = cc.get(cacheKey);
17
- if (cached) {
18
- return cached;
9
+ import { loadLiveDevTranslations } from "../internal/dev-live-translations.js";
10
+ import { getI18nServerConfig } from "./locale-detector.js";
11
+ import { getOrCreateServerTranslationCache } from "../internal/server-translation-cache.js";
12
+ import { readSharedChunk, sharedChunkKey, writeSharedChunk } from "../internal/shared-cache.js";
13
+ import { takeRootMark, toEntry } from "../../utils/translation-entry.js";
14
+ const EMPTY = { data: {}, json: "{}" };
15
+ export const NO_FILE = { data: {}, json: "{}" };
16
+ const pending = /* @__PURE__ */ new Map();
17
+ const requestBoundIo = typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers";
18
+ function runtimeHost() {
19
+ try {
20
+ return process.env.NUXT_I18N_APP_BASE_SERVER_HOST;
21
+ } catch {
22
+ return void 0;
19
23
  }
20
- const config = resolveI18nConfigWithRuntimeOverrides(getI18nConfig());
21
- const privateConfig = getI18nPrivateConfig();
22
- if (!isEnabledLocale(config.locales, locale)) {
23
- const empty = { data: {}, json: "{}" };
24
- cc.set(cacheKey, empty);
25
- return empty;
24
+ }
25
+ export function resolveServerHost() {
26
+ return runtimeHost() ?? getI18nPrivateConfig().apiBaseServerHost;
27
+ }
28
+ export async function loadTranslationsFromServer(locale, routeName) {
29
+ const config = getI18nServerConfig();
30
+ if (!isEnabledLocale(config.locales, locale)) return EMPTY;
31
+ const page = resolveTranslationPayloadPage(routeName, config.routesLocaleLinks || {});
32
+ if (import.meta.dev && !resolveServerHost()) return toEntry(await loadLiveDevTranslations(locale, page, config));
33
+ return cached(`${locale}:${page}`, () => readChunk(locale, page, config));
34
+ }
35
+ function cached(key, read) {
36
+ const cache = getOrCreateServerTranslationCache();
37
+ const hit = cache.get(key);
38
+ if (hit) return Promise.resolve(hit);
39
+ const inFlight = requestBoundIo && !isEmbeddedRead() ? void 0 : pending.get(key);
40
+ if (inFlight) return inFlight;
41
+ const promise = read().then(({ entry, shared }) => {
42
+ if (!shared) cache.set(key, entry);
43
+ return entry;
44
+ }).finally(() => pending.delete(key));
45
+ pending.set(key, promise);
46
+ return promise;
47
+ }
48
+ const isEmbeddedRead = () => embeddedPayloads && !resolveServerHost();
49
+ const rootChunk = (locale, config) => cached(`${locale}:${PREMERGED_ROOT_PAGE}`, () => readChunk(locale, PREMERGED_ROOT_PAGE, config));
50
+ function readChunk(locale, page, config) {
51
+ const key = isEmbeddedRead() ? null : sharedChunkKey(locale, page, config, resolveServerHost());
52
+ return key ? readThroughShared(key, locale, page, config) : load(locale, page, config);
53
+ }
54
+ async function readThroughShared(key, locale, page, config) {
55
+ const hit = await readSharedChunk(key);
56
+ if (hit) return { entry: hit };
57
+ const loaded = await load(locale, page, config);
58
+ const { entry, shared, partial, marked } = loaded;
59
+ if (entry === NO_FILE || partial) return loaded;
60
+ if (shared) return { entry };
61
+ const json = JSON.stringify(entry.data);
62
+ if (json === "{}") return { entry };
63
+ if (page === PREMERGED_ROOT_PAGE) {
64
+ await writeSharedChunk(key, json, config);
65
+ return { entry };
26
66
  }
27
- const routesLocaleLinks = config.routesLocaleLinks || {};
28
- const resolvedPage = resolveTranslationPayloadPage(routeName, routesLocaleLinks);
29
- if (privateConfig.apiBaseServerHost) {
30
- const data2 = await fetchTranslationPayloadFromHost(
31
- { apiBaseUrl: config.apiBaseUrl, apiBaseServerHost: privateConfig.apiBaseServerHost, dateBuild: config.dateBuild },
67
+ const root = await rootChunk(locale, config).catch(() => null);
68
+ if (!root || !Object.keys(root.data).length) return { entry };
69
+ if (!marked && JSON.stringify(root.data) === json) return { entry: root };
70
+ if (Object.keys(root.data).every((rootKey) => Object.hasOwn(entry.data, rootKey))) await writeSharedChunk(key, json, config);
71
+ return { entry };
72
+ }
73
+ function toEntryWithMark(data, mark) {
74
+ const entry = toEntry(data);
75
+ entry.mark = mark;
76
+ return entry;
77
+ }
78
+ async function load(locale, page, config) {
79
+ const apiBaseServerHost = resolveServerHost();
80
+ if (apiBaseServerHost) {
81
+ const data = await fetchTranslationPayloadFromHost(
82
+ { apiBaseUrl: config.apiBaseUrl, apiBaseServerHost, dateBuild: config.dateBuild },
32
83
  locale,
33
- resolvedPage,
84
+ page,
34
85
  $fetch
35
86
  );
36
- const json2 = JSON.stringify(data2).replace(/</g, "\\u003c");
37
- const entry2 = { data: data2, json: json2 };
38
- cc.set(cacheKey, entry2);
39
- return entry2;
87
+ return data ? settle(locale, page, data, config) : { entry: NO_FILE, shared: true };
40
88
  }
41
- let data;
42
- if (import.meta.dev) {
43
- data = await loadLiveDevTranslations(locale, resolvedPage, config);
44
- } else {
45
- const mode = config.translationPayloadMode === "source" ? "source" : "premerged";
46
- if (mode === "source") {
47
- data = await loadSourceTranslationsFromStorage(
48
- { getItem: (key) => readPayload(key) },
49
- {
50
- locale,
51
- pageName: resolvedPage,
52
- locales: normalizeConfiguredLocales(config.locales),
53
- globalFallbackLocale: config.fallbackLocale,
54
- disablePageLocales: config.disablePageLocales
55
- }
56
- );
57
- } else {
58
- data = toTranslations(await readPayload(toPremergedStorageKey(resolvedPage, locale)));
59
- }
89
+ if (config.translationPayloadMode === "source") {
90
+ const data = await loadSourceTranslationsFromStorage(
91
+ { getItem: (key) => readPayload(key) },
92
+ {
93
+ locale,
94
+ pageName: page,
95
+ // a fallback may live on another domain: its file is still this build's
96
+ locales: normalizeConfiguredLocales(config.linkLocales ?? config.locales),
97
+ globalFallbackLocale: config.fallbackLocale,
98
+ disablePageLocales: config.disablePageLocales
99
+ }
100
+ );
101
+ return { entry: toEntry(data) };
60
102
  }
61
- const json = JSON.stringify(data).replace(/</g, "\\u003c");
62
- const entry = { data, json };
63
- cc.set(cacheKey, entry);
64
- return entry;
103
+ const own = toTranslations(await readPayload(toPremergedStorageKey(page, locale)));
104
+ if (Object.keys(own).length || page === PREMERGED_ROOT_PAGE) return settle(locale, page, own, config);
105
+ return { entry: await rootChunk(locale, config), shared: true };
106
+ }
107
+ async function settle(locale, page, file, config) {
108
+ const mark = takeRootMark(file);
109
+ if (!mark) return { entry: toEntry(file) };
110
+ if (page === PREMERGED_ROOT_PAGE) return { entry: toEntryWithMark(file, mark) };
111
+ let root = await rootChunk(locale, config);
112
+ if (root.mark && root.mark !== mark) {
113
+ getOrCreateServerTranslationCache().delete(`${locale}:${PREMERGED_ROOT_PAGE}`);
114
+ root = await rootChunk(locale, config);
115
+ }
116
+ const merged = mergeOverRoot(root.data, file);
117
+ const entry = merged === root.data ? root : toEntry(merged);
118
+ return root === NO_FILE ? { entry, shared: true, partial: true } : { entry, marked: true };
65
119
  }
@@ -1,44 +1,27 @@
1
1
  import { interpolate, resolveTranslation } from "@i18n-micro/core";
2
- import { getEnabledLocales } from "@i18n-micro/utils/active-locales";
3
- import { resolveI18nConfigWithRuntimeOverrides } from "@i18n-micro/utils/runtime-config";
4
- import { getI18nConfig } from "#i18n-internal/strategy";
5
- import { useRuntimeConfig } from "#imports";
6
- import { detectCurrentLocale } from "./locale-detector.js";
2
+ import { getI18nServerConfig, getRequestLocale } from "./locale-detector.js";
7
3
  import { loadTranslationsFromServer } from "./server-loader.js";
8
4
  const I18N_CONTEXT_KEY = "__i18n_translations__";
9
- export const useTranslationServerMiddleware = async (event, defaultLocale, currentLocale) => {
10
- const config = resolveI18nConfigWithRuntimeOverrides(
11
- getI18nConfig(),
12
- useRuntimeConfig(event).public
13
- );
14
- const { locales, fallbackLocale, defaultLocale: configDefaultLocale } = config;
15
- const locale = currentLocale || event.context.i18n?.locale || detectCurrentLocale(
16
- event,
17
- {
18
- fallbackLocale,
19
- defaultLocale: defaultLocale || configDefaultLocale,
20
- locales: getEnabledLocales(locales),
21
- localeCookie: config.localeCookie,
22
- autoDetectLanguage: config.autoDetectLanguage
23
- },
24
- defaultLocale
25
- );
26
- if (!event.context[I18N_CONTEXT_KEY]) {
27
- let translations2;
28
- if (event.context.i18n?.translations && Object.keys(event.context.i18n.translations).length > 0) {
29
- translations2 = event.context.i18n.translations;
30
- } else {
31
- const { data } = await loadTranslationsFromServer(locale, "index");
32
- translations2 = data;
33
- }
34
- event.context[I18N_CONTEXT_KEY] = translations2;
5
+ async function dictionaryOf(event, locale) {
6
+ const loaded = event.context[I18N_CONTEXT_KEY] ??= /* @__PURE__ */ Object.create(null);
7
+ const own = loaded[locale];
8
+ if (own) return own;
9
+ const page = event.context.i18n;
10
+ const preloaded = page?.locale === locale && page.translations && Object.keys(page.translations).length > 0 ? page.translations : null;
11
+ if (preloaded) return loaded[locale] = preloaded;
12
+ const chunk = await loadTranslationsFromServer(locale, "index").catch(() => null);
13
+ if (!chunk) {
14
+ console.warn("[i18n] Translation load failed:", locale, "index");
15
+ return {};
35
16
  }
36
- const translations = event.context[I18N_CONTEXT_KEY];
17
+ return loaded[locale] = chunk.data;
18
+ }
19
+ export const useTranslationServerMiddleware = async (event, defaultLocale, currentLocale) => {
20
+ const locale = currentLocale || getRequestLocale(event, getI18nServerConfig(event), defaultLocale);
21
+ const translations = await dictionaryOf(event, locale);
37
22
  function t(key, params, defaultValue) {
38
23
  const value = resolveTranslation(translations, key);
39
- if (value === null) {
40
- return defaultValue || key;
41
- }
24
+ if (value === null) return defaultValue && params ? interpolate(defaultValue, params) : defaultValue || key;
42
25
  const str = typeof value === "string" ? value : String(value);
43
26
  return params ? interpolate(str, params) : str;
44
27
  }
@@ -0,0 +1,6 @@
1
+ export declare const STORAGE_CC_KEY: unique symbol;
2
+ /**
3
+ * Entries the server keeps when `cacheMaxSize` is not set. Never unbounded: every distinct
4
+ * `locale:page` a request names would otherwise stay in memory for the life of the process.
5
+ */
6
+ export declare const DEFAULT_SERVER_CACHE_SIZE = 1000;
@@ -0,0 +1,2 @@
1
+ export const STORAGE_CC_KEY = Symbol.for("__NUXT_I18N_STORAGE_CC__");
2
+ export const DEFAULT_SERVER_CACHE_SIZE = 1e3;
@@ -0,0 +1,185 @@
1
+ import type { PathStrategy, ResolvedRouteLike, RouteLike } from '@i18n-micro/path-strategy';
2
+ import type { DefineI18nRouteConfig, I18nRouteParams, MissingHandler, ResolvedI18nConfig, Translations } from '@i18n-micro/types';
3
+ import type { RouteLocationNamedRaw, RouteLocationNormalizedLoaded, RouteLocationResolved, RouteLocationResolvedGeneric, Router } from 'vue-router';
4
+ import type { useI18nLocale } from '../composables/useI18nLocale.js';
5
+ import type { NuxtI18n, NuxtTranslationLoader } from './nuxt-i18n.js';
6
+ /** URL of a switch result: the strategy hands out a URL string or a resolved route (fullPath set). */
7
+ export declare function switchedPath(switched: RouteLike | string): string;
8
+ export interface NuxtI18nPluginApiDeps {
9
+ i18n: NuxtI18n;
10
+ loader: NuxtTranslationLoader;
11
+ i18nStrategy: PathStrategy;
12
+ i18nConfig: ResolvedI18nConfig;
13
+ router: Router;
14
+ locale: ReturnType<typeof useI18nLocale>;
15
+ getCurrentLocale: (route?: ResolvedRouteLike) => string;
16
+ i18nRouteParams: {
17
+ value: I18nRouteParams;
18
+ };
19
+ navigateTo: (to: string, options?: {
20
+ redirectCode?: number;
21
+ external?: boolean;
22
+ }) => unknown;
23
+ setMissingHandler: (handler: MissingHandler | null) => void;
24
+ isHydrating: () => boolean;
25
+ }
26
+ export declare function createNuxtI18nPluginApi(deps: NuxtI18nPluginApiDeps): {
27
+ helper: {
28
+ mergeTranslation(locale: string, routeName: string, newTranslations: Translations): Promise<void>;
29
+ };
30
+ provide: {
31
+ i18nStrategy: PathStrategy;
32
+ getLocale: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric) => string;
33
+ getLocaleName: () => string | null;
34
+ defaultLocale: () => string;
35
+ getLocales: () => import("@i18n-micro/types").Locale[];
36
+ getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
37
+ t: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").Params, defaultValue?: string | null, routeContext?: unknown) => import("@i18n-micro/types").CleanTranslation;
38
+ ts: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").Params, defaultValue?: string, routeContext?: unknown) => string;
39
+ _t: (route: RouteLocationNormalizedLoaded) => import("./nuxt-i18n.js").TranslationFn<import("@i18n-micro/types").CleanTranslation>;
40
+ _ts: (route: RouteLocationNormalizedLoaded) => import("./nuxt-i18n.js").TranslationFn<string>;
41
+ tc: (key: import("@i18n-micro/types").TranslationKey, count: number | import("@i18n-micro/types").Params, defaultValue?: string) => string;
42
+ pluralFunc: import("@i18n-micro/types").PluralFunc;
43
+ pluralLocale: () => string;
44
+ tn: {
45
+ (value: number, options?: Intl.NumberFormatOptions): string;
46
+ (value: number, key: string, overrides?: Intl.NumberFormatOptions): string;
47
+ (value: number, key: string, locale: string, overrides?: Intl.NumberFormatOptions): string;
48
+ };
49
+ td: {
50
+ (value: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
51
+ (value: Date | number | string, key: string, overrides?: Intl.DateTimeFormatOptions): string;
52
+ (value: Date | number | string, key: string, locale: string, overrides?: Intl.DateTimeFormatOptions): string;
53
+ };
54
+ tdr: (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions) => string;
55
+ has: (key: import("@i18n-micro/types").TranslationKey, routeContext?: unknown) => boolean;
56
+ resolveTranslations: (routeContext?: unknown) => Translations;
57
+ setTranslation: (key: import("@i18n-micro/types").TranslationKey, value: unknown) => void;
58
+ mergeTranslations: (newTranslations: Translations) => void;
59
+ switchLocaleRoute: (toLocale: string) => string | import("vue-router").RouteLocationAsRelativeGeneric | import("vue-router").RouteLocationAsPathGeneric;
60
+ clearCache: () => void;
61
+ switchLocalePath: (toLocale: string) => string;
62
+ switchLocale: (toLocale: string) => Promise<boolean>;
63
+ switchRoute: (route: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, toLocale?: string) => Promise<unknown>;
64
+ localeRoute(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): RouteLocationResolved;
65
+ localePath(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): string;
66
+ setI18nRouteParams: (value: I18nRouteParams) => I18nRouteParams;
67
+ loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
68
+ setMissingHandler: (handler: MissingHandler | null) => void;
69
+ };
70
+ };
71
+ export type NuxtI18nPluginApi = ReturnType<typeof createNuxtI18nPluginApi>;
72
+ export type NuxtI18nPluginProvide = NuxtI18nPluginApi['provide'];
73
+ /** 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'>): ({ locales }: DefineI18nRouteConfig) => Promise<void>;
75
+ export interface I18nProvideDeps {
76
+ api: NuxtI18nPluginApi;
77
+ i18nConfig: ResolvedI18nConfig;
78
+ router: Router;
79
+ i18nRouteParams: {
80
+ value: I18nRouteParams;
81
+ };
82
+ }
83
+ /**
84
+ * What the plugin provides: each helper as `$name` on the app, and under both spellings in `useI18n()`.
85
+ * `install` is apart so that the plugin's return type, which `NuxtApp` is made of, does not depend on `NuxtApp`.
86
+ */
87
+ export declare function createI18nProvide({ api: { helper, provide }, i18nConfig, router, i18nRouteParams }: I18nProvideDeps): {
88
+ provide: {
89
+ i18n: {
90
+ helper: {
91
+ mergeTranslation(locale: string, routeName: string, newTranslations: Translations): Promise<void>;
92
+ };
93
+ getI18nConfig: () => ResolvedI18nConfig;
94
+ __micro: boolean;
95
+ i18nStrategy: PathStrategy;
96
+ getLocale: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric) => string;
97
+ getLocaleName: () => string | null;
98
+ defaultLocale: () => string;
99
+ getLocales: () => import("@i18n-micro/types").Locale[];
100
+ getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
101
+ t: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").Params, defaultValue?: string | null, routeContext?: unknown) => import("@i18n-micro/types").CleanTranslation;
102
+ ts: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").Params, defaultValue?: string, routeContext?: unknown) => string;
103
+ _t: (route: RouteLocationNormalizedLoaded) => import("./nuxt-i18n.js").TranslationFn<import("@i18n-micro/types").CleanTranslation>;
104
+ _ts: (route: RouteLocationNormalizedLoaded) => import("./nuxt-i18n.js").TranslationFn<string>;
105
+ tc: (key: import("@i18n-micro/types").TranslationKey, count: number | import("@i18n-micro/types").Params, defaultValue?: string) => string;
106
+ pluralFunc: import("@i18n-micro/types").PluralFunc;
107
+ pluralLocale: () => string;
108
+ tn: {
109
+ (value: number, options?: Intl.NumberFormatOptions): string;
110
+ (value: number, key: string, overrides?: Intl.NumberFormatOptions): string;
111
+ (value: number, key: string, locale: string, overrides?: Intl.NumberFormatOptions): string;
112
+ };
113
+ td: {
114
+ (value: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
115
+ (value: Date | number | string, key: string, overrides?: Intl.DateTimeFormatOptions): string;
116
+ (value: Date | number | string, key: string, locale: string, overrides?: Intl.DateTimeFormatOptions): string;
117
+ };
118
+ tdr: (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions) => string;
119
+ has: (key: import("@i18n-micro/types").TranslationKey, routeContext?: unknown) => boolean;
120
+ resolveTranslations: (routeContext?: unknown) => Translations;
121
+ setTranslation: (key: import("@i18n-micro/types").TranslationKey, value: unknown) => void;
122
+ mergeTranslations: (newTranslations: Translations) => void;
123
+ switchLocaleRoute: (toLocale: string) => string | import("vue-router").RouteLocationAsRelativeGeneric | import("vue-router").RouteLocationAsPathGeneric;
124
+ clearCache: () => void;
125
+ switchLocalePath: (toLocale: string) => string;
126
+ switchLocale: (toLocale: string) => Promise<boolean>;
127
+ switchRoute: (route: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, toLocale?: string) => Promise<unknown>;
128
+ localeRoute(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): RouteLocationResolved;
129
+ localePath(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): string;
130
+ loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
131
+ setMissingHandler: (handler: MissingHandler | null) => void;
132
+ };
133
+ defineI18nRoute: ReturnType<typeof createDefineI18nRoute>;
134
+ helper: {
135
+ mergeTranslation(locale: string, routeName: string, newTranslations: Translations): Promise<void>;
136
+ };
137
+ getI18nConfig: () => ResolvedI18nConfig;
138
+ __micro: boolean;
139
+ i18nStrategy: PathStrategy;
140
+ getLocale: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric) => string;
141
+ getLocaleName: () => string | null;
142
+ defaultLocale: () => string;
143
+ getLocales: () => import("@i18n-micro/types").Locale[];
144
+ getRouteName: (route?: RouteLocationNormalizedLoaded | RouteLocationResolvedGeneric, _locale?: string) => string;
145
+ t: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").Params, defaultValue?: string | null, routeContext?: unknown) => import("@i18n-micro/types").CleanTranslation;
146
+ ts: (key: import("@i18n-micro/types").TranslationKey, params?: import("@i18n-micro/types").Params, defaultValue?: string, routeContext?: unknown) => string;
147
+ _t: (route: RouteLocationNormalizedLoaded) => import("./nuxt-i18n.js").TranslationFn<import("@i18n-micro/types").CleanTranslation>;
148
+ _ts: (route: RouteLocationNormalizedLoaded) => import("./nuxt-i18n.js").TranslationFn<string>;
149
+ tc: (key: import("@i18n-micro/types").TranslationKey, count: number | import("@i18n-micro/types").Params, defaultValue?: string) => string;
150
+ pluralFunc: import("@i18n-micro/types").PluralFunc;
151
+ pluralLocale: () => string;
152
+ tn: {
153
+ (value: number, options?: Intl.NumberFormatOptions): string;
154
+ (value: number, key: string, overrides?: Intl.NumberFormatOptions): string;
155
+ (value: number, key: string, locale: string, overrides?: Intl.NumberFormatOptions): string;
156
+ };
157
+ td: {
158
+ (value: Date | number | string, options?: Intl.DateTimeFormatOptions): string;
159
+ (value: Date | number | string, key: string, overrides?: Intl.DateTimeFormatOptions): string;
160
+ (value: Date | number | string, key: string, locale: string, overrides?: Intl.DateTimeFormatOptions): string;
161
+ };
162
+ tdr: (value: Date | number | string, options?: Intl.RelativeTimeFormatOptions) => string;
163
+ has: (key: import("@i18n-micro/types").TranslationKey, routeContext?: unknown) => boolean;
164
+ resolveTranslations: (routeContext?: unknown) => Translations;
165
+ setTranslation: (key: import("@i18n-micro/types").TranslationKey, value: unknown) => void;
166
+ mergeTranslations: (newTranslations: Translations) => void;
167
+ switchLocaleRoute: (toLocale: string) => string | import("vue-router").RouteLocationAsRelativeGeneric | import("vue-router").RouteLocationAsPathGeneric;
168
+ clearCache: () => void;
169
+ switchLocalePath: (toLocale: string) => string;
170
+ switchLocale: (toLocale: string) => Promise<boolean>;
171
+ switchRoute: (route: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, toLocale?: string) => Promise<unknown>;
172
+ localeRoute(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): RouteLocationResolved;
173
+ localePath(to: RouteLocationNamedRaw | RouteLocationResolvedGeneric | string, locale?: string): string;
174
+ loadPageTranslations: (locale: string, routeName: string, translations: Translations) => Promise<void>;
175
+ setMissingHandler: (handler: MissingHandler | null) => void;
176
+ };
177
+ install: (nuxtApp: {
178
+ vueApp: {
179
+ config: {
180
+ globalProperties: Record<string, unknown>;
181
+ };
182
+ };
183
+ }) => void;
184
+ };
185
+ export {};
@@ -0,0 +1,196 @@
1
+ import { isNoPrefixStrategy, urlPath } from "@i18n-micro/core";
2
+ import { trimTrailingSlashes } from "@i18n-micro/utils/app-path";
3
+ import { hasProtocol } from "ufo";
4
+ import { getCurrentInstance, onActivated, watch } from "vue";
5
+ import { domains, defineRoute, serverPathFallback } from "#build/i18n.strategy.mjs";
6
+ import { usePageRoute } from "./page-head.js";
7
+ import { applyTrailingSlash } from "./trailing-slash.js";
8
+ function toRouteLocationResolved(result) {
9
+ const fullPath = result.fullPath ?? result.path ?? "";
10
+ const path = result.path ?? urlPath(fullPath);
11
+ const out = { path, fullPath, href: fullPath };
12
+ if (result.name) out.name = result.name;
13
+ if (result.params && Object.keys(result.params).length) out.params = result.params;
14
+ if (result.query && Object.keys(result.query).length) out.query = result.query;
15
+ if (result.hash) out.hash = result.hash;
16
+ return out;
17
+ }
18
+ const URL_SCHEME = /^(?:https?:|mailto:|tel:)/i;
19
+ const DUPLICATED = 16;
20
+ const ABORTED = 4;
21
+ const APP_SCHEME = /^(?:mailto:|tel:)/i;
22
+ export function switchedPath(switched) {
23
+ return typeof switched === "string" ? switched : switched.fullPath ?? switched.path ?? "";
24
+ }
25
+ export function createNuxtI18nPluginApi(deps) {
26
+ const {
27
+ i18n,
28
+ loader,
29
+ i18nStrategy,
30
+ i18nConfig,
31
+ router,
32
+ locale: localeState,
33
+ getCurrentLocale,
34
+ i18nRouteParams,
35
+ navigateTo,
36
+ setMissingHandler,
37
+ isHydrating
38
+ } = deps;
39
+ const { getEffectiveLocale, setLocale, isValidLocale } = localeState;
40
+ const setLocaleState = (code) => {
41
+ localeState.locale.value = code;
42
+ };
43
+ const isNoPrefix = isNoPrefixStrategy(i18nConfig.strategy);
44
+ const withSlashRule = (switched) => i18nConfig.trailingSlash ? applyTrailingSlash(switchedPath(switched), i18nConfig.trailingSlash) : switched;
45
+ const linkRoute = () => {
46
+ const route = router.currentRoute.value;
47
+ if (!route.hash || !isHydrating()) return route;
48
+ return { ...route, hash: "", fullPath: route.fullPath.slice(0, route.fullPath.indexOf("#")) };
49
+ };
50
+ const switchedLink = (toLocale) => i18nStrategy.switchLocaleRoute(toLocale, linkRoute(), { i18nRouteParams: i18nRouteParams.value });
51
+ const isUrl = (to) => typeof to === "string" && URL_SCHEME.test(to);
52
+ const localized = (to, locale) => i18nStrategy.localeRoute(locale !== void 0 && locale !== "" ? String(locale) : getCurrentLocale(), to);
53
+ const switchLocaleLogic = (toLocale, i18nParams, to) => {
54
+ if (typeof to === "string" && APP_SCHEME.test(to)) return navigateTo(to, { redirectCode: 200, external: true });
55
+ const from = typeof to === "string" ? i18nStrategy.localeRoute(getCurrentLocale(), to) : to ?? router.currentRoute.value;
56
+ const switchedRoute = i18nStrategy.switchLocaleRoute(toLocale, from, {
57
+ i18nRouteParams: i18nParams,
58
+ named: isNoPrefix
59
+ });
60
+ const url = switchedPath(switchedRoute);
61
+ const domainUrl = i18nConfig.locales?.find((locale) => locale.code === toLocale)?.baseUrl;
62
+ const domain = domainUrl && trimTrailingSlashes(domainUrl);
63
+ if (domain && (url === domain || url.startsWith(`${domain}/`))) return navigateTo(url, { redirectCode: 200, external: true });
64
+ if (isNoPrefix) {
65
+ const { path, fullPath, ...named } = switchedRoute;
66
+ return router.replace({ ...named.name ? named : { path, fullPath, ...named }, force: true });
67
+ }
68
+ return router.push(withSlashRule(switchedRoute));
69
+ };
70
+ const settleSwitch = (toLocale, failure) => {
71
+ const type = failure instanceof Error ? failure.type : void 0;
72
+ const reloaded = serverPathFallback && type === ABORTED && !i18nStrategy.probe(provide.switchLocalePath(toLocale))?.matched?.length;
73
+ if (reloaded) setLocale(toLocale);
74
+ else if (failure instanceof Error && type !== DUPLICATED) setLocaleState(i18n.getLocale());
75
+ else if (type === DUPLICATED || i18n.getLocale() === toLocale) setLocale(toLocale);
76
+ };
77
+ const helper = {
78
+ async mergeTranslation(locale, routeName, newTranslations) {
79
+ await i18n.mergeTranslationAsync(locale, routeName, newTranslations, (l, r) => loader.loadAsync(l, r));
80
+ }
81
+ };
82
+ const provide = {
83
+ i18nStrategy,
84
+ getLocale: (route) => getEffectiveLocale(route, (r) => getCurrentLocale(r)),
85
+ getLocaleName: () => i18nStrategy.getCurrentLocaleName(router.currentRoute.value, getCurrentLocale() ?? null),
86
+ defaultLocale: () => i18nConfig.defaultLocale,
87
+ getLocales: () => i18nConfig.locales,
88
+ getRouteName: (route, _locale) => i18nStrategy.getRouteBaseName(route ?? router.currentRoute.value) ?? "",
89
+ t: i18n.t.bind(i18n),
90
+ ts: i18n.ts.bind(i18n),
91
+ _t: (route) => i18n.tForRoute(route),
92
+ _ts: (route) => i18n.tsForRoute(route),
93
+ tc: i18n.tc.bind(i18n),
94
+ pluralFunc: i18n.pluralFunc,
95
+ pluralLocale: i18n.pluralLocale.bind(i18n),
96
+ tn: i18n.tn.bind(i18n),
97
+ td: i18n.td.bind(i18n),
98
+ tdr: i18n.tdr.bind(i18n),
99
+ has: i18n.has.bind(i18n),
100
+ resolveTranslations: i18n.resolveTranslations.bind(i18n),
101
+ setTranslation: i18n.setTranslation.bind(i18n),
102
+ mergeTranslations: i18n.mergeTranslations.bind(i18n),
103
+ switchLocaleRoute: (toLocale) => {
104
+ const switched = switchedLink(toLocale);
105
+ if (typeof switched === "string" || !i18nConfig.trailingSlash) return switched;
106
+ const fullPath = applyTrailingSlash(switchedPath(switched), i18nConfig.trailingSlash);
107
+ return { path: urlPath(fullPath), fullPath, query: switched.query, hash: switched.hash };
108
+ },
109
+ clearCache: () => {
110
+ void loader.reload();
111
+ },
112
+ switchLocalePath: (toLocale) => applyTrailingSlash(switchedPath(switchedLink(toLocale)), i18nConfig.trailingSlash),
113
+ switchLocale: async (toLocale) => {
114
+ if (!isValidLocale(toLocale)) {
115
+ if (process.env.NODE_ENV !== "production") console.warn(`[i18n] Invalid locale '${toLocale}'`);
116
+ return false;
117
+ }
118
+ const urlless = isNoPrefix || i18nConfig.hashMode;
119
+ const retry = getCurrentLocale() === toLocale && i18n.getLocale() !== toLocale;
120
+ const route = router.currentRoute.value;
121
+ const loaded = urlless || retry ? await loader.switchContext(toLocale, i18nStrategy.getPluginRouteName(route, toLocale)) : null;
122
+ if (loaded === false) return false;
123
+ if (urlless || hasProtocol(provide.switchLocalePath(toLocale))) setLocale(toLocale);
124
+ else setLocaleState(toLocale);
125
+ try {
126
+ const failure = await switchLocaleLogic(toLocale, i18nRouteParams.value);
127
+ if (!urlless) settleSwitch(toLocale, failure);
128
+ } catch (error) {
129
+ if (!urlless) setLocaleState(i18n.getLocale());
130
+ throw error;
131
+ }
132
+ return loaded ?? i18n.getLocale() === toLocale;
133
+ },
134
+ switchRoute: async (route, toLocale) => {
135
+ return switchLocaleLogic(toLocale ?? getCurrentLocale(), i18nRouteParams.value, route);
136
+ },
137
+ localeRoute(to, locale) {
138
+ return toRouteLocationResolved(isUrl(to) ? { path: to, fullPath: to } : localized(to, locale));
139
+ },
140
+ localePath(to, locale) {
141
+ return isUrl(to) ? to : applyTrailingSlash(switchedPath(localized(to, locale)), i18nConfig.trailingSlash);
142
+ },
143
+ setI18nRouteParams: (value) => {
144
+ i18nRouteParams.value = value;
145
+ return i18nRouteParams.value;
146
+ },
147
+ loadPageTranslations: i18n.loadPageTranslations.bind(i18n),
148
+ setMissingHandler
149
+ };
150
+ if (domains) {
151
+ const { switchLocale } = provide;
152
+ provide.switchLocale = async (code) => {
153
+ if (isValidLocale(code) || !i18nConfig.linkLocales?.some((locale) => locale.code === code && !locale.disabled)) return switchLocale(code);
154
+ await navigateTo(provide.switchLocalePath(code), { external: true });
155
+ return true;
156
+ };
157
+ }
158
+ return { helper, provide };
159
+ }
160
+ function createDefineI18nRoute({ getLocale, mergeTranslations }) {
161
+ return async ({ locales }) => {
162
+ if (!locales || Array.isArray(locales)) return;
163
+ const apply = () => {
164
+ const translations = locales[getLocale()];
165
+ if (translations) mergeTranslations(translations);
166
+ };
167
+ apply();
168
+ if (import.meta.client) watch(() => getLocale(), apply);
169
+ };
170
+ }
171
+ export function createI18nProvide({ api: { helper, provide }, i18nConfig, router, i18nRouteParams }) {
172
+ const { setI18nRouteParams, ...provided } = provide;
173
+ const pageRouteParams = {
174
+ enumerable: true,
175
+ get: () => {
176
+ const page = getCurrentInstance() && usePageRoute();
177
+ if (!page) return setI18nRouteParams;
178
+ let own;
179
+ onActivated(() => own && setI18nRouteParams(own));
180
+ return (value) => {
181
+ if (page.path !== router.currentRoute.value.path) return i18nRouteParams.value;
182
+ own = value;
183
+ return setI18nRouteParams(value);
184
+ };
185
+ }
186
+ };
187
+ const bound = { ...provided, helper, getI18nConfig: () => i18nConfig, __micro: true };
188
+ const dollar = Object.fromEntries(Object.entries(bound).map(([key, value]) => [`$${key}`, value]));
189
+ const spellings = Object.defineProperties({ ...bound, ...dollar }, { setI18nRouteParams: pageRouteParams, $setI18nRouteParams: pageRouteParams });
190
+ const install = (nuxtApp) => {
191
+ Object.defineProperty(nuxtApp, "$setI18nRouteParams", pageRouteParams);
192
+ nuxtApp.vueApp.config.globalProperties.$setI18nRouteParams = setI18nRouteParams;
193
+ };
194
+ const define = defineRoute ? { defineI18nRoute: createDefineI18nRoute(provide) } : {};
195
+ return { provide: { ...bound, ...define, i18n: spellings }, install };
196
+ }
@@ -0,0 +1,18 @@
1
+ import type { ModuleOptionsExtend, ResolvedI18nConfig } from '@i18n-micro/types';
2
+ import { type RuntimeI18nOverrides } from '@i18n-micro/utils/runtime-config';
3
+ import type { NuxtApp } from 'nuxt/app';
4
+ /** What the server resolved: overrides and the host. */
5
+ type Shipped = RuntimeI18nOverrides & {
6
+ domain?: string;
7
+ };
8
+ /**
9
+ * What the browser has to be told, or null when the server resolved what the build says. By value:
10
+ * Nitro and the app bundle each hold their own copy of the build config, so `config !== base` is always true.
11
+ */
12
+ export declare function shippedOverrides(config: ModuleOptionsExtend, base: ModuleOptionsExtend): Shipped | null;
13
+ /**
14
+ * The module config with runtime overrides, the same on server and client: routing (the path
15
+ * strategy), validation (`useI18nLocale`) and redirects all read this one.
16
+ */
17
+ export declare function useResolvedI18nConfig(nuxtApp: NuxtApp): ResolvedI18nConfig;
18
+ export {};