/** * Localization Module for AI Interactive Fiction * Handles translations and locale settings */ import { BaseModule } from './base-module.js'; import { moduleRegistry } from './module-registry.js'; class LocalizationModule extends BaseModule { /** * Create a new localization module */ constructor() { super('localization', 'Localization'); // Current locale this.translations = {}; this.defaultLocale = 'en-us'; this.currentLocale = this.defaultLocale; this.dependencies = ['persistence-manager']; // Available translations this.languageNames = { 'en-us': 'English (US)', 'en-gb': 'English (UK)', 'de-de': 'Deutsch (Deutschland)' }; // Bind methods this.bindMethods([ 'setLocale', 'getLocale', 'translate', 'getAvailableLocales', 'getLanguageName', 'getLanguage' ]); } /** * Initialize the module * @returns {Promise} - Resolves with success status */ async initialize() { try { this.reportProgress(10, "Initializing localization"); // Load default English locale await this.loadTranslations('en-us'); this.reportProgress(50, "Loaded default locale"); // Get stored locale from persistence manager if available const persistenceManager = this.getModule('persistence-manager'); if (persistenceManager) { const storedLocale = persistenceManager.getPreference('app', 'locale'); if (storedLocale) { console.log(`Localization: Found stored locale: ${storedLocale}`); await this.loadTranslations(storedLocale); this.currentLocale = storedLocale; this.reportProgress(80, `Loaded stored locale: ${storedLocale}`); } else { // If no stored locale, ensure en-us is the default and persist it console.log('Localization: No stored locale found, using default en-us'); persistenceManager.updatePreference('app', 'locale', 'en-us'); persistenceManager.updatePreference('tts', 'language', 'en-us'); this.currentLocale = 'en-us'; this.reportProgress(80, "Using default locale: en-us"); } } else { console.log('Localization: Persistence manager not available, using default en-us locale'); this.reportProgress(80, "Using default locale: en-us"); } // Dispatch event to notify about loaded locale document.dispatchEvent(new CustomEvent('localization:languageChanged', { detail: { locale: this.currentLocale } })); this.reportProgress(100, "Localization ready"); return true; } catch (error) { console.error("Error initializing localization:", error); this.reportProgress(100, "Localization failed"); return false; } } /** * Load translations for a locale * @param {string} locale - Locale to load * @returns {Promise} */ async loadTranslations(locale) { if (this.translations[locale]) { return; // Already loaded } try { // Normalize locale const normalizedLocale = locale.toLowerCase(); // Try to load the exact locale const response = await fetch(`/locales/${normalizedLocale}.json`); if (response.ok) { const translations = await response.json(); this.translations[normalizedLocale] = translations; } else { // Don't try to load language part without region (e.g., "en") - we only support full locales // Fallback to en-us if the requested locale isn't found if (normalizedLocale !== 'en-us') { console.warn(`Translations for ${normalizedLocale} not found, falling back to en-us`); await this.loadTranslations('en-us'); this.translations[normalizedLocale] = this.translations['en-us']; } else { // If en-us is not found, create an empty translation set console.warn('English translations not found, using empty set'); this.translations[normalizedLocale] = {}; } } } catch (error) { console.error(`Error loading translations for ${locale}:`, error); throw error; } } /** * Set the current locale * @param {string} locale - Locale to set * @returns {Promise} - Success status */ async setLocale(locale) { if (!locale) return false; try { // Normalize locale const normalizedLocale = locale.toLowerCase(); // Load translations if not already loaded if (!this.translations[normalizedLocale]) { await this.loadTranslations(normalizedLocale); } // Set current locale this.currentLocale = normalizedLocale; // Update persistence const persistenceManager = this.getModule('persistence-manager'); if (persistenceManager) { persistenceManager.updatePreference('app', 'locale', normalizedLocale); } // Dispatch locale change event this.dispatchEvent('locale-changed', { locale: normalizedLocale }); return true; } catch (error) { console.error(`Error setting locale to ${locale}:`, error); return false; } } /** * Get the current locale * @returns {string} - Current locale */ getLocale() { return this.currentLocale; } /** * Get the language part of the current locale (e.g., 'en' from 'en-us') * @returns {string} - Language code */ getLanguage() { return this.currentLocale.split('-')[0]; } /** * Get all available locales * @returns {Array} - Array of locale codes */ getAvailableLocales() { // Return the keys of the language names object // This is a simplification - in a real app, we would dynamically load available locales return Object.keys(this.languageNames); } /** * Get the language name for a locale * @param {string} locale - Locale code * @returns {string} - Language name */ getLanguageName(locale) { if (!locale) return ''; // Normalize locale const normalizedLocale = locale.toLowerCase(); // Try exact match if (this.languageNames[normalizedLocale]) { return this.languageNames[normalizedLocale]; } // Try language part only const langPart = normalizedLocale.split('-')[0]; if (this.languageNames[langPart]) { return this.languageNames[langPart]; } // Fallback: return the locale code itself return locale; } /** * Translate a key * @param {string} key - Translation key * @param {Object} params - Parameters for interpolation * @returns {string} - Translated text */ translate(key, params = {}) { if (!key) return ''; // Get translations for current locale const translations = this.translations[this.currentLocale] || {}; // Get translation or fallback to key let translation = translations[key] || key; // Interpolate parameters if (params && Object.keys(params).length > 0) { for (const [param, value] of Object.entries(params)) { translation = translation.replace(new RegExp(`\{\{${param}\}\}`, 'g'), value); } } return translation; } /** * Clean up when module is disposed */ dispose() { // Clear translations this.translations = {}; } } // Create the singleton instance const Localization = new LocalizationModule(); // Register with the module registry moduleRegistry.register(Localization); // Export the module export { Localization };