feat: implement comprehensive i18n system with German and English support

- Created type-safe i18n system with TranslationKeys interface
- Added German (de) and English (en) translations with 160+ keys
- Implemented I18nContext provider and useI18n hook
- Added LanguageSwitcher component for language selection
- Refactored all major components to use translations:
  * Home page, StatsDashboard, DramOfTheDay
  * BottleGrid, EditBottleForm, CameraCapture
  * BuddyList, SessionList, TastingNoteForm
  * StatusSwitcher and bottle management features
- Implemented locale-aware currency formatting (EUR)
- Implemented locale-aware date formatting
- Added localStorage persistence for language preference
- Added automatic browser language detection
- Organized translations into 8 main categories
- System is extensible for additional languages
This commit is contained in:
2025-12-18 13:44:48 +01:00
parent acf02a78dd
commit 334bece471
16 changed files with 741 additions and 120 deletions

162
src/i18n/de.ts Normal file
View File

@@ -0,0 +1,162 @@
import { TranslationKeys } from './types';
export const de: TranslationKeys = {
common: {
save: 'Speichern',
cancel: 'Abbrechen',
edit: 'Bearbeiten',
delete: 'Löschen',
loading: 'Wird geladen...',
error: 'Fehler',
success: 'Erfolg',
search: 'Suchen',
back: 'Zurück',
confirm: 'Bestätigen',
check: 'Prüfen',
link: 'Verknüpfen',
none: 'Keine',
},
home: {
title: 'Whisky Vault',
logout: 'Abmelden',
stats: {
title: 'Deine Bar-Statistiken',
totalValue: 'Gesamtwert',
activeBottles: 'In der Bar',
avgRating: 'Ø Bewertung',
topDistillery: 'Top Brennerei',
},
dramOfDay: {
button: 'Dram of the Day',
rollAgain: 'Noch mal würfeln',
suggestion: 'Wie wäre es heute mit einem...',
noOpenBottles: 'Keine offenen Flaschen gefunden! Vielleicht Zeit für ein neues Tasting? 🥃',
title: 'Dein heutiger Dram',
viewBottle: 'Flasche anschauen',
},
searchPlaceholder: 'Flaschen oder Noten suchen...',
noBottles: 'Keine Flaschen gefunden. Zeit für einen Einkauf! 🥃',
collection: 'Deine Sammlung',
reTry: 'Erneut versuchen',
all: 'Alle',
},
grid: {
searchPlaceholder: 'Suchen nach Name oder Distille...',
noResults: 'Keine Flaschen gefunden, die deinen Filtern entsprechen. 🔎',
sortBy: {
createdAt: 'Neueste zuerst',
lastTasted: 'Zuletzt verkostet',
name: 'Alphabetisch',
},
filter: {
category: 'Kategorie',
distillery: 'Brennerei',
status: 'Status',
},
addSession: 'ZU SESSION HINZUFÜGEN',
addedOn: 'Hinzugefügt am',
reviewRequired: 'REVIEW',
unknownBottle: 'Unbekannte Flasche',
},
bottle: {
details: 'Details',
distillery: 'Brennerei',
category: 'Kategorie',
abv: 'Alkoholgehalt',
age: 'Alter',
years: 'Jahre',
lastTasted: 'Zuletzt verkostet',
neverTasted: 'Noch nie',
purchasePrice: 'Kaufpreis',
distilled: 'Destilliert',
bottled: 'Abgefüllt',
batch: 'Batch / Code',
status: {
sealed: 'Versiegelt',
open: 'Offen',
sampled: 'Sample',
empty: 'Leer',
},
whiskybaseId: 'Whiskybase ID',
tastingNotes: 'Tasting Notes',
tastingNotesDesc: 'Hier findest du deine bisherigen Eindrücke.',
noNotes: 'Noch keine Noten vorhanden.',
editDetails: 'Details bearbeiten',
editTitle: 'Details korrigieren',
autoSearch: 'Automatisch suchen',
applyId: 'ID Übernehmen',
saveChanges: 'Änderungen speichern',
noMatchFound: 'Keinen Treffer gefunden.',
priceLabel: 'Kaufpreis',
nameLabel: 'Name',
distilleryLabel: 'Brennerei',
categoryLabel: 'Kategorie',
abvLabel: 'ABV%',
ageLabel: 'Alter',
distilledLabel: 'Destilliert',
bottledLabel: 'Abgefüllt',
batchLabel: 'Batch / Code',
bottleStatus: 'Flaschenstatus',
},
camera: {
scanBottle: 'Flasche scannen',
uploadImage: 'Bild hochladen',
analyzing: 'Analysiere Flasche...',
analysisError: 'Analyse fehlgeschlagen',
matchFound: 'Flasche erkannt!',
notAWhisky: 'Das sieht nicht nach Whisky aus.',
lowConfidence: 'Ich bin mir unsicher. Bitte Details prüfen.',
saveToVault: 'In den Vault legen',
tastingNow: 'Jetzt verkosten',
backToList: 'Zurück zur Liste',
whiskybaseSearch: 'Whiskybase-Link suchen',
searchingWb: 'Suche auf Whiskybase...',
wbMatchFound: 'Treffer gefunden',
magicShot: 'Magic Shot',
saveSuccess: 'Erfolgreich gespeichert!',
later: 'Später (Zurück zur Liste)',
openingCamera: 'Kamera öffnen',
saving: 'Wird gespeichert...',
nextBottle: 'Nächste Flasche',
newPhoto: 'Neu aufnehmen',
inVault: 'Im Vault speichern',
offlineNotice: 'Offline! Foto wurde gemerkt wird automatisch analysiert, sobald du wieder Netz hast. 📡',
alreadyInVault: 'Bereits im Vault!',
alreadyInVaultDesc: 'Du hast diesen Whisky bereits in deiner Sammlung. Willst du direkt zur Flasche gehen?',
saveAnyway: 'Trotzdem als neue Flasche speichern',
analysisSuccess: 'Bild erfolgreich analysiert',
results: 'Ergebnisse',
toVault: 'Zum Whisky im Vault',
authRequired: 'Bitte melde dich an, um Flaschen zu speichern.',
processingError: 'Verarbeitung fehlgeschlagen. Bitte erneut versuchen.',
},
tasting: {
addNote: 'Neue Note hinzufügen',
isSample: 'Ich trinke ein Sample',
isBottle: 'Ich trinke aus der Flasche',
rating: 'Bewertung',
nose: 'Nase',
palate: 'Gaumen',
finish: 'Abgang',
notesPlaceholder: 'Was riechst und schmeckst du?',
overall: 'Gesamteindruck',
saveTasting: 'Tasting speichern',
participants: 'Teilnehmer',
addParticipant: 'Mitbuddy hinzufügen',
},
buddy: {
title: 'Deine Buddies',
addBuddy: 'Buddy hinzufügen',
placeholder: 'Name des Buddies...',
noBuddies: 'Noch keine Buddies hinzugefügt.',
},
session: {
title: 'Tasting Sessions',
activeSession: 'Aktive Session',
allSessions: 'Alle Sessions',
newSession: 'Neue Session starten',
sessionName: 'Name der Session',
noSessions: 'Noch keine Sessions vorhanden.',
expiryWarning: 'Diese Session läuft bald ab.',
},
};