diff --git a/scripts/docs-site/build.mjs b/scripts/docs-site/build.mjs index 4da0e61a7..d91d2f9fd 100644 --- a/scripts/docs-site/build.mjs +++ b/scripts/docs-site/build.mjs @@ -292,7 +292,7 @@ function layout({ page, nav, activeTab, html, toc, prev, next }) { ${escapeHtml(title)} ${canonicalUrl ? `` : ""} -${page.hidden ? '' : ""} +${hreflangLinks(page)}${page.hidden ? '' : ""} @@ -909,6 +909,28 @@ function pageRoute(page) { return page.slug === "index" ? (prefix || "/") : `${prefix}/${page.slug}`; } +function hreflangLinks(page) { + // hreflang alternates require absolute URLs; skip when no canonical origin is set + // or when the page is excluded from indexing. + if (!canonicalOrigin || page.hidden) return ""; + // Collect every locale that publishes this same slug, using the current page for + // its own locale and skipping any locale variant that is itself hidden. + const variants = []; + for (const locale of locales) { + const variant = locale.code === page.locale ? page : allPageByKey.get(pageKey(locale.code, page.slug)); + if (variant && !variant.hidden) variants.push(variant); + } + // Nothing to cross-link if the page exists in only one locale. + if (variants.length < 2) return ""; + const links = variants.map( + (variant) => ``, + ); + // x-default points at the English variant when available, otherwise the current page. + const defaultPage = variants.find((variant) => variant.locale === "en") ?? page; + links.push(``); + return `${links.join("\n")}\n`; +} + function pageMarkdownRoute(page) { const prefix = page.locale === "en" ? "" : `/${page.locale}`; return page.slug === "index" ? `${prefix || ""}/index.md` : `${prefix}/${page.slug}.md`; diff --git a/scripts/docs-site/smoke.mjs b/scripts/docs-site/smoke.mjs index b9d1bd03f..cab565486 100644 --- a/scripts/docs-site/smoke.mjs +++ b/scripts/docs-site/smoke.mjs @@ -128,6 +128,15 @@ if (!/class="tab-link active" href="(?:\/docs)?\/it\/channels"/.test(itChannels) if (!/