translations

This commit is contained in:
Stefan
2025-12-14 11:52:39 +01:00
parent 47c37487b3
commit 1bee218cce
6 changed files with 133 additions and 18 deletions
@@ -78,7 +78,7 @@ export default function OpeningTrainingPage() {
<div className="flex items-center justify-center min-h-[400px]"> <div className="flex items-center justify-center min-h-[400px]">
<div className="text-center space-y-4"> <div className="text-center space-y-4">
<div className="w-12 h-12 border-4 border-blue-600 border-t-transparent rounded-full animate-spin mx-auto"></div> <div className="w-12 h-12 border-4 border-blue-600 border-t-transparent rounded-full animate-spin mx-auto"></div>
<p className="text-gray-600 dark:text-gray-400">Loading opening training session...</p> <p className="text-gray-600 dark:text-gray-400">{t.learning.openingTrainer.loadingSession}</p>
</div> </div>
</div> </div>
</div> </div>
@@ -95,16 +95,16 @@ export default function OpeningTrainingPage() {
<div className="max-w-6xl mx-auto w-full"> <div className="max-w-6xl mx-auto w-full">
<div className="text-center py-12"> <div className="text-center py-12">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4"> <h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
Opening Not Found {t.learning.openingTrainer.openingNotFound}
</h2> </h2>
<p className="text-gray-600 dark:text-gray-400 mb-6"> <p className="text-gray-600 dark:text-gray-400 mb-6">
The requested opening could not be found. {t.learning.openingTrainer.openingNotFoundDescription}
</p> </p>
<button <button
onClick={() => router.push('/learning/openings')} onClick={() => router.push('/learning/openings')}
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors" className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
> >
Back to Opening Selection {t.learning.openingTrainer.backToOpeningSelection}
</button> </button>
</div> </div>
</div> </div>
@@ -125,7 +125,7 @@ export default function OpeningTrainingPage() {
className="p-2 md:px-4 md:py-2 bg-gray-200 dark:bg-gray-700 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 text-sm font-medium transition-colors flex items-center gap-2" className="p-2 md:px-4 md:py-2 bg-gray-200 dark:bg-gray-700 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 text-sm font-medium transition-colors flex items-center gap-2"
> >
<ArrowLeft size={20} /> <ArrowLeft size={20} />
<span className="hidden md:inline">Back to Opening Selection</span> <span className="hidden md:inline">{t.learning.openingTrainer.backToOpeningSelection}</span>
</button> </button>
</div> </div>
+3 -2
View File
@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import Link from 'next/link';
import { useTranslation } from '@/lib/i18n/useTranslation'; import { useTranslation } from '@/lib/i18n/useTranslation';
import { SupportedLanguage } from '@/lib/i18n/translations'; import { SupportedLanguage } from '@/lib/i18n/translations';
@@ -14,7 +15,7 @@ export default function Header({ language }: HeaderProps) {
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex items-center justify-between h-16"> <div className="flex items-center justify-between h-16">
{/* App Title/Branding */} {/* App Title/Branding */}
<div className="flex items-center space-x-3"> <Link href="/" className="flex items-center space-x-3 hover:opacity-80 transition-opacity cursor-pointer">
<div className="text-2xl"></div> <div className="text-2xl"></div>
<h1 className="text-xl font-semibold text-gray-900 dark:text-white"> <h1 className="text-xl font-semibold text-gray-900 dark:text-white">
Chess Tutor Chess Tutor
@@ -22,7 +23,7 @@ export default function Header({ language }: HeaderProps) {
<span className="hidden sm:inline-block text-sm text-gray-500 dark:text-gray-400"> <span className="hidden sm:inline-block text-sm text-gray-500 dark:text-gray-400">
{t.header.tagline} {t.header.tagline}
</span> </span>
</div> </Link>
{/* Action Buttons */} {/* Action Buttons */}
<div className="flex items-center space-x-3"> <div className="flex items-center space-x-3">
@@ -1,11 +1,15 @@
'use client'; 'use client';
import { useTranslation } from '@/lib/i18n/useTranslation';
import { SupportedLanguage } from '@/lib/i18n/translations';
interface DeviationDialogProps { interface DeviationDialogProps {
openingName: string; openingName: string;
movesCompleted: number; movesCompleted: number;
onUndo: () => void; onUndo: () => void;
onStartGame: () => void; onStartGame: () => void;
onContinueExploring?: () => void; onContinueExploring?: () => void;
language: SupportedLanguage;
} }
export default function DeviationDialog({ export default function DeviationDialog({
@@ -14,18 +18,20 @@ export default function DeviationDialog({
onUndo, onUndo,
onStartGame, onStartGame,
onContinueExploring, onContinueExploring,
language,
}: DeviationDialogProps) { }: DeviationDialogProps) {
const t = useTranslation(language);
return ( return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl p-8 max-w-md mx-4"> <div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl p-8 max-w-md mx-4">
<div className="text-center mb-6"> <div className="text-center mb-6">
<div className="text-4xl mb-4">🤔</div> <div className="text-4xl mb-4">🤔</div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-2"> <h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
You've Left the Opening! {t.learning.openingTrainer.deviationTitle}
</h2> </h2>
<p className="text-gray-600 dark:text-gray-400"> <p className="text-gray-600 dark:text-gray-400">
You've studied <span className="font-semibold">{movesCompleted}</span> move {t.learning.openingTrainer.deviationMessage} <span className="font-semibold">{movesCompleted}</span>{' '}
{movesCompleted !== 1 ? 's' : ''} of the{' '} {movesCompleted !== 1 ? t.learning.openingTrainer.moves : t.learning.openingTrainer.move} of the{' '}
<span className="font-semibold">{openingName}</span>. <span className="font-semibold">{openingName}</span>.
</p> </p>
<p className="text-gray-600 dark:text-gray-400 mt-2"> <p className="text-gray-600 dark:text-gray-400 mt-2">
@@ -40,7 +46,7 @@ export default function DeviationDialog({
className="w-full px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium transition-colors flex items-center justify-center gap-2" className="w-full px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium transition-colors flex items-center justify-center gap-2"
> >
<span></span> <span></span>
<span>Continue Playing (Start Game)</span> <span>{t.learning.openingTrainer.continuePlayingButton}</span>
</button> </button>
{/* Secondary action: Undo */} {/* Secondary action: Undo */}
@@ -49,7 +55,7 @@ export default function DeviationDialog({
className="w-full px-6 py-3 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 font-medium transition-colors flex items-center justify-center gap-2" className="w-full px-6 py-3 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 font-medium transition-colors flex items-center justify-center gap-2"
> >
<span></span> <span></span>
<span>Undo & Return to Opening</span> <span>{t.learning.openingTrainer.undoReturnButton}</span>
</button> </button>
{/* Optional: Continue exploring */} {/* Optional: Continue exploring */}
@@ -59,13 +65,13 @@ export default function DeviationDialog({
className="w-full px-6 py-3 bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-medium transition-colors border border-gray-300 dark:border-gray-600 flex items-center justify-center gap-2" className="w-full px-6 py-3 bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-700 font-medium transition-colors border border-gray-300 dark:border-gray-600 flex items-center justify-center gap-2"
> >
<span>🔍</span> <span>🔍</span>
<span>Explore This Variation</span> <span>{t.learning.openingTrainer.exploreVariationButton}</span>
</button> </button>
)} )}
</div> </div>
<p className="text-xs text-gray-500 dark:text-gray-500 mt-4 text-center"> <p className="text-xs text-gray-500 dark:text-gray-500 mt-4 text-center">
Tip: You can always navigate back using the move history {t.learning.openingTrainer.deviationTip}
</p> </p>
</div> </div>
</div> </div>
@@ -559,6 +559,7 @@ export default function OpeningTrainer({ opening, personality, apiKey, language
<WikipediaSummary <WikipediaSummary
openingName={opening.name} openingName={opening.name}
wikipediaSlug={opening.wikipediaSlug} wikipediaSlug={opening.wikipediaSlug}
language={language}
/> />
{/* Session info */} {/* Session info */}
@@ -589,6 +590,7 @@ export default function OpeningTrainer({ opening, personality, apiKey, language
onUndo={handleUndoDeviation} onUndo={handleUndoDeviation}
onStartGame={handleStartGameFromPosition} onStartGame={handleStartGameFromPosition}
onContinueExploring={handleContinueExploring} onContinueExploring={handleContinueExploring}
language={language}
/> />
)} )}
</div> </div>
@@ -3,16 +3,20 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { WikipediaSummary as WikipediaSummaryType } from '@/types/openingTraining'; import { WikipediaSummary as WikipediaSummaryType } from '@/types/openingTraining';
import { getWikipediaSummary } from '@/lib/openingTrainer/wikipediaService'; import { getWikipediaSummary } from '@/lib/openingTrainer/wikipediaService';
import { useTranslation } from '@/lib/i18n/useTranslation';
import { SupportedLanguage } from '@/lib/i18n/translations';
interface WikipediaSummaryProps { interface WikipediaSummaryProps {
openingName: string; openingName: string;
wikipediaSlug?: string; // Preferred: direct slug from database wikipediaSlug?: string; // Preferred: direct slug from database
language: SupportedLanguage;
} }
export default function WikipediaSummary({ openingName, wikipediaSlug }: WikipediaSummaryProps) { export default function WikipediaSummary({ openingName, wikipediaSlug, language }: WikipediaSummaryProps) {
const [summary, setSummary] = useState<WikipediaSummaryType | null>(null); const [summary, setSummary] = useState<WikipediaSummaryType | null>(null);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const t = useTranslation(language);
useEffect(() => { useEffect(() => {
fetchSummary(); fetchSummary();
@@ -46,7 +50,7 @@ export default function WikipediaSummary({ openingName, wikipediaSlug }: Wikiped
<div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4"> <div className="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div className="w-4 h-4 border-2 border-blue-600 dark:border-blue-400 border-t-transparent rounded-full animate-spin"></div> <div className="w-4 h-4 border-2 border-blue-600 dark:border-blue-400 border-t-transparent rounded-full animate-spin"></div>
<p className="text-sm text-blue-800 dark:text-blue-300">Loading opening background...</p> <p className="text-sm text-blue-800 dark:text-blue-300">{t.learning.openingTrainer.loadingBackground}</p>
</div> </div>
</div> </div>
); );
@@ -56,7 +60,7 @@ export default function WikipediaSummary({ openingName, wikipediaSlug }: Wikiped
return ( return (
<div className="bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4"> <div className="bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4">
<p className="text-sm text-gray-600 dark:text-gray-400"> <p className="text-sm text-gray-600 dark:text-gray-400">
No background information available for this opening. {t.learning.openingTrainer.noBackgroundInfo}
</p> </p>
</div> </div>
); );
@@ -77,7 +81,7 @@ export default function WikipediaSummary({ openingName, wikipediaSlug }: Wikiped
</div> </div>
<p className="text-sm text-gray-700 dark:text-gray-300 leading-relaxed">{summary.extract}</p> <p className="text-sm text-gray-700 dark:text-gray-300 leading-relaxed">{summary.extract}</p>
<p className="text-xs text-gray-500 dark:text-gray-400"> <p className="text-xs text-gray-500 dark:text-gray-400">
Source: Wikipedia (cached {new Date(summary.fetchedAt).toLocaleDateString()}) {t.learning.openingTrainer.wikipediaSource} {new Date(summary.fetchedAt).toLocaleDateString()})
</p> </p>
</div> </div>
); );
+102
View File
@@ -215,6 +215,23 @@ export interface Translations {
backToLearning: string; backToLearning: string;
findTheMove: string; findTheMove: string;
}; };
openingTrainer: {
loadingSession: string;
openingNotFound: string;
openingNotFoundDescription: string;
backToOpeningSelection: string;
loadingBackground: string;
noBackgroundInfo: string;
wikipediaSource: string;
deviationTitle: string;
deviationMessage: string;
deviationTip: string;
continuePlayingButton: string;
undoReturnButton: string;
exploreVariationButton: string;
move: string;
moves: string;
};
}; };
} }
@@ -419,6 +436,23 @@ const en: Translations = {
backToLearning: 'Back to Learning Area', backToLearning: 'Back to Learning Area',
findTheMove: 'Find the move that creates a', findTheMove: 'Find the move that creates a',
}, },
openingTrainer: {
loadingSession: 'Loading opening training session...',
openingNotFound: 'Opening Not Found',
openingNotFoundDescription: 'The requested opening could not be found.',
backToOpeningSelection: 'Back to Opening Selection',
loadingBackground: 'Loading opening background...',
noBackgroundInfo: 'No background information available for this opening.',
wikipediaSource: 'Source: Wikipedia (cached',
deviationTitle: "You've Left the Opening!",
deviationMessage: "You've studied",
deviationTip: 'Tip: You can always navigate back using the move history',
continuePlayingButton: 'Continue Playing (Start Game)',
undoReturnButton: 'Undo & Return to Opening',
exploreVariationButton: 'Explore This Variation',
move: 'move',
moves: 'moves',
},
}, },
}; };
@@ -623,6 +657,23 @@ const de: Translations = {
backToLearning: 'Zurück zum Lernbereich', backToLearning: 'Zurück zum Lernbereich',
findTheMove: 'Finde den Zug, der eine', findTheMove: 'Finde den Zug, der eine',
}, },
openingTrainer: {
loadingSession: 'Lade Eröffnungstraining...',
openingNotFound: 'Eröffnung nicht gefunden',
openingNotFoundDescription: 'Die angeforderte Eröffnung konnte nicht gefunden werden.',
backToOpeningSelection: 'Zurück zur Eröffnungsauswahl',
loadingBackground: 'Lade Eröffnungshintergrund...',
noBackgroundInfo: 'Keine Hintergrundinformationen für diese Eröffnung verfügbar.',
wikipediaSource: 'Quelle: Wikipedia (zwischengespeichert',
deviationTitle: 'Du hast die Eröffnung verlassen!',
deviationMessage: 'Du hast',
deviationTip: 'Tipp: Du kannst jederzeit über die Zughistorie zurücknavigieren',
continuePlayingButton: 'Weiterspielen (Spiel starten)',
undoReturnButton: 'Rückgängig & zur Eröffnung zurück',
exploreVariationButton: 'Diese Variante erkunden',
move: 'Zug',
moves: 'Züge',
},
}, },
}; };
@@ -827,6 +878,23 @@ const fr: Translations = {
backToLearning: 'Retour à la zone d\'apprentissage', backToLearning: 'Retour à la zone d\'apprentissage',
findTheMove: 'Trouvez le coup qui crée un', findTheMove: 'Trouvez le coup qui crée un',
}, },
openingTrainer: {
loadingSession: 'Chargement de la session d\'entraînement...',
openingNotFound: 'Ouverture non trouvée',
openingNotFoundDescription: 'L\'ouverture demandée n\'a pas pu être trouvée.',
backToOpeningSelection: 'Retour à la sélection d\'ouverture',
loadingBackground: 'Chargement des informations sur l\'ouverture...',
noBackgroundInfo: 'Aucune information disponible pour cette ouverture.',
wikipediaSource: 'Source : Wikipédia (en cache',
deviationTitle: 'Vous avez quitté l\'ouverture !',
deviationMessage: 'Vous avez étudié',
deviationTip: 'Astuce : Vous pouvez toujours revenir en arrière en utilisant l\'historique des coups',
continuePlayingButton: 'Continuer à jouer (Démarrer la partie)',
undoReturnButton: 'Annuler et retourner à l\'ouverture',
exploreVariationButton: 'Explorer cette variante',
move: 'coup',
moves: 'coups',
},
}, },
}; };
@@ -1031,6 +1099,23 @@ const it: Translations = {
backToLearning: 'Torna all\'area di apprendimento', backToLearning: 'Torna all\'area di apprendimento',
findTheMove: 'Trova la mossa che crea un', findTheMove: 'Trova la mossa che crea un',
}, },
openingTrainer: {
loadingSession: 'Caricamento sessione di allenamento...',
openingNotFound: 'Apertura non trovata',
openingNotFoundDescription: 'L\'apertura richiesta non è stata trovata.',
backToOpeningSelection: 'Torna alla selezione delle aperture',
loadingBackground: 'Caricamento informazioni sull\'apertura...',
noBackgroundInfo: 'Nessuna informazione disponibile per questa apertura.',
wikipediaSource: 'Fonte: Wikipedia (in cache',
deviationTitle: 'Hai lasciato l\'apertura!',
deviationMessage: 'Hai studiato',
deviationTip: 'Suggerimento: Puoi sempre tornare indietro usando la cronologia delle mosse',
continuePlayingButton: 'Continua a giocare (Inizia partita)',
undoReturnButton: 'Annulla e torna all\'apertura',
exploreVariationButton: 'Esplora questa variante',
move: 'mossa',
moves: 'mosse',
},
}, },
}; };
@@ -1235,6 +1320,23 @@ const pl: Translations = {
backToLearning: 'Powrót do strefy nauki', backToLearning: 'Powrót do strefy nauki',
findTheMove: 'Znajdź ruch, który tworzy', findTheMove: 'Znajdź ruch, który tworzy',
}, },
openingTrainer: {
loadingSession: 'Ładowanie sesji treningowej...',
openingNotFound: 'Nie znaleziono otwarcia',
openingNotFoundDescription: 'Żądane otwarcie nie zostało znalezione.',
backToOpeningSelection: 'Powrót do wyboru otwarcia',
loadingBackground: 'Ładowanie informacji o otwarciu...',
noBackgroundInfo: 'Brak informacji o tym otwarciu.',
wikipediaSource: 'Źródło: Wikipedia (w pamięci podręcznej',
deviationTitle: 'Opuściłeś otwarcie!',
deviationMessage: 'Przestudiowałeś',
deviationTip: 'Wskazówka: Zawsze możesz wrócić używając historii ruchów',
continuePlayingButton: 'Kontynuuj grę (Rozpocznij partię)',
undoReturnButton: 'Cofnij i wróć do otwarcia',
exploreVariationButton: 'Zbadaj tę wariant',
move: 'ruch',
moves: 'ruchy',
},
}, },
}; };