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/en.ts Normal file
View File

@@ -0,0 +1,162 @@
import { TranslationKeys } from './types';
export const en: TranslationKeys = {
common: {
save: 'Save',
cancel: 'Cancel',
edit: 'Edit',
delete: 'Delete',
loading: 'Loading...',
error: 'Error',
success: 'Success',
search: 'Search',
back: 'Back',
confirm: 'Confirm',
check: 'Check',
link: 'Link',
none: 'None',
},
home: {
title: 'Whisky Vault',
logout: 'Logout',
stats: {
title: 'Your Bar Statistics',
totalValue: 'Total Value',
activeBottles: 'In the Bar',
avgRating: 'Avg Rating',
topDistillery: 'Top Distillery',
},
dramOfDay: {
button: 'Dram of the Day',
rollAgain: 'Not today, roll again',
suggestion: 'How about a...',
noOpenBottles: 'No open bottles found! Maybe time for a new tasting? 🥃',
title: 'Your Dram for today',
viewBottle: 'View Bottle',
},
searchPlaceholder: 'Search bottles or notes...',
noBottles: 'No bottles found. Time to go shopping! 🥃',
collection: 'Your Collection',
reTry: 'Retry',
all: 'All',
},
grid: {
searchPlaceholder: 'Search by name or distillery...',
noResults: 'No bottles found matching your filters. 🔎',
sortBy: {
createdAt: 'Newest first',
lastTasted: 'Last tasted',
name: 'Alphabetical',
},
filter: {
category: 'Category',
distillery: 'Distillery',
status: 'Status',
},
addSession: 'ADD TO SESSION',
addedOn: 'Added on',
reviewRequired: 'REVIEW',
unknownBottle: 'Unknown Bottle',
},
bottle: {
details: 'Details',
distillery: 'Distillery',
category: 'Category',
abv: 'ABV',
age: 'Age',
years: 'years',
lastTasted: 'Last Tasted',
neverTasted: 'Never',
purchasePrice: 'Purchase Price',
distilled: 'Distilled',
bottled: 'Bottled',
batch: 'Batch / Code',
status: {
sealed: 'Sealed',
open: 'Open',
sampled: 'Sample',
empty: 'Empty',
},
whiskybaseId: 'Whiskybase ID',
tastingNotes: 'Tasting Notes',
tastingNotesDesc: 'Your previous impressions and notes.',
noNotes: 'No notes yet.',
editDetails: 'Edit Details',
editTitle: 'Fix Details',
autoSearch: 'Auto Search',
applyId: 'Apply ID',
saveChanges: 'Save Changes',
noMatchFound: 'No match found.',
priceLabel: 'Purchase Price',
nameLabel: 'Name',
distilleryLabel: 'Distillery',
categoryLabel: 'Category',
abvLabel: 'ABV%',
ageLabel: 'Age',
distilledLabel: 'Distilled',
bottledLabel: 'Bottled',
batchLabel: 'Batch / Code',
bottleStatus: 'Bottle Status',
},
camera: {
scanBottle: 'Scan Bottle',
uploadImage: 'Upload Image',
analyzing: 'Analyzing bottle...',
analysisError: 'Analysis failed',
matchFound: 'Bottle identified!',
notAWhisky: "Doesn't look like whisky.",
lowConfidence: 'Unsure about details. Please check.',
saveToVault: 'Save to Vault',
tastingNow: 'Tasting Now',
backToList: 'Back to List',
whiskybaseSearch: 'Search Whiskybase',
searchingWb: 'Searching Whiskybase...',
wbMatchFound: 'Match found',
magicShot: 'Magic Shot',
saveSuccess: 'Successfully saved!',
later: 'Later (Back to List)',
openingCamera: 'Open Camera',
saving: 'Saving...',
nextBottle: 'Next Bottle',
newPhoto: 'Take New Photo',
inVault: 'Save in Vault',
offlineNotice: "Offline! Photo captured it'll be analyzed automatically once you're back online. 📡",
alreadyInVault: 'Already in Vault!',
alreadyInVaultDesc: 'You already have this whisky in your collection. Want to go directly to the bottle?',
saveAnyway: 'Save as new bottle anyway',
analysisSuccess: 'Image analyzed successfully',
results: 'Results',
toVault: 'Go to bottle in Vault',
authRequired: 'Please sign in to save bottles.',
processingError: 'Processing failed. Please try again.',
},
tasting: {
addNote: 'Add Tasting Note',
isSample: "I'm drinking a sample",
isBottle: "I'm drinking from the bottle",
rating: 'Rating',
nose: 'Nose',
palate: 'Palate',
finish: 'Finish',
notesPlaceholder: 'What do you smell and taste?',
overall: 'Overall Impression',
saveTasting: 'Save Tasting',
participants: 'Participants',
addParticipant: 'Add Buddy',
},
buddy: {
title: 'Your Buddies',
addBuddy: 'Add Buddy',
placeholder: 'Buddy name...',
noBuddies: 'No buddies added yet.',
},
session: {
title: 'Tasting Sessions',
activeSession: 'Active Session',
allSessions: 'All Sessions',
newSession: 'Start New Session',
sessionName: 'Session Name',
noSessions: 'No sessions yet.',
expiryWarning: 'This session will expire soon.',
},
};