@@ -94,7 +97,7 @@ export function Pagination({
? 'bg-blue-600 text-white'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
)}
- aria-label={`Сторінка ${page}`}
+ aria-label={t('page', { page })}
aria-current={page === currentPage ? 'page' : undefined}
>
{page}
@@ -113,9 +116,9 @@ export function Pagination({
? 'text-gray-400 dark:text-gray-600 cursor-not-allowed'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800'
)}
- aria-label="Наступна сторінка"
+ aria-label={t('nextPage')}
>
- Вперед →
+ {t('next')} →
);
diff --git a/frontend/components/quiz/CountdownTimer.tsx b/frontend/components/quiz/CountdownTimer.tsx
index c15848c1..9e7060a0 100644
--- a/frontend/components/quiz/CountdownTimer.tsx
+++ b/frontend/components/quiz/CountdownTimer.tsx
@@ -1,6 +1,7 @@
'use client';
import { useEffect, useState } from 'react';
+import { useTranslations } from 'next-intl';
import { cn } from '@/lib/utils';
interface CountdownTimerProps {
@@ -14,6 +15,7 @@ export function CountdownTimer({
onTimeUp,
isActive,
}: CountdownTimerProps) {
+ const t = useTranslations('quiz.timer');
const [endTime] = useState(() => Date.now() + timeLimitSeconds * 1000);
const [remainingSeconds, setRemainingSeconds] = useState(timeLimitSeconds);
@@ -79,7 +81,7 @@ export function CountdownTimer({
percentage <= 10 && 'animate-pulse'
)}>
-
Залишилось часу:
+
{t('label')}
{String(minutes).padStart(2, '0')}:{String(seconds).padStart(2, '0')}
@@ -97,7 +99,7 @@ export function CountdownTimer({
{percentage <= 30 && (
- {percentage <= 10 ? '⚠️ Час майже закінчився!' : '⏰ Поспішайте!'}
+ {percentage <= 10 ? t('almostDone') : t('hurryUp')}
)}
diff --git a/frontend/components/quiz/QuizCard.tsx b/frontend/components/quiz/QuizCard.tsx
index ae581475..678ba146 100644
--- a/frontend/components/quiz/QuizCard.tsx
+++ b/frontend/components/quiz/QuizCard.tsx
@@ -1,5 +1,6 @@
'use client';
+import { useTranslations } from 'next-intl';
import { Link } from '@/i18n/routing';
import { Badge } from '@/components/ui/badge';
@@ -21,6 +22,7 @@ interface QuizCardProps {
}
export function QuizCard({ quiz, userProgress }: QuizCardProps) {
+ const t = useTranslations('quiz.card');
const percentage = userProgress && userProgress.totalQuestions > 0
? Math.round((userProgress.bestScore / userProgress.totalQuestions) * 100)
: 0;
@@ -28,9 +30,9 @@ export function QuizCard({ quiz, userProgress }: QuizCardProps) {
return (
- {quiz.categoryName ?? 'Uncategorized'}
+ {quiz.categoryName ?? t('uncategorized')}
{userProgress && (
- ✓ Completed
+ {t('completed')}
)}
@@ -42,16 +44,16 @@ export function QuizCard({ quiz, userProgress }: QuizCardProps) {
)}
- 📝 {quiz.questionsCount} questions
+ 📝 {quiz.questionsCount} {t('questions')}
- ⏱️ {Math.floor((quiz.timeLimitSeconds ?? quiz.questionsCount * 30) / 60)} min
+ ⏱️ {Math.floor((quiz.timeLimitSeconds ?? quiz.questionsCount * 30) / 60)} {t('min')}
{userProgress && (
- Best: {userProgress.bestScore}/{userProgress.totalQuestions}
+ {t('best')} {userProgress.bestScore}/{userProgress.totalQuestions}
{percentage}%
@@ -64,7 +66,7 @@ export function QuizCard({ quiz, userProgress }: QuizCardProps) {
/>
- {userProgress.attemptsCount} {userProgress.attemptsCount === 1 ? 'attempt' : 'attempts'}
+ {userProgress.attemptsCount} {userProgress.attemptsCount === 1 ? t('attempt') : t('attempts')}
)}
@@ -72,7 +74,7 @@ export function QuizCard({ quiz, userProgress }: QuizCardProps) {
href={`/quiz/${quiz.slug}`}
className="block w-full text-center rounded-lg bg-blue-600 text-white px-4 py-2.5 text-sm font-medium hover:bg-blue-500 transition-colors"
>
- {userProgress ? 'Retake Quiz' : 'Start Quiz'}
+ {userProgress ? t('retake') : t('start')}
);
diff --git a/frontend/components/quiz/QuizContainer.tsx b/frontend/components/quiz/QuizContainer.tsx
index cef7223e..ac5e0da1 100644
--- a/frontend/components/quiz/QuizContainer.tsx
+++ b/frontend/components/quiz/QuizContainer.tsx
@@ -1,6 +1,6 @@
'use client';
-import { useLocale } from 'next-intl';
+import { useLocale, useTranslations } from 'next-intl';
import { savePendingQuizResult } from '@/lib/guest-quiz';
import { useReducer, useTransition } from 'react';
import { useAntiCheat } from '@/hooks/useAntiCheat';
@@ -114,6 +114,7 @@ export function QuizContainer({
timeLimitSeconds,
onBackToTopics,
}: QuizContainerProps) {
+ const tRules = useTranslations('quiz.rules');
const [isPending, startTransition] = useTransition();
const [state, dispatch] = useReducer(quizReducer, {
status: 'rules',
@@ -230,17 +231,16 @@ const locale = useLocale();
return (
- Правила проходження квізу
+ {tRules('title')}
📝
-
Загальні правила
+
{tRules('general.title')}
- Відповідайте на питання чесно. Кожне питання має тільки одну
- правильну відповідь.
+ {tRules('general.description')}
@@ -248,12 +248,12 @@ const locale = useLocale();
🚫
-
Заборонено
+
{tRules('forbidden.title')}
- Копіювання та вставка тексту
- Використання контекстного меню (права кнопка миші)
- Переключення на інші вкладки або програми
- Використання сторонніх джерел інформації
+ {tRules('forbidden.copyPaste')}
+ {tRules('forbidden.contextMenu')}
+ {tRules('forbidden.tabSwitch')}
+ {tRules('forbidden.externalSources')}
@@ -261,10 +261,9 @@ const locale = useLocale();
⚠️
-
Система контролю
+
{tRules('control.title')}
- Порушення правил фіксуються автоматично. При 3+ порушеннях
- результат не зараховується до рейтингу.
+ {tRules('control.description')}
@@ -272,17 +271,16 @@ const locale = useLocale();
⏱️
-
Час проходження
+
{tRules('time.title')}
- Мінімальний час: {totalQuestions * 3} секунд (по 3 секунди на
- питання). Занадто швидке проходження не зараховується.
+ {tRules('time.description', { seconds: totalQuestions * 3 })}
- Почати квіз
+ {tRules('startButton')}
);
diff --git a/frontend/components/quiz/QuizProgress.tsx b/frontend/components/quiz/QuizProgress.tsx
index 797f89cf..f46ed9fe 100644
--- a/frontend/components/quiz/QuizProgress.tsx
+++ b/frontend/components/quiz/QuizProgress.tsx
@@ -1,5 +1,6 @@
'use client';
+import { useTranslations } from 'next-intl';
import { cn } from '@/lib/utils';
interface Answer {
@@ -46,13 +47,14 @@ function getVisibleIndices(current: number, total: number): (number | 'ellipsis'
}
export function QuizProgress({ current, total, answers }: QuizProgressProps) {
+ const t = useTranslations('quiz.progress');
const visibleIndices = getVisibleIndices(current, total);
return (
- Питання {current + 1} / {total}
+ {t('label', { current: current + 1, total })}
diff --git a/frontend/components/quiz/QuizQuestion.tsx b/frontend/components/quiz/QuizQuestion.tsx
index a59be16b..c6ec2f56 100644
--- a/frontend/components/quiz/QuizQuestion.tsx
+++ b/frontend/components/quiz/QuizQuestion.tsx
@@ -1,5 +1,6 @@
'use client';
+import { useTranslations } from 'next-intl';
import { QuizQuestionWithAnswers } from '@/db/queries/quiz';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { Button } from '@/components/ui/button';
@@ -23,6 +24,7 @@ export function QuizQuestion({
onNext,
isLoading = false,
}: QuizQuestionProps) {
+ const t = useTranslations('quiz.question');
const isAnswering = status === 'answering';
const isRevealed = status === 'revealed';
@@ -63,12 +65,12 @@ export function QuizQuestion({
{answer.answerText}
{showCorrect && (
- ✓ Правильно
+ ✓ {t('correct')}
)}
{showIncorrect && (
- ✗ Неправильно
+ ✗ {t('incorrect')}
)}
@@ -83,7 +85,7 @@ export function QuizQuestion({
)}
>
- Пояснення:
+ {t('explanationLabel')}
@@ -99,10 +101,10 @@ export function QuizQuestion({
💡
- Рекомендація
+ {t('recommendation.title')}
- Рекомендуємо повторити цю тему після завершення квізу
+ {t('recommendation.description')}
@@ -114,7 +116,7 @@ export function QuizQuestion({
disabled={isLoading}
className="mt-2 animate-in fade-in slide-in-from-bottom-2 duration-300"
>
- {isLoading ? 'Завантаження...' : 'Далі'}
+ {isLoading ? t('loading') : t('nextButton')}
)}