121 lines
4.8 KiB
TypeScript
121 lines
4.8 KiB
TypeScript
import React, { useEffect } from 'react';
|
|
import { Head, Link, usePage, router } from '@inertiajs/react';
|
|
import { useLocalizedRoutes } from '@/hooks/useLocalizedRoutes';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
interface MarketingLayoutProps {
|
|
children: React.ReactNode;
|
|
title?: string;
|
|
}
|
|
|
|
const MarketingLayout: React.FC<MarketingLayoutProps> = ({ children, title }) => {
|
|
const page = usePage<{ translations?: Record<string, Record<string, string>> }>();
|
|
const { url } = page;
|
|
const { t } = useTranslation('marketing');
|
|
const i18n = useTranslation();
|
|
const { locale, localizedPath } = useLocalizedRoutes();
|
|
|
|
useEffect(() => {
|
|
const localeCandidate = locale || (url.startsWith('/en/') ? 'en' : 'de');
|
|
|
|
if (localeCandidate && i18n.i18n.language !== localeCandidate) {
|
|
i18n.i18n.changeLanguage(localeCandidate);
|
|
}
|
|
}, [url, i18n, locale]);
|
|
|
|
const marketing = page.props.translations?.marketing ?? {};
|
|
|
|
const getString = (key: string, fallback: string) => {
|
|
const value = marketing[key];
|
|
return typeof value === 'string' ? value : fallback;
|
|
};
|
|
|
|
const activeLocale = locale || (url.startsWith('/en/') ? 'en' : 'de');
|
|
const alternateLocale = activeLocale === 'de' ? 'en' : 'de';
|
|
const path = url.replace(/^\/(de|en)/, '');
|
|
const canonicalUrl = `https://fotospiel.app${localizedPath(path || '/')}`;
|
|
|
|
const handleLocaleChange = (nextLocale: string) => {
|
|
const normalizedPath = url.replace(/^\/(de|en)/, '') || '/';
|
|
const destination = normalizedPath === '/' ? `/${nextLocale}` : `/${nextLocale}${normalizedPath}`;
|
|
router.visit(destination);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Head>
|
|
<title>{title || t('meta.title', getString('title', 'Fotospiel'))}</title>
|
|
<meta
|
|
name="description"
|
|
content={t('meta.description', getString('description', 'Sammle Gastfotos für Events mit QR-Codes'))}
|
|
/>
|
|
<meta property="og:title" content={title || t('meta.title', getString('title', 'Fotospiel'))} />
|
|
<meta
|
|
property="og:description"
|
|
content={t('meta.description', getString('description', 'Sammle Gastfotos für Events mit QR-Codes'))}
|
|
/>
|
|
<meta property="og:url" content={canonicalUrl} />
|
|
<link rel="canonical" href={canonicalUrl} />
|
|
<link rel="alternate" hrefLang="de" href={`https://fotospiel.app/de${path}`} />
|
|
<link rel="alternate" hrefLang="en" href={`https://fotospiel.app/en${path}`} />
|
|
<link rel="alternate" hrefLang="x-default" href="https://fotospiel.app/de" />
|
|
</Head>
|
|
<div className="min-h-screen bg-white">
|
|
<header className="bg-white shadow-sm">
|
|
<div className="container mx-auto px-4 py-4">
|
|
<nav className="flex justify-between items-center">
|
|
<Link href={localizedPath('/')} className="text-xl font-bold text-gray-900">
|
|
Fotospiel
|
|
</Link>
|
|
<div className="hidden md:flex space-x-8">
|
|
<Link href={localizedPath('/')} className="text-gray-700 hover:text-gray-900">
|
|
{t('nav.home')}
|
|
</Link>
|
|
<Link href={localizedPath('/packages')} className="text-gray-700 hover:text-gray-900">
|
|
{t('nav.packages')}
|
|
</Link>
|
|
<Link href={localizedPath('/blog')} className="text-gray-700 hover:text-gray-900">
|
|
{t('nav.blog')}
|
|
</Link>
|
|
<Link href={localizedPath('/kontakt')} className="text-gray-700 hover:text-gray-900">
|
|
{t('nav.contact')}
|
|
</Link>
|
|
</div>
|
|
<div className="flex items-center space-x-4">
|
|
<select
|
|
value={activeLocale}
|
|
onChange={(event) => handleLocaleChange(event.target.value)}
|
|
className="border border-gray-300 rounded-md px-2 py-1"
|
|
>
|
|
<option value="de">DE</option>
|
|
<option value="en">EN</option>
|
|
</select>
|
|
</div>
|
|
</nav>
|
|
</div>
|
|
</header>
|
|
<main>
|
|
{children}
|
|
</main>
|
|
<footer className="bg-gray-800 text-white py-8">
|
|
<div className="container mx-auto px-4 text-center">
|
|
<p>© 2025 Fotospiel. Alle Rechte vorbehalten.</p>
|
|
<div className="mt-4 space-x-4">
|
|
<Link href={localizedPath('/datenschutz')} className="hover:underline">
|
|
{t('nav.privacy')}
|
|
</Link>
|
|
<Link href={localizedPath('/impressum')} className="hover:underline">
|
|
{t('nav.impressum')}
|
|
</Link>
|
|
<Link href={localizedPath('/kontakt')} className="hover:underline">
|
|
{t('nav.contact')}
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default MarketingLayout; |