import type { Metadata } from "next"; import { routing } from "../i18n/routing"; import { PAGE_TITLE_TOKEN, SITE_NAME_TOKEN, type SiteSettings, type SiteSettingsMediaBindings, } from "./site-settings"; import { getSiteSettings, getSiteSettingsMediaBindings, } from "./app-config"; import { AppLocale, getLocalizedPath, resolveLocale } from "./locale"; function getSiteUrl(): URL { return new URL(process.env.NEXT_PUBLIC_SITE_URL ?? "https://mohfarawati.de"); } function toAbsoluteUrl(pathname: string): string { return new URL(pathname, getSiteUrl()).toString(); } export function buildLocaleAlternates(pathname: string) { const languages = Object.fromEntries( routing.locales.map((locale) => [locale, toAbsoluteUrl(getLocalizedPath(locale, pathname))]), ) as Record; return { canonical: toAbsoluteUrl(getLocalizedPath(routing.defaultLocale, pathname)), languages: { ...languages, "x-default": toAbsoluteUrl(getLocalizedPath(routing.defaultLocale, pathname)), }, }; } export function applyTitleTemplateFn(title: string, template: string, siteName: string): string { const safeTemplate = template.includes(PAGE_TITLE_TOKEN) ? template : `${PAGE_TITLE_TOKEN} | ${SITE_NAME_TOKEN}`; return safeTemplate .replaceAll(SITE_NAME_TOKEN, siteName) .replace(PAGE_TITLE_TOKEN, title); } function buildMetadataImages(imageUrl?: string | null) { if (!imageUrl) { return undefined; } return [ { url: toAbsoluteUrl(imageUrl), }, ]; } export async function buildAppMetadata(): Promise { const [settings, bindings] = await Promise.all([getSiteSettings(), getSiteSettingsMediaBindings()]); return buildAppMetadataFromConfig(settings, bindings); } export function buildAppMetadataFromConfig( settings: SiteSettings, bindings: SiteSettingsMediaBindings, ): Metadata { const defaultLocaleSettings = settings.locales[routing.defaultLocale]; const openGraphImages = buildMetadataImages(bindings.defaultOgImage?.url); return { metadataBase: getSiteUrl(), title: defaultLocaleSettings.siteName, description: defaultLocaleSettings.siteDescription, applicationName: defaultLocaleSettings.siteName, icons: bindings.favicon?.url ? { icon: [bindings.favicon.url], shortcut: [bindings.favicon.url], apple: [bindings.favicon.url], } : undefined, openGraph: { title: defaultLocaleSettings.siteName, description: defaultLocaleSettings.siteDescription, url: toAbsoluteUrl(getLocalizedPath(routing.defaultLocale, "/")), siteName: defaultLocaleSettings.siteName, locale: routing.defaultLocale, type: "website", images: openGraphImages, }, twitter: { card: openGraphImages ? "summary_large_image" : "summary", title: defaultLocaleSettings.siteName, description: defaultLocaleSettings.siteDescription, images: openGraphImages?.map((image) => image.url), }, }; } type LocalizedMetadataInput = { locale: string; pathname: string; title: string; description?: string; applyTitleTemplate?: boolean; }; export async function buildLocalizedMetadata({ locale, pathname, title, description, applyTitleTemplate, }: LocalizedMetadataInput): Promise { const localeKey = resolveLocale(locale); const [settings, bindings] = await Promise.all([getSiteSettings(), getSiteSettingsMediaBindings()]); return buildLocalizedMetadataFromConfig({ settings, bindings, locale: localeKey, pathname, title, description, applyTitleTemplate, }); } export function buildLocalizedMetadataFromConfig(input: { settings: SiteSettings; bindings: SiteSettingsMediaBindings; locale: AppLocale; pathname: string; title: string; description?: string; applyTitleTemplate?: boolean; }): Metadata { const { settings, bindings, locale, pathname, title, description, applyTitleTemplate = true, } = input; const localeKey = resolveLocale(locale); const localeSettings = settings.locales[localeKey]; const resolvedDescription = description?.trim() || localeSettings.siteDescription; const resolvedTitle = applyTitleTemplate ? applyTitleTemplateFn(title, localeSettings.titleTemplate, localeSettings.siteName) : title; const openGraphImages = buildMetadataImages(bindings.defaultOgImage?.url); return { title: resolvedTitle, description: resolvedDescription, alternates: buildLocaleAlternates(pathname), openGraph: { title: resolvedTitle, description: resolvedDescription, url: toAbsoluteUrl(getLocalizedPath(localeKey, pathname)), siteName: localeSettings.siteName, locale: localeKey, type: "website", images: openGraphImages, }, twitter: { card: openGraphImages ? "summary_large_image" : "summary", title: resolvedTitle, description: resolvedDescription, images: openGraphImages?.map((image) => image.url), }, }; }