324 lines
13 KiB
JavaScript
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;
|
||
|
|
}
|