Split everything up into dynamically loaded modules.

This commit is contained in:
2025-04-04 00:00:43 +00:00
parent 2f7cda4b6d
commit aa29a6fd93
32 changed files with 8768 additions and 3935 deletions
+260
View File
@@ -0,0 +1,260 @@
/**
* 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<boolean>} - 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': '<i><sup>*</sup>click on page or press spacebar to fast forward text animation</i>',
// 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': '<i><sup>*</sup>Klicke auf die Seite oder drücke die Leertaste, um die Textanimation zu beschleunigen</i>',
'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': '<i><sup>*</sup>cliquez sur la page ou appuyez sur la barre d\'espace pour accélérer l\'animation du texte</i>',
'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<string>} - 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;