- Add 100 blog posts covering AI, development, and tech topics - Add .env.example for environment configuration - Add accessibility and lighthouse audit scripts - Remove obsolete SEO reports and temporary files - Remove dev-dist build artifacts and backup files - Remove unused portrait images (moved/consolidated elsewhere) - Update contact form and component improvements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
130 lines
4.4 KiB
TypeScript
130 lines
4.4 KiB
TypeScript
import { notFound } from 'next/navigation';
|
|
import { NextIntlClientProvider } from 'next-intl';
|
|
import { getMessages, setRequestLocale } from 'next-intl/server';
|
|
import { Inter } from 'next/font/google';
|
|
import { locales, localeMetadata, seoKeywords, type Locale } from '@/i18n/config';
|
|
import type { Metadata } from 'next';
|
|
import { Header, Footer, GlobalBackground } from '@/components/layout';
|
|
import { PersonJsonLd, ProfessionalServiceJsonLd, OrganizationJsonLd, WebSiteJsonLd } from '@/components/seo';
|
|
import '../globals.css';
|
|
|
|
const inter = Inter({
|
|
subsets: ['latin'],
|
|
display: 'swap',
|
|
variable: '--font-inter',
|
|
});
|
|
|
|
type Props = {
|
|
children: React.ReactNode;
|
|
params: Promise<{ locale: string }>;
|
|
};
|
|
|
|
export function generateStaticParams() {
|
|
return locales.map((locale) => ({ locale }));
|
|
}
|
|
|
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
|
const { locale } = await params;
|
|
const currentLocale = locale as Locale;
|
|
const meta = localeMetadata[currentLocale] || localeMetadata.de;
|
|
const keywords = seoKeywords[currentLocale] || seoKeywords.de;
|
|
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://damjan-savic.com';
|
|
|
|
const titles: Record<Locale, string> = {
|
|
de: 'Damjan Savić | Fullstack Developer aus Köln',
|
|
en: 'Damjan Savić | Fullstack Developer from Cologne',
|
|
sr: 'Damjan Savić | Fullstack Developer iz Kelna',
|
|
};
|
|
|
|
const descriptions: Record<Locale, string> = {
|
|
de: 'Fullstack Entwicklung für Websites, Apps und SaaS mit Next.js, React und TypeScript.',
|
|
en: 'Fullstack development for websites, apps and SaaS with Next.js, React and TypeScript.',
|
|
sr: 'Fullstack razvoj za web stranice, aplikacije i SaaS sa Next.js, React i TypeScript.',
|
|
};
|
|
|
|
// Generate alternates using localeMetadata
|
|
const languageAlternates: Record<string, string> = {
|
|
'x-default': `${BASE_URL}/de`,
|
|
};
|
|
locales.forEach((loc) => {
|
|
languageAlternates[loc] = `${BASE_URL}/${loc}`;
|
|
});
|
|
|
|
// Generate OG alternate locales
|
|
const allOgLocales = locales.map((loc) => localeMetadata[loc].ogLocale);
|
|
const alternateOgLocales = allOgLocales.filter((l) => l !== meta.ogLocale);
|
|
|
|
return {
|
|
title: {
|
|
template: '%s | Damjan Savić',
|
|
default: titles[currentLocale] || titles.de,
|
|
},
|
|
description: descriptions[currentLocale] || descriptions.de,
|
|
keywords: keywords,
|
|
alternates: {
|
|
canonical: `${BASE_URL}/${locale}`,
|
|
languages: languageAlternates,
|
|
},
|
|
openGraph: {
|
|
type: 'website',
|
|
locale: meta.ogLocale,
|
|
alternateLocale: alternateOgLocales,
|
|
siteName: 'Damjan Savić',
|
|
images: [
|
|
{
|
|
url: '/images/og-image.jpg',
|
|
width: 1200,
|
|
height: 630,
|
|
alt: locale === 'de' ? 'Damjan Savić - KI & Automatisierung Spezialist' : locale === 'sr' ? 'Damjan Savić - AI & Automatizacija Specijalista' : 'Damjan Savić - AI & Automation Specialist',
|
|
},
|
|
],
|
|
},
|
|
twitter: {
|
|
card: 'summary_large_image',
|
|
creator: '@damjansavic',
|
|
},
|
|
};
|
|
}
|
|
|
|
export default async function LocaleLayout({ children, params }: Props) {
|
|
const { locale } = await params;
|
|
|
|
if (!locales.includes(locale as Locale)) {
|
|
notFound();
|
|
}
|
|
|
|
setRequestLocale(locale);
|
|
|
|
const messages = await getMessages();
|
|
|
|
return (
|
|
<html lang={locale} className={`${inter.variable} dark`}>
|
|
<head>
|
|
{/* Preconnect für bessere Performance */}
|
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
|
<link rel="dns-prefetch" href="https://mxadgucxhmstlzsbgmoz.supabase.co" />
|
|
|
|
{/* Theme Color für mobile Browser */}
|
|
<meta name="theme-color" content="#18181b" />
|
|
|
|
{/* JSON-LD Structured Data */}
|
|
<PersonJsonLd locale={locale as Locale} />
|
|
<ProfessionalServiceJsonLd locale={locale as Locale} />
|
|
<OrganizationJsonLd locale={locale as Locale} />
|
|
<WebSiteJsonLd locale={locale as Locale} />
|
|
</head>
|
|
<body className="min-h-screen bg-zinc-900 text-zinc-50 antialiased">
|
|
<NextIntlClientProvider messages={messages}>
|
|
<div className="min-h-screen flex flex-col relative">
|
|
<GlobalBackground />
|
|
<Header />
|
|
<main className="flex-1 pt-16 relative z-10">{children}</main>
|
|
<Footer />
|
|
</div>
|
|
</NextIntlClientProvider>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|