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 <noreply@anthropic.com>
This commit is contained in:
2025-07-08 17:41:02 +02:00
parent d594d00227
commit 1bc9b547c7
11 changed files with 491 additions and 356 deletions
+32 -30
View File
@@ -16,6 +16,7 @@ import { format, parseISO } from 'date-fns';
import { Button, Card, Loading } from '../../components/common';
import { useQuery } from '@tanstack/react-query';
import { adminService } from '../../services/admin.service';
import { useTranslation } from 'react-i18next';
// Map API response to component format
interface ComponentAnalyticsData {
@@ -47,6 +48,7 @@ interface ComponentAnalyticsData {
}
export const AnalyticsPage: React.FC = () => {
const { t } = useTranslation();
const [dateRange, setDateRange] = useState<'7d' | '30d' | '90d'>('7d');
const [isEmbedMode, setIsEmbedMode] = useState(false);
@@ -158,7 +160,7 @@ export const AnalyticsPage: React.FC = () => {
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<Loading size="lg" text="Loading analytics..." />
<Loading size="lg" text={t('analytics.loadingAnalytics')} />
</div>
);
}
@@ -169,15 +171,15 @@ export const AnalyticsPage: React.FC = () => {
<div>
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-2xl font-bold text-neutral-900">Analytics Dashboard</h1>
<p className="text-neutral-600 mt-1">Detailed analytics powered by Umami</p>
<h1 className="text-2xl font-bold text-neutral-900">{t('analytics.title')}</h1>
<p className="text-neutral-600 mt-1">{t('analytics.detailedSubtitle')}</p>
</div>
<Button
variant="outline"
onClick={() => setIsEmbedMode(false)}
leftIcon={<BarChart3 className="w-4 h-4" />}
>
Show Summary View
{t('analytics.showSummaryView')}
</Button>
</div>
@@ -197,8 +199,8 @@ export const AnalyticsPage: React.FC = () => {
{/* Page Header */}
<div className="flex justify-between items-center mb-6">
<div>
<h1 className="text-2xl font-bold text-neutral-900">Analytics Dashboard</h1>
<p className="text-neutral-600 mt-1">Track gallery performance and visitor engagement</p>
<h1 className="text-2xl font-bold text-neutral-900">{t('analytics.title')}</h1>
<p className="text-neutral-600 mt-1">{t('analytics.subtitle')}</p>
</div>
<div className="flex items-center gap-3">
{umamiShareUrl && (
@@ -207,7 +209,7 @@ export const AnalyticsPage: React.FC = () => {
onClick={() => setIsEmbedMode(true)}
leftIcon={<Activity className="w-4 h-4" />}
>
Full Dashboard
{t('analytics.fullDashboard')}
</Button>
)}
<Button
@@ -215,16 +217,16 @@ export const AnalyticsPage: React.FC = () => {
onClick={() => refetch()}
leftIcon={<RefreshCw className="w-4 h-4" />}
>
Refresh
{t('analytics.refresh')}
</Button>
<select
value={dateRange}
onChange={(e) => setDateRange(e.target.value as any)}
className="px-4 py-2 border border-neutral-300 rounded-lg focus:ring-2 focus:ring-primary-500"
>
<option value="7d">Last 7 days</option>
<option value="30d">Last 30 days</option>
<option value="90d">Last 90 days</option>
<option value="7d">{t('analytics.last7Days')}</option>
<option value="30d">{t('analytics.last30Days')}</option>
<option value="90d">{t('analytics.last90Days')}</option>
</select>
</div>
</div>
@@ -234,7 +236,7 @@ export const AnalyticsPage: React.FC = () => {
<Card padding="md">
<div className="flex items-start justify-between mb-4">
<div>
<p className="text-sm text-neutral-600">Page Views</p>
<p className="text-sm text-neutral-600">{t('analytics.pageViews')}</p>
<p className="text-3xl font-bold text-neutral-900">{analytics?.pageViews.total.toLocaleString()}</p>
<div className="mt-1">
{renderTrendBadge(analytics?.pageViews.trend || 0)}
@@ -251,7 +253,7 @@ export const AnalyticsPage: React.FC = () => {
<Card padding="md">
<div className="flex items-start justify-between mb-4">
<div>
<p className="text-sm text-neutral-600">Unique Visitors</p>
<p className="text-sm text-neutral-600">{t('analytics.uniqueVisitors')}</p>
<p className="text-3xl font-bold text-neutral-900">{analytics?.uniqueVisitors.total.toLocaleString()}</p>
<div className="mt-1">
{renderTrendBadge(analytics?.uniqueVisitors.trend || 0)}
@@ -268,7 +270,7 @@ export const AnalyticsPage: React.FC = () => {
<Card padding="md">
<div className="flex items-start justify-between mb-4">
<div>
<p className="text-sm text-neutral-600">Total Downloads</p>
<p className="text-sm text-neutral-600">{t('analytics.totalDownloads')}</p>
<p className="text-3xl font-bold text-neutral-900">{analytics?.downloads.total.toLocaleString()}</p>
<div className="mt-1">
{renderTrendBadge(analytics?.downloads.trend || 0)}
@@ -277,7 +279,7 @@ export const AnalyticsPage: React.FC = () => {
<Download className="w-8 h-8 text-purple-600" />
</div>
<div className="mt-4 space-y-2">
<p className="text-xs text-neutral-500 uppercase">Top Gallery</p>
<p className="text-xs text-neutral-500 uppercase">{t('analytics.topGallery')}</p>
<p className="text-sm font-medium text-neutral-900 truncate">
{analytics?.downloads.topGalleries[0]?.name}
</p>
@@ -289,19 +291,19 @@ export const AnalyticsPage: React.FC = () => {
{/* Top Pages */}
<div className="lg:col-span-2">
<Card padding="md">
<h2 className="text-lg font-semibold text-neutral-900 mb-4">Top Pages</h2>
<h2 className="text-lg font-semibold text-neutral-900 mb-4">{t('analytics.topPages')}</h2>
<div className="space-y-3">
{analytics?.topPages.map((page, index) => (
<div key={index} className="flex items-center justify-between">
<div className="flex-1">
<p className="text-sm font-medium text-neutral-900">{page.path}</p>
<p className="text-xs text-neutral-500">
{page.uniqueVisitors} unique visitors
{page.uniqueVisitors} {t('analytics.visitors')}
</p>
</div>
<div className="text-right">
<p className="text-sm font-semibold text-neutral-900">{page.views}</p>
<p className="text-xs text-neutral-500">views</p>
<p className="text-xs text-neutral-500">{t('analytics.views')}</p>
</div>
</div>
))}
@@ -310,7 +312,7 @@ export const AnalyticsPage: React.FC = () => {
{/* Top Downloads */}
<Card padding="md" className="mt-6">
<h2 className="text-lg font-semibold text-neutral-900 mb-4">Top Downloads by Gallery</h2>
<h2 className="text-lg font-semibold text-neutral-900 mb-4">{t('analytics.topDownloadsByGallery')}</h2>
<div className="space-y-3">
{analytics?.downloads.topGalleries.map((gallery, index) => (
<div key={index} className="flex items-center justify-between">
@@ -340,26 +342,26 @@ export const AnalyticsPage: React.FC = () => {
<div className="space-y-6">
{/* Device Breakdown */}
<Card padding="md">
<h2 className="text-lg font-semibold text-neutral-900 mb-4">Device Breakdown</h2>
<h2 className="text-lg font-semibold text-neutral-900 mb-4">{t('analytics.deviceBreakdown')}</h2>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Monitor className="w-5 h-5 text-neutral-600" />
<span className="text-sm text-neutral-700">Desktop</span>
<span className="text-sm text-neutral-700">{t('analytics.desktop')}</span>
</div>
<span className="text-sm font-semibold">{analytics?.devices.desktop}%</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Smartphone className="w-5 h-5 text-neutral-600" />
<span className="text-sm text-neutral-700">Mobile</span>
<span className="text-sm text-neutral-700">{t('analytics.mobile')}</span>
</div>
<span className="text-sm font-semibold">{analytics?.devices.mobile}%</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Tablet className="w-5 h-5 text-neutral-600" />
<span className="text-sm text-neutral-700">Tablet</span>
<span className="text-sm text-neutral-700">{t('analytics.tablet')}</span>
</div>
<span className="text-sm font-semibold">{analytics?.devices.tablet}%</span>
</div>
@@ -369,11 +371,11 @@ export const AnalyticsPage: React.FC = () => {
{/* Storage Information */}
{dashboardStats && (
<Card padding="md">
<h2 className="text-lg font-semibold text-neutral-900 mb-4">Storage Usage</h2>
<h2 className="text-lg font-semibold text-neutral-900 mb-4">{t('analytics.storageUsage')}</h2>
<div className="space-y-4">
<div>
<div className="flex justify-between text-sm mb-1">
<span className="text-neutral-600">Used</span>
<span className="text-neutral-600">{t('analytics.used')}</span>
<span className="font-medium">{adminService.formatBytes(dashboardStats.storageUsed)}</span>
</div>
<div className="w-full bg-neutral-200 rounded-full h-2">
@@ -383,16 +385,16 @@ export const AnalyticsPage: React.FC = () => {
/>
</div>
<p className="text-xs text-neutral-500 mt-1">
{Math.round((dashboardStats.storageUsed / (10 * 1024 * 1024 * 1024)) * 100)}% of 10 GB
{Math.round((dashboardStats.storageUsed / (10 * 1024 * 1024 * 1024)) * 100)}% {t('analytics.of')} 10 GB
</p>
</div>
<div className="pt-2 border-t">
<div className="flex justify-between text-sm">
<span className="text-neutral-600">Total Photos</span>
<span className="text-neutral-600">{t('analytics.totalPhotos')}</span>
<span className="font-medium">{dashboardStats.totalPhotos.toLocaleString()}</span>
</div>
<div className="flex justify-between text-sm mt-2">
<span className="text-neutral-600">Active Events</span>
<span className="text-neutral-600">{t('analytics.activeEvents')}</span>
<span className="font-medium">{dashboardStats.activeEvents}</span>
</div>
</div>
@@ -408,9 +410,9 @@ export const AnalyticsPage: React.FC = () => {
<div className="flex items-start gap-3">
<Activity className="w-5 h-5 text-amber-600 flex-shrink-0" />
<div>
<p className="text-sm font-medium text-amber-900">Umami Analytics Not Configured</p>
<p className="text-sm font-medium text-amber-900">{t('analytics.notConfigured')}</p>
<p className="text-sm text-amber-700 mt-1">
To see real analytics data, configure Umami by setting VITE_UMAMI_URL and VITE_UMAMI_WEBSITE_ID in your environment variables.
{t('analytics.configureInstructions')}
</p>
</div>
</div>