nuxt-i18n-micro
Version:
Nuxt I18n Micro is a lightweight, high-performance internationalization module for Nuxt, designed to handle multi-language support with minimal overhead, fast build times, and efficient runtime performance.
112 lines (111 loc) • 6.02 kB
JavaScript
import { isInternalPath } from "@i18n-micro/route-strategy";
import { getEnabledLocaleCodes } from "@i18n-micro/utils/active-locales";
import { withoutAppBaseURL } from "@i18n-micro/utils/app-path";
import { getLocaleCookieName, getLocaleCookieOptions } from "@i18n-micro/utils/cookie";
import { resolvePreferredLocale } from "@i18n-micro/utils/resolve-locale";
import { getCookie, getHeader, getRequestURL, setCookie } from "h3";
import { createError, defineNuxtPlugin, navigateTo, useRequestEvent, useRuntimeConfig, useState } from "#imports";
const DEBUG = process.env.NUXT_I18N_DEBUG_REDIRECT === "1";
function resolveI18nStrategy(nuxtApp) {
if (nuxtApp.$i18nStrategy) {
return nuxtApp.$i18nStrategy;
}
throw new Error("[nuxt-i18n-micro] i18n redirect plugin requires the main i18n plugin ($i18nStrategy).");
}
export default defineNuxtPlugin({
name: "i18n-redirect",
dependsOn: ["i18n-plugin-loader"],
setup(nuxtApp) {
const i18nStrategy = resolveI18nStrategy(nuxtApp);
const getRuntimeConfig = nuxtApp.$getI18nConfig;
if (typeof getRuntimeConfig !== "function") {
throw new Error("[nuxt-i18n-micro] i18n redirect plugin requires $getI18nConfig from the main i18n plugin.");
}
const i18nConfig = getRuntimeConfig();
const appBaseURL = useRuntimeConfig().app.baseURL;
const validLocales = getEnabledLocaleCodes(i18nConfig.locales);
const defaultLocale = i18nConfig.defaultLocale || "en";
const autoDetectPath = i18nConfig.autoDetectPath || "/";
const cookieName = getLocaleCookieName(i18nConfig);
if (import.meta.server) {
const event = useRequestEvent();
if (!event) return;
const url = getRequestURL(event);
const path = withoutAppBaseURL(url.pathname, appBaseURL);
const performRedirect = (targetUrl, code = 302) => {
return navigateTo(targetUrl, { redirectCode: code });
};
if (path.startsWith("/api") || path.startsWith("/_nuxt") || path.startsWith("/_locales") || path.startsWith("/__")) return;
if (path.includes(".") && !path.endsWith(".html")) return;
if (isInternalPath(path, i18nConfig.excludePatterns)) {
if (DEBUG) console.error("[i18n-redirect] 404: isInternalPath", path);
throw createError({ statusCode: 404, statusMessage: "Static file - should not be processed by i18n" });
}
const pathSegments = path.replace(/^\//, "").split("/").filter(Boolean);
const firstSegment = pathSegments[0];
const customRegex = i18nConfig.customRegexMatcher;
if (firstSegment && customRegex && !validLocales.includes(firstSegment)) {
const source = typeof customRegex === "string" ? customRegex : customRegex.source;
const regex = new RegExp(`^${source}$`);
if (regex.test(firstSegment)) {
if (DEBUG) console.error("[i18n-redirect] 404: unknown locale", firstSegment);
throw createError({ statusCode: 404, statusMessage: "Page Not Found" });
}
}
const errorMessage = i18nStrategy.shouldReturn404(path);
if (errorMessage) {
if (DEBUG) console.error("[i18n-redirect] 404:", errorMessage, path);
throw createError({ statusCode: 404, statusMessage: "Page Not Found" });
}
const hasLocalePrefix = Boolean(firstSegment && validLocales.includes(firstSegment));
const shouldDeferCookieSync = i18nConfig.redirects !== false && autoDetectPath === "*";
if (hasLocalePrefix && cookieName && !shouldDeferCookieSync) {
const currentLocale = firstSegment;
const { watch: _w, ...cookieOpts } = getLocaleCookieOptions();
setCookie(event, cookieName, currentLocale, cookieOpts);
}
if (i18nConfig.redirects !== false) {
const prerenderHeader = getHeader(event, "x-nitro-prerender");
const userAgent = getHeader(event, "user-agent") || "";
const isRootPath = path === "/" || path === "";
const isPrerenderOrBot = !!(prerenderHeader || userAgent.includes("Nitro") || !userAgent);
const skipRedirect = !isRootPath && autoDetectPath !== "*" && isPrerenderOrBot;
if (!skipRedirect) {
const localeState = autoDetectPath !== "*" ? useState("i18n-locale", () => null) : null;
let preferredLocale = resolvePreferredLocale({
defaultLocale,
validLocales,
autoDetectLanguage: i18nConfig.autoDetectLanguage,
stateLocale: localeState?.value ?? null,
cookieLocale: cookieName ? getCookie(event, cookieName) : null,
acceptLanguageHeader: getHeader(event, "accept-language"),
ignoreStateLocale: autoDetectPath === "*"
});
if (autoDetectPath === "*" && !hasLocalePrefix) {
preferredLocale = defaultLocale;
}
if (autoDetectPath === "*" && hasLocalePrefix && firstSegment !== preferredLocale) {
const rest = pathSegments.slice(1).join("/");
let targetPath;
if (preferredLocale === defaultLocale && i18nConfig.strategy === "prefix_except_default") {
targetPath = rest ? `/${rest}` : "/";
} else {
targetPath = rest ? `/${preferredLocale}/${rest}` : `/${preferredLocale}`;
}
if (cookieName) {
const { watch: _w2, ...cookieOpts2 } = getLocaleCookieOptions();
setCookie(event, cookieName, preferredLocale, cookieOpts2);
}
if (DEBUG) console.error("[i18n-redirect] REDIRECT autoDetectPath *", { path, targetPath, preferredLocale });
return performRedirect(targetPath + (url.search || "") + (url.hash || ""));
}
const redirectPath = i18nStrategy.getClientRedirect(path, preferredLocale);
if (redirectPath) {
if (DEBUG) console.error("[i18n-redirect] REDIRECT", { path, redirectPath, preferredLocale });
return performRedirect(redirectPath + (url.search || "") + (url.hash || ""));
}
}
}
}
}
});