UNPKG

@nuxtjs/i18n

Version:

Internationalization for Nuxt

183 lines (182 loc) 7.53 kB
import { stringify } from "devalue"; import { defineI18nMiddleware } from "@intlify/h3"; import { defineNitroPlugin, useStorage } from "nitropack/runtime"; import { initializeI18nContext, tryUseI18nContext, useI18nContext } from "./context.js"; import { createUserLocaleDetector } from "./utils/locale-detector.js"; import { pickNested } from "./utils/messages-utils.js"; import { isSupportedLocale } from "../shared/locales.js"; import { setupVueI18nOptions } from "../shared/vue-i18n.js"; import { joinURL } from "ufo"; import { appId } from "#internal/nuxt.config.mjs"; import { localeDetector } from "#internal/i18n-locale-detector.mjs"; import { resolveRootRedirect, useI18nDetection, useRuntimeI18n } from "../shared/utils.js"; import { isFunction } from "@intlify/shared"; import { getRequestURL, sendRedirect, setCookie } from "h3"; import { useDetectors } from "../shared/detection.js"; import { domainFromLocale } from "../shared/domain.js"; import { isExistingNuxtRoute, matchLocalized } from "../shared/matching.js"; function* detect(detectors, detection, path) { if (detection.enabled) { yield { locale: detectors.cookie(), source: "cookie" }; yield { locale: detectors.header(), source: "header" }; } if (__DIFFERENT_DOMAINS__ || __MULTI_DOMAIN_LOCALES__) { yield { locale: detectors.host(path), source: "domain" }; } if (__I18N_ROUTING__) { yield { locale: detectors.route(path), source: "route" }; } yield { locale: detection.fallbackLocale, source: "fallback" }; } export default defineNitroPlugin(async (nitro) => { const runtimeI18n = useRuntimeI18n(); const rootRedirect = resolveRootRedirect(runtimeI18n.rootRedirect); const _defaultLocale = runtimeI18n.defaultLocale || ""; try { const cacheStorage = useStorage("cache"); const cachedKeys = await cacheStorage.getKeys("nitro:handlers:i18n"); await Promise.all(cachedKeys.map((key) => cacheStorage.removeItem(key))); } catch { } const detection = useI18nDetection(void 0); const cookieOptions = { path: "/", domain: detection.cookieDomain || void 0, maxAge: 60 * 60 * 24 * 365, sameSite: "lax", secure: detection.cookieSecure }; const getDomainFromLocale = (event, locale) => { if (!__MULTI_DOMAIN_LOCALES__ && !__DIFFERENT_DOMAINS__) { return; } return domainFromLocale(runtimeI18n.domainLocales, getRequestURL(event, { xForwardedHost: true }), locale); }; const createBaseUrlGetter = () => { const baseUrl = isFunction(runtimeI18n.baseUrl) ? "" : runtimeI18n.baseUrl || ""; if (isFunction(runtimeI18n.baseUrl)) { import.meta.dev && console.warn("[nuxt-i18n] Configuring baseUrl as a function is deprecated and will be removed in v11."); return () => ""; } return (event, defaultLocale) => { if ((__MULTI_DOMAIN_LOCALES__ || __DIFFERENT_DOMAINS__) && defaultLocale) { return getDomainFromLocale(event, defaultLocale) || baseUrl; } return ""; }; }; function resolveRedirectPath(event, path, pathLocale, defaultLocale, detector) { let locale = ""; for (const detected of detect(detector, detection, event.path)) { if (detected.locale && isSupportedLocale(detected.locale)) { locale = detected.locale; break; } } locale ||= defaultLocale; function getLocalizedMatch(locale2) { const res = matchLocalized(path || "/", locale2, defaultLocale); if (res && res !== event.path) { return res; } } let resolvedPath = void 0; let redirectCode = 302; const requestURL = getRequestURL(event); if (rootRedirect && requestURL.pathname === "/") { locale = detection.enabled && locale || defaultLocale; resolvedPath = isSupportedLocale(detector.route(rootRedirect.path)) && rootRedirect.path || matchLocalized(rootRedirect.path, locale, defaultLocale); redirectCode = rootRedirect.code; } else if (runtimeI18n.redirectStatusCode) { redirectCode = runtimeI18n.redirectStatusCode; } switch (detection.redirectOn) { case "root": if (requestURL.pathname !== "/") { break; } // fallthrough (root has no prefix) case "no prefix": if (pathLocale) { break; } // fallthrough to resolve case "all": resolvedPath ??= getLocalizedMatch(locale); break; } if (requestURL.pathname === "/" && __I18N_STRATEGY__ === "prefix") { resolvedPath ??= getLocalizedMatch(defaultLocale); } return { path: resolvedPath, code: redirectCode, locale }; } const baseUrlGetter = createBaseUrlGetter(); nitro.hooks.hook("request", async (event) => { await initializeI18nContext(event); }); nitro.hooks.hook("render:before", async ({ event }) => { if (!__I18N_SERVER_REDIRECT__) { return; } const ctx = import.meta.prerender && !event.context.nuxtI18n ? await initializeI18nContext(event) : useI18nContext(event); const url = getRequestURL(event); const detector = useDetectors(event, detection); const localeSegment = detector.route(event.path); const pathLocale = isSupportedLocale(localeSegment) && localeSegment || void 0; const path = (pathLocale && url.pathname.slice(pathLocale.length + 1)) ?? url.pathname; if (!url.pathname.includes(__I18N_SERVER_ROUTE__) && !isExistingNuxtRoute(path)) { return; } const resolved = resolveRedirectPath(event, path, pathLocale, ctx.vueI18nOptions.defaultLocale, detector); if (resolved.path && resolved.path !== url.pathname) { ctx.detectLocale = resolved.locale; detection.useCookie && setCookie(event, detection.cookieKey, resolved.locale, cookieOptions); await sendRedirect( event, joinURL(baseUrlGetter(event, ctx.vueI18nOptions.defaultLocale), resolved.path + url.search), resolved.code ); return; } }); nitro.hooks.hook("render:html", (htmlContext, { event }) => { const ctx = tryUseI18nContext(event); if (__I18N_PRELOAD__) { if (ctx == null || Object.keys(ctx.messages ?? {}).length == 0) { return; } if (__I18N_STRIP_UNUSED__ && !__IS_SSG__) { const trackedLocales = Object.keys(ctx.trackMap); for (const locale of Object.keys(ctx.messages)) { if (!trackedLocales.includes(locale)) { ctx.messages[locale] = {}; continue; } const usedKeys = Array.from(ctx.trackMap[locale]); ctx.messages[locale] = pickNested(usedKeys, ctx.messages[locale]); } } try { htmlContext.bodyAppend.unshift( `<script type="application/json" data-nuxt-i18n="${appId}">${stringify(ctx.messages)}<\/script>` ); } catch (_) { console.warn(_); } } if (__I18N_STRICT_SEO__) { const raw = JSON.stringify(ctx?.slp ?? {}); const safe = raw.replace(/</g, "\\u003c").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029"); htmlContext.head.push(`<script type="application/json" data-nuxt-i18n-slp="${appId}">${safe}<\/script>`); } }); if (localeDetector != null) { const options = await setupVueI18nOptions(_defaultLocale); const i18nMiddleware = defineI18nMiddleware({ ...options, locale: createUserLocaleDetector(options.locale, options.fallbackLocale) }); nitro.hooks.hook("request", i18nMiddleware.onRequest); nitro.hooks.hook("afterResponse", i18nMiddleware.onAfterResponse); } });