@nuxtjs/i18n
Version:
Internationalization for Nuxt
249 lines (248 loc) • 10.4 kB
JavaScript
import { isEqual } from "ufo";
import { isArray, isFunction, isString } from "@intlify/shared";
import { navigateTo, useNuxtApp, useRouter, useRuntimeConfig, useState } from "#imports";
import {
NUXT_I18N_MODULE_ID,
isSSG,
localeCodes,
localeLoaders,
normalizedLocales,
vueI18nConfigs
} from "#build/i18n.options.mjs";
import { getComposer } from "./compatibility.js";
import { getDomainFromLocale, getHost, getLocaleDomain } from "./domain.js";
import { detectBrowserLanguage, runtimeDetectBrowserLanguage } from "./internal.js";
import { loadAndSetLocaleMessages, loadLocale, loadVueI18nOptions, makeFallbackLocaleCodes } from "./messages.js";
import { localePath, switchLocalePath } from "./routing/routing.js";
import { createLogger } from "#nuxt-i18n/logger";
import { unref } from "vue";
export function formatMessage(message) {
return `[${NUXT_I18N_MODULE_ID}]: ${message}`;
}
export function initCommonComposableOptions(i18n) {
return {
i18n: i18n ?? useNuxtApp().$i18n,
router: useRouter(),
runtimeConfig: useRuntimeConfig(),
metaState: useState("nuxt-i18n-meta", () => ({}))
};
}
export async function loadAndSetLocale(newLocale, initial = false) {
const logger = /* @__PURE__ */ createLogger("loadAndSetLocale");
const nuxtApp = useNuxtApp();
const { differentDomains, skipSettingLocaleOnNavigate } = nuxtApp.$config.public.i18n;
const opts = runtimeDetectBrowserLanguage();
const oldLocale = unref(nuxtApp.$i18n.locale);
const localeCodes2 = unref(nuxtApp.$i18n.localeCodes);
function syncCookie(locale = oldLocale) {
if (opts === false || !opts.useCookie) return;
if (skipSettingLocaleOnNavigate) return;
nuxtApp.$i18n.setLocaleCookie(locale);
}
const localeOverride = await nuxtApp.$i18n.onBeforeLanguageSwitch(oldLocale, newLocale, initial, nuxtApp);
if (localeOverride && localeCodes2.includes(localeOverride)) {
if (oldLocale === localeOverride) {
syncCookie();
return false;
}
newLocale = localeOverride;
}
__DEBUG__ && logger.log({ newLocale, oldLocale, initial });
if (!newLocale) {
syncCookie();
return false;
}
if (!initial && differentDomains) {
syncCookie();
return false;
}
if (oldLocale === newLocale) {
syncCookie();
return false;
}
const i18nFallbackLocales = unref(nuxtApp.$i18n.fallbackLocale);
const setter = nuxtApp.$i18n.mergeLocaleMessage.bind(nuxtApp.$i18n);
if (i18nFallbackLocales) {
const fallbackLocales = makeFallbackLocaleCodes(i18nFallbackLocales, [newLocale]);
await Promise.all(fallbackLocales.map((locale) => loadLocale(locale, localeLoaders, setter, nuxtApp)));
}
await loadLocale(newLocale, localeLoaders, setter, nuxtApp);
if (skipSettingLocaleOnNavigate) {
return false;
}
syncCookie(newLocale);
nuxtApp._vueI18n.__setLocale(newLocale);
await nuxtApp.$i18n.onLanguageSwitched(oldLocale, newLocale);
return true;
}
export function detectLocale(route, routeLocale, currentLocale, localeCookie) {
const nuxtApp = useNuxtApp();
const { strategy, defaultLocale, differentDomains, multiDomainLocales } = nuxtApp.$config.public.i18n;
const _detectBrowserLanguage = runtimeDetectBrowserLanguage();
const logger = /* @__PURE__ */ createLogger("detectLocale");
const detectedBrowser = detectBrowserLanguage(route, localeCookie, currentLocale);
__DEBUG__ && logger.log({ detectBrowserLanguage: detectedBrowser });
if (detectedBrowser.locale && detectedBrowser.from != null && localeCodes.includes(detectedBrowser.locale)) {
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 ||= routeLocale;
}
__DEBUG__ && logger.log("2/3", { detected, detectBrowserLanguage: _detectBrowserLanguage });
const cookieLocale = (localeCodes.includes(detectedBrowser.locale) || localeCookie && localeCodes.includes(localeCookie)) && _detectBrowserLanguage && _detectBrowserLanguage.useCookie && localeCookie;
detected ||= cookieLocale || currentLocale || defaultLocale || "";
__DEBUG__ && logger.log("3/3", { detected, cookieLocale, defaultLocale, localeCookie });
return detected;
}
export function detectRedirect({ to, from, locale, routeLocale }, inMiddleware = false) {
if (routeLocale === locale || useNuxtApp().$i18n.strategy === "no_prefix") {
return "";
}
const common = initCommonComposableOptions();
const logger = /* @__PURE__ */ createLogger("detectRedirect");
__DEBUG__ && logger.log({ to, from });
__DEBUG__ && logger.log({ locale, routeLocale, inMiddleware });
let redirectPath = switchLocalePath(common, locale, to);
if (inMiddleware && !redirectPath) {
redirectPath = localePath(common, to.fullPath, locale);
}
if (isEqual(redirectPath, to.fullPath) || from && isEqual(redirectPath, from.fullPath)) {
return "";
}
return redirectPath;
}
const useRedirectState = () => useState(NUXT_I18N_MODULE_ID + ":redirect", () => "");
export async function navigate({ nuxt, locale, route, redirectPath }, enableNavigate = false) {
const { rootRedirect, differentDomains, multiDomainLocales, skipSettingLocaleOnNavigate, locales, strategy } = nuxt.$config.public.i18n;
const logger = /* @__PURE__ */ createLogger("navigate");
__DEBUG__ && logger.log("options", { rootRedirect, differentDomains, skipSettingLocaleOnNavigate, enableNavigate, isSSG });
if (route.path === "/" && rootRedirect) {
let redirectCode = 302;
if (isString(rootRedirect)) {
redirectPath = "/" + rootRedirect;
} else {
redirectPath = "/" + rootRedirect.path;
redirectCode = rootRedirect.statusCode;
}
redirectPath = nuxt.$localePath(redirectPath, locale);
__DEBUG__ && logger.log("rootRedirect mode", { redirectPath, redirectCode });
return navigateTo(redirectPath, { redirectCode });
}
if (import.meta.client && skipSettingLocaleOnNavigate) {
nuxt._vueI18n.__pendingLocale = locale;
nuxt._vueI18n.__pendingLocalePromise = new Promise((resolve) => {
nuxt._vueI18n.__resolvePendingLocalePromise = () => resolve();
});
if (!enableNavigate) {
return;
}
}
if (multiDomainLocales && strategy === "prefix_except_default") {
const host = getHost();
const currentDomain = locales.find((locale2) => {
if (isString(locale2)) return;
return locale2.defaultForDomains?.find((domain) => domain === host);
});
const defaultLocaleForDomain = !isString(currentDomain) ? currentDomain?.code : void 0;
if (route.path.startsWith(`/${defaultLocaleForDomain}`)) {
return navigateTo(route.path.replace(`/${defaultLocaleForDomain}`, ""));
}
if (!route.path.startsWith(`/${locale}`) && locale !== defaultLocaleForDomain) {
const oldLocale = nuxt._vueI18n.__localeFromRoute(route.path);
if (oldLocale !== "") {
return navigateTo(`/${locale + route.path.replace(`/${oldLocale}`, "")}`);
}
return navigateTo(`/${locale + (route.path === "/" ? "" : route.path)}`);
}
if (redirectPath && route.path !== redirectPath) {
return navigateTo(redirectPath);
}
return;
}
if (differentDomains) {
const state = useRedirectState();
__DEBUG__ && logger.log("redirect", { state: state.value, redirectPath });
if (state.value && state.value !== redirectPath) {
if (import.meta.client) {
state.value = "";
window.location.assign(redirectPath);
}
if (import.meta.server) {
__DEBUG__ && logger.log("differentDomains servermode", { redirectPath });
state.value = redirectPath;
}
}
} else if (redirectPath) {
return navigateTo(redirectPath);
}
}
export function prefixable(currentLocale, defaultLocale, strategy) {
return (
// strategy has no prefixes
strategy !== "no_prefix" && // strategy should not prefix default locale
!(currentLocale === defaultLocale && (strategy === "prefix_and_default" || strategy === "prefix_except_default"))
);
}
export function extendBaseUrl(ctx) {
const logger = /* @__PURE__ */ createLogger("extendBaseUrl");
const { baseUrl, defaultLocale, differentDomains } = ctx.$config.public.i18n;
if (isFunction(baseUrl)) {
return () => {
const baseUrlResult = baseUrl(ctx);
__DEBUG__ && logger.log("using localeLoader function -", { baseUrlResult });
return baseUrlResult;
};
}
const localeCode = isFunction(defaultLocale) ? defaultLocale() : defaultLocale;
return () => {
if (differentDomains && localeCode) {
const domain = getDomainFromLocale(localeCode);
if (domain) {
__DEBUG__ && logger.log("using differentDomains -", { domain });
return domain;
}
}
__DEBUG__ && logger.log("using runtimeConfig -", { baseUrl });
return baseUrl ?? "";
};
}
function uniqueKeys(...objects) {
const keySet = /* @__PURE__ */ new Set();
for (const obj of objects) {
for (const key of Object.keys(obj)) {
keySet.add(key);
}
}
return Array.from(keySet);
}
export function createNuxtI18nDev() {
const nuxtApp = useNuxtApp();
const composer = getComposer(nuxtApp._vueI18n);
async function resetI18nProperties(locale) {
const opts = await loadVueI18nOptions(vueI18nConfigs, nuxtApp);
const messageLocales = uniqueKeys(opts.messages || {}, composer.messages.value);
for (const k of messageLocales) {
if (locale && k !== locale) continue;
const current = opts.messages?.[k] || {};
await loadAndSetLocaleMessages(k, localeLoaders, { [k]: current }, nuxtApp);
composer.setLocaleMessage(k, current);
}
if (locale != null) return;
const numberFormatLocales = uniqueKeys(opts.numberFormats || {}, composer.numberFormats.value);
for (const k of numberFormatLocales) {
composer.setNumberFormat(k, opts.numberFormats?.[k] || {});
}
const datetimeFormatsLocales = uniqueKeys(opts.datetimeFormats || {}, composer.datetimeFormats.value);
for (const k of datetimeFormatsLocales) {
composer.setDateTimeFormat(k, opts.datetimeFormats?.[k] || {});
}
}
return { resetI18nProperties };
}
export function toArray(value) {
return isArray(value) ? value : [value];
}