Add internationalization support with English and Dutch translations

- Integrated i18next for language detection and translation support.
- Added English and Dutch JSON translation files for various app sections.
- Updated TweaksPanel and Workspace components to use translated strings.
- Imported i18n configuration in main entry file.
This commit is contained in:
LaurensBartelds committed 2026-04-26 21:19:47 +02:00
1 parent c64cdcd9e3
commit 6277826472
19 files changed
+1193 -369

No files matched your search

+20 -18
View File
@@ -1,4 +1,5 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useAuth } from '../contexts/AuthContext'
import { ApiError } from '../utils/api'
@@ -10,6 +11,7 @@ interface Props {
type Tab = 'login' | 'register'
export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>) {
const { t } = useTranslation()
const { login, register } = useAuth()
const [tab, setTab] = useState<Tab>('login')
const [email, setEmail] = useState('')
@@ -34,7 +36,7 @@ export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>
await login(email, password)
onClose()
} catch (err) {
setError(err instanceof ApiError ? err.message : 'Login failed')
setError(err instanceof ApiError ? err.message : t('auth.loginFailed'))
} finally {
setSubmitting(false)
}
@@ -44,7 +46,7 @@ export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>
e.preventDefault()
setError(null)
if (password !== confirmPassword) {
setError('Passwords do not match')
setError(t('auth.passwordsDoNotMatch'))
return
}
setSubmitting(true)
@@ -52,7 +54,7 @@ export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>
const message = await register(email, password, displayName)
setSuccess(message)
} catch (err) {
setError(err instanceof ApiError ? err.message : 'Registration failed')
setError(err instanceof ApiError ? err.message : t('auth.registrationFailed'))
} finally {
setSubmitting(false)
}
@@ -61,17 +63,17 @@ export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>
return (
<div className="auth-overlay" onClick={onClose}>
<div className="auth-modal" onClick={(e) => e.stopPropagation()}>
<button className="auth-close" onClick={onClose} aria-label="Close">×</button>
<button className="auth-close" onClick={onClose} aria-label={t('common.close')}>×</button>
<div className="auth-header">
<div className="brand-mark" />
<h2 className="auth-title">
{tab === 'login' ? 'Welcome back' : 'Create account'}
{tab === 'login' ? t('auth.welcomeBack') : t('auth.createAccount')}
</h2>
<p className="auth-subtitle">
{tab === 'login'
? 'Sign in to access your saved exams and results'
: 'Create an account to save your progress'}
? t('auth.signInSubtitle')
: t('auth.registerSubtitle')}
</p>
</div>
@@ -80,13 +82,13 @@ export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>
className={`auth-tab ${tab === 'login' ? 'active' : ''}`}
onClick={() => switchTab('login')}
>
Sign In
{t('auth.signIn')}
</button>
<button
className={`auth-tab ${tab === 'register' ? 'active' : ''}`}
onClick={() => switchTab('register')}
>
Register
{t('auth.register')}
</button>
</div>
@@ -96,7 +98,7 @@ export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>
{!success && tab === 'login' && (
<form onSubmit={(e) => void handleLogin(e)} className="auth-form">
<div className="auth-field">
<label className="auth-label" htmlFor="login-email">Email</label>
<label className="auth-label" htmlFor="login-email">{t('auth.email')}</label>
<input
id="login-email"
className="input"
@@ -109,7 +111,7 @@ export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>
/>
</div>
<div className="auth-field">
<label className="auth-label" htmlFor="login-password">Password</label>
<label className="auth-label" htmlFor="login-password">{t('auth.password')}</label>
<input
id="login-password"
className="input"
@@ -122,14 +124,14 @@ export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>
/>
</div>
<button className="btn btn-primary auth-submit" type="submit" disabled={submitting}>
{submitting ? 'Signing in…' : 'Sign In'}
{submitting ? t('auth.signingIn') : t('auth.signIn')}
</button>
<button
type="button"
className="auth-forgot"
onClick={() => { onClose(); onForgotPassword() }}
>
Forgot your password?
{t('auth.forgotPassword')}
</button>
</form>
)}
@@ -137,7 +139,7 @@ export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>
{!success && tab === 'register' && (
<form onSubmit={(e) => void handleRegister(e)} className="auth-form">
<div className="auth-field">
<label className="auth-label" htmlFor="reg-name">Display Name</label>
<label className="auth-label" htmlFor="reg-name">{t('auth.displayName')}</label>
<input
id="reg-name"
className="input"
@@ -150,7 +152,7 @@ export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>
/>
</div>
<div className="auth-field">
<label className="auth-label" htmlFor="reg-email">Email</label>
<label className="auth-label" htmlFor="reg-email">{t('auth.email')}</label>
<input
id="reg-email"
className="input"
@@ -163,7 +165,7 @@ export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>
/>
</div>
<div className="auth-field">
<label className="auth-label" htmlFor="reg-password">Password</label>
<label className="auth-label" htmlFor="reg-password">{t('auth.password')}</label>
<input
id="reg-password"
className="input"
@@ -177,7 +179,7 @@ export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>
/>
</div>
<div className="auth-field">
<label className="auth-label" htmlFor="reg-confirm">Confirm Password</label>
<label className="auth-label" htmlFor="reg-confirm">{t('auth.confirmPassword')}</label>
<input
id="reg-confirm"
className="input"
@@ -191,7 +193,7 @@ export default function AuthModal({ onClose, onForgotPassword }: Readonly<Props>
/>
</div>
<button className="btn btn-primary auth-submit" type="submit" disabled={submitting}>
{submitting ? 'Creating account…' : 'Create Account'}
{submitting ? t('auth.creatingAccount') : t('auth.register')}
</button>
</form>
)}