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="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>
<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>
@@ -95,16 +95,16 @@ export default function OpeningTrainingPage() {
<div className="max-w-6xl mx-auto w-full">
<div className="text-center py-12">
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
Opening Not Found
{t.learning.openingTrainer.openingNotFound}
</h2>
<p className="text-gray-600 dark:text-gray-400 mb-6">
The requested opening could not be found.
{t.learning.openingTrainer.openingNotFoundDescription}
</p>
<button
onClick={() => router.push('/learning/openings')}
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>
</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"
>
<ArrowLeft size={20} />
<span className="hidden md:inline">Back to Opening Selection</span>
<span className="hidden md:inline">{t.learning.openingTrainer.backToOpeningSelection}</span>
</button>
</div>
+3 -2
View File
@@ -1,4 +1,5 @@
import React from 'react';
import Link from 'next/link';
import { useTranslation } from '@/lib/i18n/useTranslation';
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="flex items-center justify-between h-16">
{/* 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>
<h1 className="text-xl font-semibold text-gray-900 dark:text-white">
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">
{t.header.tagline}
</span>
</div>
</Link>
{/* Action Buttons */}
<div className="flex items-center space-x-3">
@@ -1,11 +1,15 @@
'use client';
import { useTranslation } from '@/lib/i18n/useTranslation';
import { SupportedLanguage } from '@/lib/i18n/translations';
interface DeviationDialogProps {
openingName: string;
movesCompleted: number;
onUndo: () => void;
onStartGame: () => void;
onContinueExploring?: () => void;
language: SupportedLanguage;
}
export default function DeviationDialog({
@@ -14,18 +18,20 @@ export default function DeviationDialog({
onUndo,
onStartGame,
onContinueExploring,
language,
}: DeviationDialogProps) {
const t = useTranslation(language);
return (
<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="text-center mb-6">
<div className="text-4xl mb-4">🤔</div>
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
You've Left the Opening!
{t.learning.openingTrainer.deviationTitle}
</h2>
<p className="text-gray-600 dark:text-gray-400">
You've studied <span className="font-semibold">{movesCompleted}</span> move
{movesCompleted !== 1 ? 's' : ''} of the{' '}
{t.learning.openingTrainer.deviationMessage} <span className="font-semibold">{movesCompleted}</span>{' '}
{movesCompleted !== 1 ? t.learning.openingTrainer.moves : t.learning.openingTrainer.move} of the{' '}
<span className="font-semibold">{openingName}</span>.
</p>
<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"
>
<span></span>
<span>Continue Playing (Start Game)</span>
<span>{t.learning.openingTrainer.continuePlayingButton}</span>
</button>
{/* 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"
>
<span></span>
<span>Undo & Return to Opening</span>
<span>{t.learning.openingTrainer.undoReturnButton}</span>
</button>
{/* 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"
>
<span>🔍</span>
<span>Explore This Variation</span>
<span>{t.learning.openingTrainer.exploreVariationButton}</span>
</button>
)}
</div>
<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>
</div>
</div>
@@ -559,6 +559,7 @@ export default function OpeningTrainer({ opening, personality, apiKey, language
<WikipediaSummary
openingName={opening.name}
wikipediaSlug={opening.wikipediaSlug}
language={language}
/>
{/* Session info */}
@@ -589,6 +590,7 @@ export default function OpeningTrainer({ opening, personality, apiKey, language
onUndo={handleUndoDeviation}
onStartGame={handleStartGameFromPosition}
onContinueExploring={handleContinueExploring}
language={language}
/>
)}
</div>
@@ -3,16 +3,20 @@
import { useState, useEffect } from 'react';
import { WikipediaSummary as WikipediaSummaryType } from '@/types/openingTraining';
import { getWikipediaSummary } from '@/lib/openingTrainer/wikipediaService';
import { useTranslation } from '@/lib/i18n/useTranslation';
import { SupportedLanguage } from '@/lib/i18n/translations';
interface WikipediaSummaryProps {
openingName: string;
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 [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const t = useTranslation(language);
useEffect(() => {
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="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>
<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>
);
@@ -56,7 +60,7 @@ export default function WikipediaSummary({ openingName, wikipediaSlug }: Wikiped
return (
<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">
No background information available for this opening.
{t.learning.openingTrainer.noBackgroundInfo}
</p>
</div>
);
@@ -77,7 +81,7 @@ export default function WikipediaSummary({ openingName, wikipediaSlug }: Wikiped
</div>
<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">
Source: Wikipedia (cached {new Date(summary.fetchedAt).toLocaleDateString()})
{t.learning.openingTrainer.wikipediaSource} {new Date(summary.fetchedAt).toLocaleDateString()})
</p>
</div>
);
+102
View File
@@ -215,6 +215,23 @@ export interface Translations {
backToLearning: 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',
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',
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',
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',
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',
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',
},
},
};