UNPKG

besper-frontend-site-dev-main

Version:

Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment

331 lines (291 loc) 9.43 kB
/** * Management Interface I18n Service * Enhanced internationalization service for the bot management interface */ import { managementTranslations } from './managementTranslations.js'; export class ManagementI18nService { constructor() { this.currentLanguage = 'en'; this.translations = managementTranslations; this.supportedLanguages = ['en', 'de']; this.fallbackLanguage = 'en'; // Auto-detect and set browser language this.detectAndSetLanguage(); // Language change callbacks this.languageChangeCallbacks = new Set(); } /** * Detect browser language and set it if supported */ detectAndSetLanguage() { try { // Get browser language preference const browserLang = this.getBrowserLanguage(); // Set language if supported if (this.supportedLanguages.includes(browserLang)) { this.currentLanguage = browserLang; console.log( `[Management i18n] Language set to: ${browserLang} (browser detected)` ); } else { console.log( `[Management i18n] Browser language ${browserLang} not supported, using: ${this.fallbackLanguage}` ); this.currentLanguage = this.fallbackLanguage; } } catch (error) { console.warn('[Management i18n] Language detection failed:', error); this.currentLanguage = this.fallbackLanguage; } } /** * Get browser language with enhanced detection */ getBrowserLanguage() { let language = 'en'; if (typeof navigator !== 'undefined') { // Try multiple sources for better compatibility language = navigator.language || navigator.userLanguage || navigator.browserLanguage || navigator.systemLanguage || 'en'; // Handle language arrays (some browsers provide multiple preferences) if ( Array.isArray(navigator.languages) && navigator.languages.length > 0 ) { language = navigator.languages[0]; } } // Extract language code (remove country code) return language.split('-')[0].toLowerCase(); } /** * Set current language * @param {string} language - Language code ('en', 'de') * @returns {boolean} Success status */ setLanguage(language) { if (!this.supportedLanguages.includes(language)) { console.warn(`[Management i18n] Language '${language}' not supported`); return false; } const previousLanguage = this.currentLanguage; this.currentLanguage = language; console.log( `[Management i18n] Language changed from '${previousLanguage}' to '${language}'` ); // Notify listeners about language change this.notifyLanguageChange(language, previousLanguage); return true; } /** * Get current language * @returns {string} Current language code */ getCurrentLanguage() { return this.currentLanguage; } /** * Get supported languages with display names * @returns {Array} Array of language objects with code and name */ getSupportedLanguages() { return this.supportedLanguages.map(code => ({ code, name: this.getLanguageDisplayName(code), nativeName: this.getLanguageNativeName(code), })); } /** * Get language display name in English * @param {string} langCode - Language code * @returns {string} Display name */ getLanguageDisplayName(langCode) { const names = { en: 'English', de: 'German', }; return names[langCode] || langCode.toUpperCase(); } /** * Get language native name * @param {string} langCode - Language code * @returns {string} Native name */ getLanguageNativeName(langCode) { const nativeNames = { en: 'English', de: 'Deutsch', }; return nativeNames[langCode] || langCode.toUpperCase(); } /** * Translate a key with variable substitution * @param {string} key - Translation key (supports dot notation like 'tabs.general') * @param {Object} variables - Variables for substitution (e.g., {count: 5}) * @returns {string} Translated text */ t(key, variables = {}) { // Get translation from current language let translation = this.getNestedTranslation(this.currentLanguage, key); // Fallback to default language if not found if (translation === undefined) { translation = this.getNestedTranslation(this.fallbackLanguage, key); } // Final fallback to the key itself if (translation === undefined) { console.warn(`[Management i18n] Translation missing for key: ${key}`); translation = key; } // Perform variable substitution if (typeof translation === 'string' && Object.keys(variables).length > 0) { translation = this.substituteVariables(translation, variables); } return translation; } /** * Get nested translation using dot notation * @param {string} language - Language code * @param {string} key - Dot notation key (e.g., 'tabs.general') * @returns {string|undefined} Translation or undefined if not found */ getNestedTranslation(language, key) { const langTranslations = this.translations[language]; if (!langTranslations) return undefined; // Split key by dots and traverse object const keys = key.split('.'); let current = langTranslations; for (const k of keys) { if (current && typeof current === 'object' && k in current) { current = current[k]; } else { return undefined; } } return current; } /** * Substitute variables in translation string * @param {string} translation - Translation with variables like {{variable}} * @param {Object} variables - Variables to substitute * @returns {string} Translation with substituted variables */ substituteVariables(translation, variables) { return translation.replace(/\{\{(\w+)\}\}/g, (match, variableName) => { return variables[variableName] !== undefined ? variables[variableName] : match; }); } /** * Check if a translation exists * @param {string} key - Translation key * @returns {boolean} True if translation exists */ hasTranslation(key) { return ( this.getNestedTranslation(this.currentLanguage, key) !== undefined || this.getNestedTranslation(this.fallbackLanguage, key) !== undefined ); } /** * Add language change listener * @param {Function} callback - Callback function (newLang, oldLang) => {} */ onLanguageChange(callback) { if (typeof callback === 'function') { this.languageChangeCallbacks.add(callback); } } /** * Remove language change listener * @param {Function} callback - Callback function to remove */ offLanguageChange(callback) { this.languageChangeCallbacks.delete(callback); } /** * Notify all listeners about language change * @param {string} newLanguage - New language code * @param {string} oldLanguage - Previous language code */ notifyLanguageChange(newLanguage, oldLanguage) { this.languageChangeCallbacks.forEach(callback => { try { callback(newLanguage, oldLanguage); } catch (error) { console.error( '[Management i18n] Error in language change callback:', error ); } }); } /** * Get all translations for a category in current language * @param {string} category - Category key (e.g., 'tabs', 'common') * @returns {Object} Object with all translations in category */ getCategory(category) { const categoryTranslations = this.getNestedTranslation(this.currentLanguage, category) || this.getNestedTranslation(this.fallbackLanguage, category) || {}; return { ...categoryTranslations }; } /** * Create a translation function bound to a specific prefix * @param {string} prefix - Translation key prefix (e.g., 'configuration') * @returns {Function} Translation function for the prefix */ createScopedTranslator(prefix) { return (key, variables = {}) => { const fullKey = prefix ? `${prefix}.${key}` : key; return this.t(fullKey, variables); }; } /** * Get language direction (for future RTL support) * @param {string} langCode - Language code * @returns {string} 'ltr' or 'rtl' */ getLanguageDirection(langCode = this.currentLanguage) { // Currently all supported languages are LTR // This can be expanded for RTL languages like Arabic const rtlLanguages = ['ar', 'he', 'fa']; return rtlLanguages.includes(langCode) ? 'rtl' : 'ltr'; } /** * Format number according to current language * @param {number} number - Number to format * @returns {string} Formatted number */ formatNumber(number) { try { return new Intl.NumberFormat(this.currentLanguage).format(number); } catch (error) { return number.toString(); } } /** * Format date according to current language * @param {Date} date - Date to format * @param {Object} options - Intl.DateTimeFormat options * @returns {string} Formatted date */ formatDate(date, options = {}) { try { return new Intl.DateTimeFormat(this.currentLanguage, options).format( date ); } catch (error) { return date.toString(); } } } // Create and export default instance export const managementI18n = new ManagementI18nService();