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

View File

@@ -2,6 +2,7 @@
import React, { useState } from 'react';
import { Sparkles, GlassWater, Dices, X } from 'lucide-react';
import { useI18n } from '@/i18n/I18nContext';
import Link from 'next/link';
interface Bottle {
@@ -16,6 +17,7 @@ interface DramOfTheDayProps {
}
export default function DramOfTheDay({ bottles }: DramOfTheDayProps) {
const { t } = useI18n();
const [suggestion, setSuggestion] = useState<Bottle | null>(null);
const [isRolling, setIsRolling] = useState(false);
@@ -24,7 +26,7 @@ export default function DramOfTheDay({ bottles }: DramOfTheDayProps) {
const openBottles = bottles.filter(b => b.status === 'open' || b.status === 'sampled');
if (openBottles.length === 0) {
alert('Keine offenen Flaschen gefunden! Vielleicht Zeit für ein neues Tasting? 🥃');
alert(t('home.dramOfDay.noOpenBottles'));
setIsRolling(false);
return;
}
@@ -49,7 +51,7 @@ export default function DramOfTheDay({ bottles }: DramOfTheDayProps) {
) : (
<Sparkles size={18} />
)}
Dram of the Day
{t('home.dramOfDay.button')}
</button>
{suggestion && (
@@ -68,7 +70,7 @@ export default function DramOfTheDay({ bottles }: DramOfTheDayProps) {
</div>
<div className="space-y-2">
<h3 className="text-[10px] font-black uppercase tracking-[0.2em] text-amber-600">Dein heutiger Dram</h3>
<h3 className="text-[10px] font-black uppercase tracking-[0.2em] text-amber-600">{t('home.dramOfDay.title')}</h3>
<h2 className="text-2xl font-black text-zinc-900 dark:text-white leading-tight">
{suggestion.name}
</h2>
@@ -83,13 +85,13 @@ export default function DramOfTheDay({ bottles }: DramOfTheDayProps) {
onClick={() => setSuggestion(null)}
className="block w-full py-4 bg-zinc-900 dark:bg-zinc-100 text-white dark:text-zinc-900 rounded-2xl font-black uppercase tracking-widest text-xs hover:bg-amber-600 dark:hover:bg-amber-600 hover:text-white transition-all shadow-xl"
>
Flasche anschauen
{t('home.dramOfDay.viewBottle')}
</Link>
<button
onClick={suggestDram}
className="w-full mt-3 py-2 text-zinc-400 hover:text-amber-600 text-[10px] font-black uppercase tracking-widest transition-colors"
>
Nicht heute, noch mal würfeln
{t('home.dramOfDay.rollAgain')}
</button>
</div>
</div>