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
JavaScript
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 };
};