Files
e2e 3bb535efe8
Some checks failed
Main / Setup and Test (push) Has been cancelled
Main / Build Website (push) Has been cancelled
chore: heygermany customer project
2026-07-11 10:35:04 +02:00

226 lines
6.1 KiB
TypeScript

import type { SupportedLocale } from '@heygermany/sdk/config'
import { DEFAULT_LOCALE, LOCALES } from '@/config'
import { mTextIn } from '@/i18n/messages'
type MessageKey = string
export const SITE_NAME = 'HeyGermany'
export const BRAND_ALT_NAME = 'Hey Germany'
export const CONTACT_EMAIL = 'info@hey-germany.com'
const viteEnv = (import.meta as ImportMeta & {
env?: Record<string, string | undefined>
}).env
export const SITE_URL =
viteEnv?.VITE_SITE_URL || process.env.VITE_SITE_URL || 'https://hey-germany.com'
export const DEFAULT_SITE_TITLE = `${SITE_NAME} | Nursing Career and Qualification Check in Germany`
export const DEFAULT_SITE_DESCRIPTION =
'HeyGermany helps internationally qualified nurses check their qualifications and take the next step toward a nursing career in Germany.'
export const DEFAULT_OG_IMAGE = '/heygermany-logo.png'
export const NO_INDEX_METADATA = {
robots: {
index: false,
follow: false,
nocache: true,
googleBot: {
index: false,
follow: false,
noimageindex: true,
},
},
}
type CreatePageMetadataInput = {
locale: string
pathname?: string
title: string
description: string
imagePath?: string
index?: boolean
availableLocales?: readonly SupportedLocale[]
canonicalLocale?: SupportedLocale
}
function isSupportedLocale(locale: string): locale is SupportedLocale {
return LOCALES.includes(locale as SupportedLocale)
}
function normalizePath(pathname: string): string {
if (!pathname || pathname === '/') {
return '/'
}
return pathname.startsWith('/') ? pathname : `/${pathname}`
}
function buildRobots(index: boolean) {
return index
? {
index: true,
follow: true,
}
: NO_INDEX_METADATA.robots
}
export function normalizeLocale(locale: string): SupportedLocale {
return isSupportedLocale(locale) ? locale : DEFAULT_LOCALE
}
export function getAbsoluteUrl(pathname: string): string {
return new URL(pathname, SITE_URL).toString()
}
export function getLocalizedPath(locale: string, pathname = '/'): string {
const normalizedLocale = normalizeLocale(locale)
const normalizedPath = normalizePath(pathname)
return normalizedPath === '/'
? `/${normalizedLocale}`
: `/${normalizedLocale}${normalizedPath}`
}
export function withSiteName(title: string): string {
return title.includes(SITE_NAME) ? title : `${title} | ${SITE_NAME}`
}
export function getMessage(locale: string, key: MessageKey): string {
return mTextIn(normalizeLocale(locale), key)
}
function buildLanguageAlternates(
pathname: string,
locales: readonly SupportedLocale[]
): Record<string, string> | undefined {
if (locales.length < 2) {
return undefined
}
const alternates = Object.fromEntries(
locales.map((locale) => [locale, getAbsoluteUrl(getLocalizedPath(locale, pathname))])
) as Record<string, string>
alternates['x-default'] = getAbsoluteUrl(getLocalizedPath(DEFAULT_LOCALE, pathname))
return alternates
}
export function createPageMetadata({
locale,
pathname = '/',
title,
description,
imagePath = DEFAULT_OG_IMAGE,
index = true,
availableLocales = LOCALES,
canonicalLocale,
}: CreatePageMetadataInput) {
const resolvedLocale = normalizeLocale(locale)
const resolvedCanonicalLocale = canonicalLocale ?? resolvedLocale
const canonicalPath = getLocalizedPath(resolvedCanonicalLocale, pathname)
const resolvedTitle = withSiteName(title)
const imageUrl = getAbsoluteUrl(imagePath)
const languages = buildLanguageAlternates(pathname, availableLocales)
return {
title: resolvedTitle,
description,
alternates: {
canonical: getAbsoluteUrl(canonicalPath),
...(languages ? { languages } : {}),
},
openGraph: {
title: resolvedTitle,
description,
url: getAbsoluteUrl(canonicalPath),
siteName: SITE_NAME,
type: 'website',
images: [
{
url: imageUrl,
alt: resolvedTitle,
},
],
},
twitter: {
card: 'summary_large_image',
title: resolvedTitle,
description,
images: [imageUrl],
},
robots: buildRobots(index),
}
}
export function createHomepageMetadata(locale: string) {
const resolvedLocale = normalizeLocale(locale)
const homepageMetadata: Partial<
Record<SupportedLocale, { title: string; description: string }>
> = {
en: {
title: DEFAULT_SITE_TITLE,
description: DEFAULT_SITE_DESCRIPTION,
},
de: {
title: `${SITE_NAME} | Pflegekarriere und Qualifikationscheck in Deutschland`,
description:
'HeyGermany unterstützt internationale Pflegefachkräfte dabei, ihre Qualifikation für Deutschland zu prüfen und den nächsten Schritt in ihre Pflegekarriere zu gehen.',
},
}
const localizedBrandMetadata = homepageMetadata[resolvedLocale]
return createPageMetadata({
locale: resolvedLocale,
title: localizedBrandMetadata?.title ?? `${SITE_NAME} | ${getMessage(locale, 'website.hero.title')}`,
description: localizedBrandMetadata?.description ?? getMessage(locale, 'website.hero.subtitle'),
})
}
export function getOrganizationStructuredData() {
return {
'@context': 'https://schema.org',
'@type': 'Organization',
'@id': `${SITE_URL}#organization`,
name: SITE_NAME,
alternateName: BRAND_ALT_NAME,
url: SITE_URL,
logo: {
'@type': 'ImageObject',
url: getAbsoluteUrl(DEFAULT_OG_IMAGE),
},
email: CONTACT_EMAIL,
description: DEFAULT_SITE_DESCRIPTION,
contactPoint: [
{
'@type': 'ContactPoint',
contactType: 'customer support',
email: CONTACT_EMAIL,
url: SITE_URL,
availableLanguage: LOCALES,
},
],
brand: {
'@type': 'Brand',
name: SITE_NAME,
alternateName: BRAND_ALT_NAME,
},
}
}
export function getWebsiteStructuredData() {
return {
'@context': 'https://schema.org',
'@type': 'WebSite',
'@id': `${SITE_URL}#website`,
url: SITE_URL,
name: SITE_NAME,
alternateName: BRAND_ALT_NAME,
description: DEFAULT_SITE_DESCRIPTION,
publisher: {
'@id': `${SITE_URL}#organization`,
},
inLanguage: LOCALES,
}
}