UNPKG

@nuxtjs/i18n

Version:

Internationalization for Nuxt

193 lines (192 loc) 7.26 kB
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, "_"); }