184 lines
5.2 KiB
TypeScript
184 lines
5.2 KiB
TypeScript
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";
|
|
import { buildSiteIconUrls } from "./site-icons";
|
|
|
|
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<AppLocale, string>;
|
|
|
|
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<Metadata> {
|
|
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);
|
|
const siteIconUrls = buildSiteIconUrls({
|
|
siteName: defaultLocaleSettings.siteName,
|
|
faviconVersion: bindings.favicon?.version,
|
|
faviconUrl: bindings.favicon?.url,
|
|
});
|
|
|
|
return {
|
|
metadataBase: getSiteUrl(),
|
|
title: defaultLocaleSettings.siteName,
|
|
description: defaultLocaleSettings.siteDescription,
|
|
applicationName: defaultLocaleSettings.siteName,
|
|
manifest: siteIconUrls.manifestHref,
|
|
icons: {
|
|
icon: [{ url: siteIconUrls.faviconHref }],
|
|
shortcut: [{ url: siteIconUrls.faviconHref }],
|
|
apple: [{ url: siteIconUrls.appleIconHref }],
|
|
},
|
|
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<Metadata> {
|
|
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),
|
|
},
|
|
};
|
|
}
|