2025-09-05 14:59:21 +08:00

324 lines
13 KiB
JavaScript

import { joinURL, isEqual } from "ufo";
import { isString, isFunction, isObject } from "@intlify/shared";
import { navigateTo, useNuxtApp, useRouter, useRuntimeConfig, useState } from "#imports";
import {
NUXT_I18N_MODULE_ID,
isSSG,
localeLoaders,
normalizedLocales
} from "#build/i18n.options.mjs";
import {
wrapComposable,
detectBrowserLanguage,
callVueI18nInterfaces,
getVueI18nPropertyValue,
defineGetter,
getLocaleDomain,
getDomainFromLocale,
runtimeDetectBrowserLanguage,
getHost,
DetectFailure
} from "./internal.js";
import { loadLocale, makeFallbackLocaleCodes } from "./messages.js";
import {
localeHead,
localePath,
localeRoute,
getRouteBaseName,
switchLocalePath,
DefaultPrefixable,
DefaultSwitchLocalePathIntercepter
} from "./routing/compatibles/index.js";
import { getLocale, setLocale, getLocaleCodes, getI18nTarget } from "./routing/utils.js";
import { createLocaleFromRouteGetter } from "./routing/extends/router.js";
export function _setLocale(i18n, locale) {
return callVueI18nInterfaces(i18n, "setLocale", locale);
}
export function setCookieLocale(i18n, locale) {
return callVueI18nInterfaces(i18n, "setLocaleCookie", locale);
}
export function setLocaleMessage(i18n, locale, messages) {
return callVueI18nInterfaces(i18n, "setLocaleMessage", locale, messages);
}
export function mergeLocaleMessage(i18n, locale, messages) {
return callVueI18nInterfaces(i18n, "mergeLocaleMessage", locale, messages);
}
async function onBeforeLanguageSwitch(i18n, oldLocale, newLocale, initial, context) {
return callVueI18nInterfaces(i18n, "onBeforeLanguageSwitch", oldLocale, newLocale, initial, context);
}
export function onLanguageSwitched(i18n, oldLocale, newLocale) {
return callVueI18nInterfaces(i18n, "onLanguageSwitched", oldLocale, newLocale);
}
export async function finalizePendingLocaleChange(i18n) {
return callVueI18nInterfaces(i18n, "finalizePendingLocaleChange");
}
export function initCommonComposableOptions(i18n) {
return {
i18n: i18n ?? useNuxtApp().$i18n,
router: useRouter(),
runtimeConfig: useRuntimeConfig(),
metaState: useState("nuxt-i18n-meta", () => ({}))
};
}
export async function loadAndSetLocale(newLocale, i18n, runtimeI18n, initial = false) {
const { differentDomains, skipSettingLocaleOnNavigate, lazy } = runtimeI18n;
const opts = runtimeDetectBrowserLanguage(runtimeI18n);
const nuxtApp = useNuxtApp();
const oldLocale = getLocale(i18n);
const localeCodes = getLocaleCodes(i18n);
function syncCookie(locale = oldLocale) {
if (opts === false || !opts.useCookie) return;
if (skipSettingLocaleOnNavigate) return;
setCookieLocale(i18n, locale);
}
__DEBUG__ && console.log("setLocale: new -> ", newLocale, " old -> ", oldLocale, " initial -> ", initial);
if (!newLocale) {
syncCookie();
return false;
}
if (!initial && differentDomains) {
syncCookie();
return false;
}
if (oldLocale === newLocale) {
syncCookie();
return false;
}
const localeOverride = await onBeforeLanguageSwitch(i18n, oldLocale, newLocale, initial, nuxtApp);
if (localeOverride && localeCodes.includes(localeOverride)) {
if (oldLocale === localeOverride) {
syncCookie();
return false;
}
newLocale = localeOverride;
}
if (lazy) {
const i18nFallbackLocales = getVueI18nPropertyValue(i18n, "fallbackLocale");
const setter = (locale, message) => mergeLocaleMessage(i18n, locale, message);
if (i18nFallbackLocales) {
const fallbackLocales = makeFallbackLocaleCodes(i18nFallbackLocales, [newLocale]);
await Promise.all(fallbackLocales.map((locale) => loadLocale(locale, localeLoaders, setter)));
}
await loadLocale(newLocale, localeLoaders, setter);
}
if (skipSettingLocaleOnNavigate) {
return false;
}
syncCookie(newLocale);
setLocale(i18n, newLocale);
await onLanguageSwitched(i18n, oldLocale, newLocale);
return true;
}
export function createLogger(label) {
return {
log: console.log.bind(console, `${label}:`)
// change to this after implementing logger across runtime code
// log: console.log.bind(console, `[i18n:${label}]`)
};
}
export function detectLocale(route, routeLocaleGetter, initialLocaleLoader, detectLocaleContext, runtimeI18n) {
const { strategy, defaultLocale, differentDomains, multiDomainLocales } = runtimeI18n;
const { localeCookie } = detectLocaleContext;
const _detectBrowserLanguage = runtimeDetectBrowserLanguage(runtimeI18n);
const logger = createLogger("detectLocale");
const initialLocale = isFunction(initialLocaleLoader) ? initialLocaleLoader() : initialLocaleLoader;
__DEBUG__ && logger.log({ initialLocale });
const detectedBrowser = detectBrowserLanguage(route, detectLocaleContext, initialLocale);
__DEBUG__ && logger.log({ detectBrowserLanguage: detectedBrowser });
if (detectedBrowser.reason === DetectFailure.SSG_IGNORE) {
return initialLocale;
}
if (detectedBrowser.locale && detectedBrowser.from != null) {
return detectedBrowser.locale;
}
let detected = "";
__DEBUG__ && logger.log("1/3", { detected, strategy });
if (differentDomains || multiDomainLocales) {
detected ||= getLocaleDomain(normalizedLocales, strategy, route);
} else if (strategy !== "no_prefix") {
detected ||= routeLocaleGetter(route);
}
__DEBUG__ && logger.log("2/3", { detected, detectBrowserLanguage: _detectBrowserLanguage });
const cookieLocale = _detectBrowserLanguage && _detectBrowserLanguage.useCookie && localeCookie;
detected ||= cookieLocale || initialLocale || defaultLocale || "";
__DEBUG__ && logger.log("3/3", { detected, cookieLocale, initialLocale, defaultLocale });
return detected;
}
export function detectRedirect({
route,
targetLocale,
routeLocaleGetter,
calledWithRouting = false
}) {
const nuxtApp = useNuxtApp();
const common = initCommonComposableOptions();
const { strategy, differentDomains } = common.runtimeConfig.public.i18n;
__DEBUG__ && console.log("detectRedirect: targetLocale -> ", targetLocale);
__DEBUG__ && console.log("detectRedirect: route -> ", route);
__DEBUG__ && console.log("detectRedirect: calledWithRouting -> ", calledWithRouting, routeLocaleGetter(route.to));
let redirectPath = "";
const { fullPath: toFullPath } = route.to;
const isStaticGenerate = isSSG && import.meta.server;
if (!isStaticGenerate && !differentDomains && (calledWithRouting || strategy !== "no_prefix") && routeLocaleGetter(route.to) !== targetLocale) {
const routePath = nuxtApp.$switchLocalePath(targetLocale) || nuxtApp.$localePath(toFullPath, targetLocale);
__DEBUG__ && console.log("detectRedirect: calculate routePath -> ", routePath, toFullPath);
if (isString(routePath) && routePath && !isEqual(routePath, toFullPath) && !routePath.startsWith("//")) {
redirectPath = !(route.from && route.from.fullPath === routePath) ? routePath : "";
}
}
if ((differentDomains || isSSG && import.meta.client) && routeLocaleGetter(route.to) !== targetLocale) {
const routePath = switchLocalePath(common, targetLocale, route.to);
__DEBUG__ && console.log("detectRedirect: calculate domain or ssg routePath -> ", routePath);
if (isString(routePath) && routePath && !isEqual(routePath, toFullPath) && !routePath.startsWith("//")) {
redirectPath = routePath;
}
}
return redirectPath;
}
function isRootRedirectOptions(rootRedirect) {
return isObject(rootRedirect) && "path" in rootRedirect && "statusCode" in rootRedirect;
}
const useRedirectState = () => useState(NUXT_I18N_MODULE_ID + ":redirect", () => "");
function _navigate(redirectPath, status) {
return navigateTo(redirectPath, { redirectCode: status });
}
export async function navigate(args, { status = 302, enableNavigate = false } = {}) {
const { nuxtApp, i18n, locale, route } = args;
const { rootRedirect, differentDomains, multiDomainLocales, skipSettingLocaleOnNavigate, configLocales, strategy } = nuxtApp.$config.public.i18n;
let { redirectPath } = args;
__DEBUG__ && console.log(
"navigate options ",
status,
rootRedirect,
differentDomains,
skipSettingLocaleOnNavigate,
enableNavigate
);
__DEBUG__ && console.log("navigate isSSG", isSSG);
if (route.path === "/" && rootRedirect) {
if (isString(rootRedirect)) {
redirectPath = "/" + rootRedirect;
} else if (isRootRedirectOptions(rootRedirect)) {
redirectPath = "/" + rootRedirect.path;
status = rootRedirect.statusCode;
}
redirectPath = nuxtApp.$localePath(redirectPath, locale);
__DEBUG__ && console.log("navigate: rootRedirect mode redirectPath -> ", redirectPath, " status -> ", status);
return _navigate(redirectPath, status);
}
if (import.meta.client && skipSettingLocaleOnNavigate) {
i18n.__pendingLocale = locale;
i18n.__pendingLocalePromise = new Promise((resolve) => {
i18n.__resolvePendingLocalePromise = resolve;
});
if (!enableNavigate) {
return;
}
}
if (multiDomainLocales && strategy === "prefix_except_default") {
const host = getHost();
const currentDomain = configLocales.find((locale2) => {
if (typeof locale2 !== "string") {
return locale2.defaultForDomains?.find((domain) => domain === host);
}
return false;
});
const defaultLocaleForDomain = typeof currentDomain !== "string" ? currentDomain?.code : void 0;
if (route.path.startsWith(`/${defaultLocaleForDomain}`)) {
return _navigate(route.path.replace(`/${defaultLocaleForDomain}`, ""), status);
} else if (!route.path.startsWith(`/${locale}`) && locale !== defaultLocaleForDomain) {
const getLocaleFromRoute = createLocaleFromRouteGetter();
const oldLocale = getLocaleFromRoute(route.path);
if (oldLocale !== "") {
return _navigate(`/${locale + route.path.replace(`/${oldLocale}`, "")}`, status);
} else {
return _navigate(`/${locale + (route.path === "/" ? "" : route.path)}`, status);
}
} else if (redirectPath && route.path !== redirectPath) {
return _navigate(redirectPath, status);
}
return;
}
if (!differentDomains) {
if (redirectPath) {
return _navigate(redirectPath, status);
}
} else {
const state = useRedirectState();
__DEBUG__ && console.log("redirect state ->", state.value, "redirectPath -> ", redirectPath);
if (state.value && state.value !== redirectPath) {
if (import.meta.client) {
state.value = "";
window.location.assign(redirectPath);
} else if (import.meta.server) {
__DEBUG__ && console.log("differentDomains servermode ", redirectPath);
state.value = redirectPath;
}
}
}
}
export function injectNuxtHelpers(nuxt, i18n) {
defineGetter(nuxt, "$i18n", getI18nTarget(i18n));
defineGetter(nuxt, "$getRouteBaseName", wrapComposable(getRouteBaseName));
defineGetter(nuxt, "$localePath", wrapComposable(localePath));
defineGetter(nuxt, "$localeRoute", wrapComposable(localeRoute));
defineGetter(nuxt, "$switchLocalePath", wrapComposable(switchLocalePath));
defineGetter(nuxt, "$localeHead", wrapComposable(localeHead));
}
export function extendPrefixable(runtimeConfig = useRuntimeConfig()) {
return (opts) => {
__DEBUG__ && console.log("extendPrefixable", DefaultPrefixable(opts));
return DefaultPrefixable(opts) && !runtimeConfig.public.i18n.differentDomains;
};
}
export function extendSwitchLocalePathIntercepter(runtimeConfig = useRuntimeConfig()) {
return (path, locale) => {
if (runtimeConfig.public.i18n.differentDomains) {
const domain = getDomainFromLocale(locale);
__DEBUG__ && console.log("extendSwitchLocalePathIntercepter: domain -> ", domain, " path -> ", path);
if (domain) {
return joinURL(domain, path);
} else {
return path;
}
} else {
return DefaultSwitchLocalePathIntercepter(path, locale);
}
};
}
export function extendBaseUrl() {
return () => {
const ctx = useNuxtApp();
const { baseUrl, defaultLocale, differentDomains } = ctx.$config.public.i18n;
if (isFunction(baseUrl)) {
const baseUrlResult = baseUrl(ctx);
__DEBUG__ && console.log("baseUrl: using localeLoader function -", baseUrlResult);
return baseUrlResult;
}
const localeCode = isFunction(defaultLocale) ? defaultLocale() : defaultLocale;
if (differentDomains && localeCode) {
const domain = getDomainFromLocale(localeCode);
if (domain) {
__DEBUG__ && console.log("baseUrl: using differentDomains -", domain);
return domain;
}
}
if (baseUrl) {
__DEBUG__ && console.log("baseUrl: using runtimeConfig -", baseUrl);
return baseUrl;
}
return baseUrl;
};
}
export function getNormalizedLocales(locales) {
const normalized = [];
for (const locale of locales) {
if (isString(locale)) {
normalized.push({ code: locale });
continue;
}
normalized.push(locale);
}
return normalized;
}