import type { I18nConfig, LocalizedSlugs } from '../i18n/index.js' import { buildLocalizedPath, getDefaultLocale, getLocaleCodes } from '../i18n/index.js' type BuildHreflangArgs = { /** Absolute site origin, e.g. 'https://example.com'. Omit for relative paths. */ baseUrl?: string config: I18nConfig /** Home slug that collapses to the locale root. Defaults to 'home'. */ homeSlug?: string /** * Localized segment the document lives under (an archive page's slugs), * e.g. { pl: 'artykuly', en: 'articles' }. Locales missing from the prefix * are omitted — an entry with no archive in that language has no URL there. */ prefix?: LocalizedSlugs /** slug per locale for the current document, e.g. { pl: 'o-nas', en: 'about' } */ slugs: LocalizedSlugs } /** * Next.js Metadata `alternates.languages`. * * Bridges SEO and i18n: for each configured locale that the document has a * slug in, it produces the locale-aware path (via buildLocalizedPath), * optionally prefixed with an absolute origin. * * Also emits `x-default` pointing at the default locale — the version Google * serves when the user's language/region matches no hreflang, and the fallback * when the root ('/') redirect is ambiguous (Googlebot with no/foreign * Accept-Language). * * @example * buildHreflangAlternates({ * slugs: { pl: 'o-nas', en: 'about' }, * config, * baseUrl: 'https://example.com', * }) * // → { * // pl: 'https://example.com/pl/o-nas', * // en: 'https://example.com/en/about', * // 'x-default': 'https://example.com/pl/o-nas', * // } */ export function buildHreflangAlternates({ baseUrl, config, homeSlug = 'home', prefix, slugs, }: BuildHreflangArgs): Record { const origin = baseUrl?.replace(/\/$/, '') ?? '' const alternates: Record = {} for (const locale of getLocaleCodes(config)) { const path = buildLocalizedPath({ config, homeSlug, locale, prefix, slugs }) if (path) { alternates[locale] = `${origin}${path}` } } // x-default: the version Google serves when the user's language/region doesn't // match any hreflang — and, crucially here, the fallback when the root ('/') // redirect is ambiguous (Googlebot with no/foreign Accept-Language). Point it // at the default locale (the primary market) so search shows that version by // default instead of guessing. Only set when the default locale has a URL. const defaultLocalePath = alternates[getDefaultLocale(config)] if (defaultLocalePath) { alternates['x-default'] = defaultLocalePath } return alternates }