261 lines
8.6 KiB
JavaScript
261 lines
8.6 KiB
JavaScript
/**
|
|
* 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;
|