mirror of
https://github.com/mivodev/mivo.git
synced 2026-01-26 05:25:42 +07:00
123 lines
3.7 KiB
JavaScript
123 lines
3.7 KiB
JavaScript
/**
|
|
* Mivo Module: I18n
|
|
* Internationalization support.
|
|
*/
|
|
class I18n {
|
|
constructor() {
|
|
this.currentLang = localStorage.getItem('mivo_lang') || 'en';
|
|
this.translations = {};
|
|
this.isLoaded = false;
|
|
|
|
// Expose global helper for legacy onclicks
|
|
window.changeLanguage = (lang) => this.loadLanguage(lang);
|
|
|
|
this.ready = this.init();
|
|
}
|
|
|
|
async init() {
|
|
await this.loadLanguage(this.currentLang);
|
|
this.isLoaded = true;
|
|
}
|
|
|
|
async loadLanguage(lang) {
|
|
try {
|
|
const cacheBuster = Date.now();
|
|
const response = await fetch(`/lang/${lang}.json?v=${cacheBuster}`);
|
|
if (!response.ok) throw new Error(`Failed to load language: ${lang}`);
|
|
|
|
this.translations = await response.json();
|
|
this.currentLang = lang;
|
|
localStorage.setItem('mivo_lang', lang);
|
|
|
|
this.applyTranslations();
|
|
|
|
// Dispatch via Mivo Event Bus
|
|
if (window.Mivo) {
|
|
window.Mivo.emit('languageChanged', { lang });
|
|
}
|
|
|
|
// Legacy Event for compatibility
|
|
window.dispatchEvent(new CustomEvent('languageChanged', { detail: { lang } }));
|
|
|
|
document.documentElement.lang = lang;
|
|
} catch (error) {
|
|
console.error('I18n Error:', error);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Extend translations at runtime (e.g. from Plugins)
|
|
* @param {Object} newTranslations - Nested object matching the structure
|
|
*/
|
|
extend(newTranslations) {
|
|
// Deep merge helper
|
|
const deepMerge = (target, source) => {
|
|
for (const key in source) {
|
|
if (source[key] instanceof Object && key in target) {
|
|
Object.assign(source[key], deepMerge(target[key], source[key]));
|
|
}
|
|
}
|
|
Object.assign(target || {}, source);
|
|
return target;
|
|
};
|
|
|
|
this.translations = deepMerge(this.translations, newTranslations);
|
|
|
|
// Re-apply in case the new keys are already present in DOM
|
|
if (this.isLoaded) {
|
|
this.applyTranslations();
|
|
}
|
|
}
|
|
|
|
applyTranslations() {
|
|
document.querySelectorAll('[data-i18n]').forEach(element => {
|
|
const key = element.getAttribute('data-i18n');
|
|
const translation = this.getNestedValue(this.translations, key);
|
|
|
|
if (translation) {
|
|
if (element.tagName === 'INPUT' && element.getAttribute('placeholder')) {
|
|
element.placeholder = translation;
|
|
} else {
|
|
element.innerHTML = translation;
|
|
}
|
|
} else {
|
|
if (this.isLoaded) {
|
|
console.warn(`[i18n] Missing translation for key: "${key}" (lang: ${this.currentLang})`);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
getNestedValue(obj, path) {
|
|
return path.split('.').reduce((acc, part) => acc && acc[part], obj);
|
|
}
|
|
|
|
t(key, params = {}) {
|
|
let text = this.getNestedValue(this.translations, key);
|
|
|
|
if (!text) {
|
|
if (this.isLoaded) console.warn(`[i18n] Missing translation for key: "${key}"`);
|
|
text = key;
|
|
}
|
|
|
|
if (params) {
|
|
Object.keys(params).forEach(param => {
|
|
text = text.replace(new RegExp(`{${param}}`, 'g'), params[param]);
|
|
});
|
|
}
|
|
return text;
|
|
}
|
|
}
|
|
|
|
// Register Module
|
|
if (window.Mivo) {
|
|
window.Mivo.registerModule('I18n', new I18n());
|
|
// Alias for global usage if needed
|
|
window.i18n = window.Mivo.modules.I18n;
|
|
} else {
|
|
// Fallback if Mivo not loaded
|
|
window.i18n = new I18n();
|
|
}
|