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

160
src/i18n/types.ts Normal file
View File

@@ -0,0 +1,160 @@
export type TranslationKeys = {
common: {
save: string;
cancel: string;
edit: string;
delete: string;
loading: string;
error: string;
success: string;
search: string;
back: string;
confirm: string;
check: string;
link: string;
none: string;
};
home: {
title: string;
logout: string;
stats: {
title: string;
totalValue: string;
activeBottles: string;
avgRating: string;
topDistillery: string;
};
dramOfDay: {
button: string;
rollAgain: string;
suggestion: string;
noOpenBottles: string;
title: string;
viewBottle: string;
};
searchPlaceholder: string;
noBottles: string;
collection: string;
reTry: string;
all: string;
};
grid: {
searchPlaceholder: string;
noResults: string;
sortBy: {
createdAt: string;
lastTasted: string;
name: string;
};
filter: {
category: string;
distillery: string;
status: string;
};
addSession: string;
addedOn: string;
reviewRequired: string;
unknownBottle: string;
};
bottle: {
details: string;
distillery: string;
category: string;
abv: string;
age: string;
years: string;
lastTasted: string;
neverTasted: string;
purchasePrice: string;
distilled: string;
bottled: string;
batch: string;
status: {
sealed: string;
open: string;
sampled: string;
empty: string;
};
whiskybaseId: string;
tastingNotes: string;
tastingNotesDesc: string;
noNotes: string;
editDetails: string;
editTitle: string;
autoSearch: string;
applyId: string;
saveChanges: string;
noMatchFound: string;
priceLabel: string;
nameLabel: string;
distilleryLabel: string;
categoryLabel: string;
abvLabel: string;
ageLabel: string;
distilledLabel: string;
bottledLabel: string;
batchLabel: string;
bottleStatus: string;
};
camera: {
scanBottle: string;
uploadImage: string;
analyzing: string;
analysisError: string;
matchFound: string;
notAWhisky: string;
lowConfidence: string;
saveToVault: string;
tastingNow: string;
backToList: string;
whiskybaseSearch: string;
searchingWb: string;
wbMatchFound: string;
magicShot: string;
saveSuccess: string;
later: string;
openingCamera: string;
saving: string;
nextBottle: string;
newPhoto: string;
inVault: string;
offlineNotice: string;
alreadyInVault: string;
alreadyInVaultDesc: string;
saveAnyway: string;
analysisSuccess: string;
results: string;
toVault: string;
authRequired: string;
processingError: string;
};
tasting: {
addNote: string;
isSample: string;
isBottle: string;
rating: string;
nose: string;
palate: string;
finish: string;
notesPlaceholder: string;
overall: string;
saveTasting: string;
participants: string;
addParticipant: string;
};
buddy: {
title: string;
addBuddy: string;
placeholder: string;
noBuddies: string;
};
session: {
title: string;
activeSession: string;
allSessions: string;
newSession: string;
sessionName: string;
noSessions: string;
expiryWarning: string;
};
};