tractatus/public/js/i18n-simple.js
TheFlow 9d8fe404df chore: update dependencies and documentation
Update project dependencies, documentation, and supporting files:
- i18n improvements for multilingual support
- Admin dashboard enhancements
- Documentation updates for Koha/Stripe and deployment
- Server middleware and model updates
- Package dependency updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 12:48:37 +13:00

198 lines
5.2 KiB
JavaScript

/**
* 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',
'/about/values.html': 'values',
'/about/values': 'values',
'/faq.html': 'faq',
'/koha.html': 'koha',
'/koha/transparency.html': 'transparency',
'/koha/transparency': 'transparency'
};
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
// Using innerHTML to preserve formatting like <em>, <strong>, <a> tags in translations
document.querySelectorAll('[data-i18n]').forEach(el => {
const key = el.dataset.i18n;
const translation = this.t(key);
if (typeof translation === 'string') {
el.innerHTML = translation;
}
});
// Handle data-i18n-html for HTML content (kept for backward compatibility)
document.querySelectorAll('[data-i18n-html]').forEach(el => {
const key = el.dataset.i18nHtml;
const translation = this.t(key);
if (typeof translation === 'string') {
el.innerHTML = translation;
}
});
// Handle data-i18n-placeholder for input placeholders
document.querySelectorAll('[data-i18n-placeholder]').forEach(el => {
const key = el.dataset.i18nPlaceholder;
const translation = this.t(key);
if (typeof translation === 'string') {
el.placeholder = 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;