Files

269 lines
9.1 KiB
JavaScript

/**
* Localization Module for AI Interactive Fiction
* Handles translations and locale settings
*/
import { BaseModule } from './base-module.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)',
'de_DE': 'Deutsch (Deutschland)'
};
// Bind methods
this.bindMethods([
'setLocale',
'normalizeLocale',
'getLocale',
'translate',
'getAvailableLocales',
'getLanguageName',
'getLanguage'
]);
}
/**
* Initialize the module
* @returns {Promise<boolean>} - Resolves with success status
*/
async initialize() {
try {
this.reportProgress(10, "Initializing localization");
// Load default English locale
await this.loadTranslations(this.defaultLocale);
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) {
const normalizedLocale = this.normalizeLocale(storedLocale);
console.log(`Localization: Found stored locale: ${normalizedLocale}`);
await this.loadTranslations(normalizedLocale);
this.currentLocale = normalizedLocale;
this.reportProgress(80, `Loaded stored locale: ${normalizedLocale}`);
} else {
console.log(`Localization: No stored locale found, using default ${this.defaultLocale}`);
this.currentLocale = this.defaultLocale;
this.reportProgress(80, `Using default locale: ${this.defaultLocale}`);
}
} else {
console.log(`Localization: Persistence manager not available, using default ${this.defaultLocale} locale`);
this.reportProgress(80, `Using default locale: ${this.defaultLocale}`);
}
// Dispatch event to notify about loaded locale
document.dispatchEvent(new CustomEvent('localization:languageChanged', {
detail: { locale: this.currentLocale }
}));
document.documentElement.lang = this.currentLocale.replace('_', '-');
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<void>}
*/
async loadTranslations(locale) {
const normalizedLocale = this.normalizeLocale(locale);
if (this.translations[normalizedLocale]) {
return; // Already loaded
}
try {
// 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 {
// If exact locale not found, try to load just the language part
const langPart = normalizedLocale.split('_')[0];
if (langPart !== normalizedLocale) {
const langResponse = await fetch(`/locales/${langPart}.json`);
if (langResponse.ok) {
const translations = await langResponse.json();
this.translations[normalizedLocale] = translations;
} else {
console.warn(`No translations found for ${locale} or ${langPart}`);
}
}
}
} catch (error) {
console.error(`Error loading translations for ${locale}:`, error);
throw error;
}
}
/**
* Set the current locale
* @param {string} locale - Locale to set
* @returns {Promise<boolean>} - Success status
*/
async setLocale(locale, options = {}) {
if (!locale) return false;
try {
const normalizedLocale = this.normalizeLocale(locale);
const userInitiated = options.userInitiated !== false;
// 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);
if (userInitiated) {
persistenceManager.updatePreference('app', 'localeUserOverride', true);
}
}
document.documentElement.lang = normalizedLocale.replace('_', '-');
// Dispatch locale change event
this.dispatchEvent('locale-changed', {
locale: normalizedLocale
});
document.dispatchEvent(new CustomEvent('locale:changed', {
detail: { locale: normalizedLocale }
}));
document.dispatchEvent(new CustomEvent('localization:languageChanged', {
detail: { locale: normalizedLocale }
}));
return true;
} catch (error) {
console.error(`Error setting locale to ${locale}:`, error);
return false;
}
}
normalizeLocale(locale) {
const normalized = String(locale || this.defaultLocale).trim().replace('-', '_').toLowerCase();
if (normalized.startsWith('de')) return 'de_DE';
return 'en_US';
}
/**
* 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<string>} - 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 = this.normalizeLocale(locale);
// 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();
// Export the module
export { Localization };