255 lines
8.3 KiB
JavaScript
255 lines
8.3 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)',
|
|
'en-gb': 'English (UK)',
|
|
'de-de': 'Deutsch (Deutschland)'
|
|
};
|
|
|
|
// Bind methods
|
|
this.bindMethods([
|
|
'setLocale',
|
|
'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('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<void>}
|
|
*/
|
|
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 {
|
|
// 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) {
|
|
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<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 = 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();
|
|
|
|
// Export the module
|
|
export { Localization };
|