tractatus/public/js/i18n-simple.js
TheFlow 44a91e7fcf feat: add case submission portal admin interface and i18n support
Case Submission Portal (Admin Moderation Queue):
- Add statistics endpoint (GET /api/cases/submissions/stats)
- Enhance filtering: status, failure_mode, AI relevance score
- Add sorting options: date, relevance, completeness
- Create admin moderation interface (case-moderation.html)
- Implement CSP-compliant admin UI (no inline event handlers)
- Deploy moderation actions: approve, reject, request-info
- Fix API parameter mapping for different action types

Internationalization (i18n):
- Implement lightweight i18n system (i18n-simple.js, ~5KB)
- Add language selector component with flag emojis
- Create German and French translations for homepage
- Document Te Reo Māori translation requirements
- Add i18n attributes to homepage
- Integrate language selector into navbar

Bug Fixes:
- Fix search button modal display on docs.html (remove conflicting flex class)

Page Enhancements:
- Add dedicated JS modules for researcher, leader, koha pages
- Improve page-specific functionality and interactions

Documentation:
- Add I18N_IMPLEMENTATION_SUMMARY.md (implementation guide)
- Add TE_REO_MAORI_TRANSLATION_REQUIREMENTS.md (cultural sensitivity guide)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-16 14:50:47 +13:00

151 lines
3.7 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';
},
async loadTranslations(lang) {
try {
const response = await fetch(`/locales/${lang}/homepage.json`);
if (!response.ok) {
throw new Error(`Failed to load translations for ${lang}`);
}
this.translations = await response.json();
} 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;
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;