/** * Simple i18n system for Tractatus * Supports: en (English), de (German), fr (French), mi (Te Reo Māori - coming soon) */ const I18n = { currentLang: 'en', translations: {}, supportedLanguages: ['en', 'de', 'fr'], async init() { // 1. Detect language preference this.currentLang = this.detectLanguage(); // 2. Load translations await this.loadTranslations(this.currentLang); // 3. Apply to page this.applyTranslations(); // 4. Update language selector if present this.updateLanguageSelector(); console.log(`[i18n] Initialized with language: ${this.currentLang}`); }, detectLanguage() { // Check localStorage first const saved = localStorage.getItem('tractatus-lang'); if (saved && this.supportedLanguages.includes(saved)) { return saved; } // Check browser language const browserLang = (navigator.language || navigator.userLanguage).split('-')[0]; if (this.supportedLanguages.includes(browserLang)) { return browserLang; } // Default to English return 'en'; }, detectPageName() { // Try to get page name from data attribute first const pageAttr = document.documentElement.getAttribute('data-page'); if (pageAttr) { return pageAttr; } // Detect from URL path const path = window.location.pathname; // Map paths to translation file names const pageMap = { '/': 'homepage', '/index.html': 'homepage', '/researcher.html': 'researcher', '/leader.html': 'leader', '/implementer.html': 'implementer', '/about.html': 'about', '/faq.html': 'faq' }; return pageMap[path] || 'homepage'; }, async loadTranslations(lang) { try { const pageName = this.detectPageName(); const response = await fetch(`/locales/${lang}/${pageName}.json`); if (!response.ok) { throw new Error(`Failed to load translations for ${lang}/${pageName}`); } this.translations = await response.json(); console.log(`[i18n] Loaded translations for page: ${pageName}`); } catch (error) { console.error(`[i18n] Error loading translations:`, error); // Fallback to English if loading fails if (lang !== 'en') { this.currentLang = 'en'; await this.loadTranslations('en'); } } }, t(key) { const keys = key.split('.'); let value = this.translations; for (const k of keys) { if (value && typeof value === 'object') { value = value[k]; } else { return key; // Return key if translation not found } } return value || key; }, applyTranslations() { // Find all elements with data-i18n attribute document.querySelectorAll('[data-i18n]').forEach(el => { const key = el.dataset.i18n; const translation = this.t(key); if (typeof translation === 'string') { el.textContent = translation; } }); // Handle data-i18n-html for HTML content document.querySelectorAll('[data-i18n-html]').forEach(el => { const key = el.dataset.i18nHtml; const translation = this.t(key); if (typeof translation === 'string') { el.innerHTML = translation; } }); }, async setLanguage(lang) { if (!this.supportedLanguages.includes(lang)) { console.error(`[i18n] Unsupported language: ${lang}`); return; } // Save preference localStorage.setItem('tractatus-lang', lang); // Update current language this.currentLang = lang; // Reload translations await this.loadTranslations(lang); // Reapply to page this.applyTranslations(); // Update selector this.updateLanguageSelector(); // Update HTML lang attribute document.documentElement.lang = lang; // Dispatch event for language change window.dispatchEvent(new CustomEvent('languageChanged', { detail: { language: lang } })); console.log(`[i18n] Language changed to: ${lang}`); }, updateLanguageSelector() { const selector = document.getElementById('language-selector'); if (selector) { selector.value = this.currentLang; } }, getLanguageName(code) { const names = { 'en': 'English', 'de': 'Deutsch', 'fr': 'Français', 'mi': 'Te Reo Māori' }; return names[code] || code; } }; // Auto-initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => I18n.init()); } else { I18n.init(); } // Make available globally window.I18n = I18n;