From 6c30ea1f4c8d4e778dca2f334c9d91c26c665aad Mon Sep 17 00:00:00 2001 From: Nils Pukropp Date: Tue, 17 Feb 2026 03:47:23 +0100 Subject: [PATCH] added more translations --- frontend/src/App.tsx | 11 +- frontend/src/i18n.ts | 90 ++++- frontend/src/pages/Dashboard.tsx | 24 +- frontend/src/pages/Settings.tsx | 370 ++++++++++---------- frontend/src/pages/flavors/FlavorDetail.tsx | 12 +- 5 files changed, 298 insertions(+), 209 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 13e9206..b5d0799 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -59,6 +59,7 @@ interface SearchResult { } const GlobalSearch = () => { + const { t } = useTranslation(); const [query, setQuery] = useState(''); const [options, setOptions] = useState([]); const navigate = useNavigate(); @@ -120,7 +121,7 @@ const GlobalSearch = () => { { {following.length > 0 && ( <> - Following + {t('nav.following')} {following.map(f => ( setDrawerOpen(false)}> @@ -419,10 +420,10 @@ const App: React.FC = () => { elevation={3} sx={{ mt: 1 }} > - setAnchorEl(null)}>Dashboard - setAnchorEl(null)}>Settings + setAnchorEl(null)}>{t('nav.dashboard')} + setAnchorEl(null)}>{t('nav.settings')} - Logout + {t('nav.logout')} ) : ( diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 0e2c942..fff8407 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -12,7 +12,50 @@ const resources = { "dashboard": "Dashboard", "settings": "Settings", "logout": "Logout", - "login": "Login / Signup" + "login": "Login / Signup", + "following": "Following" + }, + "dashboard": { + "welcome": "Welcome back, {{username}}!", + "shareProfile": "Share Profile:", + "copySuccess": "Link copied to clipboard!", + "myRatings": "My Ratings", + "missing": "Missing", + "noRatings": "You haven't rated any flavors yet.", + "exploreFlavors": "Go explore flavors", + "allRated": "Amazing! You've rated everything! 🏆" + }, + "settings": { + "subtitle": "Manage your account and preferences", + "appearanceTitle": "Appearance & Language", + "themeLabel": "Theme", + "langLabel": "Language", + "avatarTitle": "Profile Picture", + "avatarButton": "Upload New Avatar", + "avatarHint": "Max size: 2MB. JPG, PNG or WEBP.", + "avatarSuccess": "Avatar updated successfully!", + "infoTitle": "Profile Information", + "usernameLabel": "Username", + "emailLabel": "Email", + "emailHint": "Change will require confirmation", + "updateButton": "Update Profile", + "updateSuccess": "Profile updated successfully!", + "confirmEmailTitle": "Confirm Email Change", + "confirmEmailHint": "Enter the 6-digit code sent to {{email}}", + "confirmButton": "Verify & Update Email", + "passwordTitle": "Change Password", + "oldPasswordLabel": "Old Password", + "newPasswordLabel": "New Password", + "passwordButton": "Change Password", + "passwordSuccess": "Password changed successfully!", + "dangerTitle": "Danger Zone", + "dangerDesc": "Permanently delete your account and all associated data. This action is irreversible.", + "deleteButton": "Delete My Account", + "deletionCodeLabel": "Deletion Code", + "deletionCodeHint": "Enter verification code:", + "confirmDeleteButton": "Confirm Permanent Deletion", + "cancelButton": "Cancel", + "codeSent": "Verification code sent to your email." }, "hero": { "title": "The Ultimate Holy Archive", @@ -70,7 +113,50 @@ const resources = { "dashboard": "Dashboard", "settings": "Einstellungen", "logout": "Abmelden", - "login": "Anmelden / Registrieren" + "login": "Anmelden / Registrieren", + "following": "Folge ich" + }, + "dashboard": { + "welcome": "Willkommen zurĂŒck, {{username}}!", + "shareProfile": "Profil teilen:", + "copySuccess": "Link in die Zwischenablage kopiert!", + "myRatings": "Meine Bewertungen", + "missing": "Fehlend", + "noRatings": "Du hast noch keine Sorten bewertet.", + "exploreFlavors": "Sorten entdecken", + "allRated": "Wahnsinn! Du hast alles bewertet! 🏆" + }, + "settings": { + "subtitle": "Verwalte dein Konto und deine Einstellungen", + "appearanceTitle": "Erscheinungsbild & Sprache", + "themeLabel": "Design", + "langLabel": "Sprache", + "avatarTitle": "Profilbild", + "avatarButton": "Neues Bild hochladen", + "avatarHint": "Max. GrĂ¶ĂŸe: 2MB. JPG, PNG oder WEBP.", + "avatarSuccess": "Profilbild erfolgreich aktualisiert!", + "infoTitle": "Profil-Informationen", + "usernameLabel": "Benutzername", + "emailLabel": "E-Mail", + "emailHint": "Änderung erfordert BestĂ€tigung", + "updateButton": "Profil aktualisieren", + "updateSuccess": "Profil erfolgreich aktualisiert!", + "confirmEmailTitle": "E-Mail-Änderung bestĂ€tigen", + "confirmEmailHint": "Gib den 6-stelligen Code ein, der an {{email}} gesendet wurde", + "confirmButton": "E-Mail verifizieren & aktualisieren", + "passwordTitle": "Passwort Ă€ndern", + "oldPasswordLabel": "Altes Passwort", + "newPasswordLabel": "Neues Passwort", + "passwordButton": "Passwort Ă€ndern", + "passwordSuccess": "Passwort erfolgreich geĂ€ndert!", + "dangerTitle": "Gefahrenzone", + "dangerDesc": "Lösche dein Konto und alle damit verbundenen Daten dauerhaft. Dies kann nicht rĂŒckgĂ€ngig gemacht werden.", + "deleteButton": "Mein Konto löschen", + "deletionCodeLabel": "Löschcode", + "deletionCodeHint": "BestĂ€tigungscode eingeben:", + "confirmDeleteButton": "Dauerhafte Löschung bestĂ€tigen", + "cancelButton": "Abbrechen", + "codeSent": "BestĂ€tigungscode an deine E-Mail gesendet." }, "hero": { "title": "Das ultimative Holy-Archiv", diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 4d149bb..c804a1a 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -17,6 +17,7 @@ import { import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import api from '../api'; import { useTitle } from '../hooks/useTitle'; @@ -29,7 +30,8 @@ interface DashboardData { } const Dashboard: React.FC = () => { - useTitle('Dashboard'); + const { t } = useTranslation(); + useTitle(t('nav.dashboard')); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState(0); @@ -38,7 +40,7 @@ const Dashboard: React.FC = () => { const copyToClipboard = () => { navigator.clipboard.writeText(shareUrl); - alert('Link copied to clipboard!'); + alert(t('dashboard.copySuccess')); }; useEffect(() => { @@ -78,7 +80,7 @@ const Dashboard: React.FC = () => { startIcon={} sx={{ mb: 4, textTransform: 'none', borderRadius: 2 }} > - Back to Home + {t('common.backToHome')} @@ -87,12 +89,12 @@ const Dashboard: React.FC = () => { {!data.user.avatar && data.user.username.charAt(0).toUpperCase()} - Dashboard - Welcome back, {data.user.username}! + {t('nav.dashboard')} + {t('dashboard.welcome', { username: data.user.username })} - Share Profile: + {t('dashboard.shareProfile')} { setActiveTab(v)} textColor="primary" indicatorColor="primary"> - - + + @@ -117,8 +119,8 @@ const Dashboard: React.FC = () => { {Object.keys(ratedGrouped).length === 0 ? ( - You haven't rated any flavors yet. - + {t('dashboard.noRatings')} + ) : ( Object.entries(ratedGrouped).map(([category, items]: [string, any]) => ( @@ -189,7 +191,7 @@ const Dashboard: React.FC = () => { {Object.keys(missingGrouped).length === 0 ? ( - Amazing! You've rated everything! 🏆 + {t('dashboard.allRated')} ) : ( Object.entries(missingGrouped).map(([category, flavors]: [string, any]) => ( diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index a1ff2eb..f331ab5 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -13,7 +13,8 @@ import { Select, MenuItem, ListSubheader, - Divider + Divider, + Grid } from '@mui/material'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import { Link } from 'react-router-dom'; @@ -78,7 +79,7 @@ const Settings: React.FC = ({ themeName, onThemeChange }) => { headers: { 'Content-Type': 'multipart/form-data' } }); setAvatar(res.data.avatar); - setMessage({ type: 'success', text: 'Avatar updated successfully!' }); + setMessage({ type: 'success', text: t('settings.avatarSuccess') }); } catch (err: any) { setMessage({ type: 'error', text: err.response?.data?.error || 'Failed to upload avatar' }); } @@ -100,9 +101,9 @@ const Settings: React.FC = ({ themeName, onThemeChange }) => { const res = await api.patch('users/update_profile/', { username, email }); if (res.data.message) { setPendingConfirmation(true); - setMessage({ type: 'success', text: `Profile updated.${res.data.message}` }); + setMessage({ type: 'success', text: `${t('settings.updateSuccess')} ${res.data.message}` }); } else { - setMessage({ type: 'success', text: 'Profile updated successfully!' }); + setMessage({ type: 'success', text: t('settings.updateSuccess') }); setCurrentEmail(email); } } catch (err: any) { @@ -114,7 +115,7 @@ const Settings: React.FC = ({ themeName, onThemeChange }) => { e.preventDefault(); try { const res = await api.post('users/confirm_email/', { code: confirmationCode }); - setMessage({ type: 'success', text: 'Email confirmed and updated!' }); + setMessage({ type: 'success', text: t('settings.updateSuccess') }); setCurrentEmail(res.data.email); setPendingConfirmation(false); setConfirmationCode(''); @@ -127,7 +128,7 @@ const Settings: React.FC = ({ themeName, onThemeChange }) => { e.preventDefault(); try { await api.post('users/change_password/', { old_password: oldPassword, new_password: newPassword }); - setMessage({ type: 'success', text: 'Password changed successfully!' }); + setMessage({ type: 'success', text: t('settings.passwordSuccess') }); setOldPassword(''); setNewPassword(''); } catch (err: any) { @@ -139,7 +140,7 @@ const Settings: React.FC = ({ themeName, onThemeChange }) => { try { await api.post('users/request_account_deletion/'); setIsDeleting(true); - setMessage({ type: 'success', text: 'Verification code sent to your email.' }); + setMessage({ type: 'success', text: t('settings.codeSent') }); } catch (err: any) { setMessage({ type: 'error', text: 'Failed to request deletion.' }); } @@ -158,7 +159,7 @@ const Settings: React.FC = ({ themeName, onThemeChange }) => { }; return ( - + - + {!avatar && username.charAt(0).toUpperCase()} {username} - Manage your account and preferences + {t('settings.subtitle')} {message && ( - setMessage(null)}> + setMessage(null)}> {message.text} )} - - Appearance & Language - - - Theme - - + + {/* Left Column: Visuals & Language */} + + + {t('settings.appearanceTitle')} + + {t('settings.themeLabel')} + + - - Language - - - - + + {t('settings.langLabel')} + + + - - Profile Picture - - - {!avatar && username.charAt(0).toUpperCase()} - - - - - Max size: 2MB. JPG, PNG or WEBP. - - - - - - - Profile Information -
- setUsername(e.target.value)} - /> - setEmail(e.target.value)} - helperText={email !== currentEmail ? "Change will require confirmation" : ""} - /> - - - - {pendingConfirmation && ( - - Confirm Email Change - Enter the 6-digit code sent to {email} -
- setConfirmationCode(e.target.value)} - /> - - -
- )} -
- - - Change Password -
- setOldPassword(e.target.value)} - /> - setNewPassword(e.target.value)} - /> - - -
- - - Danger Zone - - Permanently delete your account and all associated data. This action is irreversible. - - - {!isDeleting ? ( - - ) : ( -
- Enter verification code: - setDeletionCode(e.target.value)} - sx={{ mb: 2 }} - /> - - - + + {t('settings.avatarTitle')} + + + {!avatar && username.charAt(0).toUpperCase()} + + + + + {t('settings.avatarHint')} + + - - )} - +
+ + + {/* Right Column: Account Info & Security */} + + + {t('settings.infoTitle')} +
+ setUsername(e.target.value)} + InputLabelProps={{ shrink: true }} + /> + setEmail(e.target.value)} + helperText={email !== currentEmail ? t('settings.emailHint') : ""} + InputLabelProps={{ shrink: true }} + /> + + + + {pendingConfirmation && ( + + {t('settings.confirmEmailTitle')} + {t('settings.confirmEmailHint', { email })} +
+ setConfirmationCode(e.target.value)} + InputLabelProps={{ shrink: true }} + /> + + +
+ )} +
+ + + {t('settings.passwordTitle')} +
+ setOldPassword(e.target.value)} + InputLabelProps={{ shrink: true }} + /> + setNewPassword(e.target.value)} + InputLabelProps={{ shrink: true }} + /> + + +
+ + theme.palette.mode === 'dark' ? 'rgba(255,0,0,0.05)' : 'rgba(255,0,0,0.02)', '& .MuiInputLabel-root': { bgcolor: 'background.paper', px: 0.5 } }}> + {t('settings.dangerTitle')} + + {t('settings.dangerDesc')} + + + {!isDeleting ? ( + + ) : ( +
+ {t('settings.deletionCodeHint')} + setDeletionCode(e.target.value)} + sx={{ mb: 2 }} + InputLabelProps={{ shrink: true }} + /> + + + + + + )} +
+
+
); }; diff --git a/frontend/src/pages/flavors/FlavorDetail.tsx b/frontend/src/pages/flavors/FlavorDetail.tsx index 1f05d7d..914a188 100644 --- a/frontend/src/pages/flavors/FlavorDetail.tsx +++ b/frontend/src/pages/flavors/FlavorDetail.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react'; import { useParams, Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import { Typography, Box, @@ -51,6 +52,7 @@ interface Flavor { } const FlavorDetail: React.FC = () => { + const { t } = useTranslation(); const { id } = useParams<{ id: string }>(); const [flavor, setFlavor] = useState(null); const [loading, setLoading] = useState(true); @@ -88,7 +90,7 @@ const FlavorDetail: React.FC = () => { fetchFlavor(); }, [id]); - useTitle(flavor?.name || 'Flavor Detail'); + useTitle(flavor?.name || t('common.loading')); const handleRatingSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -156,7 +158,7 @@ const FlavorDetail: React.FC = () => { startIcon={} sx={{ mb: 2, textTransform: 'none', borderRadius: 2 }} > - Back to {flavor.category_name} + {t('common.backTo')} {flavor.category_name} @@ -212,7 +214,7 @@ const FlavorDetail: React.FC = () => { {flavor.shop_url && ( )} @@ -248,11 +250,11 @@ const FlavorDetail: React.FC = () => { )} - Community Reviews + {t('home.communityVoice')} {flavor.ratings.length === 0 ? ( - No reviews yet. Be the first to rate it! + {t('dashboard.noRatings')} ) : ( flavor.ratings.map((rating: Rating) => (