mehr übersetzungen, added pending purchase indicator. datenschutzfenster funktioniert.
This commit is contained in:
32
resources/js/i18n.ts
Normal file
32
resources/js/i18n.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import Backend from 'i18next-http-backend';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
|
||||
i18n
|
||||
.use(Backend)
|
||||
.use(LanguageDetector)
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
fallbackLng: 'de',
|
||||
supportedLngs: ['de', 'en'],
|
||||
ns: ['marketing', 'auth', 'common'],
|
||||
defaultNS: 'marketing',
|
||||
debug: import.meta.env.DEV,
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
backend: {
|
||||
loadPath: '/lang/{{lng}}/{{ns}}.json',
|
||||
},
|
||||
detection: {
|
||||
order: ['path', 'localStorage', 'htmlTag'],
|
||||
lookupFromPathIndex: 0,
|
||||
caches: ['localStorage'],
|
||||
},
|
||||
react: {
|
||||
useSuspense: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Head, Link, usePage } from '@inertiajs/react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Head, Link, usePage, router } from '@inertiajs/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface MarketingLayoutProps {
|
||||
@@ -10,6 +10,14 @@ interface MarketingLayoutProps {
|
||||
const MarketingLayout: React.FC<MarketingLayoutProps> = ({ children, title }) => {
|
||||
const { t } = useTranslation('marketing');
|
||||
const { url } = usePage();
|
||||
const i18n = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
const locale = url.startsWith('/en/') ? 'en' : 'de';
|
||||
if (i18n.i18n.language !== locale) {
|
||||
i18n.i18n.changeLanguage(locale);
|
||||
}
|
||||
}, [url, i18n]);
|
||||
|
||||
const { translations } = usePage().props as any;
|
||||
const marketing = translations?.marketing || {};
|
||||
@@ -39,6 +47,43 @@ const MarketingLayout: React.FC<MarketingLayoutProps> = ({ children, title }) =>
|
||||
<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="/" className="text-xl font-bold text-gray-900">
|
||||
Fotospiel
|
||||
</Link>
|
||||
<div className="hidden md:flex space-x-8">
|
||||
<Link href="/" className="text-gray-700 hover:text-gray-900">
|
||||
{t('nav.home')}
|
||||
</Link>
|
||||
<Link href="/packages" className="text-gray-700 hover:text-gray-900">
|
||||
{t('nav.packages')}
|
||||
</Link>
|
||||
<Link href="/blog" className="text-gray-700 hover:text-gray-900">
|
||||
{t('nav.blog')}
|
||||
</Link>
|
||||
<Link href="/kontakt" className="text-gray-700 hover:text-gray-900">
|
||||
{t('nav.contact')}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<select
|
||||
value={currentLocale}
|
||||
onChange={(e) => {
|
||||
const newLocale = e.target.value;
|
||||
const newPath = url.replace(/^\/(de|en)?/, `/${newLocale}`);
|
||||
router.visit(newPath);
|
||||
}}
|
||||
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>
|
||||
@@ -47,13 +92,13 @@ const MarketingLayout: React.FC<MarketingLayoutProps> = ({ children, title }) =>
|
||||
<p>© 2025 Fotospiel. Alle Rechte vorbehalten.</p>
|
||||
<div className="mt-4 space-x-4">
|
||||
<Link href="/datenschutz" className="hover:underline">
|
||||
{t('nav.privacy', getString('nav.privacy', 'Datenschutz'))}
|
||||
{t('nav.privacy')}
|
||||
</Link>
|
||||
<Link href="/impressum" className="hover:underline">
|
||||
{t('nav.impressum', getString('nav.impressum', 'Impressum'))}
|
||||
{t('nav.impressum')}
|
||||
</Link>
|
||||
<Link href="/kontakt" className="hover:underline">
|
||||
{t('nav.contact', getString('nav.contact', 'Kontakt'))}
|
||||
{t('nav.contact')}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@ import MarketingLayout from '@/layouts/marketing/MarketingLayout';
|
||||
export default function Register({ package: initialPackage, privacyHtml }: RegisterProps) {
|
||||
const [privacyOpen, setPrivacyOpen] = useState(false);
|
||||
const [hasTriedSubmit, setHasTriedSubmit] = useState(false);
|
||||
const { t } = useTranslation('auth');
|
||||
const { t } = useTranslation(['auth', 'common']);
|
||||
|
||||
const { data, setData, post, processing, errors, clearErrors } = useForm({
|
||||
username: '',
|
||||
@@ -87,7 +87,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="md:col-span-1">
|
||||
<label htmlFor="first_name" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t('register.first_name')} *
|
||||
{t('register.first_name')} {t('common:required')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
|
||||
@@ -112,7 +112,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
|
||||
|
||||
<div className="md:col-span-1">
|
||||
<label htmlFor="last_name" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t('register.last_name')} *
|
||||
{t('register.last_name')} {t('common:required')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
|
||||
@@ -137,7 +137,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t('register.email')} *
|
||||
{t('register.email')} {t('common:required')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
|
||||
@@ -162,7 +162,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<label htmlFor="address" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t('register.address')} *
|
||||
{t('register.address')} {t('common:required')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<MapPin className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
|
||||
@@ -187,7 +187,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
|
||||
|
||||
<div className="md:col-span-1">
|
||||
<label htmlFor="phone" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t('register.phone')} *
|
||||
{t('register.phone')} {t('common:required')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Phone className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
|
||||
@@ -212,7 +212,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
|
||||
|
||||
<div className="md:col-span-1">
|
||||
<label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t('register.username')} *
|
||||
{t('register.username')} {t('common:required')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
|
||||
@@ -237,7 +237,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
|
||||
|
||||
<div className="md:col-span-1">
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t('register.password')} *
|
||||
{t('register.password')} {t('common:required')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
|
||||
@@ -265,7 +265,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
|
||||
|
||||
<div className="md:col-span-1">
|
||||
<label htmlFor="password_confirmation" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
{t('register.confirm_password')} *
|
||||
{t('register.confirm_password')} {t('common:required')}
|
||||
</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
|
||||
@@ -326,7 +326,7 @@ export default function Register({ package: initialPackage, privacyHtml }: Regis
|
||||
<ul className="text-sm text-red-800 space-y-1">
|
||||
{Object.entries(errors).map(([key, value]) => (
|
||||
<li key={key} className="flex items-start">
|
||||
<span className="font-medium">{key.replace('_', ' ')}:</span> {value}
|
||||
<span className="font-medium">{t(`register.errors.${key}`)}:</span> {value}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
@@ -122,8 +122,8 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
<div key={pkg.id} className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md text-center">
|
||||
<h3 className="text-2xl font-bold mb-2">{pkg.name}</h3>
|
||||
<p className="text-gray-600 dark:text-gray-300 mb-4">{pkg.description}</p>
|
||||
<p className="text-3xl font-bold text-[#FFB6C1]">{pkg.price} {t('home.currency.euro')}</p>
|
||||
<Link href={`/packages/${pkg.id}`} className="mt-4 inline-block bg-[#FFB6C1] text-white px-6 py-2 rounded-full hover:bg-pink-600">
|
||||
<p className="text-3xl font-bold text-[#FFB6C1]">{pkg.price} {t('common.currency.euro')}</p>
|
||||
<Link href={`/register?package_id=${pkg.id}`} className="mt-4 inline-block bg-[#FFB6C1] text-white px-6 py-2 rounded-full hover:bg-pink-600">
|
||||
{t('home.view_details')}
|
||||
</Link>
|
||||
</div>
|
||||
@@ -144,7 +144,7 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="name" className="block text-sm font-medium mb-2">
|
||||
{t('home.name_label')}
|
||||
{t('home.name_label')} {t('common.required')}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -157,7 +157,7 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="email" className="block text-sm font-medium mb-2">
|
||||
{t('home.email_label')}
|
||||
{t('home.email_label')} {t('common.required')}
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
@@ -170,7 +170,7 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="message" className="block text-sm font-medium mb-2">
|
||||
{t('home.message_label')}
|
||||
{t('home.message_label')} {t('common.required')}
|
||||
</label>
|
||||
<textarea
|
||||
id="message"
|
||||
@@ -199,15 +199,15 @@ const Home: React.FC<Props> = ({ packages }) => {
|
||||
<div className="grid md:grid-cols-3 gap-8">
|
||||
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md">
|
||||
<p className="italic mb-4">"{t('home.testimonial1')}"</p>
|
||||
<p className="font-semibold">- Anna M.</p>
|
||||
<p className="font-semibold">{t('common.testimonials.anna.name')}</p>
|
||||
</div>
|
||||
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md">
|
||||
<p className="italic mb-4">"{t('home.testimonial2')}"</p>
|
||||
<p className="font-semibold">- Max S.</p>
|
||||
<p className="font-semibold">{t('common.testimonials.max.name')}</p>
|
||||
</div>
|
||||
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-md">
|
||||
<p className="italic mb-4">"{t('home.testimonial3')}"</p>
|
||||
<p className="font-semibold">- Lisa K.</p>
|
||||
<p className="font-semibold">{t('common.testimonials.lisa.name')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Head, Link, usePage } from '@inertiajs/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel"
|
||||
@@ -43,10 +43,24 @@ const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackag
|
||||
const { auth } = props as any;
|
||||
const { t } = useTranslation('marketing');
|
||||
|
||||
useEffect(() => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const packageId = urlParams.get('package_id');
|
||||
if (packageId) {
|
||||
const id = parseInt(packageId);
|
||||
const pkg = [...endcustomerPackages, ...resellerPackages].find(p => p.id === id);
|
||||
if (pkg) {
|
||||
setSelectedPackage(pkg);
|
||||
setOpen(true);
|
||||
setCurrentStep('step1');
|
||||
}
|
||||
}
|
||||
}, [endcustomerPackages, resellerPackages]);
|
||||
|
||||
const testimonials = [
|
||||
{ name: 'Anna M.', text: t('packages.testimonials.anna'), rating: 5 },
|
||||
{ name: 'Max B.', text: t('packages.testimonials.max'), rating: 5 },
|
||||
{ name: 'Lisa K.', text: t('packages.testimonials.lisa'), rating: 5 },
|
||||
{ name: t('common.testimonials.anna.name'), text: t('packages.testimonials.anna'), rating: 5 },
|
||||
{ name: t('common.testimonials.max.name'), text: t('packages.testimonials.max'), rating: 5 },
|
||||
{ name: t('common.testimonials.lisa.name'), text: t('packages.testimonials.lisa'), rating: 5 },
|
||||
];
|
||||
|
||||
const allPackages = [...endcustomerPackages, ...resellerPackages];
|
||||
@@ -190,7 +204,7 @@ const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackag
|
||||
{endcustomerPackages.map((pkg) => (
|
||||
<div key={pkg.id} className="text-center">
|
||||
<p className="font-bold">{pkg.name}</p>
|
||||
<p>{pkg.price === 0 ? t('free') : `${pkg.price} ${t('currency.euro')}`}</p>
|
||||
<p>{pkg.price === 0 ? t('packages.free') : `${pkg.price} ${t('common.currency.euro')}`}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -203,7 +217,7 @@ const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackag
|
||||
{endcustomerPackages.map((pkg) => (
|
||||
<div key={pkg.id} className="text-center">
|
||||
<p className="font-bold">{pkg.name}</p>
|
||||
<p>{pkg.limits?.max_photos || t('unlimited')}</p>
|
||||
<p>{pkg.limits?.max_photos || t('common.unlimited')}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -216,7 +230,7 @@ const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackag
|
||||
{endcustomerPackages.map((pkg) => (
|
||||
<div key={pkg.id} className="text-center">
|
||||
<p className="font-bold">{pkg.name}</p>
|
||||
<p>{pkg.limits?.max_guests || t('unlimited')}</p>
|
||||
<p>{pkg.limits?.max_guests || t('common.unlimited')}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -229,7 +243,7 @@ const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackag
|
||||
{endcustomerPackages.map((pkg) => (
|
||||
<div key={pkg.id} className="text-center">
|
||||
<p className="font-bold">{pkg.name}</p>
|
||||
<p>{pkg.limits?.gallery_days || t('unlimited')}</p>
|
||||
<p>{pkg.limits?.gallery_days || t('common.unlimited')}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -267,7 +281,7 @@ const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackag
|
||||
<TableCell className="font-semibold">{t('packages.price')}</TableCell>
|
||||
{endcustomerPackages.map((pkg) => (
|
||||
<TableCell key={pkg.id} className="text-center">
|
||||
{pkg.price === 0 ? t('free') : `${pkg.price} ${t('currency.euro')}`}
|
||||
{pkg.price === 0 ? t('packages.free') : `${pkg.price} ${t('common.currency.euro')}`}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
@@ -275,7 +289,7 @@ const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackag
|
||||
<TableCell className="font-semibold">{t('packages.max_photos_label')} {getFeatureIcon('max_photos')}</TableCell>
|
||||
{endcustomerPackages.map((pkg) => (
|
||||
<TableCell key={pkg.id} className="text-center">
|
||||
{pkg.limits?.max_photos || t('unlimited')}
|
||||
{pkg.limits?.max_photos || t('common.unlimited')}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
@@ -283,7 +297,7 @@ const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackag
|
||||
<TableCell className="font-semibold">{t('packages.max_guests_label')} {getFeatureIcon('max_guests')}</TableCell>
|
||||
{endcustomerPackages.map((pkg) => (
|
||||
<TableCell key={pkg.id} className="text-center">
|
||||
{pkg.limits?.max_guests || t('unlimited')}
|
||||
{pkg.limits?.max_guests || t('common.unlimited')}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
@@ -291,7 +305,7 @@ const Packages: React.FC<PackagesProps> = ({ endcustomerPackages, resellerPackag
|
||||
<TableCell className="font-semibold">{t('packages.gallery_days_label')} {getFeatureIcon('gallery_days')}</TableCell>
|
||||
{endcustomerPackages.map((pkg) => (
|
||||
<TableCell key={pkg.id} className="text-center">
|
||||
{pkg.limits?.gallery_days || t('unlimited')}
|
||||
{pkg.limits?.gallery_days || t('common.unlimited')}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
|
||||
Reference in New Issue
Block a user