UNPKG

nuxt-i18n-micro

Version:

Nuxt I18n Micro is a lightweight, high-performance internationalization module for Nuxt, designed to handle multi-language support with minimal overhead, fast build times, and efficient runtime performance.

188 lines (187 loc) 7.51 kB
import { isNoPrefixStrategy } from "@i18n-micro/core"; import { findAllowedLocalesForRoute } from "@i18n-micro/utils/route"; import { resolveOgLocale, warnUnresolvedOgLocale } from "@i18n-micro/utils/resolve-og-locale"; import { joinURL, parseURL, withQuery } from "ufo"; import { ref, unref, watch } from "vue"; import { useNuxtApp, useRoute } from "#app"; export const useLocaleHead = ({ addDirAttribute = true, identifierAttribute = "id", addSeoAttributes = true, baseUrl = "/", autoUpdate = true } = {}) => { const nuxtApp = useNuxtApp(); const route = useRoute(); const metaObject = ref({ htmlAttrs: {}, link: [], meta: [] }); function filterQuery(fullPath, whitelist) { const { pathname, search } = parseURL(fullPath); const params = new URLSearchParams(search); const filtered = {}; for (const key of whitelist) { if (params.has(key)) { filtered[key] = params.get(key); } } return withQuery(pathname, filtered); } function filterLocalizedHref(pathOrUrl, whitelist) { if (!pathOrUrl) return ""; const parsed = parseURL(pathOrUrl); const filteredPath = filterQuery(pathOrUrl, whitelist); if (parsed.protocol && parsed.host) { return `${parsed.protocol}//${parsed.host}${filteredPath}`; } return filteredPath; } function updateMeta() { if (route.matched.length === 0 || route.matched.some((record) => record.name === "custom-fallback-route")) { metaObject.value = { htmlAttrs: {}, link: [], meta: [] }; return; } const i18nConfig = nuxtApp.$getI18nConfig(); const { canonicalQueryWhitelist, routeLocales, localizedRouteNamePrefix } = i18nConfig; const strategy = i18nConfig.strategy; const localizedRouteNamePrefixResolved = localizedRouteNamePrefix || "localized-"; const { $getLocales, $getLocale, $switchLocalePath } = nuxtApp; const allLocales = ($getLocales ? unref($getLocales()) : i18nConfig.locales) ?? []; const firstSegment = route.path.replace(/^\//, "").split("/").filter(Boolean)[0]; const fallbackLocale = allLocales.find((loc) => loc.code === firstSegment)?.code || i18nConfig.defaultLocale || "en"; const locale = ($getLocale ? unref($getLocale()) : fallbackLocale) || fallbackLocale; const switchLocalePath = $switchLocalePath || (() => ""); const routeName = (route.name ?? "").toString(); const currentLocale = allLocales.find((loc) => loc.code === locale); if (!currentLocale) return; const currentRouteLocales = findAllowedLocalesForRoute(route, routeLocales, localizedRouteNamePrefixResolved); const enabledLocales = allLocales.filter((loc) => !loc.disabled); const locales = currentRouteLocales ? enabledLocales.filter((loc) => currentRouteLocales.includes(loc.code)) : enabledLocales; const currentIso = currentLocale.iso || locale; const currentOg = resolveOgLocale(currentLocale); const missingWarn = i18nConfig.missingWarn ?? true; const currentDir = currentLocale.dir || "auto"; let fullPath = unref(route.fullPath); if (!fullPath.startsWith("/")) { fullPath = `/${fullPath}`; } const matchedLocale = [...locales].sort((a, b) => b.code.length - a.code.length).find((locale2) => fullPath.startsWith(`/${locale2.code}`)); let localizedPath = fullPath; let ogUrl; let canonicalPath; if (routeName.startsWith(localizedRouteNamePrefixResolved) && matchedLocale) { localizedPath = fullPath.slice(matchedLocale.code.length + 1); canonicalPath = filterQuery(localizedPath, canonicalQueryWhitelist ?? []); ogUrl = joinURL(unref(baseUrl), locale, canonicalPath); } else { canonicalPath = filterQuery(fullPath, canonicalQueryWhitelist ?? []); ogUrl = joinURL(unref(baseUrl), canonicalPath); } const htmlAttrs = { lang: currentIso, ...addDirAttribute ? { dir: currentDir } : {} }; if (!addSeoAttributes) { metaObject.value = { htmlAttrs, link: [], meta: [] }; return; } const localesForSeo = locales.filter((loc) => loc.seo !== false); if (!currentOg) { warnUnresolvedOgLocale(currentLocale, { missingWarn, tag: "og:locale" }); } const ogLocaleMeta = currentOg ? { [identifierAttribute]: "i18n-og", property: "og:locale", content: currentOg } : null; const ogUrlMeta = { [identifierAttribute]: "i18n-og-url", property: "og:url", content: ogUrl }; const alternateOgLocalesMeta = localesForSeo.filter((loc) => loc.code !== locale).map((loc) => { const ogAlt = resolveOgLocale(loc); if (!ogAlt) { warnUnresolvedOgLocale(loc, { missingWarn, tag: "og:locale:alternate" }); return null; } return { [identifierAttribute]: `i18n-og-alt-${ogAlt}`, property: "og:locale:alternate", content: ogAlt }; }).filter((meta) => meta !== null); const canonicalLink = { [identifierAttribute]: "i18n-can", rel: "canonical", href: ogUrl }; const defaultLocale = i18nConfig.defaultLocale || "en"; const defaultLocaleObj = allLocales.find((loc) => loc.code === defaultLocale); const whitelist = canonicalQueryWhitelist ?? []; const alternateLinks = isNoPrefixStrategy(strategy) ? [] : localesForSeo.flatMap((loc) => { const switchedPath = switchLocalePath(loc.code); if (!switchedPath) { return []; } let href; if (switchedPath.startsWith("http://") || switchedPath.startsWith("https://")) { href = filterLocalizedHref(switchedPath, whitelist); } else { const filteredPath = filterLocalizedHref(switchedPath, whitelist); href = joinURL(unref(baseUrl), filteredPath.startsWith("/") ? filteredPath : `/${filteredPath}`); } const links = [ { [identifierAttribute]: `i18n-alternate-${loc.code}`, rel: "alternate", href, hreflang: unref(loc.code) } ]; if (loc.iso && loc.iso !== loc.code) { links.push({ [identifierAttribute]: `i18n-alternate-${loc.iso}`, rel: "alternate", href, hreflang: unref(loc.iso) }); } return links; }); let xDefaultLink = null; if (!isNoPrefixStrategy(strategy) && defaultLocaleObj?.seo !== false) { const defaultSwitchedPath = switchLocalePath(defaultLocale); if (defaultSwitchedPath) { let xDefaultHref; if (defaultSwitchedPath.startsWith("http://") || defaultSwitchedPath.startsWith("https://")) { xDefaultHref = filterLocalizedHref(defaultSwitchedPath, whitelist); } else { const filteredPath = filterLocalizedHref(defaultSwitchedPath, whitelist); xDefaultHref = joinURL(unref(baseUrl), filteredPath.startsWith("/") ? filteredPath : `/${filteredPath}`); } xDefaultLink = { [identifierAttribute]: "i18n-xd", rel: "alternate", href: xDefaultHref, hreflang: "x-default" }; } } metaObject.value = { htmlAttrs, meta: [...ogLocaleMeta ? [ogLocaleMeta] : [], ogUrlMeta, ...alternateOgLocalesMeta], link: [canonicalLink, ...alternateLinks, ...xDefaultLink ? [xDefaultLink] : []] }; } if (autoUpdate) { watch( () => [route.fullPath, route.name, route.matched.length], () => updateMeta(), { immediate: true } ); } return { metaObject, updateMeta }; };