Fix translation issues in event creation and detail pages

- Add missing translation keys for event creation form
- Replace all hardcoded strings with translation keys in CreateEventPage
- Add validation error message translations
- Add event types and color theme translations
- Add errors and validation sections to translation files
- Update German translations with corresponding keys

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-09 09:25:15 +02:00
parent 8231f2b60d
commit 0a203d16cf
3 changed files with 153 additions and 58 deletions
+58 -1
View File
@@ -28,7 +28,8 @@
"refresh": "Aktualisieren",
"preview": "Vorschau",
"processing": "Wird verarbeitet...",
"upload": "Hochladen"
"upload": "Hochladen",
"days": "Tage"
},
"upload": {
"photoCategory": "Fotokategorie",
@@ -245,6 +246,30 @@
"days365": "1 Jahr",
"createNewEvent": "Neue Veranstaltung erstellen",
"setupNewGallery": "Richten Sie eine neue Fotogalerie für Ihre Veranstaltung ein",
"createNewEventSubtitle": "Richten Sie eine neue Fotogalerie für Ihre Veranstaltung ein",
"eventNamePlaceholder": "z.B. Max & Maria's Hochzeit",
"welcomeMessageOptional": "Willkommensnachricht (Optional)",
"welcomeMessagePlaceholder": "Willkommen zu unserem besonderen Tag! Laden Sie diese Erinnerungen gerne herunter und teilen Sie sie...",
"hostEmailPlaceholder": "gastgeber@beispiel.de",
"adminEmailPlaceholder": "admin@beispiel.de",
"securityAndAccess": "Sicherheit & Zugriff",
"enterPassword": "Passwort eingeben",
"confirmPasswordPlaceholder": "Passwort bestätigen",
"galleryExpiresOn": "Galerie läuft ab am {{date}}",
"guestsWillReceiveWarning": "Gäste erhalten 7 Tage vor Ablauf eine Warn-E-Mail.",
"types": {
"wedding": "Hochzeit",
"birthday": "Geburtstag",
"corporate": "Geschäftlich",
"other": "Andere"
},
"themes": {
"default": "Standard",
"oceanBlue": "Ozeanblau",
"royalPurple": "Königliches Lila",
"roseGold": "Roségold",
"sunsetAmber": "Sonnenuntergang Bernstein"
},
"adminNotificationEmail": "Admin-Benachrichtigungs-E-Mail",
"inactive": "Inaktiv",
"expired": "Abgelaufen",
@@ -602,6 +627,38 @@
"datenschutz": "Datenschutzerklärung",
"pageUpdated": "Seite erfolgreich aktualisiert"
},
"errors": {
"notFound": "Nicht gefunden",
"galleryNotFound": "Galerie nicht gefunden",
"galleryNotFoundMessage": "Diese Galerie existiert nicht oder wurde entfernt.",
"galleryArchived": "Galerie archiviert",
"galleryArchivedMessage": "Diese Galerie wurde archiviert und ist nicht mehr zugänglich. Bitte kontaktieren Sie den Veranstalter, wenn Sie Zugriff auf diese Fotos benötigen.",
"unauthorized": "Nicht autorisiert",
"forbidden": "Verboten",
"serverError": "Serverfehler",
"somethingWentWrong": "Etwas ist schiefgelaufen",
"tryAgainLater": "Bitte versuchen Sie es später erneut",
"refreshPage": "Seite aktualisieren",
"oopsSomethingWentWrong": "Hoppla! Etwas ist schiefgelaufen",
"unexpectedError": "Ein unerwarteter Fehler ist aufgetreten. Keine Sorge, Ihre Daten sind sicher.",
"goToHomepage": "Zur Startseite",
"errorDetails": "Fehlerdetails",
"requiredFields": "Bitte füllen Sie alle Pflichtfelder aus",
"enterTestEmail": "Bitte geben Sie eine Test-E-Mail-Adresse ein",
"failedToCreateEvent": "Veranstaltung konnte nicht erstellt werden",
"networkError": "Netzwerkfehler. Bitte überprüfen Sie Ihre Verbindung und versuchen Sie es erneut.",
"sessionExpired": "Sitzung abgelaufen. Bitte melden Sie sich erneut an."
},
"validation": {
"eventNameRequired": "Veranstaltungsname ist erforderlich",
"hostEmailRequired": "Gastgeber-E-Mail ist erforderlich",
"adminEmailRequired": "Admin-E-Mail ist erforderlich",
"invalidEmailFormat": "Ungültiges E-Mail-Format",
"passwordRequired": "Passwort ist erforderlich",
"passwordMinLength": "Passwort muss mindestens 6 Zeichen lang sein",
"passwordsDoNotMatch": "Passwörter stimmen nicht überein",
"expirationRange": "Ablauf muss zwischen 1 und 365 Tagen liegen"
},
"maintenance": {
"title": "Systemwartung",
"message": "Wir führen derzeit geplante Wartungsarbeiten durch, um unseren Service zu verbessern. Wir sind in Kürze wieder online.",
+40 -2
View File
@@ -28,7 +28,8 @@
"refresh": "Refresh",
"preview": "Preview",
"processing": "Processing...",
"upload": "Upload"
"upload": "Upload",
"days": "days"
},
"upload": {
"photoCategory": "Photo Category",
@@ -165,6 +166,30 @@
"createEvent": "Create Event",
"createNewEvent": "Create New Event",
"setupNewGallery": "Set up a new photo gallery for your event",
"createNewEventSubtitle": "Set up a new photo gallery for your event",
"eventNamePlaceholder": "e.g., John & Jane's Wedding",
"welcomeMessageOptional": "Welcome Message (Optional)",
"welcomeMessagePlaceholder": "Welcome to our special day! Feel free to download and share these memories...",
"hostEmailPlaceholder": "host@example.com",
"adminEmailPlaceholder": "admin@example.com",
"securityAndAccess": "Security & Access",
"enterPassword": "Enter password",
"confirmPasswordPlaceholder": "Confirm password",
"galleryExpiresOn": "Gallery will expire on {{date}}",
"guestsWillReceiveWarning": "Guests will receive a warning email 7 days before expiration.",
"types": {
"wedding": "Wedding",
"birthday": "Birthday",
"corporate": "Corporate",
"other": "Other"
},
"themes": {
"default": "Default",
"oceanBlue": "Ocean Blue",
"royalPurple": "Royal Purple",
"roseGold": "Rose Gold",
"sunsetAmber": "Sunset Amber"
},
"eventDetails": "Event Details",
"eventName": "Event Name",
"eventType": "Event Type",
@@ -548,7 +573,20 @@
"goToHomepage": "Go to Homepage",
"errorDetails": "Error Details",
"requiredFields": "Please fill in all required fields",
"enterTestEmail": "Please enter a test email address"
"enterTestEmail": "Please enter a test email address",
"failedToCreateEvent": "Failed to create event",
"networkError": "Network error. Please check your connection and try again.",
"sessionExpired": "Session expired. Please login again."
},
"validation": {
"eventNameRequired": "Event name is required",
"hostEmailRequired": "Host email is required",
"adminEmailRequired": "Admin email is required",
"invalidEmailFormat": "Invalid email format",
"passwordRequired": "Password is required",
"passwordMinLength": "Password must be at least 6 characters",
"passwordsDoNotMatch": "Passwords do not match",
"expirationRange": "Expiration must be between 1 and 365 days"
},
"legal": {
"impressum": "Legal Notice",
+55 -55
View File
@@ -34,16 +34,16 @@ interface FormData {
}
const EVENT_TYPES = [
{ value: 'wedding', label: 'Wedding', emoji: '💒' },
{ value: 'birthday', label: 'Birthday', emoji: '🎂' },
{ value: 'corporate', label: 'Corporate', emoji: '🏢' },
{ value: 'other', label: 'Other', emoji: '📸' },
{ value: 'wedding', labelKey: 'events.types.wedding', emoji: '💒' },
{ value: 'birthday', labelKey: 'events.types.birthday', emoji: '🎂' },
{ value: 'corporate', labelKey: 'events.types.corporate', emoji: '🏢' },
{ value: 'other', labelKey: 'events.types.other', emoji: '📸' },
];
const COLOR_THEMES = [
{
value: 'default',
label: 'Default (Green)',
labelKey: 'events.themes.default',
color: 'bg-primary-600',
theme: {
primaryColor: '#5C8762',
@@ -55,7 +55,7 @@ const COLOR_THEMES = [
},
{
value: 'blue',
label: 'Ocean Blue',
labelKey: 'events.themes.oceanBlue',
color: 'bg-blue-600',
theme: {
primaryColor: '#2563eb',
@@ -67,7 +67,7 @@ const COLOR_THEMES = [
},
{
value: 'purple',
label: 'Royal Purple',
labelKey: 'events.themes.royalPurple',
color: 'bg-purple-600',
theme: {
primaryColor: '#9333ea',
@@ -79,7 +79,7 @@ const COLOR_THEMES = [
},
{
value: 'rose',
label: 'Rose Gold',
labelKey: 'events.themes.roseGold',
color: 'bg-rose-600',
theme: {
primaryColor: '#e11d48',
@@ -91,7 +91,7 @@ const COLOR_THEMES = [
},
{
value: 'amber',
label: 'Sunset Amber',
labelKey: 'events.themes.sunsetAmber',
color: 'bg-amber-600',
theme: {
primaryColor: '#d97706',
@@ -142,7 +142,7 @@ export const CreateEventPage: React.FC = () => {
mutationFn: eventsService.createEvent,
onSuccess: (data) => {
if (isMountedRef.current) {
toast.success('Event created successfully!');
toast.success(t('toast.eventCreated'));
// Add a small delay to ensure navigation works properly
setTimeout(() => {
if (isMountedRef.current && data?.id) {
@@ -157,7 +157,7 @@ export const CreateEventPage: React.FC = () => {
if (!isMountedRef.current) return;
if (error.code === 'ERR_NETWORK' || error.code === 'ERR_CONNECTION_RESET') {
toast.error('Network error. Please check your connection and try again.');
toast.error(t('errors.networkError'));
} else if (error.response?.data?.errors) {
const newErrors: Record<string, string> = {};
error.response.data.errors.forEach((err: any) => {
@@ -165,10 +165,10 @@ export const CreateEventPage: React.FC = () => {
});
setErrors(newErrors);
} else if (error.response?.status === 401) {
toast.error('Session expired. Please login again.');
toast.error(t('errors.sessionExpired'));
navigate('/admin/login');
} else {
toast.error(error.response?.data?.error || 'Failed to create event');
toast.error(error.response?.data?.error || t('errors.failedToCreateEvent'));
}
},
});
@@ -177,33 +177,33 @@ export const CreateEventPage: React.FC = () => {
const newErrors: Partial<Record<keyof FormData, string>> = {};
if (!formData.event_name.trim()) {
newErrors.event_name = 'Event name is required';
newErrors.event_name = t('validation.eventNameRequired');
}
if (!formData.host_email) {
newErrors.host_email = 'Host email is required';
newErrors.host_email = t('validation.hostEmailRequired');
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.host_email)) {
newErrors.host_email = 'Invalid email format';
newErrors.host_email = t('validation.invalidEmailFormat');
}
if (!formData.admin_email) {
newErrors.admin_email = 'Admin email is required';
newErrors.admin_email = t('validation.adminEmailRequired');
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.admin_email)) {
newErrors.admin_email = 'Invalid email format';
newErrors.admin_email = t('validation.invalidEmailFormat');
}
if (!formData.password) {
newErrors.password = 'Password is required';
newErrors.password = t('validation.passwordRequired');
} else if (formData.password.length < 6) {
newErrors.password = 'Password must be at least 6 characters';
newErrors.password = t('validation.passwordMinLength');
}
if (formData.password !== formData.confirm_password) {
newErrors.confirm_password = 'Passwords do not match';
newErrors.confirm_password = t('validation.passwordsDoNotMatch');
}
if (formData.expires_in_days < 1 || formData.expires_in_days > 365) {
newErrors.expires_in_days = 'Expiration must be between 1 and 365 days';
newErrors.expires_in_days = t('validation.expirationRange');
}
setErrors(newErrors);
@@ -257,23 +257,23 @@ export const CreateEventPage: React.FC = () => {
onClick={() => navigate('/admin/events')}
className="mb-4"
>
Back to Events
{t('events.backToEvents')}
</Button>
<h1 className="text-2xl font-bold text-neutral-900">Create New Event</h1>
<p className="text-neutral-600 mt-1">Set up a new photo gallery for your event</p>
<h1 className="text-2xl font-bold text-neutral-900">{t('events.createNewEvent')}</h1>
<p className="text-neutral-600 mt-1">{t('events.createNewEventSubtitle')}</p>
</div>
<form onSubmit={handleSubmit}>
{/* Event Details */}
<Card padding="md" className="mb-6">
<h2 className="text-lg font-semibold text-neutral-900 mb-4">Event Details</h2>
<h2 className="text-lg font-semibold text-neutral-900 mb-4">{t('events.eventDetails')}</h2>
<div className="space-y-4">
{/* Event Type */}
<div>
<label className="block text-sm font-medium text-neutral-700 mb-1">
Event Type
{t('events.eventType')}
</label>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
{EVENT_TYPES.map(type => (
@@ -288,7 +288,7 @@ export const CreateEventPage: React.FC = () => {
}`}
>
<div className="text-2xl mb-1">{type.emoji}</div>
<div className="text-sm font-medium">{type.label}</div>
<div className="text-sm font-medium">{t(type.labelKey)}</div>
</button>
))}
</div>
@@ -297,7 +297,7 @@ export const CreateEventPage: React.FC = () => {
{/* Event Name */}
<div>
<label htmlFor="event_name" className="block text-sm font-medium text-neutral-700 mb-1">
Event Name
{t('events.eventName')}
</label>
<Input
id="event_name"
@@ -305,7 +305,7 @@ export const CreateEventPage: React.FC = () => {
value={formData.event_name}
onChange={handleInputChange('event_name')}
error={errors.event_name}
placeholder="e.g., Smith-Jones Wedding"
placeholder={t('events.eventNamePlaceholder')}
leftIcon={<Calendar className="w-5 h-5 text-neutral-400" />}
/>
</div>
@@ -313,7 +313,7 @@ export const CreateEventPage: React.FC = () => {
{/* Event Date */}
<div>
<label htmlFor="event_date" className="block text-sm font-medium text-neutral-700 mb-1">
Event Date
{t('events.eventDate')}
</label>
<Input
id="event_date"
@@ -327,13 +327,13 @@ export const CreateEventPage: React.FC = () => {
{/* Welcome Message */}
<div>
<label htmlFor="welcome_message" className="block text-sm font-medium text-neutral-700 mb-1">
Welcome Message (Optional)
{t('events.welcomeMessageOptional')}
</label>
<textarea
id="welcome_message"
value={formData.welcome_message}
onChange={handleInputChange('welcome_message')}
placeholder="A personalized message for your guests..."
placeholder={t('events.welcomeMessagePlaceholder')}
className="w-full px-3 py-2 border border-neutral-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
rows={3}
/>
@@ -343,13 +343,13 @@ export const CreateEventPage: React.FC = () => {
{/* Contact Information */}
<Card padding="md" className="mb-6">
<h2 className="text-lg font-semibold text-neutral-900 mb-4">Contact Information</h2>
<h2 className="text-lg font-semibold text-neutral-900 mb-4">{t('events.contactInformation')}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Host Email */}
<div>
<label htmlFor="host_email" className="block text-sm font-medium text-neutral-700 mb-1">
Host Email
{t('events.hostEmail')}
</label>
<Input
id="host_email"
@@ -357,18 +357,18 @@ export const CreateEventPage: React.FC = () => {
value={formData.host_email}
onChange={handleInputChange('host_email')}
error={errors.host_email}
placeholder="host@example.com"
placeholder={t('events.hostEmailPlaceholder')}
leftIcon={<Mail className="w-5 h-5 text-neutral-400" />}
/>
<p className="text-xs text-neutral-500 mt-1">
Will receive gallery creation and expiration notifications
{t('events.hostEmailHelp')}
</p>
</div>
{/* Admin Email */}
<div>
<label htmlFor="admin_email" className="block text-sm font-medium text-neutral-700 mb-1">
Admin Notification Email
{t('events.adminNotificationEmail')}
</label>
<Input
id="admin_email"
@@ -376,11 +376,11 @@ export const CreateEventPage: React.FC = () => {
value={formData.admin_email}
onChange={handleInputChange('admin_email')}
error={errors.admin_email}
placeholder="admin@example.com"
placeholder={t('events.adminEmailPlaceholder')}
leftIcon={<Mail className="w-5 h-5 text-neutral-400" />}
/>
<p className="text-xs text-neutral-500 mt-1">
Will receive system notifications and archive confirmations
{t('events.adminEmailHelp')}
</p>
</div>
</div>
@@ -388,13 +388,13 @@ export const CreateEventPage: React.FC = () => {
{/* Security & Access */}
<Card padding="md" className="mb-6">
<h2 className="text-lg font-semibold text-neutral-900 mb-4">Security & Access</h2>
<h2 className="text-lg font-semibold text-neutral-900 mb-4">{t('events.securityAndAccess')}</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Password */}
<div>
<label htmlFor="password" className="block text-sm font-medium text-neutral-700 mb-1">
Gallery Password
{t('events.galleryPassword')}
</label>
<div className="relative">
<Input
@@ -403,7 +403,7 @@ export const CreateEventPage: React.FC = () => {
value={formData.password}
onChange={handleInputChange('password')}
error={errors.password}
placeholder="Enter password"
placeholder={t('events.enterPassword')}
leftIcon={<Lock className="w-5 h-5 text-neutral-400" />}
/>
</div>
@@ -412,7 +412,7 @@ export const CreateEventPage: React.FC = () => {
{/* Confirm Password */}
<div>
<label htmlFor="confirm_password" className="block text-sm font-medium text-neutral-700 mb-1">
Confirm Password
{t('events.confirmPassword')}
</label>
<Input
id="confirm_password"
@@ -420,7 +420,7 @@ export const CreateEventPage: React.FC = () => {
value={formData.confirm_password}
onChange={handleInputChange('confirm_password')}
error={errors.confirm_password}
placeholder="Confirm password"
placeholder={t('events.confirmPasswordPlaceholder')}
leftIcon={<Lock className="w-5 h-5 text-neutral-400" />}
/>
</div>
@@ -434,20 +434,20 @@ export const CreateEventPage: React.FC = () => {
onChange={(e) => setShowPassword(e.target.checked)}
className="w-4 h-4 text-primary-600 border-neutral-300 rounded focus:ring-primary-500"
/>
<span className="ml-2 text-sm text-neutral-700">Show passwords</span>
<span className="ml-2 text-sm text-neutral-700">{t('events.showPasswords')}</span>
</label>
</div>
</Card>
{/* Gallery Settings */}
<Card padding="md" className="mb-6">
<h2 className="text-lg font-semibold text-neutral-900 mb-4">Gallery Settings</h2>
<h2 className="text-lg font-semibold text-neutral-900 mb-4">{t('events.gallerySettings')}</h2>
<div className="space-y-4">
{/* Color Theme */}
<div>
<label className="block text-sm font-medium text-neutral-700 mb-1">
Color Theme
{t('events.colorTheme')}
</label>
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
{COLOR_THEMES.map(theme => (
@@ -462,7 +462,7 @@ export const CreateEventPage: React.FC = () => {
}`}
>
<div className={`w-full h-8 ${theme.color} rounded mb-2`} />
<div className="text-xs font-medium">{theme.label}</div>
<div className="text-xs font-medium">{t(theme.labelKey)}</div>
</button>
))}
</div>
@@ -471,7 +471,7 @@ export const CreateEventPage: React.FC = () => {
{/* Expiration */}
<div>
<label htmlFor="expires_in_days" className="block text-sm font-medium text-neutral-700 mb-1">
Gallery Expires In
{t('events.galleryExpiresIn')}
</label>
<div className="flex items-center gap-4">
<Input
@@ -485,13 +485,13 @@ export const CreateEventPage: React.FC = () => {
className="w-32"
leftIcon={<Clock className="w-5 h-5 text-neutral-400" />}
/>
<span className="text-sm text-neutral-700">days</span>
<span className="text-sm text-neutral-700">{t('common.days')}</span>
</div>
<div className="mt-2 p-3 bg-blue-50 rounded-lg flex items-start gap-2">
<Info className="w-5 h-5 text-blue-600 flex-shrink-0" />
<div className="text-sm text-blue-800">
<p>Gallery will expire on {format(addDays(new Date(), formData.expires_in_days), 'MMMM d, yyyy')}</p>
<p className="mt-1">Guests will receive a warning email 7 days before expiration.</p>
<p>{t('events.galleryExpiresOn', { date: format(addDays(new Date(), formData.expires_in_days), 'MMMM d, yyyy') })}</p>
<p className="mt-1">{t('events.guestsWillReceiveWarning')}</p>
</div>
</div>
</div>
@@ -565,14 +565,14 @@ export const CreateEventPage: React.FC = () => {
variant="outline"
onClick={() => navigate('/admin/events')}
>
Cancel
{t('common.cancel')}
</Button>
<Button
type="submit"
variant="primary"
isLoading={createMutation.isPending}
>
Create Event
{t('events.createEvent')}
</Button>
</div>
</form>