Files
MOH c18128c965
CI / quality (push) Waiting to run
Fix runtime default locale resolution
2026-03-15 06:02:27 +01:00

184 lines
5.4 KiB
TypeScript

import type { Metadata } from "next";
import { appLocales } 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, getLocalizedPathWithDefault, 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, defaultLocale: AppLocale) {
const languages = Object.fromEntries(
appLocales.map((locale) => [locale, toAbsoluteUrl(getLocalizedPathWithDefault(locale, pathname, defaultLocale))]),
) as Record<AppLocale, string>;
return {
canonical: toAbsoluteUrl(getLocalizedPathWithDefault(defaultLocale, pathname, defaultLocale)),
languages: {
...languages,
"x-default": toAbsoluteUrl(getLocalizedPathWithDefault(defaultLocale, pathname, defaultLocale)),
},
};
}
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[settings.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(getLocalizedPathWithDefault(settings.defaultLocale, "/", settings.defaultLocale)),
siteName: defaultLocaleSettings.siteName,
locale: settings.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 [settings, bindings] = await Promise.all([getSiteSettings(), getSiteSettingsMediaBindings()]);
const localeKey = resolveLocale(locale, settings.defaultLocale);
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, settings.defaultLocale);
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, settings.defaultLocale),
openGraph: {
title: resolvedTitle,
description: resolvedDescription,
url: toAbsoluteUrl(getLocalizedPath(localeKey, pathname, settings.defaultLocale)),
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),
},
};
}