266 lines
8.5 KiB
JavaScript
266 lines
8.5 KiB
JavaScript
/**
|
|
* Localization Module for AI Interactive Fiction
|
|
* Handles translations and locale settings
|
|
*/
|
|
import { BaseModule } from './base-module.js';
|
|
import { moduleRegistry } from './module-registry.js';
|
|
|
|
class LocalizationModule extends BaseModule {
|
|
/**
|
|
* Create a new localization module
|
|
*/
|
|
constructor() {
|
|
super('localization', 'Localization');
|
|
|
|
// Current locale
|
|
this.currentLocale = 'en-us';
|
|
|
|
// Available translations
|
|
this.translations = {};
|
|
|
|
// Language names mapping
|
|
this.languageNames = {
|
|
'en-us': 'English (US)',
|
|
'en-gb': 'English (UK)',
|
|
'de': 'Deutsch',
|
|
'de-de': 'Deutsch (Deutschland)',
|
|
'fr': 'Français',
|
|
'fr-fr': 'Français (France)',
|
|
'es': 'Español',
|
|
'es-es': 'Español (España)',
|
|
'it': 'Italiano',
|
|
'ja': 'Japanese',
|
|
'ko': 'Korean',
|
|
'zh': 'Chinese (Simplified)',
|
|
'zh-tw': 'Chinese (Traditional)',
|
|
'ru': 'Russian',
|
|
'pt': 'Portuguese',
|
|
'pt-br': 'Portuguese (Brazil)'
|
|
};
|
|
|
|
// 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 translations
|
|
await this.loadTranslations('en-us');
|
|
|
|
// Try to load browser locale if available
|
|
const browserLocale = navigator.language.toLowerCase();
|
|
if (browserLocale && browserLocale !== 'en-us') {
|
|
try {
|
|
this.reportProgress(50, `Loading browser locale: ${browserLocale}`);
|
|
await this.loadTranslations(browserLocale);
|
|
this.currentLocale = browserLocale;
|
|
} catch (localeError) {
|
|
console.warn(`Failed to load browser locale ${browserLocale}:`, localeError);
|
|
}
|
|
}
|
|
|
|
// We don't check for persistence manager here to avoid circular dependency
|
|
// The persistence manager will update our locale after it initializes if needed
|
|
|
|
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 {
|
|
// Try to load the language part only
|
|
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 {
|
|
// Fallback to English
|
|
if (normalizedLocale !== 'en-us' && normalizedLocale !== 'en') {
|
|
await this.loadTranslations('en-us');
|
|
this.translations[normalizedLocale] = this.translations['en-us'];
|
|
} else {
|
|
// If English is not found, create an empty translation set
|
|
console.warn('English translations not found, using empty set');
|
|
this.translations[normalizedLocale] = {};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} 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();
|
|
|
|
// Register with the module registry
|
|
moduleRegistry.register(Localization);
|
|
|
|
// Export the module
|
|
export { Localization };
|