Files
orion/static/shared/js/i18n.js
Samir Boulahtit c13eb8d8c2 fix: resolve all architecture validation warnings
JavaScript improvements:
- Add try/catch error handling to all async init() functions
- Move initialization guards before try/catch blocks (JS-005)
- Use centralized logger in i18n.js with silent fallback (JS-001)
- Add loading state to icons-page.js (JS-007)

Payments module structure:
- Add templates/, static/, and locales/ directories (MOD-005)
- Add locale files for en, de, fr, lb (MOD-006)

Architecture validation now passes with 0 errors, 0 warnings, 0 info.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 21:21:03 +01:00

172 lines
5.1 KiB
JavaScript

/**
* JavaScript i18n Support
*
* Loads translations from module locale files for use in JavaScript.
* Provides the same translation interface as the Python/Jinja2 system.
*
* Usage:
* // Initialize with language and modules to preload
* await I18n.init('en', ['catalog', 'orders']);
*
* // Or load modules later
* await I18n.loadModule('inventory');
*
* // Translate
* const message = I18n.t('catalog.messages.product_created');
* const withVars = I18n.t('common.welcome', { name: 'John' });
*/
// Create logger for i18n module (with silent fallback if LogConfig not yet loaded)
const i18nLog = window.LogConfig ? window.LogConfig.createLogger('I18N') : {
warn: () => {}, // Silent fallback - i18n loads early before LogConfig
error: () => {},
info: () => {},
debug: () => {}
};
const I18n = {
_translations: {},
_language: 'en',
_loaded: new Set(),
_loading: new Map(), // Track in-progress loads
/**
* Initialize with language (call once on page load)
* @param {string} language - Language code (en, fr, de, lb)
* @param {string[]} modules - Optional array of modules to preload
*/
async init(language = 'en', modules = []) {
this._language = language;
// Load shared translations first
await this.loadShared();
// Preload any specified modules
if (modules && modules.length > 0) {
await Promise.all(modules.map(m => this.loadModule(m)));
}
},
/**
* Load shared/common translations from static/locales
*/
async loadShared() {
if (this._loaded.has('shared')) return;
try {
const response = await fetch(`/static/locales/${this._language}.json`);
if (response.ok) {
const data = await response.json();
this._translations = { ...this._translations, ...data };
this._loaded.add('shared');
}
} catch (e) {
i18nLog.warn('Failed to load shared translations:', e);
}
},
/**
* Load module-specific translations
* @param {string} module - Module name (e.g., 'catalog', 'orders')
*/
async loadModule(module) {
if (this._loaded.has(module)) return;
try {
const response = await fetch(`/static/modules/${module}/locales/${this._language}.json`);
if (response.ok) {
const data = await response.json();
// Namespace under module code (matching Python i18n behavior)
this._translations[module] = data;
this._loaded.add(module);
}
} catch (e) {
i18nLog.warn(`Failed to load ${module} translations:`, e);
}
},
/**
* Get translation by key path
* @param {string} key - Dot-notation key (e.g., 'catalog.messages.product_created')
* @param {object} vars - Variables for interpolation
* @returns {string} Translated string or key if not found
*/
t(key, vars = {}) {
const keys = key.split('.');
let value = this._translations;
for (const k of keys) {
if (value && typeof value === 'object' && k in value) {
value = value[k];
} else {
i18nLog.warn(`Missing translation: ${key}`);
return key;
}
}
if (typeof value !== 'string') return key;
// Interpolate variables: {name} -> value
return value.replace(/\{(\w+)\}/g, (match, name) => {
return vars[name] !== undefined ? vars[name] : match;
});
},
/**
* Check if a translation key exists
* @param {string} key - Dot-notation key
* @returns {boolean} True if key exists
*/
has(key) {
const keys = key.split('.');
let value = this._translations;
for (const k of keys) {
if (value && typeof value === 'object' && k in value) {
value = value[k];
} else {
return false;
}
}
return typeof value === 'string';
},
/**
* Get current language
* @returns {string} Current language code
*/
getLanguage() {
return this._language;
},
/**
* Change language (reloads all loaded modules)
* @param {string} language - New language code
*/
async setLanguage(language) {
if (language === this._language) return;
try {
const loadedModules = [...this._loaded];
this._language = language;
this._translations = {};
this._loaded.clear();
// Reload all previously loaded modules
for (const module of loadedModules) {
if (module === 'shared') {
await this.loadShared();
} else {
await this.loadModule(module);
}
}
} catch (e) {
i18nLog.error('Failed to change language:', e);
}
}
};
// Export for module usage
if (typeof window !== 'undefined') {
window.I18n = I18n;
}