/** * Localization Module * Manages translations and locale settings for the application */ import { BaseModule } from './base-module.js'; import { moduleRegistry } from './module-registry.js'; class LocalizationModule extends BaseModule { constructor() { super('localization', 'Localization'); this.currentLocale = 'en-us'; // Default locale this.translations = {}; this.observers = new Set(); // Modules that need to be notified of locale changes } /** * Initialize the module * @returns {Promise} - Resolves with success status */ async initialize() { try { // Load translations this.loadTranslations(); // Set global locale for SmartyPants window.locale = this.currentLocale; this.reportProgress(100, "Localization module ready"); return true; } catch (error) { console.error("Error initializing localization module:", error); return false; } } /** * Load all translations */ loadTranslations() { // Add English translations (default) this.addTranslations('en-us', { // UI elements 'by': 'powered by Generative AI', 'title': 'AI Interactive Fiction', 'subtitle': 'An open-world text adventure', 'speech': 'speech', 'speed': 'speed', 'restart': 'restart', 'save': 'save', 'load': 'load', 'prompt': 'What do you want to do next?', 'remark': '*click on page or press spacebar to fast forward text animation', // Tooltips 'title_speech': 'Toggle text to speech', 'title_speech_unavailable': 'Text-to-Speech not available', 'title_restart': 'Restart story from beginning', 'title_save': 'Save progress', 'title_load': 'Reload from save point', // Confirm dialogs 'confirm_restart': 'Are you sure you want to restart the game? All progress will be lost.' }); // Add German translations this.addTranslations('de', { 'by': 'unterstützt durch KI', 'title': 'KI Interaktive Fiktion', 'subtitle': 'Ein Textabenteuer in offener Welt', 'speech': 'Sprache', 'speed': 'Tempo', 'restart': 'Neustart', 'save': 'Speichern', 'load': 'Laden', 'prompt': 'Was möchtest du als nächstes tun?', 'remark': '*Klicke auf die Seite oder drücke die Leertaste, um die Textanimation zu beschleunigen', 'title_speech': 'Text-zu-Sprache umschalten', 'title_speech_unavailable': 'Text-zu-Sprache nicht verfügbar', 'title_restart': 'Geschichte von Anfang an neu starten', 'title_save': 'Fortschritt speichern', 'title_load': 'Von Speicherpunkt neu laden', 'confirm_restart': 'Bist du sicher, dass du das Spiel neu starten möchtest? Der gesamte Fortschritt geht verloren.' }); // Add French translations this.addTranslations('fr', { 'by': 'propulsé par l\'IA', 'title': 'Fiction Interactive IA', 'subtitle': 'Une aventure textuelle en monde ouvert', 'speech': 'parole', 'speed': 'vitesse', 'restart': 'recommencer', 'save': 'sauver', 'load': 'charger', 'prompt': 'Que voulez-vous faire ensuite?', 'remark': '*cliquez sur la page ou appuyez sur la barre d\'espace pour accélérer l\'animation du texte', 'title_speech': 'Activer/désactiver la synthèse vocale', 'title_speech_unavailable': 'Synthèse vocale non disponible', 'title_restart': 'Redémarrer l\'histoire depuis le début', 'title_save': 'Sauvegarder la progression', 'title_load': 'Recharger depuis le point de sauvegarde', 'confirm_restart': 'Êtes-vous sûr de vouloir redémarrer le jeu? Tous les progrès seront perdus.' }); } /** * Add translations for a specific locale * @param {string} locale - Locale code * @param {Object} translations - Translation key-value pairs */ addTranslations(locale, translations) { if (!this.translations[locale]) { this.translations[locale] = {}; } Object.assign(this.translations[locale], translations); } /** * Get translation for a key in current locale * @param {string} key - Translation key * @param {string} [defaultValue] - Default value if translation not found * @returns {string} - Translated text or default value */ translate(key, defaultValue = null) { const localeTranslations = this.translations[this.currentLocale]; if (localeTranslations && localeTranslations[key] !== undefined) { return localeTranslations[key]; } // Fall back to English if translation not found if (this.currentLocale !== 'en-us' && this.translations['en-us'] && this.translations['en-us'][key]) { return this.translations['en-us'][key]; } // Return default value or key if no translation found return defaultValue || key; } /** * Set the current locale * @param {string} locale - Locale code */ setLocale(locale) { if (this.translations[locale]) { this.currentLocale = locale; // Update global locale for SmartyPants window.locale = locale; // Notify observers of locale change this.notifyObservers(); console.log(`Localization: Locale set to ${locale}`); return true; } console.warn(`Localization: Locale ${locale} not available`); return false; } /** * Get the current locale * @returns {string} - Current locale code */ getLocale() { return this.currentLocale; } /** * Register a module to be notified of locale changes * @param {Object} module - Module to register * @param {Function} updateMethod - Method to call on locale change */ registerObserver(module, updateMethod) { if (typeof updateMethod !== 'function') { console.error('Localization: Update method must be a function'); return; } this.observers.add({ module, updateMethod }); } /** * Unregister an observer module * @param {Object} module - Module to unregister */ unregisterObserver(module) { this.observers.forEach(observer => { if (observer.module === module) { this.observers.delete(observer); } }); } /** * Notify all observer modules of locale change */ notifyObservers() { this.observers.forEach(observer => { try { observer.updateMethod(this.currentLocale); } catch (error) { console.error(`Error notifying observer for locale change:`, error); } }); } /** * Get all available locales * @returns {Array} - Array of locale codes */ getAvailableLocales() { return Object.keys(this.translations); } /** * Get all translations for a specific locale * @param {string} locale - Locale code * @returns {Object} - Translations for the locale */ getTranslationsForLocale(locale) { return this.translations[locale] || {}; } /** * Get the current locale's direction (ltr or rtl) * @returns {string} - Text direction ('ltr' or 'rtl') */ getTextDirection() { // List of RTL languages const rtlLocales = ['ar', 'he', 'fa', 'ur']; // Check if current locale starts with any RTL language code for (const rtl of rtlLocales) { if (this.currentLocale.startsWith(rtl)) { return 'rtl'; } } return 'ltr'; } } // Create the singleton instance const Localization = new LocalizationModule(); // Register with the module registry moduleRegistry.register(Localization); // Export the module export { Localization }; // Keep a reference in window for loader system window.Localization = Localization;