From bb166b2d30856359b65dec13d8a6cfd5a3b848a4 Mon Sep 17 00:00:00 2001 From: tetiana zorii Date: Sun, 11 Jan 2026 15:50:13 -0500 Subject: [PATCH] feat: add translations for blog, QA and quiz --- frontend/app/[locale]/quiz/[slug]/page.tsx | 10 +- frontend/app/[locale]/quizzes/page.tsx | 14 +-- frontend/components/q&a/Pagination.tsx | 15 +-- frontend/components/quiz/CountdownTimer.tsx | 6 +- frontend/components/quiz/QuizCard.tsx | 16 +-- frontend/components/quiz/QuizContainer.tsx | 32 +++--- frontend/components/quiz/QuizProgress.tsx | 4 +- frontend/components/quiz/QuizQuestion.tsx | 14 +-- frontend/components/quiz/QuizResult.tsx | 50 ++++----- frontend/components/quiz/QuizzesSection.tsx | 4 +- frontend/messages/en.json | 113 +++++++++++++++++++- frontend/messages/pl.json | 113 +++++++++++++++++++- frontend/messages/uk.json | 113 +++++++++++++++++++- 13 files changed, 426 insertions(+), 78 deletions(-) diff --git a/frontend/app/[locale]/quiz/[slug]/page.tsx b/frontend/app/[locale]/quiz/[slug]/page.tsx index aee5a1f9..e6a1ee75 100644 --- a/frontend/app/[locale]/quiz/[slug]/page.tsx +++ b/frontend/app/[locale]/quiz/[slug]/page.tsx @@ -1,5 +1,6 @@ import { getQuizBySlug, getQuizQuestionsRandomized } from '@/db/queries/quiz'; import { notFound } from 'next/navigation'; +import { getTranslations } from 'next-intl/server'; import { QuizContainer } from '@/components/quiz/QuizContainer'; import { PendingResultHandler } from '@/components/quiz/PendingResultHandler'; @@ -11,6 +12,7 @@ interface QuizPageProps { export default async function QuizPage({ params }: QuizPageProps) { const { locale, slug } = await params; + const t = await getTranslations({ locale, namespace: 'quiz.page' }); const user = await getCurrentUser(); @@ -22,11 +24,11 @@ export default async function QuizPage({ params }: QuizPageProps) { const seed = Date.now(); const questions = await getQuizQuestionsRandomized(quiz.id, locale, seed); - + if (!questions.length) { return (
-

Немає питань для цього квізу

+

{t('noQuestions')}

); } @@ -44,9 +46,9 @@ export default async function QuizPage({ params }: QuizPageProps) {

)}
- Питань: {quiz.questionsCount} + {t('questionsLabel')}: {quiz.questionsCount} - Час: {Math.floor((quiz.timeLimitSeconds ?? questions.length * 30) / 60)} хв + {t('timeLabel')}: {Math.floor((quiz.timeLimitSeconds ?? questions.length * 30) / 60)} {t('minutes')}
diff --git a/frontend/app/[locale]/quizzes/page.tsx b/frontend/app/[locale]/quizzes/page.tsx index 002e4c24..2f6ad19d 100644 --- a/frontend/app/[locale]/quizzes/page.tsx +++ b/frontend/app/[locale]/quizzes/page.tsx @@ -1,5 +1,6 @@ import { getActiveQuizzes, getUserQuizzesProgress } from '@/db/queries/quiz'; import { getCurrentUser } from '@/lib/auth'; +import { getTranslations } from 'next-intl/server'; import QuizzesSection from '@/components/quiz/QuizzesSection'; type PageProps = { params: Promise<{ locale: string }> }; @@ -8,8 +9,9 @@ export const dynamic = 'force-dynamic'; export default async function QuizzesPage({ params }: PageProps) { const { locale } = await params; + const t = await getTranslations({ locale, namespace: 'quiz.list' }); const session = await getCurrentUser(); - + const quizzes = await getActiveQuizzes(locale); let userProgressMap: Record = {}; @@ -22,9 +24,9 @@ export default async function QuizzesPage({ params }: PageProps) { if (!quizzes.length) { return (
-

Quizzes

+

{t('title')}

- No quizzes available yet. Please check back soon. + {t('noQuizzes')}

); @@ -34,11 +36,11 @@ export default async function QuizzesPage({ params }: PageProps) {

- Practice + {t('practice')}

-

Quizzes

+

{t('title')}

- Choose a quiz to test your knowledge. + {t('subtitle')}

diff --git a/frontend/components/q&a/Pagination.tsx b/frontend/components/q&a/Pagination.tsx index 7bb10ba9..42e1b1b7 100644 --- a/frontend/components/q&a/Pagination.tsx +++ b/frontend/components/q&a/Pagination.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useTranslations } from 'next-intl'; import { cn } from '@/lib/utils'; interface PaginationProps { @@ -13,6 +14,8 @@ export function Pagination({ totalPages, onPageChange, }: PaginationProps) { + const t = useTranslations('qa.pagination'); + if (totalPages <= 1) return null; const getPageNumbers = (): (number | 'ellipsis')[] => { @@ -57,7 +60,7 @@ export function Pagination({ return ( ); 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 })}

); 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')} )} diff --git a/frontend/components/quiz/QuizResult.tsx b/frontend/components/quiz/QuizResult.tsx index abefaa00..81e8c107 100644 --- a/frontend/components/quiz/QuizResult.tsx +++ b/frontend/components/quiz/QuizResult.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useLocale } from 'next-intl'; +import { useLocale, useTranslations } from 'next-intl'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; @@ -30,21 +30,22 @@ export function QuizResult({ onBackToTopics, }: QuizResultProps) { const locale = useLocale(); + const t = useTranslations('quiz.result'); const getMotivationalMessage = () => { if (score === 0 && answeredCount === 0) { return { emoji: '⏱️', - title: 'Час вийшов', - message: 'Ви не встигли дати жодної відповіді. Спробуйте ще раз і розподіляйте час ефективніше', + title: t('timeUp.title'), + message: t('timeUp.message'), color: 'text-gray-600 dark:text-gray-400', }; } - + if (score === 0 && answeredCount > 0) { return { emoji: '📚', - title: 'Всі відповіді неправильні', - message: 'Рекомендуємо ретельно вивчити матеріал перед наступною спробою', + title: t('allWrong.title'), + message: t('allWrong.message'), color: 'text-red-600 dark:text-red-400', }; } @@ -52,22 +53,22 @@ export function QuizResult({ if (percentage < 50) { return { emoji: '📚', - title: 'Потрібно більше практики', - message: 'Рекомендуємо приділити більше часу теорії та практиці', + title: t('needPractice.title'), + message: t('needPractice.message'), color: 'text-red-600 dark:text-red-400', }; } else if (percentage < 80) { return { emoji: '💪', - title: 'Непоганий результат!', - message: 'Повторіть складні теми та спробуйте ще раз', + title: t('goodJob.title'), + message: t('goodJob.message'), color: 'text-orange-600 dark:text-orange-400', }; } else { return { emoji: '🎉', - title: 'Чудова робота!', - message: 'Ви добре засвоїли матеріал', + title: t('excellent.title'), + message: t('excellent.message'), color: 'text-green-600 dark:text-green-400', }; } @@ -83,7 +84,7 @@ export function QuizResult({ {score} / {total}

- {percentage.toFixed(0)}% правильних відповідей + {percentage.toFixed(0)}% {t('correctAnswers')}

@@ -108,8 +109,7 @@ export function QuizResult({ {violationsCount >= 3 && (

- ⚠️ Квіз завершено з порушеннями правил ({violationsCount} порушень). - Результат не зараховано до рейтингу. + {t('violations', { count: violationsCount })}

)} @@ -120,13 +120,13 @@ export function QuizResult({ : 'bg-gray-50 dark:bg-gray-900/20 border-gray-200 dark:border-gray-800' }`}>

0 - ? 'text-green-800 dark:text-green-200' + pointsAwarded > 0 + ? 'text-green-800 dark:text-green-200' : 'text-gray-600 dark:text-gray-400' }`}> - {pointsAwarded > 0 - ? `+${pointsAwarded} балів додано до рейтингу` - : 'Бали не нараховано (результат не покращено)'} + {pointsAwarded > 0 + ? t('pointsAwarded', { points: pointsAwarded }) + : t('noPointsAwarded')}

)} @@ -134,7 +134,7 @@ export function QuizResult({

- Щоб зберегти результат та потрапити в рейтинг, увійдіть або зареєструйтесь + {t('guestMessage')}

@@ -146,23 +146,23 @@ export function QuizResult({ }} variant="primary" > - Увійти + {t('loginButton')}
) : (
)} diff --git a/frontend/components/quiz/QuizzesSection.tsx b/frontend/components/quiz/QuizzesSection.tsx index 7ada300d..89ba5f3d 100644 --- a/frontend/components/quiz/QuizzesSection.tsx +++ b/frontend/components/quiz/QuizzesSection.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { useParams } from 'next/navigation'; +import { useTranslations } from 'next-intl'; import { QuizCard } from './QuizCard'; import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; import { categoryData } from '@/data/category'; @@ -32,6 +33,7 @@ export default function QuizzesSection({ quizzes, userProgressMap, }: QuizzesSectionProps) { + const t = useTranslations('quiz.section'); const params = useParams(); const locale = params.locale as string; const localeKey = (['uk', 'en', 'pl'] as const).includes( @@ -88,7 +90,7 @@ export default function QuizzesSection({ ) : (

- У цій категорії ще немає квізів. + {t('noQuizzes')}

)} diff --git a/frontend/messages/en.json b/frontend/messages/en.json index c18de2d0..b3d75aed 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -39,7 +39,118 @@ "metaDescription": "Questions and answers for technical interview preparation", "frontend": "Frontend", "backend": "Backend", - "fullstack": "Full-stack" + "fullstack": "Full-stack", + "pagination": { + "label": "Pagination", + "previous": "Previous", + "next": "Next", + "previousPage": "Previous page", + "nextPage": "Next page", + "page": "Page {page}" + } + }, + "quiz": { + "metaTitle": "Quizzes | DevLovers", + "metaDescription": "Test your knowledge with interactive quizzes", + "page": { + "noQuestions": "No questions available for this quiz", + "questionsLabel": "Questions", + "timeLabel": "Time", + "minutes": "min" + }, + "list": { + "title": "Quizzes", + "practice": "Practice", + "subtitle": "Choose a quiz to test your knowledge", + "noQuizzes": "No quizzes available yet. Please check back soon." + }, + "rules": { + "title": "Quiz Rules", + "general": { + "title": "General Rules", + "description": "Answer questions honestly. Each question has only one correct answer." + }, + "forbidden": { + "title": "Forbidden", + "copyPaste": "Copying and pasting text", + "contextMenu": "Using context menu (right-click)", + "tabSwitch": "Switching to other tabs or programs", + "externalSources": "Using external information sources" + }, + "control": { + "title": "Control System", + "description": "Rule violations are automatically detected. With 3+ violations, the result will not count towards the leaderboard." + }, + "time": { + "title": "Time Requirements", + "description": "Minimum time: {seconds} seconds (3 seconds per question). Results that are too fast will not be counted." + }, + "startButton": "Start Quiz" + }, + "question": { + "correct": "Correct", + "incorrect": "Incorrect", + "explanationLabel": "Explanation:", + "recommendation": { + "title": "Recommendation", + "description": "We recommend reviewing this topic after completing the quiz" + }, + "loading": "Loading...", + "nextButton": "Next" + }, + "progress": { + "label": "Question {current} / {total}" + }, + "timer": { + "label": "Time remaining:", + "almostDone": "⚠️ Time almost up!", + "hurryUp": "⏰ Hurry up!" + }, + "result": { + "correctAnswers": "correct answers", + "timeUp": { + "title": "Time's Up", + "message": "You didn't manage to answer any questions. Try again and manage your time better" + }, + "allWrong": { + "title": "All Answers Incorrect", + "message": "We recommend thoroughly studying the material before your next attempt" + }, + "needPractice": { + "title": "Need More Practice", + "message": "We recommend spending more time on theory and practice" + }, + "goodJob": { + "title": "Good Job!", + "message": "Review the difficult topics and try again" + }, + "excellent": { + "title": "Excellent Work!", + "message": "You have mastered the material well" + }, + "violations": "⚠️ Quiz completed with rule violations ({count} violations). Result not counted towards leaderboard.", + "pointsAwarded": "+{points} points added to rating", + "noPointsAwarded": "No points awarded (result not improved)", + "guestMessage": "To save your result and appear on the leaderboard, log in or sign up", + "loginButton": "Log In", + "signupButton": "Sign Up", + "retryButton": "Try Again", + "backButton": "Back to Topics" + }, + "card": { + "uncategorized": "Uncategorized", + "completed": "✓ Completed", + "questions": "questions", + "min": "min", + "best": "Best:", + "attempt": "attempt", + "attempts": "attempts", + "retake": "Retake Quiz", + "start": "Start Quiz" + }, + "section": { + "noQuizzes": "No quizzes in this category yet." + } }, "blog": { "title": "All Blog Posts", diff --git a/frontend/messages/pl.json b/frontend/messages/pl.json index f228430e..33cc5f93 100644 --- a/frontend/messages/pl.json +++ b/frontend/messages/pl.json @@ -39,7 +39,118 @@ "metaDescription": "Pytania i odpowiedzi do przygotowania do rozmów kwalifikacyjnych", "frontend": "Frontend", "backend": "Backend", - "fullstack": "Full-stack" + "fullstack": "Full-stack", + "pagination": { + "label": "Paginacja", + "previous": "Poprzedni", + "next": "Następny", + "previousPage": "Poprzednia strona", + "nextPage": "Następna strona", + "page": "Strona {page}" + } + }, + "quiz": { + "metaTitle": "Quizy | DevLovers", + "metaDescription": "Sprawdź swoją wiedzę za pomocą interaktywnych quizów", + "page": { + "noQuestions": "Brak pytań dla tego quizu", + "questionsLabel": "Pytania", + "timeLabel": "Czas", + "minutes": "min" + }, + "list": { + "title": "Quizy", + "practice": "Praktyka", + "subtitle": "Wybierz quiz, aby sprawdzić swoją wiedzę", + "noQuizzes": "Brak dostępnych quizów. Sprawdź ponownie wkrótce." + }, + "rules": { + "title": "Zasady Quizu", + "general": { + "title": "Zasady Ogólne", + "description": "Odpowiadaj na pytania uczciwie. Każde pytanie ma tylko jedną prawidłową odpowiedź." + }, + "forbidden": { + "title": "Zabronione", + "copyPaste": "Kopiowanie i wklejanie tekstu", + "contextMenu": "Używanie menu kontekstowego (prawy przycisk myszy)", + "tabSwitch": "Przełączanie na inne karty lub programy", + "externalSources": "Używanie zewnętrznych źródeł informacji" + }, + "control": { + "title": "System Kontroli", + "description": "Naruszenia zasad są automatycznie wykrywane. Przy 3+ naruszeniach wynik nie zostanie zaliczony do rankingu." + }, + "time": { + "title": "Wymagania Czasowe", + "description": "Minimalny czas: {seconds} sekund (3 sekundy na pytanie). Wyniki zbyt szybkie nie będą liczone." + }, + "startButton": "Rozpocznij Quiz" + }, + "question": { + "correct": "Prawidłowo", + "incorrect": "Nieprawidłowo", + "explanationLabel": "Wyjaśnienie:", + "recommendation": { + "title": "Rekomendacja", + "description": "Zalecamy przejrzenie tego tematu po ukończeniu quizu" + }, + "loading": "Ładowanie...", + "nextButton": "Dalej" + }, + "progress": { + "label": "Pytanie {current} / {total}" + }, + "timer": { + "label": "Pozostały czas:", + "almostDone": "⚠️ Czas prawie minął!", + "hurryUp": "⏰ Pośpiesz się!" + }, + "result": { + "correctAnswers": "prawidłowych odpowiedzi", + "timeUp": { + "title": "Czas Minął", + "message": "Nie zdążyłeś odpowiedzieć na żadne pytanie. Spróbuj ponownie i lepiej zarządzaj czasem" + }, + "allWrong": { + "title": "Wszystkie Odpowiedzi Nieprawidłowe", + "message": "Zalecamy dokładne przestudiowanie materiału przed następną próbą" + }, + "needPractice": { + "title": "Potrzeba Więcej Praktyki", + "message": "Zalecamy poświęcenie więcej czasu na teorię i praktykę" + }, + "goodJob": { + "title": "Dobra Robota!", + "message": "Przejrzyj trudne tematy i spróbuj ponownie" + }, + "excellent": { + "title": "Doskonała Praca!", + "message": "Dobrze opanowałeś materiał" + }, + "violations": "⚠️ Quiz ukończony z naruszeniami zasad ({count} naruszeń). Wynik nie zaliczony do rankingu.", + "pointsAwarded": "+{points} punktów dodanych do oceny", + "noPointsAwarded": "Nie przyznano punktów (wynik nie poprawiony)", + "guestMessage": "Aby zapisać wynik i pojawić się w rankingu, zaloguj się lub zarejestruj", + "loginButton": "Zaloguj się", + "signupButton": "Zarejestruj się", + "retryButton": "Spróbuj Ponownie", + "backButton": "Powrót do Tematów" + }, + "card": { + "uncategorized": "Bez kategorii", + "completed": "✓ Ukończono", + "questions": "pytań", + "min": "min", + "best": "Najlepszy:", + "attempt": "próba", + "attempts": "próby", + "retake": "Powtórz Quiz", + "start": "Rozpocznij Quiz" + }, + "section": { + "noQuizzes": "W tej kategorii nie ma jeszcze quizów." + } }, "blog": { "title": "Wszystkie Wpisy na Blogu", diff --git a/frontend/messages/uk.json b/frontend/messages/uk.json index 5d6c1ca4..2b60269f 100644 --- a/frontend/messages/uk.json +++ b/frontend/messages/uk.json @@ -39,7 +39,118 @@ "metaDescription": "Питання та відповіді для підготовки до технічних співбесід", "frontend": "Frontend", "backend": "Backend", - "fullstack": "Full-stack" + "fullstack": "Full-stack", + "pagination": { + "label": "Пагінація", + "previous": "Назад", + "next": "Вперед", + "previousPage": "Попередня сторінка", + "nextPage": "Наступна сторінка", + "page": "Сторінка {page}" + } + }, + "quiz": { + "metaTitle": "Квізи | DevLovers", + "metaDescription": "Перевірте свої знання з допомогою інтерактивних квізів", + "page": { + "noQuestions": "Немає питань для цього квізу", + "questionsLabel": "Питань", + "timeLabel": "Час", + "minutes": "хв" + }, + "list": { + "title": "Квізи", + "practice": "Практика", + "subtitle": "Оберіть квіз, щоб перевірити свої знання", + "noQuizzes": "Квізів поки що немає. Завітайте пізніше." + }, + "rules": { + "title": "Правила проходження квізу", + "general": { + "title": "Загальні правила", + "description": "Відповідайте на питання чесно. Кожне питання має тільки одну правильну відповідь." + }, + "forbidden": { + "title": "Заборонено", + "copyPaste": "Копіювання та вставка тексту", + "contextMenu": "Використання контекстного меню (права кнопка миші)", + "tabSwitch": "Переключення на інші вкладки або програми", + "externalSources": "Використання сторонніх джерел інформації" + }, + "control": { + "title": "Система контролю", + "description": "Порушення правил фіксуються автоматично. При 3+ порушеннях результат не зараховується до рейтингу." + }, + "time": { + "title": "Час проходження", + "description": "Мінімальний час: {seconds} секунд (по 3 секунди на питання). Занадто швидке проходження не зараховується." + }, + "startButton": "Почати квіз" + }, + "question": { + "correct": "Правильно", + "incorrect": "Неправильно", + "explanationLabel": "Пояснення:", + "recommendation": { + "title": "Рекомендація", + "description": "Рекомендуємо повторити цю тему після завершення квізу" + }, + "loading": "Завантаження...", + "nextButton": "Далі" + }, + "progress": { + "label": "Питання {current} / {total}" + }, + "timer": { + "label": "Залишилось часу:", + "almostDone": "⚠️ Час майже закінчився!", + "hurryUp": "⏰ Поспішайте!" + }, + "result": { + "correctAnswers": "правильних відповідей", + "timeUp": { + "title": "Час вийшов", + "message": "Ви не встигли дати жодної відповіді. Спробуйте ще раз і розподіляйте час ефективніше" + }, + "allWrong": { + "title": "Всі відповіді неправильні", + "message": "Рекомендуємо ретельно вивчити матеріал перед наступною спробою" + }, + "needPractice": { + "title": "Потрібно більше практики", + "message": "Рекомендуємо приділити більше часу теорії та практиці" + }, + "goodJob": { + "title": "Непоганий результат!", + "message": "Повторіть складні теми та спробуйте ще раз" + }, + "excellent": { + "title": "Чудова робота!", + "message": "Ви добре засвоїли матеріал" + }, + "violations": "⚠️ Квіз завершено з порушеннями правил ({count} порушень). Результат не зараховано до рейтингу.", + "pointsAwarded": "+{points} балів додано до рейтингу", + "noPointsAwarded": "Бали не нараховано (результат не покращено)", + "guestMessage": "Щоб зберегти результат та потрапити в рейтинг, увійдіть або зареєструйтесь", + "loginButton": "Увійти", + "signupButton": "Зареєструватися", + "retryButton": "Спробувати ще раз", + "backButton": "Повернутись до тем" + }, + "card": { + "uncategorized": "Без категорії", + "completed": "✓ Завершено", + "questions": "питань", + "min": "хв", + "best": "Найкращий:", + "attempt": "спроба", + "attempts": "спроб", + "retake": "Пройти знову", + "start": "Розпочати квіз" + }, + "section": { + "noQuizzes": "У цій категорії ще немає квізів." + } }, "blog": { "title": "Усі статті блогу",