/**
* 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();
}