mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 14:42:11 -03:00
247 lines
8.3 KiB
JavaScript
247 lines
8.3 KiB
JavaScript
/**
|
|
* Internationalization (i18n) system for LoRA Manager
|
|
* Uses user-selected language from settings with fallback to English
|
|
*/
|
|
|
|
import { en } from './locales/en.js';
|
|
import { zhCN } from './locales/zh-CN.js';
|
|
import { zhTW } from './locales/zh-TW.js';
|
|
import { ru } from './locales/ru.js';
|
|
import { de } from './locales/de.js';
|
|
import { ja } from './locales/ja.js';
|
|
import { ko } from './locales/ko.js';
|
|
import { fr } from './locales/fr.js';
|
|
import { es } from './locales/es.js';
|
|
|
|
class I18nManager {
|
|
constructor() {
|
|
this.locales = {
|
|
'en': en,
|
|
'zh-CN': zhCN,
|
|
'zh-TW': zhTW,
|
|
'zh': zhCN, // Fallback for 'zh' to 'zh-CN'
|
|
'ru': ru,
|
|
'de': de,
|
|
'ja': ja,
|
|
'ko': ko,
|
|
'fr': fr,
|
|
'es': es
|
|
};
|
|
|
|
this.currentLocale = this.getLanguageFromSettings();
|
|
this.translations = this.locales[this.currentLocale] || this.locales['en'];
|
|
}
|
|
|
|
/**
|
|
* Get language from user settings with fallback to English
|
|
* @returns {string} Language code
|
|
*/
|
|
getLanguageFromSettings() {
|
|
// Check localStorage for user-selected language
|
|
const STORAGE_PREFIX = 'lora_manager_';
|
|
let userLanguage = null;
|
|
|
|
try {
|
|
const settings = localStorage.getItem(STORAGE_PREFIX + 'settings');
|
|
if (settings) {
|
|
const parsedSettings = JSON.parse(settings);
|
|
userLanguage = parsedSettings.language;
|
|
}
|
|
} catch (e) {
|
|
console.warn('Failed to parse settings from localStorage:', e);
|
|
}
|
|
|
|
// If user has selected a language, use it
|
|
if (userLanguage && this.locales[userLanguage]) {
|
|
return userLanguage;
|
|
}
|
|
|
|
// Fallback to English
|
|
return 'en';
|
|
}
|
|
|
|
/**
|
|
* Set the current language and save to settings
|
|
* @param {string} languageCode - The language code to set
|
|
* @returns {boolean} True if language was successfully set
|
|
*/
|
|
setLanguage(languageCode) {
|
|
if (!this.locales[languageCode]) {
|
|
console.warn(`Language '${languageCode}' is not supported`);
|
|
return false;
|
|
}
|
|
|
|
this.currentLocale = languageCode;
|
|
this.translations = this.locales[languageCode];
|
|
|
|
// Save to localStorage
|
|
const STORAGE_PREFIX = 'lora_manager_';
|
|
try {
|
|
const currentSettings = localStorage.getItem(STORAGE_PREFIX + 'settings');
|
|
let settings = {};
|
|
|
|
if (currentSettings) {
|
|
settings = JSON.parse(currentSettings);
|
|
}
|
|
|
|
settings.language = languageCode;
|
|
localStorage.setItem(STORAGE_PREFIX + 'settings', JSON.stringify(settings));
|
|
|
|
console.log(`Language changed to: ${languageCode}`);
|
|
return true;
|
|
} catch (e) {
|
|
console.error('Failed to save language setting:', e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get list of available languages with their native names
|
|
* @returns {Array} Array of language objects
|
|
*/
|
|
getAvailableLanguages() {
|
|
return [
|
|
{ code: 'en', name: 'English', nativeName: 'English' },
|
|
{ code: 'zh-CN', name: 'Chinese (Simplified)', nativeName: '简体中文' },
|
|
{ code: 'zh-TW', name: 'Chinese (Traditional)', nativeName: '繁體中文' },
|
|
{ code: 'ru', name: 'Russian', nativeName: 'Русский' },
|
|
{ code: 'de', name: 'German', nativeName: 'Deutsch' },
|
|
{ code: 'ja', name: 'Japanese', nativeName: '日本語' },
|
|
{ code: 'ko', name: 'Korean', nativeName: '한국어' },
|
|
{ code: 'fr', name: 'French', nativeName: 'Français' },
|
|
{ code: 'es', name: 'Spanish', nativeName: 'Español' }
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get translation for a key with optional parameters
|
|
* @param {string} key - Translation key (supports dot notation)
|
|
* @param {Object} params - Parameters for string interpolation
|
|
* @returns {string} Translated text
|
|
*/
|
|
t(key, params = {}) {
|
|
const keys = key.split('.');
|
|
let value = this.translations;
|
|
|
|
// Navigate through nested object
|
|
for (const k of keys) {
|
|
if (value && typeof value === 'object' && k in value) {
|
|
value = value[k];
|
|
} else {
|
|
// Fallback to English if key not found in current locale
|
|
value = this.locales['en'];
|
|
for (const fallbackKey of keys) {
|
|
if (value && typeof value === 'object' && fallbackKey in value) {
|
|
value = value[fallbackKey];
|
|
} else {
|
|
console.warn(`Translation key not found: ${key}`);
|
|
return key; // Return key as fallback
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (typeof value !== 'string') {
|
|
console.warn(`Translation key is not a string: ${key}`);
|
|
return key;
|
|
}
|
|
|
|
// Replace parameters in the string
|
|
return this.interpolate(value, params);
|
|
}
|
|
|
|
/**
|
|
* Interpolate parameters into a string
|
|
* Supports both {{param}} and {param} syntax
|
|
* @param {string} str - String with placeholders
|
|
* @param {Object} params - Parameters to interpolate
|
|
* @returns {string} Interpolated string
|
|
*/
|
|
interpolate(str, params) {
|
|
return str.replace(/\{\{?(\w+)\}?\}/g, (match, key) => {
|
|
return params[key] !== undefined ? params[key] : match;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get current locale
|
|
* @returns {string} Current locale code
|
|
*/
|
|
getCurrentLocale() {
|
|
return this.currentLocale;
|
|
}
|
|
|
|
/**
|
|
* Check if current locale is RTL (Right-to-Left)
|
|
* @returns {boolean} True if RTL
|
|
*/
|
|
isRTL() {
|
|
const rtlLocales = ['ar', 'he', 'fa', 'ur'];
|
|
return rtlLocales.includes(this.currentLocale.split('-')[0]);
|
|
}
|
|
|
|
/**
|
|
* Format number according to current locale
|
|
* @param {number} number - Number to format
|
|
* @param {Object} options - Intl.NumberFormat options
|
|
* @returns {string} Formatted number
|
|
*/
|
|
formatNumber(number, options = {}) {
|
|
return new Intl.NumberFormat(this.currentLocale, options).format(number);
|
|
}
|
|
|
|
/**
|
|
* Format date according to current locale
|
|
* @param {Date|string|number} date - Date to format
|
|
* @param {Object} options - Intl.DateTimeFormat options
|
|
* @returns {string} Formatted date
|
|
*/
|
|
formatDate(date, options = {}) {
|
|
const dateObj = date instanceof Date ? date : new Date(date);
|
|
return new Intl.DateTimeFormat(this.currentLocale, options).format(dateObj);
|
|
}
|
|
|
|
/**
|
|
* Format file size with locale-specific formatting
|
|
* @param {number} bytes - Size in bytes
|
|
* @param {number} decimals - Number of decimal places
|
|
* @returns {string} Formatted size
|
|
*/
|
|
formatFileSize(bytes, decimals = 2) {
|
|
if (bytes === 0) return this.t('common.fileSize.zero');
|
|
|
|
const k = 1024;
|
|
const dm = decimals < 0 ? 0 : decimals;
|
|
const sizes = ['bytes', 'kb', 'mb', 'gb', 'tb'];
|
|
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
const size = parseFloat((bytes / Math.pow(k, i)).toFixed(dm));
|
|
|
|
return `${this.formatNumber(size)} ${this.t(`common.fileSize.${sizes[i]}`)}`;
|
|
}
|
|
|
|
/**
|
|
* Initialize i18n from user settings
|
|
* This prevents language flashing on page load
|
|
*/
|
|
async initializeFromSettings() {
|
|
const targetLanguage = this.getLanguageFromSettings();
|
|
|
|
// Set language immediately without animation/transition
|
|
this.currentLocale = targetLanguage;
|
|
this.translations = this.locales[targetLanguage] || this.locales['en'];
|
|
|
|
// Dispatch event to notify that language has been initialized
|
|
window.dispatchEvent(new CustomEvent('languageInitialized', {
|
|
detail: { language: targetLanguage }
|
|
}));
|
|
}
|
|
}
|
|
|
|
// Create singleton instance
|
|
export const i18n = new I18nManager();
|
|
|
|
// Export for global access (will be attached to window)
|
|
export default i18n;
|