Complete translation implementation for admin interface
- Fixed all hardcoded strings in admin components to use t() function - Updated BrandingPage.tsx to use translations for watermark settings - Updated EventsListPage.tsx to use translations for status labels - Added missing translation keys to both en.json and de.json - Fixed translations for: - System settings (general, storage, categories tabs) - Branding page (watermark settings, positions, opacity) - Email configuration and templates - Event list view (status labels, filters, actions) - Event detail view (all sections properly translated) - Added comprehensive German translations for all new keys - Ensured consistent translation usage across all admin pages 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
This commit is contained in:
@@ -19,8 +19,10 @@ import { BulkArchiveModal } from '../../components/admin';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { eventsService } from '../../services/events.service';
|
||||
import type { Event } from '../../types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const EventsListPage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
@@ -46,10 +48,10 @@ export const EventsListPage: React.FC = () => {
|
||||
mutationFn: eventsService.archiveEvent,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-events'] });
|
||||
toast.success('Event archived successfully');
|
||||
toast.success(t('toast.eventArchived'));
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Failed to archive event');
|
||||
toast.error(t('toast.saveError'));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -58,10 +60,10 @@ export const EventsListPage: React.FC = () => {
|
||||
mutationFn: eventsService.deleteEvent,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-events'] });
|
||||
toast.success('Event deleted successfully');
|
||||
toast.success(t('toast.deleteSuccess'));
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Failed to delete event');
|
||||
toast.error(t('toast.deleteError'));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -74,13 +76,13 @@ export const EventsListPage: React.FC = () => {
|
||||
setShowBulkArchiveModal(false);
|
||||
|
||||
if (data.results.failed.length === 0) {
|
||||
toast.success(`Successfully archived ${data.results.successful.length} events`);
|
||||
toast.success(t('events.bulkArchiveSuccess', { count: data.results.successful.length }));
|
||||
} else {
|
||||
toast.warning(`Archived ${data.results.successful.length} events, ${data.results.failed.length} failed`);
|
||||
toast.warning(t('events.bulkArchivePartial', { success: data.results.successful.length, failed: data.results.failed.length }));
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Failed to archive events');
|
||||
toast.error(t('toast.saveError'));
|
||||
},
|
||||
});
|
||||
|
||||
@@ -140,14 +142,14 @@ export const EventsListPage: React.FC = () => {
|
||||
};
|
||||
|
||||
const getEventStatus = (event: Event) => {
|
||||
if (event.is_archived) return { label: 'Archived', color: 'text-neutral-500 bg-neutral-100' };
|
||||
if (!event.is_active) return { label: 'Inactive', color: 'text-red-600 bg-red-100' };
|
||||
if (event.is_archived) return { label: t('events.archived'), color: 'text-neutral-500 bg-neutral-100' };
|
||||
if (!event.is_active) return { label: t('events.inactive'), color: 'text-red-600 bg-red-100' };
|
||||
|
||||
const days = event.expires_at ? differenceInDays(parseISO(event.expires_at), new Date()) : 0;
|
||||
if (days <= 0) return { label: 'Expired', color: 'text-red-600 bg-red-100' };
|
||||
if (days <= 7) return { label: `${days}d left`, color: 'text-orange-600 bg-orange-100' };
|
||||
if (days <= 0) return { label: t('events.expired'), color: 'text-red-600 bg-red-100' };
|
||||
if (days <= 7) return { label: t('events.daysLeft', { count: days }), color: 'text-orange-600 bg-orange-100' };
|
||||
|
||||
return { label: 'Active', color: 'text-green-600 bg-green-100' };
|
||||
return { label: t('events.active'), color: 'text-green-600 bg-green-100' };
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
@@ -155,8 +157,8 @@ export const EventsListPage: React.FC = () => {
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-neutral-900">Events</h1>
|
||||
<p className="text-neutral-600 mt-1">Manage your photo galleries and archives</p>
|
||||
<h1 className="text-2xl font-bold text-neutral-900">{t('events.title')}</h1>
|
||||
<p className="text-neutral-600 mt-1">{t('events.subtitle')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<SkeletonTable rows={5} />
|
||||
@@ -167,9 +169,9 @@ export const EventsListPage: React.FC = () => {
|
||||
if (error) {
|
||||
return (
|
||||
<div className="text-center py-12">
|
||||
<p className="text-red-600">Failed to load events</p>
|
||||
<p className="text-red-600">{t('events.failedToLoadEvents')}</p>
|
||||
<Button onClick={() => window.location.reload()} className="mt-4">
|
||||
Try Again
|
||||
{t('events.tryAgain')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
@@ -181,15 +183,15 @@ export const EventsListPage: React.FC = () => {
|
||||
{/* Page Header */}
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-neutral-900">Events</h1>
|
||||
<p className="text-neutral-600 mt-1">Manage your photo galleries and events</p>
|
||||
<h1 className="text-2xl font-bold text-neutral-900">{t('events.title')}</h1>
|
||||
<p className="text-neutral-600 mt-1">{t('events.subtitle')}</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
leftIcon={<Plus className="w-5 h-5" />}
|
||||
onClick={() => navigate('/admin/events/new')}
|
||||
>
|
||||
Create Event
|
||||
{t('events.createEvent')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -200,7 +202,7 @@ export const EventsListPage: React.FC = () => {
|
||||
<div className="flex-1">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search events..."
|
||||
placeholder={t('events.searchEventsPlaceholder')}
|
||||
leftIcon={<Search className="w-5 h-5 text-neutral-400" />}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
@@ -217,14 +219,14 @@ export const EventsListPage: React.FC = () => {
|
||||
setSearchParams(searchParams);
|
||||
}}
|
||||
>
|
||||
All ({data?.events.length || 0})
|
||||
{t('events.all')} ({data?.events.length || 0})
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === 'active' ? 'primary' : 'outline'}
|
||||
size="md"
|
||||
onClick={() => setSearchParams({ filter: 'active' })}
|
||||
>
|
||||
Active
|
||||
{t('events.active')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={isExpiringFilter ? 'primary' : 'outline'}
|
||||
@@ -232,7 +234,7 @@ export const EventsListPage: React.FC = () => {
|
||||
onClick={() => setSearchParams({ filter: 'expiring' })}
|
||||
leftIcon={<AlertTriangle className="w-4 h-4" />}
|
||||
>
|
||||
Expiring
|
||||
{t('events.expiring')}
|
||||
</Button>
|
||||
<Button
|
||||
variant={statusFilter === 'archived' ? 'primary' : 'outline'}
|
||||
@@ -240,7 +242,7 @@ export const EventsListPage: React.FC = () => {
|
||||
onClick={() => setSearchParams({ filter: 'archived' })}
|
||||
leftIcon={<Archive className="w-4 h-4" />}
|
||||
>
|
||||
Archived
|
||||
{t('events.archived')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -249,18 +251,18 @@ export const EventsListPage: React.FC = () => {
|
||||
{selectedEvents.length > 0 && (
|
||||
<div className="mt-4 p-3 bg-primary-50 rounded-lg flex items-center justify-between">
|
||||
<span className="text-sm text-primary-900">
|
||||
{selectedEvents.length} event{selectedEvents.length > 1 ? 's' : ''} selected
|
||||
{t('events.eventsSelected', { count: selectedEvents.length })}
|
||||
</span>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" size="sm" onClick={() => setSelectedEvents([])}>
|
||||
Clear
|
||||
{t('events.clear')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowBulkArchiveModal(true)}
|
||||
>
|
||||
Archive Selected
|
||||
{t('events.archiveSelected')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -282,22 +284,22 @@ export const EventsListPage: React.FC = () => {
|
||||
/>
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-neutral-500 uppercase tracking-wider">
|
||||
Event
|
||||
{t('events.event')}
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-neutral-500 uppercase tracking-wider">
|
||||
Type
|
||||
{t('events.type')}
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-neutral-500 uppercase tracking-wider">
|
||||
Date
|
||||
{t('events.date')}
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-neutral-500 uppercase tracking-wider">
|
||||
Status
|
||||
{t('events.status')}
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-neutral-500 uppercase tracking-wider">
|
||||
Expires
|
||||
{t('events.expires')}
|
||||
</th>
|
||||
<th className="px-6 py-3 text-right text-xs font-medium text-neutral-500 uppercase tracking-wider">
|
||||
Actions
|
||||
{t('events.actions')}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -305,7 +307,7 @@ export const EventsListPage: React.FC = () => {
|
||||
{filteredEvents.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={7} className="px-6 py-12 text-center text-neutral-500">
|
||||
No events found
|
||||
{t('events.noEventsFound')}
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
@@ -362,7 +364,7 @@ export const EventsListPage: React.FC = () => {
|
||||
className="w-full text-left px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 flex items-center gap-2"
|
||||
>
|
||||
<Edit className="w-4 h-4" />
|
||||
View Details
|
||||
{t('events.viewDetails')}
|
||||
</button>
|
||||
{event.share_link ? (
|
||||
<a
|
||||
@@ -373,7 +375,7 @@ export const EventsListPage: React.FC = () => {
|
||||
onClick={() => setActiveDropdown(null)}
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
View Gallery
|
||||
{t('events.viewGallery')}
|
||||
</a>
|
||||
) : null}
|
||||
{!event.is_archived ? (
|
||||
@@ -385,24 +387,24 @@ export const EventsListPage: React.FC = () => {
|
||||
className="w-full text-left px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 flex items-center gap-2"
|
||||
>
|
||||
<Archive className="w-4 h-4" />
|
||||
Archive Event
|
||||
{t('events.archiveEventAction')}
|
||||
</button>
|
||||
) : null}
|
||||
{event.is_archived ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
toast.info('Download archive coming soon');
|
||||
toast.info(t('events.downloadArchiveSoon'));
|
||||
setActiveDropdown(null);
|
||||
}}
|
||||
className="w-full text-left px-4 py-2 text-sm text-neutral-700 hover:bg-neutral-100 flex items-center gap-2"
|
||||
>
|
||||
<Download className="w-4 h-4" />
|
||||
Download Archive
|
||||
{t('events.downloadArchiveAction')}
|
||||
</button>
|
||||
) : null}
|
||||
<button
|
||||
onClick={() => {
|
||||
if (confirm('Are you sure you want to delete this event?')) {
|
||||
if (confirm(t('events.deleteEventConfirm'))) {
|
||||
deleteMutation.mutate(event.id);
|
||||
setActiveDropdown(null);
|
||||
}
|
||||
@@ -410,7 +412,7 @@ export const EventsListPage: React.FC = () => {
|
||||
className="w-full text-left px-4 py-2 text-sm text-red-600 hover:bg-red-50 flex items-center gap-2"
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
Delete Event
|
||||
{t('events.deleteEvent')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user