/** * Internationalization module for Code Crispies */ const translations = { en: { // Page pageTitle: "CODE CRISPIES - Learn CSS Interactively", skipLink: "Skip to main content", // Header menuOpen: "Open menu", langSwitch: "DE", langSwitchLabel: "Sprache wechseln: Deutsch", help: "Help", // Instructions loading: "Loading...", selectLesson: "Please select a lesson to begin.", editorLabel: "CSS Editor", undoTitle: "Undo (Ctrl+Z)", redoTitle: "Redo (Ctrl+Shift+Z)", resetCodeTitle: "Reset to initial code", run: "Run", rerun: "Re-run", // Preview yourOutput: "Your Output", showExpected: "Show Expected", hideExpected: "Hide Expected", previous: "Previous", next: "Next", levelIndicator: "Lesson {current} of {total}", lessonLabel: "Lesson", // Sidebar menu: "Menu", closeMenu: "Close menu", progress: "Progress", progressText: "{percent}% Complete ({completed}/{total})", lessons: "Lessons", settings: "Settings", showHints: "Show Hints", resetAllProgress: "Reset All Progress", openSource: "Open Source:", by: "by", // Help dialog helpTitle: "Help", aboutTitle: "About Code Crispies", aboutText: "Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required - just start coding!", learningModesTitle: "Learning Modes", modeCss: "CSS - Write CSS rules to style elements", modeTailwind: "Tailwind - Apply utility classes directly in HTML", modeHtml: "HTML - Practice semantic markup and native elements", gettingStartedTitle: "Getting Started", gettingStartedText: "Open the menu (☰) to browse lesson modules. Each module covers a specific topic with progressive exercises.", completingLessonsTitle: "Completing Lessons", completingStep1: "Read the task instructions on the left", completingStep2: "Write your code in the editor", completingStep3: "Watch the live preview update as you type", completingStep4: "Follow hints to fix any issues", completingStep5: "Click Next when complete", editorToolsTitle: "Editor Tools", editorToolUndo: "↶ Undo / ↷ Redo - Navigate edit history", editorToolReset: "⟲ Reset - Restore initial code for current lesson", editorToolExpected: "Show Expected - Toggle the target result overlay", keyboardShortcutsTitle: "Keyboard Shortcuts", shortcutRun: "Ctrl+Enter - Validate immediately", shortcutUndo: "Ctrl+Z - Undo", shortcutRedo: "Ctrl+Shift+Z - Redo", emmetTitle: "Emmet Shortcuts (HTML mode)", emmetText: "Type abbreviations and press Tab to expand:", emmetClass: "div.box → div with class", emmetChildren: "ul>li*3 → ul with 3 li children", emmetNested: "form>input+button → nested structure", emmetContent: "p{Hello} → p with text content", // Reset dialog resetDialogTitle: "Reset Progress", resetDialogText: "Are you sure you want to reset all your progress? This cannot be undone.", cancel: "Cancel", resetAll: "Reset All", // Dynamic content completed: "Completed", successMessage: "CRISPY! ٩(◕‿◕)۶ Your code works correctly.", keepTrying: "Keep trying!", failedToLoad: "Failed to load modules. Please refresh the page.", tailwindPlaceholder: "Enter Tailwind classes (e.g., bg-blue-500 text-white p-4)", lessonFallback: "Lesson {index}", untitledLesson: "Untitled Lesson" }, de: { // Page pageTitle: "CODE CRISPIES - CSS interaktiv lernen", skipLink: "Zum Hauptinhalt springen", // Header menuOpen: "Menü öffnen", langSwitch: "EN", langSwitchLabel: "Switch language: English", help: "Hilfe", // Instructions loading: "Laden...", selectLesson: "Bitte wähle eine Lektion aus, um zu beginnen.", editorLabel: "CSS-Editor", undoTitle: "Rückgängig (Strg+Z)", redoTitle: "Wiederholen (Strg+Umschalt+Z)", resetCodeTitle: "Auf Anfangscode zurücksetzen", run: "Ausführen", rerun: "Erneut anwenden", // Preview yourOutput: "Deine Ausgabe", showExpected: "Lösung zeigen", hideExpected: "Lösung ausblenden", previous: "Zurück", next: "Weiter", levelIndicator: "Lektion {current} von {total}", lessonLabel: "Lektion", // Sidebar menu: "Menü", closeMenu: "Menü schließen", progress: "Fortschritt", progressText: "{percent}% abgeschlossen ({completed}/{total})", lessons: "Lektionen", settings: "Einstellungen", showHints: "Hinweise anzeigen", resetAllProgress: "Fortschritt zurücksetzen", openSource: "Open Source:", by: "von", // Help dialog helpTitle: "Hilfe", aboutTitle: "Über Code Crispies", aboutText: "Code Crispies ist eine kostenlose Open-Source-Plattform zum Erlernen von Webentwicklung durch praktische Übungen. Kein Konto erforderlich - einfach loslegen!", learningModesTitle: "Lernmodi", modeCss: "CSS - Schreibe CSS-Regeln zum Stylen von Elementen", modeTailwind: "Tailwind - Wende Utility-Klassen direkt im HTML an", modeHtml: "HTML - Übe semantisches Markup und native Elemente", gettingStartedTitle: "Erste Schritte", gettingStartedText: "Öffne das Menü (☰), um Lektionsmodule zu durchsuchen. Jedes Modul behandelt ein Thema mit aufeinander aufbauenden Übungen.", completingLessonsTitle: "Lektionen abschließen", completingStep1: "Lies die Aufgabenstellung auf der linken Seite", completingStep2: "Schreibe deinen Code im Editor", completingStep3: "Beobachte die Live-Vorschau während du tippst", completingStep4: "Folge den Hinweisen, um Fehler zu beheben", completingStep5: "Klicke auf Weiter, wenn du fertig bist", editorToolsTitle: "Editor-Werkzeuge", editorToolUndo: "↶ Rückgängig / ↷ Wiederholen - Bearbeitungsverlauf navigieren", editorToolReset: "⟲ Zurücksetzen - Ursprünglichen Code wiederherstellen", editorToolExpected: "Lösung zeigen - Zielergebnis ein-/ausblenden", keyboardShortcutsTitle: "Tastenkürzel", shortcutRun: "Strg+Enter - Sofort validieren", shortcutUndo: "Strg+Z - Rückgängig", shortcutRedo: "Strg+Umschalt+Z - Wiederholen", emmetTitle: "Emmet-Kürzel (HTML-Modus)", emmetText: "Tippe Abkürzungen und drücke Tab zum Erweitern:", emmetClass: "div.box → div mit Klasse", emmetChildren: "ul>li*3 → ul mit 3 li-Kindern", emmetNested: "form>input+button → verschachtelte Struktur", emmetContent: "p{Hallo} → p mit Textinhalt", // Reset dialog resetDialogTitle: "Fortschritt zurücksetzen", resetDialogText: "Bist du sicher, dass du deinen gesamten Fortschritt zurücksetzen möchtest? Dies kann nicht rückgängig gemacht werden.", cancel: "Abbrechen", resetAll: "Alles zurücksetzen", // Dynamic content completed: "Erledigt", successMessage: "CRISPY! ٩(◕‿◕)۶ Dein Code funktioniert.", keepTrying: "Weiter versuchen!", failedToLoad: "Module konnten nicht geladen werden. Bitte Seite neu laden.", tailwindPlaceholder: "Tailwind-Klassen eingeben (z.B. bg-blue-500 text-white p-4)", lessonFallback: "Lektion {index}", untitledLesson: "Unbenannte Lektion" } }; let currentLang = "en"; /** * Detect initial language from localStorage or browser */ export function detectLanguage() { // Check localStorage first const stored = localStorage.getItem("codeCrispies.language"); if (stored && translations[stored]) { return stored; } // Check browser language const browserLang = navigator.language.split("-")[0]; if (translations[browserLang]) { return browserLang; } return "en"; } /** * Get current language */ export function getLanguage() { return currentLang; } /** * Set language and persist to localStorage */ export function setLanguage(lang) { if (!translations[lang]) { console.warn(`Language "${lang}" not supported, falling back to English`); lang = "en"; } currentLang = lang; localStorage.setItem("codeCrispies.language", lang); document.documentElement.lang = lang; } /** * Get a translation by key with optional interpolation * @param {string} key - Translation key * @param {Object} params - Optional parameters for interpolation */ export function t(key, params = {}) { const text = translations[currentLang]?.[key] || translations.en[key] || key; if (Object.keys(params).length === 0) { return text; } // Replace {param} placeholders return text.replace(/\{(\w+)\}/g, (match, param) => { return params[param] !== undefined ? params[param] : match; }); } /** * Apply translations to all elements with data-i18n attribute */ export function applyTranslations() { // Update page title document.title = t("pageTitle"); // Update elements with data-i18n attribute (text content) document.querySelectorAll("[data-i18n]").forEach((el) => { const key = el.dataset.i18n; el.textContent = t(key); }); // Update elements with data-i18n-html attribute (innerHTML) document.querySelectorAll("[data-i18n-html]").forEach((el) => { const key = el.dataset.i18nHtml; el.innerHTML = t(key); }); // Update elements with data-i18n-title attribute document.querySelectorAll("[data-i18n-title]").forEach((el) => { const key = el.dataset.i18nTitle; el.title = t(key); }); // Update elements with data-i18n-aria-label attribute document.querySelectorAll("[data-i18n-aria-label]").forEach((el) => { const key = el.dataset.i18nAriaLabel; el.setAttribute("aria-label", t(key)); }); // Update elements with data-i18n-placeholder attribute document.querySelectorAll("[data-i18n-placeholder]").forEach((el) => { const key = el.dataset.i18nPlaceholder; el.placeholder = t(key); }); } /** * Initialize i18n system */ export function initI18n() { const lang = detectLanguage(); setLanguage(lang); applyTranslations(); }