feat(i18n): add support for Polish, Spanish, Arabic, and Ukrainian
- Create lesson directories and copy English lessons as templates - Add full UI translations for pl, es, ar, uk languages - Update lessons.js with module stores for all new languages - Implement language cycling (en → de → pl → es → ar → uk → en) - Fix playground mode detection (lesson.mode takes precedence)
This commit is contained in:
@@ -2,7 +2,7 @@ import { LessonEngine } from "./impl/LessonEngine.js";
|
||||
import { CodeEditor } from "./impl/CodeEditor.js";
|
||||
import { renderLesson, renderModuleList, renderLevelIndicator, updateActiveLessonInSidebar } from "./helpers/renderer.js";
|
||||
import { loadModules } from "./config/lessons.js";
|
||||
import { initI18n, t, getLanguage, setLanguage, applyTranslations } from "./i18n.js";
|
||||
import { initI18n, t, getLanguage, setLanguage, getNextLanguage, applyTranslations } from "./i18n.js";
|
||||
|
||||
// Simplified state - LessonEngine now manages lesson state and progress
|
||||
const state = {
|
||||
@@ -119,7 +119,7 @@ function toggleExpectedResult() {
|
||||
|
||||
function toggleLanguage() {
|
||||
const currentLang = getLanguage();
|
||||
const newLang = currentLang === "en" ? "de" : "en";
|
||||
const newLang = getNextLanguage(currentLang);
|
||||
|
||||
// Add transition class before any updates
|
||||
elements.editorSection?.classList.add("transitioning");
|
||||
|
||||
@@ -37,6 +37,74 @@ import htmlMarqueeDE from "../../lessons/de/31-html-marquee.json";
|
||||
import htmlSvgDE from "../../lessons/de/32-html-svg.json";
|
||||
import flexboxDE from "../../lessons/de/flexbox.json";
|
||||
|
||||
// Polish lesson imports
|
||||
import welcomePL from "../../lessons/pl/00-welcome.json";
|
||||
import basicSelectorsPL from "../../lessons/pl/00-basic-selectors.json";
|
||||
import boxModelPL from "../../lessons/pl/01-box-model.json";
|
||||
import unitsVariablesPL from "../../lessons/pl/05-units-variables.json";
|
||||
import transitionsAnimationsPL from "../../lessons/pl/06-transitions-animations.json";
|
||||
import responsivePL from "../../lessons/pl/08-responsive.json";
|
||||
import htmlElementsPL from "../../lessons/pl/20-html-elements.json";
|
||||
import htmlFormsBasicPL from "../../lessons/pl/21-html-forms-basic.json";
|
||||
import htmlFormsValidationPL from "../../lessons/pl/22-html-forms-validation.json";
|
||||
import htmlDetailsSummaryPL from "../../lessons/pl/23-html-details-summary.json";
|
||||
import htmlProgressMeterPL from "../../lessons/pl/24-html-progress-meter.json";
|
||||
import htmlTablesPL from "../../lessons/pl/30-html-tables.json";
|
||||
import htmlMarqueePL from "../../lessons/pl/31-html-marquee.json";
|
||||
import htmlSvgPL from "../../lessons/pl/32-html-svg.json";
|
||||
import flexboxPL from "../../lessons/pl/flexbox.json";
|
||||
|
||||
// Spanish lesson imports
|
||||
import welcomeES from "../../lessons/es/00-welcome.json";
|
||||
import basicSelectorsES from "../../lessons/es/00-basic-selectors.json";
|
||||
import boxModelES from "../../lessons/es/01-box-model.json";
|
||||
import unitsVariablesES from "../../lessons/es/05-units-variables.json";
|
||||
import transitionsAnimationsES from "../../lessons/es/06-transitions-animations.json";
|
||||
import responsiveES from "../../lessons/es/08-responsive.json";
|
||||
import htmlElementsES from "../../lessons/es/20-html-elements.json";
|
||||
import htmlFormsBasicES from "../../lessons/es/21-html-forms-basic.json";
|
||||
import htmlFormsValidationES from "../../lessons/es/22-html-forms-validation.json";
|
||||
import htmlDetailsSummaryES from "../../lessons/es/23-html-details-summary.json";
|
||||
import htmlProgressMeterES from "../../lessons/es/24-html-progress-meter.json";
|
||||
import htmlTablesES from "../../lessons/es/30-html-tables.json";
|
||||
import htmlMarqueeES from "../../lessons/es/31-html-marquee.json";
|
||||
import htmlSvgES from "../../lessons/es/32-html-svg.json";
|
||||
import flexboxES from "../../lessons/es/flexbox.json";
|
||||
|
||||
// Arabic lesson imports
|
||||
import welcomeAR from "../../lessons/ar/00-welcome.json";
|
||||
import basicSelectorsAR from "../../lessons/ar/00-basic-selectors.json";
|
||||
import boxModelAR from "../../lessons/ar/01-box-model.json";
|
||||
import unitsVariablesAR from "../../lessons/ar/05-units-variables.json";
|
||||
import transitionsAnimationsAR from "../../lessons/ar/06-transitions-animations.json";
|
||||
import responsiveAR from "../../lessons/ar/08-responsive.json";
|
||||
import htmlElementsAR from "../../lessons/ar/20-html-elements.json";
|
||||
import htmlFormsBasicAR from "../../lessons/ar/21-html-forms-basic.json";
|
||||
import htmlFormsValidationAR from "../../lessons/ar/22-html-forms-validation.json";
|
||||
import htmlDetailsSummaryAR from "../../lessons/ar/23-html-details-summary.json";
|
||||
import htmlProgressMeterAR from "../../lessons/ar/24-html-progress-meter.json";
|
||||
import htmlTablesAR from "../../lessons/ar/30-html-tables.json";
|
||||
import htmlMarqueeAR from "../../lessons/ar/31-html-marquee.json";
|
||||
import htmlSvgAR from "../../lessons/ar/32-html-svg.json";
|
||||
import flexboxAR from "../../lessons/ar/flexbox.json";
|
||||
|
||||
// Ukrainian lesson imports
|
||||
import welcomeUK from "../../lessons/uk/00-welcome.json";
|
||||
import basicSelectorsUK from "../../lessons/uk/00-basic-selectors.json";
|
||||
import boxModelUK from "../../lessons/uk/01-box-model.json";
|
||||
import unitsVariablesUK from "../../lessons/uk/05-units-variables.json";
|
||||
import transitionsAnimationsUK from "../../lessons/uk/06-transitions-animations.json";
|
||||
import responsiveUK from "../../lessons/uk/08-responsive.json";
|
||||
import htmlElementsUK from "../../lessons/uk/20-html-elements.json";
|
||||
import htmlFormsBasicUK from "../../lessons/uk/21-html-forms-basic.json";
|
||||
import htmlFormsValidationUK from "../../lessons/uk/22-html-forms-validation.json";
|
||||
import htmlDetailsSummaryUK from "../../lessons/uk/23-html-details-summary.json";
|
||||
import htmlProgressMeterUK from "../../lessons/uk/24-html-progress-meter.json";
|
||||
import htmlTablesUK from "../../lessons/uk/30-html-tables.json";
|
||||
import htmlMarqueeUK from "../../lessons/uk/31-html-marquee.json";
|
||||
import htmlSvgUK from "../../lessons/uk/32-html-svg.json";
|
||||
import flexboxUK from "../../lessons/uk/flexbox.json";
|
||||
|
||||
// English module store - ordered by learning path
|
||||
const moduleStoreEN = [
|
||||
// Welcome
|
||||
@@ -89,18 +157,104 @@ const moduleStoreDE = [
|
||||
transitionsAnimationsDE
|
||||
];
|
||||
|
||||
// Polish module store - ordered by learning path
|
||||
const moduleStorePL = [
|
||||
welcomePL,
|
||||
htmlElementsPL,
|
||||
htmlFormsBasicPL,
|
||||
htmlFormsValidationPL,
|
||||
htmlDetailsSummaryPL,
|
||||
htmlProgressMeterPL,
|
||||
htmlTablesPL,
|
||||
htmlSvgPL,
|
||||
htmlMarqueePL,
|
||||
basicSelectorsPL,
|
||||
boxModelPL,
|
||||
unitsVariablesPL,
|
||||
flexboxPL,
|
||||
responsivePL,
|
||||
transitionsAnimationsPL
|
||||
];
|
||||
|
||||
// Spanish module store - ordered by learning path
|
||||
const moduleStoreES = [
|
||||
welcomeES,
|
||||
htmlElementsES,
|
||||
htmlFormsBasicES,
|
||||
htmlFormsValidationES,
|
||||
htmlDetailsSummaryES,
|
||||
htmlProgressMeterES,
|
||||
htmlTablesES,
|
||||
htmlSvgES,
|
||||
htmlMarqueeES,
|
||||
basicSelectorsES,
|
||||
boxModelES,
|
||||
unitsVariablesES,
|
||||
flexboxES,
|
||||
responsiveES,
|
||||
transitionsAnimationsES
|
||||
];
|
||||
|
||||
// Arabic module store - ordered by learning path
|
||||
const moduleStoreAR = [
|
||||
welcomeAR,
|
||||
htmlElementsAR,
|
||||
htmlFormsBasicAR,
|
||||
htmlFormsValidationAR,
|
||||
htmlDetailsSummaryAR,
|
||||
htmlProgressMeterAR,
|
||||
htmlTablesAR,
|
||||
htmlSvgAR,
|
||||
htmlMarqueeAR,
|
||||
basicSelectorsAR,
|
||||
boxModelAR,
|
||||
unitsVariablesAR,
|
||||
flexboxAR,
|
||||
responsiveAR,
|
||||
transitionsAnimationsAR
|
||||
];
|
||||
|
||||
// Ukrainian module store - ordered by learning path
|
||||
const moduleStoreUK = [
|
||||
welcomeUK,
|
||||
htmlElementsUK,
|
||||
htmlFormsBasicUK,
|
||||
htmlFormsValidationUK,
|
||||
htmlDetailsSummaryUK,
|
||||
htmlProgressMeterUK,
|
||||
htmlTablesUK,
|
||||
htmlSvgUK,
|
||||
htmlMarqueeUK,
|
||||
basicSelectorsUK,
|
||||
boxModelUK,
|
||||
unitsVariablesUK,
|
||||
flexboxUK,
|
||||
responsiveUK,
|
||||
transitionsAnimationsUK
|
||||
];
|
||||
|
||||
// Map of language codes to module stores
|
||||
const moduleStores = {
|
||||
en: moduleStoreEN,
|
||||
de: moduleStoreDE,
|
||||
pl: moduleStorePL,
|
||||
es: moduleStoreES,
|
||||
ar: moduleStoreAR,
|
||||
uk: moduleStoreUK
|
||||
};
|
||||
|
||||
/**
|
||||
* Load all available modules for a given language
|
||||
* @param {string} language - Language code ('en' or 'de')
|
||||
* @param {string} language - Language code ('en', 'de', 'pl', 'es', 'ar', 'uk')
|
||||
* @returns {Array} Array of modules
|
||||
*/
|
||||
export function loadModules(language = "en") {
|
||||
const store = language === "de" ? moduleStoreDE : moduleStoreEN;
|
||||
const store = moduleStores[language] || moduleStoreEN;
|
||||
return store.map((module) => ({
|
||||
...module,
|
||||
lessons: module.lessons.map((lesson) => ({
|
||||
...lesson,
|
||||
mode: module.mode || "css"
|
||||
mode: lesson.mode || module.mode || "css"
|
||||
}))
|
||||
}));
|
||||
}
|
||||
|
||||
427
src/i18n.js
427
src/i18n.js
@@ -11,7 +11,7 @@ const translations = {
|
||||
// Header
|
||||
menuOpen: "Open menu",
|
||||
langSwitch: "DE",
|
||||
langSwitchLabel: "Sprache wechseln: Deutsch",
|
||||
langSwitchLabel: "Switch language: Deutsch",
|
||||
help: "Help",
|
||||
|
||||
// Instructions
|
||||
@@ -109,8 +109,8 @@ const translations = {
|
||||
|
||||
// Header
|
||||
menuOpen: "Menü öffnen",
|
||||
langSwitch: "EN",
|
||||
langSwitchLabel: "Switch language: English",
|
||||
langSwitch: "PL",
|
||||
langSwitchLabel: "Zmień język: Polski",
|
||||
help: "Hilfe",
|
||||
|
||||
// Instructions
|
||||
@@ -199,11 +199,432 @@ const translations = {
|
||||
tailwindPlaceholder: "Tailwind-Klassen eingeben (z.B. bg-blue-500 text-white p-4)",
|
||||
lessonFallback: "Lektion {index}",
|
||||
untitledLesson: "Unbenannte Lektion"
|
||||
},
|
||||
|
||||
// Polish
|
||||
pl: {
|
||||
// Page
|
||||
pageTitle: "Code Crispies - Nauka HTML i CSS interaktywnie",
|
||||
skipLink: "Przejdź do głównej treści",
|
||||
|
||||
// Header
|
||||
menuOpen: "Otwórz menu",
|
||||
langSwitch: "ES",
|
||||
langSwitchLabel: "Cambiar idioma: Español",
|
||||
help: "Pomoc",
|
||||
|
||||
// Instructions
|
||||
loading: "Ładowanie...",
|
||||
selectLesson: "Wybierz lekcję, aby rozpocząć.",
|
||||
editorLabel: "Edytor CSS",
|
||||
undoTitle: "Cofnij (Ctrl+Z)",
|
||||
redoTitle: "Ponów (Ctrl+Shift+Z)",
|
||||
resetCodeTitle: "Przywróć kod początkowy",
|
||||
run: "Uruchom",
|
||||
rerun: "Uruchom ponownie",
|
||||
|
||||
// Preview
|
||||
yourOutput: "Twój wynik",
|
||||
showExpected: "Pokaż oczekiwane",
|
||||
hideExpected: "Ukryj oczekiwane",
|
||||
previous: "Poprzednia",
|
||||
next: "Następna",
|
||||
levelIndicator: "Lekcja {current} z {total}",
|
||||
lessonLabel: "Lekcja",
|
||||
|
||||
// Sidebar
|
||||
menu: "Menu",
|
||||
closeMenu: "Zamknij menu",
|
||||
progress: "Postęp",
|
||||
progressText: "{percent}% ukończone ({completed}/{total})",
|
||||
lessons: "Lekcje",
|
||||
settings: "Ustawienia",
|
||||
showHints: "Pokaż podpowiedzi",
|
||||
resetAllProgress: "Resetuj cały postęp",
|
||||
openSource: "Open Source:",
|
||||
by: "przez",
|
||||
|
||||
// Help dialog
|
||||
helpTitle: "Pomoc",
|
||||
aboutTitle: "O Code Crispies",
|
||||
aboutText: "Code Crispies to darmowa platforma open-source do nauki tworzenia stron internetowych poprzez praktyczne ćwiczenia. Nie wymaga konta - po prostu zacznij kodować!",
|
||||
learningModesTitle: "Tryby nauki",
|
||||
modeCss: "<strong>CSS</strong> - Pisz reguły CSS do stylizowania elementów",
|
||||
modeTailwind: "<strong>Tailwind</strong> - Stosuj klasy utility bezpośrednio w HTML",
|
||||
modeHtml: "<strong>HTML</strong> - Ćwicz semantyczne znaczniki i natywne elementy",
|
||||
gettingStartedTitle: "Pierwsze kroki",
|
||||
gettingStartedText: "Otwórz menu (☰), aby przeglądać moduły lekcji. Każdy moduł obejmuje konkretny temat z progresywnymi ćwiczeniami.",
|
||||
completingLessonsTitle: "Ukończanie lekcji",
|
||||
completingStep1: "Przeczytaj instrukcje zadania po lewej stronie",
|
||||
completingStep2: "Napisz swój kod w edytorze",
|
||||
completingStep3: "Obserwuj podgląd na żywo podczas pisania",
|
||||
completingStep4: "Postępuj zgodnie z podpowiedziami, aby naprawić problemy",
|
||||
completingStep5: "Kliknij <strong>Następna</strong> po ukończeniu",
|
||||
editorToolsTitle: "Narzędzia edytora",
|
||||
editorToolUndo: "<strong>↶ Cofnij</strong> / <strong>↷ Ponów</strong> - Nawiguj historię edycji",
|
||||
editorToolReset: "<strong>⟲ Resetuj</strong> - Przywróć początkowy kod",
|
||||
editorToolExpected: "<strong>Pokaż oczekiwane</strong> - Przełącz nakładkę wyniku docelowego",
|
||||
keyboardShortcutsTitle: "Skróty klawiszowe",
|
||||
shortcutRun: "<kbd>Ctrl+Enter</kbd> - Natychmiastowa walidacja",
|
||||
shortcutUndo: "<kbd>Ctrl+Z</kbd> - Cofnij",
|
||||
shortcutRedo: "<kbd>Ctrl+Shift+Z</kbd> - Ponów",
|
||||
emmetTitle: "Skróty Emmet (tryb HTML)",
|
||||
emmetText: "Wpisz skróty i naciśnij <kbd>Tab</kbd>, aby rozwinąć:",
|
||||
emmetClass: "<kbd>div.box</kbd> → div z klasą",
|
||||
emmetChildren: "<kbd>ul>li*3</kbd> → ul z 3 elementami li",
|
||||
emmetNested: "<kbd>form>input+button</kbd> → zagnieżdżona struktura",
|
||||
emmetContent: "<kbd>p{Cześć}</kbd> → p z tekstem",
|
||||
|
||||
// More Projects
|
||||
moreProjectsTitle: "Więcej projektów",
|
||||
htmlOverJsDesc: " - Naucz się wykorzystywać natywne elementy HTML zamiast niestandardowych rozwiązań JavaScript",
|
||||
mandalaDesc: " - Interaktywna wizualizacja technologii JavaScript uporządkowanych według złożoności",
|
||||
|
||||
// Contact
|
||||
contactTitle: "Kontakt i linki",
|
||||
contactText: "Code Crispies jest rozwijany przez <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>",
|
||||
|
||||
// Reset dialog
|
||||
resetDialogTitle: "Resetuj postęp",
|
||||
resetDialogText: "Czy na pewno chcesz zresetować cały postęp? Tej operacji nie można cofnąć.",
|
||||
cancel: "Anuluj",
|
||||
resetAll: "Resetuj wszystko",
|
||||
|
||||
// Dynamic content
|
||||
loadingFallbackText: "Nie można załadować lekcji. Wybierz jedną z menu lub sprawdź pomoc.",
|
||||
completed: "Ukończono",
|
||||
successMessage: "CRISPY! ٩(◕‿◕)۶ Twój kod działa poprawnie.",
|
||||
keepTrying: "Próbuj dalej!",
|
||||
failedToLoad: "Nie udało się załadować modułów. Odśwież stronę.",
|
||||
tailwindPlaceholder: "Wprowadź klasy Tailwind (np. bg-blue-500 text-white p-4)",
|
||||
lessonFallback: "Lekcja {index}",
|
||||
untitledLesson: "Lekcja bez tytułu"
|
||||
},
|
||||
|
||||
// Spanish
|
||||
es: {
|
||||
// Page
|
||||
pageTitle: "Code Crispies - Aprende HTML y CSS de forma interactiva",
|
||||
skipLink: "Saltar al contenido principal",
|
||||
|
||||
// Header
|
||||
menuOpen: "Abrir menú",
|
||||
langSwitch: "AR",
|
||||
langSwitchLabel: "تغيير اللغة: العربية",
|
||||
help: "Ayuda",
|
||||
|
||||
// Instructions
|
||||
loading: "Cargando...",
|
||||
selectLesson: "Selecciona una lección para comenzar.",
|
||||
editorLabel: "Editor CSS",
|
||||
undoTitle: "Deshacer (Ctrl+Z)",
|
||||
redoTitle: "Rehacer (Ctrl+Shift+Z)",
|
||||
resetCodeTitle: "Restaurar código inicial",
|
||||
run: "Ejecutar",
|
||||
rerun: "Volver a ejecutar",
|
||||
|
||||
// Preview
|
||||
yourOutput: "Tu resultado",
|
||||
showExpected: "Mostrar esperado",
|
||||
hideExpected: "Ocultar esperado",
|
||||
previous: "Anterior",
|
||||
next: "Siguiente",
|
||||
levelIndicator: "Lección {current} de {total}",
|
||||
lessonLabel: "Lección",
|
||||
|
||||
// Sidebar
|
||||
menu: "Menú",
|
||||
closeMenu: "Cerrar menú",
|
||||
progress: "Progreso",
|
||||
progressText: "{percent}% completado ({completed}/{total})",
|
||||
lessons: "Lecciones",
|
||||
settings: "Configuración",
|
||||
showHints: "Mostrar pistas",
|
||||
resetAllProgress: "Reiniciar todo el progreso",
|
||||
openSource: "Código abierto:",
|
||||
by: "por",
|
||||
|
||||
// Help dialog
|
||||
helpTitle: "Ayuda",
|
||||
aboutTitle: "Acerca de Code Crispies",
|
||||
aboutText: "Code Crispies es una plataforma gratuita de código abierto para aprender desarrollo web a través de ejercicios prácticos. No se requiere cuenta, ¡solo empieza a programar!",
|
||||
learningModesTitle: "Modos de aprendizaje",
|
||||
modeCss: "<strong>CSS</strong> - Escribe reglas CSS para estilizar elementos",
|
||||
modeTailwind: "<strong>Tailwind</strong> - Aplica clases de utilidad directamente en HTML",
|
||||
modeHtml: "<strong>HTML</strong> - Practica marcado semántico y elementos nativos",
|
||||
gettingStartedTitle: "Primeros pasos",
|
||||
gettingStartedText: "Abre el menú (☰) para explorar los módulos de lecciones. Cada módulo cubre un tema específico con ejercicios progresivos.",
|
||||
completingLessonsTitle: "Completar lecciones",
|
||||
completingStep1: "Lee las instrucciones de la tarea a la izquierda",
|
||||
completingStep2: "Escribe tu código en el editor",
|
||||
completingStep3: "Observa la vista previa en vivo mientras escribes",
|
||||
completingStep4: "Sigue las pistas para corregir problemas",
|
||||
completingStep5: "Haz clic en <strong>Siguiente</strong> cuando termines",
|
||||
editorToolsTitle: "Herramientas del editor",
|
||||
editorToolUndo: "<strong>↶ Deshacer</strong> / <strong>↷ Rehacer</strong> - Navegar historial de edición",
|
||||
editorToolReset: "<strong>⟲ Reiniciar</strong> - Restaurar código inicial",
|
||||
editorToolExpected: "<strong>Mostrar esperado</strong> - Alternar superposición del resultado objetivo",
|
||||
keyboardShortcutsTitle: "Atajos de teclado",
|
||||
shortcutRun: "<kbd>Ctrl+Enter</kbd> - Validar inmediatamente",
|
||||
shortcutUndo: "<kbd>Ctrl+Z</kbd> - Deshacer",
|
||||
shortcutRedo: "<kbd>Ctrl+Shift+Z</kbd> - Rehacer",
|
||||
emmetTitle: "Atajos Emmet (modo HTML)",
|
||||
emmetText: "Escribe abreviaturas y presiona <kbd>Tab</kbd> para expandir:",
|
||||
emmetClass: "<kbd>div.box</kbd> → div con clase",
|
||||
emmetChildren: "<kbd>ul>li*3</kbd> → ul con 3 hijos li",
|
||||
emmetNested: "<kbd>form>input+button</kbd> → estructura anidada",
|
||||
emmetContent: "<kbd>p{Hola}</kbd> → p con contenido de texto",
|
||||
|
||||
// More Projects
|
||||
moreProjectsTitle: "Más proyectos",
|
||||
htmlOverJsDesc: " - Aprende a aprovechar elementos HTML nativos en lugar de soluciones JavaScript personalizadas",
|
||||
mandalaDesc: " - Visualización interactiva de tecnologías JavaScript organizadas por complejidad",
|
||||
|
||||
// Contact
|
||||
contactTitle: "Contacto y enlaces",
|
||||
contactText: "Code Crispies es desarrollado por <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>",
|
||||
|
||||
// Reset dialog
|
||||
resetDialogTitle: "Reiniciar progreso",
|
||||
resetDialogText: "¿Estás seguro de que quieres reiniciar todo tu progreso? Esta acción no se puede deshacer.",
|
||||
cancel: "Cancelar",
|
||||
resetAll: "Reiniciar todo",
|
||||
|
||||
// Dynamic content
|
||||
loadingFallbackText: "No se pudo cargar la lección. Selecciona una del menú o consulta la ayuda.",
|
||||
completed: "Completado",
|
||||
successMessage: "¡CRISPY! ٩(◕‿◕)۶ Tu código funciona correctamente.",
|
||||
keepTrying: "¡Sigue intentando!",
|
||||
failedToLoad: "No se pudieron cargar los módulos. Actualiza la página.",
|
||||
tailwindPlaceholder: "Ingresa clases de Tailwind (ej. bg-blue-500 text-white p-4)",
|
||||
lessonFallback: "Lección {index}",
|
||||
untitledLesson: "Lección sin título"
|
||||
},
|
||||
|
||||
// Arabic
|
||||
ar: {
|
||||
// Page
|
||||
pageTitle: "Code Crispies - تعلم HTML و CSS بشكل تفاعلي",
|
||||
skipLink: "انتقل إلى المحتوى الرئيسي",
|
||||
|
||||
// Header
|
||||
menuOpen: "افتح القائمة",
|
||||
langSwitch: "UK",
|
||||
langSwitchLabel: "Змінити мову: Українська",
|
||||
help: "مساعدة",
|
||||
|
||||
// Instructions
|
||||
loading: "جاري التحميل...",
|
||||
selectLesson: "اختر درسًا للبدء.",
|
||||
editorLabel: "محرر CSS",
|
||||
undoTitle: "تراجع (Ctrl+Z)",
|
||||
redoTitle: "إعادة (Ctrl+Shift+Z)",
|
||||
resetCodeTitle: "استعادة الكود الأولي",
|
||||
run: "تشغيل",
|
||||
rerun: "إعادة التشغيل",
|
||||
|
||||
// Preview
|
||||
yourOutput: "نتيجتك",
|
||||
showExpected: "إظهار المتوقع",
|
||||
hideExpected: "إخفاء المتوقع",
|
||||
previous: "السابق",
|
||||
next: "التالي",
|
||||
levelIndicator: "الدرس {current} من {total}",
|
||||
lessonLabel: "درس",
|
||||
|
||||
// Sidebar
|
||||
menu: "القائمة",
|
||||
closeMenu: "إغلاق القائمة",
|
||||
progress: "التقدم",
|
||||
progressText: "{percent}% مكتمل ({completed}/{total})",
|
||||
lessons: "الدروس",
|
||||
settings: "الإعدادات",
|
||||
showHints: "إظهار التلميحات",
|
||||
resetAllProgress: "إعادة تعيين كل التقدم",
|
||||
openSource: "مفتوح المصدر:",
|
||||
by: "بواسطة",
|
||||
|
||||
// Help dialog
|
||||
helpTitle: "مساعدة",
|
||||
aboutTitle: "عن Code Crispies",
|
||||
aboutText: "Code Crispies هي منصة مجانية مفتوحة المصدر لتعلم تطوير الويب من خلال تمارين عملية. لا يلزم حساب - فقط ابدأ البرمجة!",
|
||||
learningModesTitle: "أوضاع التعلم",
|
||||
modeCss: "<strong>CSS</strong> - اكتب قواعد CSS لتنسيق العناصر",
|
||||
modeTailwind: "<strong>Tailwind</strong> - طبق فئات الأدوات مباشرة في HTML",
|
||||
modeHtml: "<strong>HTML</strong> - تدرب على الترميز الدلالي والعناصر الأصلية",
|
||||
gettingStartedTitle: "البداية",
|
||||
gettingStartedText: "افتح القائمة (☰) لتصفح وحدات الدروس. كل وحدة تغطي موضوعًا محددًا مع تمارين تدريجية.",
|
||||
completingLessonsTitle: "إكمال الدروس",
|
||||
completingStep1: "اقرأ تعليمات المهمة على اليسار",
|
||||
completingStep2: "اكتب الكود في المحرر",
|
||||
completingStep3: "شاهد المعاينة المباشرة أثناء الكتابة",
|
||||
completingStep4: "اتبع التلميحات لإصلاح أي مشاكل",
|
||||
completingStep5: "انقر على <strong>التالي</strong> عند الانتهاء",
|
||||
editorToolsTitle: "أدوات المحرر",
|
||||
editorToolUndo: "<strong>↶ تراجع</strong> / <strong>↷ إعادة</strong> - التنقل في سجل التحرير",
|
||||
editorToolReset: "<strong>⟲ إعادة تعيين</strong> - استعادة الكود الأولي",
|
||||
editorToolExpected: "<strong>إظهار المتوقع</strong> - تبديل طبقة النتيجة المستهدفة",
|
||||
keyboardShortcutsTitle: "اختصارات لوحة المفاتيح",
|
||||
shortcutRun: "<kbd>Ctrl+Enter</kbd> - التحقق فورًا",
|
||||
shortcutUndo: "<kbd>Ctrl+Z</kbd> - تراجع",
|
||||
shortcutRedo: "<kbd>Ctrl+Shift+Z</kbd> - إعادة",
|
||||
emmetTitle: "اختصارات Emmet (وضع HTML)",
|
||||
emmetText: "اكتب الاختصارات واضغط <kbd>Tab</kbd> للتوسيع:",
|
||||
emmetClass: "<kbd>div.box</kbd> ← div مع فئة",
|
||||
emmetChildren: "<kbd>ul>li*3</kbd> ← ul مع 3 عناصر li",
|
||||
emmetNested: "<kbd>form>input+button</kbd> ← هيكل متداخل",
|
||||
emmetContent: "<kbd>p{مرحبا}</kbd> ← p مع محتوى نصي",
|
||||
|
||||
// More Projects
|
||||
moreProjectsTitle: "مشاريع أخرى",
|
||||
htmlOverJsDesc: " - تعلم استخدام عناصر HTML الأصلية بدلاً من حلول JavaScript المخصصة",
|
||||
mandalaDesc: " - تصور تفاعلي لتقنيات JavaScript مرتبة حسب التعقيد",
|
||||
|
||||
// Contact
|
||||
contactTitle: "التواصل والروابط",
|
||||
contactText: "Code Crispies تم تطويره بواسطة <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>",
|
||||
|
||||
// Reset dialog
|
||||
resetDialogTitle: "إعادة تعيين التقدم",
|
||||
resetDialogText: "هل أنت متأكد أنك تريد إعادة تعيين كل تقدمك؟ لا يمكن التراجع عن هذا الإجراء.",
|
||||
cancel: "إلغاء",
|
||||
resetAll: "إعادة تعيين الكل",
|
||||
|
||||
// Dynamic content
|
||||
loadingFallbackText: "تعذر تحميل الدرس. اختر واحدًا من القائمة أو تحقق من المساعدة.",
|
||||
completed: "مكتمل",
|
||||
successMessage: "CRISPY! ٩(◕‿◕)۶ الكود يعمل بشكل صحيح.",
|
||||
keepTrying: "استمر في المحاولة!",
|
||||
failedToLoad: "فشل تحميل الوحدات. قم بتحديث الصفحة.",
|
||||
tailwindPlaceholder: "أدخل فئات Tailwind (مثل bg-blue-500 text-white p-4)",
|
||||
lessonFallback: "درس {index}",
|
||||
untitledLesson: "درس بدون عنوان"
|
||||
},
|
||||
|
||||
// Ukrainian
|
||||
uk: {
|
||||
// Page
|
||||
pageTitle: "Code Crispies - Вивчай HTML та CSS інтерактивно",
|
||||
skipLink: "Перейти до основного вмісту",
|
||||
|
||||
// Header
|
||||
menuOpen: "Відкрити меню",
|
||||
langSwitch: "EN",
|
||||
langSwitchLabel: "Switch language: English",
|
||||
help: "Допомога",
|
||||
|
||||
// Instructions
|
||||
loading: "Завантаження...",
|
||||
selectLesson: "Оберіть урок, щоб почати.",
|
||||
editorLabel: "Редактор CSS",
|
||||
undoTitle: "Скасувати (Ctrl+Z)",
|
||||
redoTitle: "Повторити (Ctrl+Shift+Z)",
|
||||
resetCodeTitle: "Відновити початковий код",
|
||||
run: "Запустити",
|
||||
rerun: "Запустити знову",
|
||||
|
||||
// Preview
|
||||
yourOutput: "Ваш результат",
|
||||
showExpected: "Показати очікуване",
|
||||
hideExpected: "Сховати очікуване",
|
||||
previous: "Попередній",
|
||||
next: "Наступний",
|
||||
levelIndicator: "Урок {current} з {total}",
|
||||
lessonLabel: "Урок",
|
||||
|
||||
// Sidebar
|
||||
menu: "Меню",
|
||||
closeMenu: "Закрити меню",
|
||||
progress: "Прогрес",
|
||||
progressText: "{percent}% завершено ({completed}/{total})",
|
||||
lessons: "Уроки",
|
||||
settings: "Налаштування",
|
||||
showHints: "Показувати підказки",
|
||||
resetAllProgress: "Скинути весь прогрес",
|
||||
openSource: "Відкритий код:",
|
||||
by: "від",
|
||||
|
||||
// Help dialog
|
||||
helpTitle: "Допомога",
|
||||
aboutTitle: "Про Code Crispies",
|
||||
aboutText: "Code Crispies — це безкоштовна платформа з відкритим кодом для вивчення веб-розробки через практичні вправи. Обліковий запис не потрібен — просто починайте кодувати!",
|
||||
learningModesTitle: "Режими навчання",
|
||||
modeCss: "<strong>CSS</strong> - Пишіть правила CSS для стилізації елементів",
|
||||
modeTailwind: "<strong>Tailwind</strong> - Застосовуйте утилітарні класи безпосередньо в HTML",
|
||||
modeHtml: "<strong>HTML</strong> - Практикуйте семантичну розмітку та нативні елементи",
|
||||
gettingStartedTitle: "Початок роботи",
|
||||
gettingStartedText: "Відкрийте меню (☰), щоб переглянути модулі уроків. Кожен модуль охоплює конкретну тему з прогресивними вправами.",
|
||||
completingLessonsTitle: "Завершення уроків",
|
||||
completingStep1: "Прочитайте інструкції завдання зліва",
|
||||
completingStep2: "Напишіть свій код у редакторі",
|
||||
completingStep3: "Спостерігайте за попереднім переглядом під час введення",
|
||||
completingStep4: "Слідуйте підказкам, щоб виправити проблеми",
|
||||
completingStep5: "Натисніть <strong>Наступний</strong> після завершення",
|
||||
editorToolsTitle: "Інструменти редактора",
|
||||
editorToolUndo: "<strong>↶ Скасувати</strong> / <strong>↷ Повторити</strong> - Навігація історією редагування",
|
||||
editorToolReset: "<strong>⟲ Скинути</strong> - Відновити початковий код",
|
||||
editorToolExpected: "<strong>Показати очікуване</strong> - Перемкнути накладення цільового результату",
|
||||
keyboardShortcutsTitle: "Гарячі клавіші",
|
||||
shortcutRun: "<kbd>Ctrl+Enter</kbd> - Негайна перевірка",
|
||||
shortcutUndo: "<kbd>Ctrl+Z</kbd> - Скасувати",
|
||||
shortcutRedo: "<kbd>Ctrl+Shift+Z</kbd> - Повторити",
|
||||
emmetTitle: "Скорочення Emmet (режим HTML)",
|
||||
emmetText: "Введіть скорочення та натисніть <kbd>Tab</kbd> для розгортання:",
|
||||
emmetClass: "<kbd>div.box</kbd> → div з класом",
|
||||
emmetChildren: "<kbd>ul>li*3</kbd> → ul з 3 дочірніми li",
|
||||
emmetNested: "<kbd>form>input+button</kbd> → вкладена структура",
|
||||
emmetContent: "<kbd>p{Привіт}</kbd> → p з текстовим вмістом",
|
||||
|
||||
// More Projects
|
||||
moreProjectsTitle: "Більше проектів",
|
||||
htmlOverJsDesc: " - Навчіться використовувати нативні HTML-елементи замість власних JavaScript-рішень",
|
||||
mandalaDesc: " - Інтерактивна візуалізація JavaScript-технологій, впорядкованих за складністю",
|
||||
|
||||
// Contact
|
||||
contactTitle: "Контакти та посилання",
|
||||
contactText: "Code Crispies розроблено <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a>",
|
||||
|
||||
// Reset dialog
|
||||
resetDialogTitle: "Скинути прогрес",
|
||||
resetDialogText: "Ви впевнені, що хочете скинути весь свій прогрес? Цю дію неможливо скасувати.",
|
||||
cancel: "Скасувати",
|
||||
resetAll: "Скинути все",
|
||||
|
||||
// Dynamic content
|
||||
loadingFallbackText: "Не вдалося завантажити урок. Виберіть один з меню або перевірте допомогу.",
|
||||
completed: "Завершено",
|
||||
successMessage: "CRISPY! ٩(◕‿◕)۶ Ваш код працює правильно.",
|
||||
keepTrying: "Продовжуйте спроби!",
|
||||
failedToLoad: "Не вдалося завантажити модулі. Оновіть сторінку.",
|
||||
tailwindPlaceholder: "Введіть класи Tailwind (напр. bg-blue-500 text-white p-4)",
|
||||
lessonFallback: "Урок {index}",
|
||||
untitledLesson: "Урок без назви"
|
||||
}
|
||||
};
|
||||
|
||||
let currentLang = "en";
|
||||
|
||||
// Available languages in cycle order
|
||||
const availableLanguages = ["en", "de", "pl", "es", "ar", "uk"];
|
||||
|
||||
/**
|
||||
* Get array of available language codes
|
||||
*/
|
||||
export function getAvailableLanguages() {
|
||||
return availableLanguages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next language in the cycle
|
||||
* @param {string} currentLang - Current language code
|
||||
* @returns {string} Next language code
|
||||
*/
|
||||
export function getNextLanguage(current = currentLang) {
|
||||
const currentIndex = availableLanguages.indexOf(current);
|
||||
const nextIndex = (currentIndex + 1) % availableLanguages.length;
|
||||
return availableLanguages[nextIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect initial language from localStorage or browser
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user