feat(i18n): Lokalisierung - Phase 3 abgeschlossen (LoadingModal, AuthModal, SetupModal)

Lokalisierte Komponenten (10/alle):
- LoadingModal: Simple loading spinner mit 'Processing' und 'Please wait...'
- AuthModal: Login-Dialog mit Benutzername/Passwort
- SetupModal: Initial Setup Wizard mit Toggle für Auth-Aktivierung

Neue Translation Keys:
- loadingModal.processing, pleaseWait
- authModal.title, description, username.*, password.*, error, actions.*
- setupModal.title, description, username.*, password.*, confirmPassword.*, enableAuth.*, errors.*, actions.*

Technische Details:
- Konditionale Beschreibungen basierend auf enableAuth-Status
- Fehler-Messages mit t() für i18n
- Alle Labels, Placeholders und Button-Texte lokalisiert
This commit is contained in:
CanbiZ
2025-10-20 19:05:40 +02:00
parent e0d5a07d18
commit 8fb9936cd6
5 changed files with 134 additions and 26 deletions

View File

@@ -6,12 +6,14 @@ import { Input } from './ui/input';
import { useAuth } from './AuthProvider';
import { Lock, User, AlertCircle } from 'lucide-react';
import { useRegisterModal } from './modal/ModalStackProvider';
import { useTranslation } from '@/lib/i18n/useTranslation';
interface AuthModalProps {
isOpen: boolean;
}
export function AuthModal({ isOpen }: AuthModalProps) {
const { t } = useTranslation('authModal');
useRegisterModal(isOpen, { id: 'auth-modal', allowEscape: false, onClose: () => null });
const { login } = useAuth();
const [username, setUsername] = useState('');
@@ -27,7 +29,7 @@ export function AuthModal({ isOpen }: AuthModalProps) {
const success = await login(username, password);
if (!success) {
setError('Invalid username or password');
setError(t('error'));
}
setIsLoading(false);
@@ -42,27 +44,27 @@ export function AuthModal({ isOpen }: AuthModalProps) {
<div className="flex items-center justify-center p-6 border-b border-border">
<div className="flex items-center gap-3">
<Lock className="h-8 w-8 text-primary" />
<h2 className="text-2xl font-bold text-card-foreground">Authentication Required</h2>
<h2 className="text-2xl font-bold text-card-foreground">{t('title')}</h2>
</div>
</div>
{/* Content */}
<div className="p-6">
<p className="text-muted-foreground text-center mb-6">
Please enter your credentials to access the application.
{t('description')}
</p>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="username" className="block text-sm font-medium text-foreground mb-2">
Username
{t('username.label')}
</label>
<div className="relative">
<User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="username"
type="text"
placeholder="Enter your username"
placeholder={t('username.placeholder')}
value={username}
onChange={(e) => setUsername(e.target.value)}
disabled={isLoading}
@@ -74,14 +76,14 @@ export function AuthModal({ isOpen }: AuthModalProps) {
<div>
<label htmlFor="password" className="block text-sm font-medium text-foreground mb-2">
Password
{t('password.label')}
</label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="password"
type="password"
placeholder="Enter your password"
placeholder={t('password.placeholder')}
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
@@ -103,7 +105,7 @@ export function AuthModal({ isOpen }: AuthModalProps) {
disabled={isLoading || !username.trim() || !password.trim()}
className="w-full"
>
{isLoading ? 'Signing In...' : 'Sign In'}
{isLoading ? t('actions.signingIn') : t('actions.signIn')}
</Button>
</form>
</div>

View File

@@ -2,6 +2,7 @@
import { Loader2 } from 'lucide-react';
import { useRegisterModal } from './modal/ModalStackProvider';
import { useTranslation } from '@/lib/i18n/useTranslation';
interface LoadingModalProps {
isOpen: boolean;
@@ -9,6 +10,7 @@ interface LoadingModalProps {
}
export function LoadingModal({ isOpen, action }: LoadingModalProps) {
const { t } = useTranslation('loadingModal');
useRegisterModal(isOpen, { id: 'loading-modal', allowEscape: false, onClose: () => null });
if (!isOpen) return null;
@@ -22,13 +24,13 @@ export function LoadingModal({ isOpen, action }: LoadingModalProps) {
</div>
<div className="text-center">
<h3 className="text-lg font-semibold text-card-foreground mb-2">
Processing
{t('processing')}
</h3>
<p className="text-sm text-muted-foreground">
{action}
</p>
<p className="text-xs text-muted-foreground mt-2">
Please wait...
{t('pleaseWait')}
</p>
</div>
</div>

View File

@@ -6,6 +6,7 @@ import { Input } from './ui/input';
import { Toggle } from './ui/toggle';
import { Lock, User, Shield, AlertCircle } from 'lucide-react';
import { useRegisterModal } from './modal/ModalStackProvider';
import { useTranslation } from '@/lib/i18n/useTranslation';
interface SetupModalProps {
isOpen: boolean;
@@ -13,6 +14,7 @@ interface SetupModalProps {
}
export function SetupModal({ isOpen, onComplete }: SetupModalProps) {
const { t } = useTranslation('setupModal');
useRegisterModal(isOpen, { id: 'setup-modal', allowEscape: true, onClose: () => null });
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
@@ -28,7 +30,7 @@ export function SetupModal({ isOpen, onComplete }: SetupModalProps) {
// Only validate passwords if authentication is enabled
if (enableAuth && password !== confirmPassword) {
setError('Passwords do not match');
setError(t('errors.passwordMismatch'));
setIsLoading(false);
return;
}
@@ -71,11 +73,11 @@ export function SetupModal({ isOpen, onComplete }: SetupModalProps) {
}
} else {
const errorData = await response.json() as { error: string };
setError(errorData.error ?? 'Failed to setup authentication');
setError(errorData.error ?? t('errors.setupFailed'));
}
} catch (error) {
console.error('Setup error:', error);
setError('Failed to setup authentication');
setError(t('errors.setupFailed'));
}
setIsLoading(false);
@@ -90,27 +92,27 @@ export function SetupModal({ isOpen, onComplete }: SetupModalProps) {
<div className="flex items-center justify-center p-6 border-b border-border">
<div className="flex items-center gap-3">
<Shield className="h-8 w-8 text-success" />
<h2 className="text-2xl font-bold text-card-foreground">Setup Authentication</h2>
<h2 className="text-2xl font-bold text-card-foreground">{t('title')}</h2>
</div>
</div>
{/* Content */}
<div className="p-6">
<p className="text-muted-foreground text-center mb-6">
Set up authentication to secure your application. This will be required for future access.
{t('description')}
</p>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="setup-username" className="block text-sm font-medium text-foreground mb-2">
Username
{t('username.label')}
</label>
<div className="relative">
<User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="setup-username"
type="text"
placeholder="Choose a username"
placeholder={t('username.placeholder')}
value={username}
onChange={(e) => setUsername(e.target.value)}
disabled={isLoading}
@@ -123,14 +125,14 @@ export function SetupModal({ isOpen, onComplete }: SetupModalProps) {
<div>
<label htmlFor="setup-password" className="block text-sm font-medium text-foreground mb-2">
Password
{t('password.label')}
</label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="setup-password"
type="password"
placeholder="Choose a password"
placeholder={t('password.placeholder')}
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
@@ -143,14 +145,14 @@ export function SetupModal({ isOpen, onComplete }: SetupModalProps) {
<div>
<label htmlFor="confirm-password" className="block text-sm font-medium text-foreground mb-2">
Confirm Password
{t('confirmPassword.label')}
</label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
id="confirm-password"
type="password"
placeholder="Confirm your password"
placeholder={t('confirmPassword.placeholder')}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
disabled={isLoading}
@@ -164,11 +166,11 @@ export function SetupModal({ isOpen, onComplete }: SetupModalProps) {
<div className="p-4 border border-border rounded-lg bg-muted/30">
<div className="flex items-center justify-between">
<div>
<h4 className="font-medium text-foreground mb-1">Enable Authentication</h4>
<h4 className="font-medium text-foreground mb-1">{t('enableAuth.title')}</h4>
<p className="text-sm text-muted-foreground">
{enableAuth
? 'Authentication will be required on every page load'
: 'Authentication will be optional (can be enabled later in settings)'
? t('enableAuth.descriptionEnabled')
: t('enableAuth.descriptionDisabled')
}
</p>
</div>
@@ -176,7 +178,7 @@ export function SetupModal({ isOpen, onComplete }: SetupModalProps) {
checked={enableAuth}
onCheckedChange={setEnableAuth}
disabled={isLoading}
label="Enable authentication"
label={t('enableAuth.label')}
/>
</div>
</div>
@@ -196,7 +198,7 @@ export function SetupModal({ isOpen, onComplete }: SetupModalProps) {
}
className="w-full"
>
{isLoading ? 'Setting Up...' : 'Complete Setup'}
{isLoading ? t('actions.settingUp') : t('actions.completeSetup')}
</Button>
</form>
</div>

View File

@@ -64,6 +64,57 @@ export const deMessages: NestedMessages = {
serverBackOnline: 'Server ist wieder online! Seite wird neu geladen...',
},
},
loadingModal: {
processing: 'Verarbeite',
pleaseWait: 'Bitte warten...',
},
authModal: {
title: 'Authentifizierung erforderlich',
description: 'Bitte geben Sie Ihre Anmeldedaten ein, um auf die Anwendung zuzugreifen.',
username: {
label: 'Benutzername',
placeholder: 'Benutzername eingeben',
},
password: {
label: 'Passwort',
placeholder: 'Passwort eingeben',
},
error: 'Ungültiger Benutzername oder Passwort',
actions: {
signIn: 'Anmelden',
signingIn: 'Anmeldung läuft...',
},
},
setupModal: {
title: 'Authentifizierung einrichten',
description: 'Richten Sie die Authentifizierung ein, um Ihre Anwendung zu sichern. Diese wird für zukünftige Zugriffe erforderlich sein.',
username: {
label: 'Benutzername',
placeholder: 'Wählen Sie einen Benutzernamen',
},
password: {
label: 'Passwort',
placeholder: 'Wählen Sie ein Passwort',
},
confirmPassword: {
label: 'Passwort bestätigen',
placeholder: 'Bestätigen Sie Ihr Passwort',
},
enableAuth: {
title: 'Authentifizierung aktivieren',
descriptionEnabled: 'Authentifizierung wird bei jedem Seitenladevorgang erforderlich sein',
descriptionDisabled: 'Authentifizierung wird optional sein (kann später in den Einstellungen aktiviert werden)',
label: 'Authentifizierung aktivieren',
},
errors: {
passwordMismatch: 'Passwörter stimmen nicht überein',
setupFailed: 'Fehler beim Einrichten der Authentifizierung',
},
actions: {
completeSetup: 'Einrichtung abschließen',
settingUp: 'Wird eingerichtet...',
},
},
layout: {
title: 'PVE Skriptverwaltung',
tagline: 'Verwalte und starte lokale Proxmox-Hilfsskripte mit Live-Ausgabe',

View File

@@ -298,4 +298,55 @@ export const enMessages: NestedMessages = {
passwordMismatch: 'Passwords do not match',
},
},
loadingModal: {
processing: 'Processing',
pleaseWait: 'Please wait...',
},
authModal: {
title: 'Authentication Required',
description: 'Please enter your credentials to access the application.',
username: {
label: 'Username',
placeholder: 'Enter your username',
},
password: {
label: 'Password',
placeholder: 'Enter your password',
},
error: 'Invalid username or password',
actions: {
signIn: 'Sign In',
signingIn: 'Signing In...',
},
},
setupModal: {
title: 'Setup Authentication',
description: 'Set up authentication to secure your application. This will be required for future access.',
username: {
label: 'Username',
placeholder: 'Choose a username',
},
password: {
label: 'Password',
placeholder: 'Choose a password',
},
confirmPassword: {
label: 'Confirm Password',
placeholder: 'Confirm your password',
},
enableAuth: {
title: 'Enable Authentication',
descriptionEnabled: 'Authentication will be required on every page load',
descriptionDisabled: 'Authentication will be optional (can be enabled later in settings)',
label: 'Enable authentication',
},
errors: {
passwordMismatch: 'Passwords do not match',
setupFailed: 'Failed to setup authentication',
},
actions: {
completeSetup: 'Complete Setup',
settingUp: 'Setting Up...',
},
},
};