@nuxtjs/i18n
Version:
Internationalization for Nuxt
193 lines (192 loc) • 7.26 kB
JavaScript
import { joinURL, withQuery } from "ufo";
import { computed, getCurrentScope, onScopeDispose, ref, unref, useHead, useNuxtApp, watch } from "#imports";
import { assign, isObject, isString } from "@intlify/shared";
import { getRouteBaseName, localeRoute, switchLocalePath } from "./routing.js";
import { getComposer } from "../compatibility.js";
import { toArray } from "../utils.js";
import { DEFAULT_DYNAMIC_PARAMS_KEY } from "#build/i18n.options.mjs";
function createHeadContext(options) {
const nuxtApp = useNuxtApp();
const locale = unref(nuxtApp.$i18n.locale);
const locales = unref(nuxtApp.$i18n.locales).map((x) => isString(x) ? { code: x } : x);
const currentLocale = locales.find((l) => l.code === locale) || { code: locale };
const baseUrl = joinURL(unref(getComposer(nuxtApp.$i18n).baseUrl), nuxtApp.$config.app.baseURL);
const runtimeI18n = nuxtApp.$config.public.i18n;
if (!baseUrl) {
console.warn("I18n `baseUrl` is required to generate valid SEO tag links.");
}
return {
dir: options.dir,
lang: options.lang,
key: options.key,
seo: options.seo,
locale,
locales,
currentDir: currentLocale.dir || runtimeI18n.defaultDirection,
currentLocale,
currentLanguage: currentLocale.language,
baseUrl,
runtimeI18n
};
}
export function localeHead(common, { dir = true, lang = true, seo = true, key = "hid" }) {
return _localeHead(common, { dir, lang, seo, key });
}
export function _useLocaleHead(common, options) {
const metaObject = ref(_localeHead(common, options));
if (import.meta.client) {
const i18n = getComposer(common.i18n);
const unsub = watch(
[() => common.router.currentRoute.value, i18n.locale],
() => metaObject.value = _localeHead(common, options)
);
if (getCurrentScope()) {
onScopeDispose(unsub);
}
}
return metaObject;
}
export function _useSetI18nParams(common, seo) {
const head = useHead({});
const router = common.router;
const experimentalSSR = common.runtimeConfig.public.i18n.experimental.switchLocalePathLinkSSR;
const _i18nParams = ref({});
const i18nParams = computed({
get() {
return experimentalSSR ? common.metaState.value : router.currentRoute.value.meta[DEFAULT_DYNAMIC_PARAMS_KEY] ?? {};
},
set(val) {
common.metaState.value = val;
_i18nParams.value = val;
router.currentRoute.value.meta[DEFAULT_DYNAMIC_PARAMS_KEY] = val;
}
});
const unsub = watch(
() => router.currentRoute.value.fullPath,
() => {
router.currentRoute.value.meta[DEFAULT_DYNAMIC_PARAMS_KEY] = experimentalSSR ? common.metaState.value : _i18nParams.value;
}
);
if (getCurrentScope()) {
onScopeDispose(unsub);
}
const ctxOptions = { dir: false, lang: false, key: "id", seo: seo ?? true };
return function(params) {
i18nParams.value = { ...params };
const { link, meta } = _localeHead(common, ctxOptions);
head?.patch({ link, meta });
};
}
function _localeHead(common, options) {
const metaObject = {
htmlAttrs: {},
link: [],
meta: []
};
const ctx = createHeadContext(options);
if (ctx.baseUrl == null) {
return metaObject;
}
if (ctx.dir) {
metaObject.htmlAttrs.dir = ctx.currentDir;
}
if (ctx.lang && ctx.currentLanguage) {
metaObject.htmlAttrs.lang = ctx.currentLanguage;
}
if (ctx.seo) {
metaObject.link = metaObject.link.concat(
getHreflangLinks(common, ctx),
getCanonicalLink(common, ctx)
);
metaObject.meta = metaObject.meta.concat(
getOgUrl(common, ctx),
getCurrentOgLocale(ctx),
getAlternateOgLocales(ctx)
);
}
return metaObject;
}
function getHreflangLinks(common, ctx) {
const { defaultLocale, strategy, differentDomains } = ctx.runtimeI18n;
const links = [];
if (strategy === "no_prefix" && !differentDomains) return links;
const localeMap = /* @__PURE__ */ new Map();
for (const locale of ctx.locales) {
if (!locale.language) {
console.warn("Locale `language` ISO code is required to generate alternate link");
continue;
}
const [language, region] = locale.language.split("-");
if (language && region && (locale.isCatchallLocale || !localeMap.has(language))) {
localeMap.set(language, locale);
}
localeMap.set(locale.language, locale);
}
const strictCanonicals = ctx.runtimeI18n.experimental.alternateLinkCanonicalQueries === true;
const routeWithoutQuery = strictCanonicals ? common.router.resolve({ query: {} }) : void 0;
if (!ctx.runtimeI18n.experimental.switchLocalePathLinkSSR && strictCanonicals) {
routeWithoutQuery.meta = common.router.currentRoute.value.meta;
}
for (const [language, mapLocale] of localeMap.entries()) {
const localePath = switchLocalePath(common, mapLocale.code, routeWithoutQuery);
if (!localePath) continue;
const fullPath = differentDomains && mapLocale.domain ? localePath : joinURL(ctx.baseUrl, localePath);
const href = withQuery(fullPath, strictCanonicals ? getCanonicalQueryParams(common, ctx) : {});
links.push({ [ctx.key]: `i18n-alt-${language}`, rel: "alternate", href, hreflang: language });
if (defaultLocale && defaultLocale === mapLocale.code) {
links.unshift({ [ctx.key]: "i18n-xd", rel: "alternate", href, hreflang: "x-default" });
}
}
return links;
}
function getCanonicalUrl(common, ctx) {
const route = common.router.currentRoute.value;
const currentRoute = localeRoute(
common,
assign({}, route, { path: void 0, name: getRouteBaseName(common, route) })
);
if (!currentRoute) return "";
return withQuery(joinURL(ctx.baseUrl, currentRoute.path), getCanonicalQueryParams(common, ctx));
}
function getCanonicalLink(common, ctx) {
const href = getCanonicalUrl(common, ctx);
if (!href) return [];
return [{ [ctx.key]: "i18n-can", rel: "canonical", href }];
}
function getCanonicalQueryParams(common, ctx) {
const route = common.router.currentRoute.value;
const currentRoute = localeRoute(
common,
assign({}, route, { path: void 0, name: getRouteBaseName(common, route) })
);
const canonicalQueries = isObject(ctx.seo) && ctx.seo?.canonicalQueries || [];
const currentRouteQuery = currentRoute?.query || {};
const params = {};
for (const param of canonicalQueries.filter((x) => x in currentRouteQuery)) {
params[param] ??= [];
for (const val of toArray(currentRouteQuery[param])) {
params[param].push(val || "");
}
}
return params;
}
function getOgUrl(common, ctx) {
const href = getCanonicalUrl(common, ctx);
if (!href) return [];
return [{ [ctx.key]: "i18n-og-url", property: "og:url", content: href }];
}
function getCurrentOgLocale(ctx) {
if (!ctx.currentLanguage) return [];
return [{ [ctx.key]: "i18n-og", property: "og:locale", content: hyphenToUnderscore(ctx.currentLanguage) }];
}
function getAlternateOgLocales(ctx) {
const alternateLocales = ctx.locales.filter((locale) => locale.language && locale.language !== ctx.currentLanguage);
return alternateLocales.map((locale) => ({
[ctx.key]: `i18n-og-alt-${locale.language}`,
property: "og:locale:alternate",
content: hyphenToUnderscore(locale.language)
}));
}
function hyphenToUnderscore(val = "") {
return val.replace(/-/g, "_");
}