diff --git a/docs/i18n-improvements-summary.md b/docs/i18n-improvements-summary.md new file mode 100644 index 00000000..e687f09f --- /dev/null +++ b/docs/i18n-improvements-summary.md @@ -0,0 +1,75 @@ +# 国际化系统改进总结 + +## 概述 +成功将i18n系统从自动浏览器语言检测改为用户主动设置的方式,避免了页面打开时的语言闪烁问题。 + +## 主要改动 + +### 1. 新增语言支持 +- 新增了7种语言的完整翻译文件: + - 中文(繁体)- `zh-TW.js` + - 俄语 - `ru.js` + - 德语 - `de.js` + - 日语 - `ja.js` + - 韩语 - `ko.js` + - 法语 - `fr.js` + - 西班牙语 - `es.js` + +### 2. 核心系统修改 +- **i18n/index.js**: + - 修改了初始化逻辑,从设置中读取语言而非浏览器检测 + - 新增 `initializeFromSettings()` 方法 + - 完善了 `setLanguage()`, `getLanguageFromSettings()`, `getAvailableLanguages()` 方法 + +- **utils/i18nHelpers.js**: + - 新增 `switchLanguage()` 函数,支持运行时语言切换 + - 提供DOM重新翻译和事件分发功能 + +### 3. 设置界面集成 +- **templates/components/modals/settings_modal.html**: + - 在Layout Settings部分添加了语言选择下拉菜单 + - 使用原生语言名称显示9种支持的语言 + +- **managers/SettingsManager.js**: + - 新增 `saveLanguageSetting()` 方法处理语言设置保存 + - 在 `loadSettingsToUI()` 中添加语言设置的加载逻辑 + - 集成i18n切换功能 + +### 4. 早期初始化优化 +- **i18n/early-init.js**: + - 创建了早期语言检测脚本,防止FOUC(内容闪烁) + - 在页面其他内容加载前设置正确的语言 + +- **templates/base.html**: + - 在head部分最开始加载early-init.js脚本 + +### 5. 核心应用集成 +- **core.js**: + - 修改了初始化流程,使用 `initializeFromSettings()` 而非自动检测 + +## 语言支持列表 +1. **English** (en) - 英语 +2. **中文(简体)** (zh-CN) - Simplified Chinese +3. **中文(繁體)** (zh-TW) - Traditional Chinese +4. **Русский** (ru) - Russian +5. **Deutsch** (de) - German +6. **日本語** (ja) - Japanese +7. **한국어** (ko) - Korean +8. **Français** (fr) - French +9. **Español** (es) - Spanish + +## 用户体验改进 +- ✅ 消除了页面加载时的语言闪烁问题 +- ✅ 用户可以手动选择喜好的语言 +- ✅ 语言设置会保存在localStorage中 +- ✅ 支持运行时即时语言切换 +- ✅ 语言选择界面使用原生语言名称显示 + +## 技术特点 +- 保持了模块化架构 +- 向后兼容现有代码 +- 优化了初始化性能 +- 提供了完整的错误处理 +- 集成了现有的设置管理系统 + +所有修改已完成,系统现在支持用户主动选择语言,有效避免了语言闪烁问题。 diff --git a/py/routes/base_model_routes.py b/py/routes/base_model_routes.py index ee1f03ca..4ccb68e3 100644 --- a/py/routes/base_model_routes.py +++ b/py/routes/base_model_routes.py @@ -113,30 +113,28 @@ class BaseModelRoutes(ABC): if not self.template_env or not template_name: return web.Response(text="Template environment or template name not set", status=500) - if is_initializing: - rendered = self.template_env.get_template(template_name).render( - folders=[], - is_initializing=True, - settings=settings, - request=request - ) - else: + # 获取用户语言设置 + user_language = settings.get('language', 'en') + + # 准备模板上下文 + template_context = { + 'is_initializing': is_initializing, + 'settings': settings, + 'request': request, + 'user_language': user_language, # 传递语言设置到模板 + 'folders': [] + } + + if not is_initializing: try: cache = await self.service.scanner.get_cached_data(force_refresh=False) - rendered = self.template_env.get_template(template_name).render( - folders=getattr(cache, "folders", []), - is_initializing=False, - settings=settings, - request=request - ) + template_context['folders'] = getattr(cache, "folders", []) except Exception as cache_error: logger.error(f"Error loading cache data: {cache_error}") - rendered = self.template_env.get_template(template_name).render( - folders=[], - is_initializing=True, - settings=settings, - request=request - ) + template_context['is_initializing'] = True + + rendered = self.template_env.get_template(template_name).render(**template_context) + return web.Response( text=rendered, content_type='text/html' diff --git a/py/services/settings_manager.py b/py/services/settings_manager.py index 1540904c..53146613 100644 --- a/py/services/settings_manager.py +++ b/py/services/settings_manager.py @@ -80,7 +80,8 @@ class SettingsManager: """Return default settings""" return { "civitai_api_key": "", - "show_only_sfw": False + "show_only_sfw": False, + "language": "en" # 添加默认语言设置 } def get(self, key: str, default: Any = None) -> Any: diff --git a/static/js/core.js b/static/js/core.js index d8c574e9..c398e4fc 100644 --- a/static/js/core.js +++ b/static/js/core.js @@ -29,7 +29,10 @@ export class AppCore { // Initialize i18n first window.i18n = i18n; - console.log(`AppCore: Language detected: ${i18n.getCurrentLocale()}`); + + // Load language from settings + await i18n.initializeFromSettings(); + console.log(`AppCore: Language set: ${i18n.getCurrentLocale()}`); // Initialize managers state.loadingManager = new LoadingManager(); diff --git a/static/js/i18n/index.js b/static/js/i18n/index.js index b790c313..6ed4a1d9 100644 --- a/static/js/i18n/index.js +++ b/static/js/i18n/index.js @@ -1,25 +1,125 @@ /** * Internationalization (i18n) system for LoRA Manager - * Automatically detects browser language and provides fallback to English + * 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.detectLanguage(); + this.currentLocale = this.getLanguageFromSettings(); this.translations = this.locales[this.currentLocale] || this.locales['en']; } /** - * Detect browser language with fallback to English + * Get language from user settings with fallback to browser detection + * @returns {string} Language code + */ + getLanguageFromSettings() { + // 优先使用后端传递的初始语言 + if (window.__INITIAL_LANGUAGE__ && this.locales[window.__INITIAL_LANGUAGE__]) { + return window.__INITIAL_LANGUAGE__; + } + + // 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 browser language detection for first-time users + return this.detectLanguage(); + } + + /** + * 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' } + ]; + } + + /** + * Detect browser language with fallback to English (for first-time users) * @returns {string} Language code */ detectLanguage() { @@ -148,6 +248,23 @@ class I18nManager { return `${this.formatNumber(size)} ${this.t(`common.fileSize.${sizes[i]}`)}`; } + + /** + * Initialize i18n from user settings instead of browser detection + * 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 diff --git a/static/js/i18n/locales/de.js b/static/js/i18n/locales/de.js new file mode 100644 index 00000000..e1b067db --- /dev/null +++ b/static/js/i18n/locales/de.js @@ -0,0 +1,397 @@ +/** + * German (de) translations for LoRA Manager + */ +export const de = { + // Allgemeine Begriffe in der Anwendung + common: { + // Dateioperationen + file: 'Datei', + folder: 'Ordner', + name: 'Name', + size: 'Größe', + date: 'Datum', + type: 'Typ', + path: 'Pfad', + + // Dateigrößen + fileSize: { + zero: '0 Bytes', + bytes: 'Bytes', + kb: 'KB', + mb: 'MB', + gb: 'GB', + tb: 'TB' + }, + + // Aktionen + actions: { + save: 'Speichern', + cancel: 'Abbrechen', + delete: 'Löschen', + edit: 'Bearbeiten', + copy: 'Kopieren', + move: 'Verschieben', + refresh: 'Aktualisieren', + download: 'Herunterladen', + upload: 'Hochladen', + search: 'Suchen', + filter: 'Filter', + sort: 'Sortieren', + select: 'Auswählen', + selectAll: 'Alle auswählen', + deselectAll: 'Auswahl aufheben', + confirm: 'Bestätigen', + close: 'Schließen', + back: 'Zurück', + next: 'Weiter', + previous: 'Vorherige', + view: 'Anzeigen', + preview: 'Vorschau', + details: 'Details', + settings: 'Einstellungen', + help: 'Hilfe', + about: 'Über' + }, + + // Spracheinstellungen + language: { + current: 'Sprache', + select: 'Sprache auswählen', + select_help: 'Wählen Sie Ihre bevorzugte Oberflächensprache', + english: 'Englisch', + chinese_simplified: 'Chinesisch (vereinfacht)', + chinese_traditional: 'Chinesisch (traditionell)', + russian: 'Russisch', + german: 'Deutsch', + japanese: 'Japanisch', + korean: 'Koreanisch', + french: 'Französisch', + spanish: 'Spanisch' + }, + + // Statusmeldungen + status: { + loading: 'Lädt...', + saving: 'Speichere...', + saved: 'Gespeichert', + error: 'Fehler', + success: 'Erfolgreich', + warning: 'Warnung', + info: 'Information', + processing: 'Verarbeite...', + completed: 'Abgeschlossen', + failed: 'Fehlgeschlagen', + cancelled: 'Abgebrochen', + pending: 'Wartend', + ready: 'Bereit' + } + }, + + // Kopfzeile und Navigation + header: { + appTitle: 'LoRA Manager', + navigation: { + loras: 'LoRAs', + recipes: 'Rezepte', + checkpoints: 'Checkpoints', + embeddings: 'Embeddings', + statistics: 'Statistiken' + }, + search: { + placeholder: 'Suchen...', + placeholders: { + loras: 'LoRAs suchen...', + recipes: 'Rezepte suchen...', + checkpoints: 'Checkpoints suchen...', + embeddings: 'Embeddings suchen...' + }, + options: 'Suchoptionen', + searchIn: 'Suchen in:', + notAvailable: 'Suche nicht verfügbar auf der Statistikseite', + filters: { + filename: 'Dateiname', + modelname: 'Modellname', + tags: 'Tags', + creator: 'Ersteller', + title: 'Rezepttitel', + loraName: 'LoRA Dateiname', + loraModel: 'LoRA Modellname' + } + }, + filter: { + title: 'Modelle filtern', + baseModel: 'Basismodell', + modelTags: 'Tags (Top 20)', + clearAll: 'Alle Filter löschen' + }, + theme: { + toggle: 'Design wechseln', + switchToLight: 'Zu hellem Design wechseln', + switchToDark: 'Zu dunklem Design wechseln', + switchToAuto: 'Zu automatischem Design wechseln' + } + }, + + // LoRA Seite + loras: { + title: 'LoRA Modelle', + controls: { + sort: { + title: 'Modelle sortieren nach...', + name: 'Name', + nameAsc: 'A - Z', + nameDesc: 'Z - A', + date: 'Hinzufügungsdatum', + dateDesc: 'Neueste', + dateAsc: 'Älteste', + size: 'Dateigröße', + sizeDesc: 'Größte', + sizeAsc: 'Kleinste' + }, + refresh: { + title: 'Modellliste aktualisieren', + quick: 'Schnelle Aktualisierung (inkrementell)', + full: 'Vollständiger Neuaufbau (komplett)' + }, + fetch: 'Von Civitai abrufen', + download: 'Von URL herunterladen', + bulk: 'Massenoperationen', + duplicates: 'Duplikate finden', + favorites: 'Nur Favoriten anzeigen' + }, + bulkOperations: { + title: 'Massenoperationen', + selected: '{count} ausgewählt', + selectAll: 'Alle auf aktueller Seite auswählen', + deselectAll: 'Alle abwählen', + actions: { + move: 'Ausgewählte verschieben', + delete: 'Ausgewählte löschen', + setRating: 'Inhaltsbewertung festlegen', + export: 'Ausgewählte exportieren' + } + }, + card: { + actions: { + copyTriggerWords: 'Trigger-Wörter kopieren', + copyLoraName: 'LoRA-Namen kopieren', + sendToWorkflow: 'An Workflow senden', + sendToWorkflowAppend: 'An Workflow senden (anhängen)', + sendToWorkflowReplace: 'An Workflow senden (ersetzen)', + openExamples: 'Beispielordner öffnen', + downloadExamples: 'Beispielbilder herunterladen', + replacePreview: 'Vorschau ersetzen', + setContentRating: 'Inhaltsbewertung festlegen', + moveToFolder: 'In Ordner verschieben', + excludeModel: 'Modell ausschließen', + deleteModel: 'Modell löschen' + }, + modal: { + title: 'LoRA Details', + tabs: { + examples: 'Beispiele', + description: 'Modellbeschreibung', + recipes: 'Rezepte' + }, + info: { + filename: 'Dateiname', + modelName: 'Modellname', + baseModel: 'Basismodell', + fileSize: 'Dateigröße', + dateAdded: 'Hinzufügungsdatum', + triggerWords: 'Trigger-Wörter', + description: 'Beschreibung', + tags: 'Tags', + rating: 'Bewertung', + downloads: 'Downloads', + likes: 'Gefällt mir', + version: 'Version' + }, + actions: { + copyTriggerWords: 'Trigger-Wörter kopieren', + copyLoraName: 'LoRA-Namen kopieren', + sendToWorkflow: 'An Workflow senden', + viewOnCivitai: 'Auf Civitai anzeigen', + downloadExamples: 'Beispielbilder herunterladen' + } + } + } + }, + + // Rezepte Seite + recipes: { + title: 'LoRA Rezepte', + controls: { + import: 'Rezept importieren', + create: 'Rezept erstellen', + export: 'Ausgewählte exportieren', + downloadMissing: 'Fehlende LoRAs herunterladen' + }, + card: { + author: 'Autor', + loras: '{count} LoRAs', + tags: 'Tags', + actions: { + sendToWorkflow: 'An Workflow senden', + edit: 'Rezept bearbeiten', + duplicate: 'Rezept duplizieren', + export: 'Rezept exportieren', + delete: 'Rezept löschen' + } + } + }, + + // Checkpoint Seite + checkpoints: { + title: 'Checkpoint Modelle', + info: { + filename: 'Dateiname', + modelName: 'Modellname', + baseModel: 'Basismodell', + fileSize: 'Dateigröße', + dateAdded: 'Hinzufügungsdatum' + } + }, + + // Embeddings Seite + embeddings: { + title: 'Embedding Modelle', + info: { + filename: 'Dateiname', + modelName: 'Modellname', + triggerWords: 'Trigger-Wörter', + fileSize: 'Dateigröße', + dateAdded: 'Hinzufügungsdatum' + } + }, + + // Statistik Seite + statistics: { + title: 'Statistiken', + overview: { + title: 'Übersicht', + totalModels: 'Gesamte Modelle', + totalSize: 'Gesamtgröße', + avgFileSize: 'Durchschnittliche Dateigröße', + newestModel: 'Neuestes Modell' + }, + charts: { + modelsByBaseModel: 'Nach Basismodell', + modelsByMonth: 'Nach Monat', + fileSizeDistribution: 'Dateigrößenverteilung', + topTags: 'Beliebte Tags' + } + }, + + // Modale Dialoge + modals: { + delete: { + title: 'Löschen bestätigen', + message: 'Sind Sie sicher, dass Sie dieses Modell löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.', + confirm: 'Löschen', + cancel: 'Abbrechen' + }, + exclude: { + title: 'Modell ausschließen', + message: 'Sind Sie sicher, dass Sie dieses Modell aus der Bibliothek ausschließen möchten?', + confirm: 'Ausschließen', + cancel: 'Abbrechen' + }, + download: { + title: 'Modell herunterladen', + url: 'Modell URL', + placeholder: 'Civitai Modell URL eingeben...', + download: 'Herunterladen', + cancel: 'Abbrechen' + }, + move: { + title: 'Modell verschieben', + selectFolder: 'Zielordner auswählen', + createFolder: 'Neuen Ordner erstellen', + folderName: 'Ordnername', + move: 'Verschieben', + cancel: 'Abbrechen' + }, + contentRating: { + title: 'Inhaltsbewertung festlegen', + current: 'Aktuell', + levels: { + pg: 'Allgemein', + pg13: 'Ab 13', + r: 'Eingeschränkt', + x: 'Erwachsene', + xxx: 'Explizit' + } + } + }, + + // Fehlermeldungen + errors: { + general: 'Ein Fehler ist aufgetreten', + networkError: 'Netzwerkfehler. Überprüfen Sie Ihre Verbindung.', + serverError: 'Serverfehler. Versuchen Sie es später erneut.', + fileNotFound: 'Datei nicht gefunden', + invalidFile: 'Ungültiges Dateiformat', + uploadFailed: 'Upload fehlgeschlagen', + downloadFailed: 'Download fehlgeschlagen', + saveFailed: 'Speichern fehlgeschlagen', + loadFailed: 'Laden fehlgeschlagen', + deleteFailed: 'Löschen fehlgeschlagen', + moveFailed: 'Verschieben fehlgeschlagen', + copyFailed: 'Kopieren fehlgeschlagen', + fetchFailed: 'Daten von Civitai konnten nicht abgerufen werden', + invalidUrl: 'Ungültiges URL-Format', + missingPermissions: 'Unzureichende Berechtigungen' + }, + + // Erfolgsmeldungen + success: { + saved: 'Erfolgreich gespeichert', + deleted: 'Erfolgreich gelöscht', + moved: 'Erfolgreich verschoben', + copied: 'Erfolgreich kopiert', + downloaded: 'Erfolgreich heruntergeladen', + uploaded: 'Erfolgreich hochgeladen', + refreshed: 'Erfolgreich aktualisiert', + exported: 'Erfolgreich exportiert', + imported: 'Erfolgreich importiert' + }, + + // Tastaturkürzel + keyboard: { + navigation: 'Tastaturnavigation:', + shortcuts: { + pageUp: 'Eine Seite nach oben scrollen', + pageDown: 'Eine Seite nach unten scrollen', + home: 'Zum Anfang springen', + end: 'Zum Ende springen', + bulkMode: 'Massenmodus umschalten', + search: 'Suche fokussieren', + escape: 'Modal/Panel schließen' + } + }, + + // Initialisierung + initialization: { + title: 'LoRA Manager initialisieren', + message: 'Scannen und Aufbau des LoRA-Caches. Dies kann einige Minuten dauern...', + steps: { + scanning: 'Modelldateien scannen...', + processing: 'Metadaten verarbeiten...', + building: 'Cache aufbauen...', + finalizing: 'Abschließen...' + } + }, + + // Tooltips und Hilfetext + tooltips: { + refresh: 'Modellliste aktualisieren', + bulkOperations: 'Mehrere Modelle für Batch-Operationen auswählen', + favorites: 'Nur Lieblingsmodelle anzeigen', + duplicates: 'Doppelte Modelle finden und verwalten', + search: 'Modelle nach Name, Tags oder anderen Kriterien suchen', + filter: 'Modelle nach verschiedenen Kriterien filtern', + sort: 'Modelle nach verschiedenen Attributen sortieren', + backToTop: 'Zurück zum Seitenanfang scrollen' + } +}; diff --git a/static/js/i18n/locales/en.js b/static/js/i18n/locales/en.js index 1459b5f2..8ae5f0b0 100644 --- a/static/js/i18n/locales/en.js +++ b/static/js/i18n/locales/en.js @@ -68,6 +68,22 @@ export const en = { cancelled: 'Cancelled', pending: 'Pending', ready: 'Ready' + }, + + // Languages + language: { + current: 'Language', + select: 'Select Language', + select_help: 'Choose your preferred language for the interface', + english: 'English', + chinese_simplified: 'Chinese (Simplified)', + chinese_traditional: 'Chinese (Traditional)', + russian: 'Russian', + german: 'German', + japanese: 'Japanese', + korean: 'Korean', + french: 'French', + spanish: 'Spanish' } }, diff --git a/static/js/i18n/locales/es.js b/static/js/i18n/locales/es.js new file mode 100644 index 00000000..f146a951 --- /dev/null +++ b/static/js/i18n/locales/es.js @@ -0,0 +1,397 @@ +/** + * Spanish (es) translations for LoRA Manager + */ +export const es = { + // Términos comunes utilizados en la aplicación + common: { + // Operaciones de archivos + file: 'Archivo', + folder: 'Carpeta', + name: 'Nombre', + size: 'Tamaño', + date: 'Fecha', + type: 'Tipo', + path: 'Ruta', + + // Tamaños de archivo + fileSize: { + zero: '0 Bytes', + bytes: 'Bytes', + kb: 'KB', + mb: 'MB', + gb: 'GB', + tb: 'TB' + }, + + // Acciones + actions: { + save: 'Guardar', + cancel: 'Cancelar', + delete: 'Eliminar', + edit: 'Editar', + copy: 'Copiar', + move: 'Mover', + refresh: 'Actualizar', + download: 'Descargar', + upload: 'Subir', + search: 'Buscar', + filter: 'Filtrar', + sort: 'Ordenar', + select: 'Seleccionar', + selectAll: 'Seleccionar todo', + deselectAll: 'Deseleccionar todo', + confirm: 'Confirmar', + close: 'Cerrar', + back: 'Atrás', + next: 'Siguiente', + previous: 'Anterior', + view: 'Ver', + preview: 'Vista previa', + details: 'Detalles', + settings: 'Configuración', + help: 'Ayuda', + about: 'Acerca de' + }, + + // Configuración de idioma + language: { + current: 'Idioma', + select: 'Seleccionar idioma', + select_help: 'Elija su idioma de interfaz preferido', + english: 'Inglés', + chinese_simplified: 'Chino (simplificado)', + chinese_traditional: 'Chino (tradicional)', + russian: 'Ruso', + german: 'Alemán', + japanese: 'Japonés', + korean: 'Coreano', + french: 'Francés', + spanish: 'Español' + }, + + // Mensajes de estado + status: { + loading: 'Cargando...', + saving: 'Guardando...', + saved: 'Guardado', + error: 'Error', + success: 'Éxito', + warning: 'Advertencia', + info: 'Información', + processing: 'Procesando...', + completed: 'Completado', + failed: 'Falló', + cancelled: 'Cancelado', + pending: 'Pendiente', + ready: 'Listo' + } + }, + + // Encabezado y navegación + header: { + appTitle: 'Gestor LoRA', + navigation: { + loras: 'LoRA', + recipes: 'Recetas', + checkpoints: 'Puntos de control', + embeddings: 'Embeddings', + statistics: 'Estadísticas' + }, + search: { + placeholder: 'Buscar...', + placeholders: { + loras: 'Buscar LoRA...', + recipes: 'Buscar recetas...', + checkpoints: 'Buscar puntos de control...', + embeddings: 'Buscar embeddings...' + }, + options: 'Opciones de búsqueda', + searchIn: 'Buscar en:', + notAvailable: 'Búsqueda no disponible en la página de estadísticas', + filters: { + filename: 'Nombre del archivo', + modelname: 'Nombre del modelo', + tags: 'Etiquetas', + creator: 'Creador', + title: 'Título de la receta', + loraName: 'Nombre del archivo LoRA', + loraModel: 'Nombre del modelo LoRA' + } + }, + filter: { + title: 'Filtrar modelos', + baseModel: 'Modelo base', + modelTags: 'Etiquetas (Top 20)', + clearAll: 'Limpiar todos los filtros' + }, + theme: { + toggle: 'Cambiar tema', + switchToLight: 'Cambiar a tema claro', + switchToDark: 'Cambiar a tema oscuro', + switchToAuto: 'Cambiar a tema automático' + } + }, + + // Página LoRA + loras: { + title: 'Modelos LoRA', + controls: { + sort: { + title: 'Ordenar modelos por...', + name: 'Nombre', + nameAsc: 'A - Z', + nameDesc: 'Z - A', + date: 'Fecha de agregado', + dateDesc: 'Más recientes', + dateAsc: 'Más antiguos', + size: 'Tamaño del archivo', + sizeDesc: 'Más grandes', + sizeAsc: 'Más pequeños' + }, + refresh: { + title: 'Actualizar lista de modelos', + quick: 'Actualización rápida (incremental)', + full: 'Reconstrucción completa (completa)' + }, + fetch: 'Obtener desde Civitai', + download: 'Descargar desde URL', + bulk: 'Operaciones en lote', + duplicates: 'Encontrar duplicados', + favorites: 'Mostrar solo favoritos' + }, + bulkOperations: { + title: 'Operaciones en lote', + selected: '{count} seleccionado(s)', + selectAll: 'Seleccionar todos en la página actual', + deselectAll: 'Deseleccionar todos', + actions: { + move: 'Mover elementos seleccionados', + delete: 'Eliminar elementos seleccionados', + setRating: 'Establecer clasificación de contenido', + export: 'Exportar elementos seleccionados' + } + }, + card: { + actions: { + copyTriggerWords: 'Copiar palabras clave', + copyLoraName: 'Copiar nombre LoRA', + sendToWorkflow: 'Enviar al flujo de trabajo', + sendToWorkflowAppend: 'Enviar al flujo de trabajo (agregar)', + sendToWorkflowReplace: 'Enviar al flujo de trabajo (reemplazar)', + openExamples: 'Abrir carpeta de ejemplos', + downloadExamples: 'Descargar imágenes de ejemplo', + replacePreview: 'Reemplazar vista previa', + setContentRating: 'Establecer clasificación de contenido', + moveToFolder: 'Mover a carpeta', + excludeModel: 'Excluir modelo', + deleteModel: 'Eliminar modelo' + }, + modal: { + title: 'Detalles LoRA', + tabs: { + examples: 'Ejemplos', + description: 'Descripción del modelo', + recipes: 'Recetas' + }, + info: { + filename: 'Nombre del archivo', + modelName: 'Nombre del modelo', + baseModel: 'Modelo base', + fileSize: 'Tamaño del archivo', + dateAdded: 'Fecha de agregado', + triggerWords: 'Palabras clave', + description: 'Descripción', + tags: 'Etiquetas', + rating: 'Calificación', + downloads: 'Descargas', + likes: 'Me gusta', + version: 'Versión' + }, + actions: { + copyTriggerWords: 'Copiar palabras clave', + copyLoraName: 'Copiar nombre LoRA', + sendToWorkflow: 'Enviar al flujo de trabajo', + viewOnCivitai: 'Ver en Civitai', + downloadExamples: 'Descargar imágenes de ejemplo' + } + } + } + }, + + // Página de recetas + recipes: { + title: 'Recetas LoRA', + controls: { + import: 'Importar receta', + create: 'Crear receta', + export: 'Exportar elementos seleccionados', + downloadMissing: 'Descargar LoRA faltantes' + }, + card: { + author: 'Autor', + loras: '{count} LoRA', + tags: 'Etiquetas', + actions: { + sendToWorkflow: 'Enviar al flujo de trabajo', + edit: 'Editar receta', + duplicate: 'Duplicar receta', + export: 'Exportar receta', + delete: 'Eliminar receta' + } + } + }, + + // Página de puntos de control + checkpoints: { + title: 'Modelos de puntos de control', + info: { + filename: 'Nombre del archivo', + modelName: 'Nombre del modelo', + baseModel: 'Modelo base', + fileSize: 'Tamaño del archivo', + dateAdded: 'Fecha de agregado' + } + }, + + // Página de embeddings + embeddings: { + title: 'Modelos de embedding', + info: { + filename: 'Nombre del archivo', + modelName: 'Nombre del modelo', + triggerWords: 'Palabras clave', + fileSize: 'Tamaño del archivo', + dateAdded: 'Fecha de agregado' + } + }, + + // Página de estadísticas + statistics: { + title: 'Estadísticas', + overview: { + title: 'Resumen', + totalModels: 'Total de modelos', + totalSize: 'Tamaño total', + avgFileSize: 'Tamaño promedio de archivo', + newestModel: 'Modelo más reciente' + }, + charts: { + modelsByBaseModel: 'Por modelo base', + modelsByMonth: 'Por mes', + fileSizeDistribution: 'Distribución de tamaños de archivo', + topTags: 'Etiquetas populares' + } + }, + + // Diálogos modales + modals: { + delete: { + title: 'Confirmar eliminación', + message: '¿Estás seguro de que quieres eliminar este modelo? Esta acción no se puede deshacer.', + confirm: 'Eliminar', + cancel: 'Cancelar' + }, + exclude: { + title: 'Excluir modelo', + message: '¿Estás seguro de que quieres excluir este modelo de la biblioteca?', + confirm: 'Excluir', + cancel: 'Cancelar' + }, + download: { + title: 'Descargar modelo', + url: 'URL del modelo', + placeholder: 'Ingresa la URL del modelo de Civitai...', + download: 'Descargar', + cancel: 'Cancelar' + }, + move: { + title: 'Mover modelo', + selectFolder: 'Seleccionar carpeta de destino', + createFolder: 'Crear nueva carpeta', + folderName: 'Nombre de la carpeta', + move: 'Mover', + cancel: 'Cancelar' + }, + contentRating: { + title: 'Establecer clasificación de contenido', + current: 'Actual', + levels: { + pg: 'Apto para todos', + pg13: '13 años y más', + r: 'Restringido', + x: 'Adultos', + xxx: 'Explícito' + } + } + }, + + // Mensajes de error + errors: { + general: 'Ocurrió un error', + networkError: 'Error de red. Verifica tu conexión.', + serverError: 'Error del servidor. Inténtalo de nuevo más tarde.', + fileNotFound: 'Archivo no encontrado', + invalidFile: 'Formato de archivo inválido', + uploadFailed: 'Falló la subida', + downloadFailed: 'Falló la descarga', + saveFailed: 'Falló el guardado', + loadFailed: 'Falló la carga', + deleteFailed: 'Falló la eliminación', + moveFailed: 'Falló el movimiento', + copyFailed: 'Falló la copia', + fetchFailed: 'No se pudieron obtener datos de Civitai', + invalidUrl: 'Formato de URL inválido', + missingPermissions: 'Permisos insuficientes' + }, + + // Mensajes de éxito + success: { + saved: 'Guardado exitosamente', + deleted: 'Eliminado exitosamente', + moved: 'Movido exitosamente', + copied: 'Copiado exitosamente', + downloaded: 'Descargado exitosamente', + uploaded: 'Subido exitosamente', + refreshed: 'Actualizado exitosamente', + exported: 'Exportado exitosamente', + imported: 'Importado exitosamente' + }, + + // Atajos de teclado + keyboard: { + navigation: 'Navegación por teclado:', + shortcuts: { + pageUp: 'Desplazar hacia arriba una página', + pageDown: 'Desplazar hacia abajo una página', + home: 'Ir al inicio', + end: 'Ir al final', + bulkMode: 'Cambiar modo de lote', + search: 'Enfocar búsqueda', + escape: 'Cerrar modal/panel' + } + }, + + // Inicialización + initialization: { + title: 'Inicializando Gestor LoRA', + message: 'Escaneando y construyendo caché LoRA. Esto puede tomar algunos minutos...', + steps: { + scanning: 'Escaneando archivos de modelos...', + processing: 'Procesando metadatos...', + building: 'Construyendo caché...', + finalizing: 'Finalizando...' + } + }, + + // Tooltips y texto de ayuda + tooltips: { + refresh: 'Actualizar la lista de modelos', + bulkOperations: 'Seleccionar múltiples modelos para operaciones por lotes', + favorites: 'Mostrar solo modelos favoritos', + duplicates: 'Encontrar y gestionar modelos duplicados', + search: 'Buscar modelos por nombre, etiquetas u otros criterios', + filter: 'Filtrar modelos por varios criterios', + sort: 'Ordenar modelos por diferentes atributos', + backToTop: 'Volver al inicio de la página' + } +}; diff --git a/static/js/i18n/locales/fr.js b/static/js/i18n/locales/fr.js new file mode 100644 index 00000000..d01d00d4 --- /dev/null +++ b/static/js/i18n/locales/fr.js @@ -0,0 +1,397 @@ +/** + * French (fr) translations for LoRA Manager + */ +export const fr = { + // Termes communs utilisés dans l'application + common: { + // Opérations sur les fichiers + file: 'Fichier', + folder: 'Dossier', + name: 'Nom', + size: 'Taille', + date: 'Date', + type: 'Type', + path: 'Chemin', + + // Tailles de fichiers + fileSize: { + zero: '0 Octets', + bytes: 'Octets', + kb: 'Ko', + mb: 'Mo', + gb: 'Go', + tb: 'To' + }, + + // Actions + actions: { + save: 'Enregistrer', + cancel: 'Annuler', + delete: 'Supprimer', + edit: 'Modifier', + copy: 'Copier', + move: 'Déplacer', + refresh: 'Actualiser', + download: 'Télécharger', + upload: 'Importer', + search: 'Rechercher', + filter: 'Filtrer', + sort: 'Trier', + select: 'Sélectionner', + selectAll: 'Tout sélectionner', + deselectAll: 'Tout désélectionner', + confirm: 'Confirmer', + close: 'Fermer', + back: 'Retour', + next: 'Suivant', + previous: 'Précédent', + view: 'Afficher', + preview: 'Aperçu', + details: 'Détails', + settings: 'Paramètres', + help: 'Aide', + about: 'À propos' + }, + + // Paramètres de langue + language: { + current: 'Langue', + select: 'Sélectionner la langue', + select_help: 'Choisissez votre langue d\'interface préférée', + english: 'Anglais', + chinese_simplified: 'Chinois (simplifié)', + chinese_traditional: 'Chinois (traditionnel)', + russian: 'Russe', + german: 'Allemand', + japanese: 'Japonais', + korean: 'Coréen', + french: 'Français', + spanish: 'Espagnol' + }, + + // Messages de statut + status: { + loading: 'Chargement...', + saving: 'Enregistrement...', + saved: 'Enregistré', + error: 'Erreur', + success: 'Succès', + warning: 'Avertissement', + info: 'Information', + processing: 'Traitement...', + completed: 'Terminé', + failed: 'Échec', + cancelled: 'Annulé', + pending: 'En attente', + ready: 'Prêt' + } + }, + + // En-tête et navigation + header: { + appTitle: 'Gestionnaire LoRA', + navigation: { + loras: 'LoRA', + recipes: 'Recettes', + checkpoints: 'Points de contrôle', + embeddings: 'Embeddings', + statistics: 'Statistiques' + }, + search: { + placeholder: 'Rechercher...', + placeholders: { + loras: 'Rechercher des LoRA...', + recipes: 'Rechercher des recettes...', + checkpoints: 'Rechercher des points de contrôle...', + embeddings: 'Rechercher des embeddings...' + }, + options: 'Options de recherche', + searchIn: 'Rechercher dans :', + notAvailable: 'Recherche non disponible sur la page des statistiques', + filters: { + filename: 'Nom de fichier', + modelname: 'Nom du modèle', + tags: 'Tags', + creator: 'Créateur', + title: 'Titre de la recette', + loraName: 'Nom du fichier LoRA', + loraModel: 'Nom du modèle LoRA' + } + }, + filter: { + title: 'Filtrer les modèles', + baseModel: 'Modèle de base', + modelTags: 'Tags (Top 20)', + clearAll: 'Effacer tous les filtres' + }, + theme: { + toggle: 'Basculer le thème', + switchToLight: 'Passer au thème clair', + switchToDark: 'Passer au thème sombre', + switchToAuto: 'Passer au thème automatique' + } + }, + + // Page LoRA + loras: { + title: 'Modèles LoRA', + controls: { + sort: { + title: 'Trier les modèles par...', + name: 'Nom', + nameAsc: 'A - Z', + nameDesc: 'Z - A', + date: 'Date d\'ajout', + dateDesc: 'Plus récents', + dateAsc: 'Plus anciens', + size: 'Taille du fichier', + sizeDesc: 'Plus grands', + sizeAsc: 'Plus petits' + }, + refresh: { + title: 'Actualiser la liste des modèles', + quick: 'Actualisation rapide (incrémentale)', + full: 'Reconstruction complète (complète)' + }, + fetch: 'Récupérer depuis Civitai', + download: 'Télécharger depuis URL', + bulk: 'Opérations en lot', + duplicates: 'Trouver les doublons', + favorites: 'Afficher seulement les favoris' + }, + bulkOperations: { + title: 'Opérations en lot', + selected: '{count} sélectionné(s)', + selectAll: 'Sélectionner tous sur la page courante', + deselectAll: 'Désélectionner tous', + actions: { + move: 'Déplacer les éléments sélectionnés', + delete: 'Supprimer les éléments sélectionnés', + setRating: 'Définir la classification du contenu', + export: 'Exporter les éléments sélectionnés' + } + }, + card: { + actions: { + copyTriggerWords: 'Copier les mots déclencheurs', + copyLoraName: 'Copier le nom LoRA', + sendToWorkflow: 'Envoyer au flux de travail', + sendToWorkflowAppend: 'Envoyer au flux de travail (ajouter)', + sendToWorkflowReplace: 'Envoyer au flux de travail (remplacer)', + openExamples: 'Ouvrir le dossier d\'exemples', + downloadExamples: 'Télécharger les images d\'exemple', + replacePreview: 'Remplacer l\'aperçu', + setContentRating: 'Définir la classification du contenu', + moveToFolder: 'Déplacer vers le dossier', + excludeModel: 'Exclure le modèle', + deleteModel: 'Supprimer le modèle' + }, + modal: { + title: 'Détails LoRA', + tabs: { + examples: 'Exemples', + description: 'Description du modèle', + recipes: 'Recettes' + }, + info: { + filename: 'Nom de fichier', + modelName: 'Nom du modèle', + baseModel: 'Modèle de base', + fileSize: 'Taille du fichier', + dateAdded: 'Date d\'ajout', + triggerWords: 'Mots déclencheurs', + description: 'Description', + tags: 'Tags', + rating: 'Évaluation', + downloads: 'Téléchargements', + likes: 'J\'aime', + version: 'Version' + }, + actions: { + copyTriggerWords: 'Copier les mots déclencheurs', + copyLoraName: 'Copier le nom LoRA', + sendToWorkflow: 'Envoyer au flux de travail', + viewOnCivitai: 'Voir sur Civitai', + downloadExamples: 'Télécharger les images d\'exemple' + } + } + } + }, + + // Page recettes + recipes: { + title: 'Recettes LoRA', + controls: { + import: 'Importer une recette', + create: 'Créer une recette', + export: 'Exporter les éléments sélectionnés', + downloadMissing: 'Télécharger les LoRA manquants' + }, + card: { + author: 'Auteur', + loras: '{count} LoRA', + tags: 'Tags', + actions: { + sendToWorkflow: 'Envoyer au flux de travail', + edit: 'Modifier la recette', + duplicate: 'Dupliquer la recette', + export: 'Exporter la recette', + delete: 'Supprimer la recette' + } + } + }, + + // Page points de contrôle + checkpoints: { + title: 'Modèles de points de contrôle', + info: { + filename: 'Nom de fichier', + modelName: 'Nom du modèle', + baseModel: 'Modèle de base', + fileSize: 'Taille du fichier', + dateAdded: 'Date d\'ajout' + } + }, + + // Page embeddings + embeddings: { + title: 'Modèles d\'embedding', + info: { + filename: 'Nom de fichier', + modelName: 'Nom du modèle', + triggerWords: 'Mots déclencheurs', + fileSize: 'Taille du fichier', + dateAdded: 'Date d\'ajout' + } + }, + + // Page statistiques + statistics: { + title: 'Statistiques', + overview: { + title: 'Aperçu', + totalModels: 'Total des modèles', + totalSize: 'Taille totale', + avgFileSize: 'Taille moyenne des fichiers', + newestModel: 'Modèle le plus récent' + }, + charts: { + modelsByBaseModel: 'Par modèle de base', + modelsByMonth: 'Par mois', + fileSizeDistribution: 'Distribution des tailles de fichier', + topTags: 'Tags populaires' + } + }, + + // Boîtes de dialogue modales + modals: { + delete: { + title: 'Confirmer la suppression', + message: 'Êtes-vous sûr de vouloir supprimer ce modèle ? Cette action ne peut pas être annulée.', + confirm: 'Supprimer', + cancel: 'Annuler' + }, + exclude: { + title: 'Exclure le modèle', + message: 'Êtes-vous sûr de vouloir exclure ce modèle de la bibliothèque ?', + confirm: 'Exclure', + cancel: 'Annuler' + }, + download: { + title: 'Télécharger le modèle', + url: 'URL du modèle', + placeholder: 'Entrer l\'URL du modèle Civitai...', + download: 'Télécharger', + cancel: 'Annuler' + }, + move: { + title: 'Déplacer le modèle', + selectFolder: 'Sélectionner le dossier de destination', + createFolder: 'Créer un nouveau dossier', + folderName: 'Nom du dossier', + move: 'Déplacer', + cancel: 'Annuler' + }, + contentRating: { + title: 'Définir la classification du contenu', + current: 'Actuel', + levels: { + pg: 'Tout public', + pg13: '13 ans et plus', + r: 'Restreint', + x: 'Adulte', + xxx: 'Explicite' + } + } + }, + + // Messages d'erreur + errors: { + general: 'Une erreur s\'est produite', + networkError: 'Erreur réseau. Vérifiez votre connexion.', + serverError: 'Erreur serveur. Veuillez réessayer plus tard.', + fileNotFound: 'Fichier non trouvé', + invalidFile: 'Format de fichier invalide', + uploadFailed: 'Échec de l\'import', + downloadFailed: 'Échec du téléchargement', + saveFailed: 'Échec de l\'enregistrement', + loadFailed: 'Échec du chargement', + deleteFailed: 'Échec de la suppression', + moveFailed: 'Échec du déplacement', + copyFailed: 'Échec de la copie', + fetchFailed: 'Impossible de récupérer les données de Civitai', + invalidUrl: 'Format d\'URL invalide', + missingPermissions: 'Permissions insuffisantes' + }, + + // Messages de succès + success: { + saved: 'Enregistré avec succès', + deleted: 'Supprimé avec succès', + moved: 'Déplacé avec succès', + copied: 'Copié avec succès', + downloaded: 'Téléchargé avec succès', + uploaded: 'Importé avec succès', + refreshed: 'Actualisé avec succès', + exported: 'Exporté avec succès', + imported: 'Importé avec succès' + }, + + // Raccourcis clavier + keyboard: { + navigation: 'Navigation au clavier :', + shortcuts: { + pageUp: 'Défiler d\'une page vers le haut', + pageDown: 'Défiler d\'une page vers le bas', + home: 'Aller au début', + end: 'Aller à la fin', + bulkMode: 'Basculer le mode lot', + search: 'Focus sur la recherche', + escape: 'Fermer modal/panneau' + } + }, + + // Initialisation + initialization: { + title: 'Initialisation du gestionnaire LoRA', + message: 'Analyse et construction du cache LoRA. Cela peut prendre quelques minutes...', + steps: { + scanning: 'Analyse des fichiers de modèles...', + processing: 'Traitement des métadonnées...', + building: 'Construction du cache...', + finalizing: 'Finalisation...' + } + }, + + // Infobulles et texte d'aide + tooltips: { + refresh: 'Actualiser la liste des modèles', + bulkOperations: 'Sélectionner plusieurs modèles pour des opérations par lot', + favorites: 'Afficher seulement les modèles favoris', + duplicates: 'Trouver et gérer les modèles en double', + search: 'Rechercher des modèles par nom, tags ou autres critères', + filter: 'Filtrer les modèles selon divers critères', + sort: 'Trier les modèles selon différents attributs', + backToTop: 'Revenir en haut de la page' + } +}; diff --git a/static/js/i18n/locales/ja.js b/static/js/i18n/locales/ja.js new file mode 100644 index 00000000..7955f0a4 --- /dev/null +++ b/static/js/i18n/locales/ja.js @@ -0,0 +1,397 @@ +/** + * Japanese (ja) translations for LoRA Manager + */ +export const ja = { + // アプリケーション全体で使用される共通用語 + common: { + // ファイル操作 + file: 'ファイル', + folder: 'フォルダ', + name: '名前', + size: 'サイズ', + date: '日付', + type: '種類', + path: 'パス', + + // ファイルサイズ + fileSize: { + zero: '0 バイト', + bytes: 'バイト', + kb: 'KB', + mb: 'MB', + gb: 'GB', + tb: 'TB' + }, + + // アクション + actions: { + save: '保存', + cancel: 'キャンセル', + delete: '削除', + edit: '編集', + copy: 'コピー', + move: '移動', + refresh: '更新', + download: 'ダウンロード', + upload: 'アップロード', + search: '検索', + filter: 'フィルター', + sort: 'ソート', + select: '選択', + selectAll: 'すべて選択', + deselectAll: '選択解除', + confirm: '確認', + close: '閉じる', + back: '戻る', + next: '次へ', + previous: '前へ', + view: '表示', + preview: 'プレビュー', + details: '詳細', + settings: '設定', + help: 'ヘルプ', + about: 'について' + }, + + // 言語設定 + language: { + current: '言語', + select: '言語を選択', + select_help: 'インターフェース言語を選択してください', + english: '英語', + chinese_simplified: '中国語(簡体字)', + chinese_traditional: '中国語(繁体字)', + russian: 'ロシア語', + german: 'ドイツ語', + japanese: '日本語', + korean: '韓国語', + french: 'フランス語', + spanish: 'スペイン語' + }, + + // ステータスメッセージ + status: { + loading: '読み込み中...', + saving: '保存中...', + saved: '保存済み', + error: 'エラー', + success: '成功', + warning: '警告', + info: '情報', + processing: '処理中...', + completed: '完了', + failed: '失敗', + cancelled: 'キャンセル', + pending: '待機中', + ready: '準備完了' + } + }, + + // ヘッダーとナビゲーション + header: { + appTitle: 'LoRA マネージャー', + navigation: { + loras: 'LoRA', + recipes: 'レシピ', + checkpoints: 'チェックポイント', + embeddings: 'エンベディング', + statistics: '統計' + }, + search: { + placeholder: '検索...', + placeholders: { + loras: 'LoRAを検索...', + recipes: 'レシピを検索...', + checkpoints: 'チェックポイントを検索...', + embeddings: 'エンベディングを検索...' + }, + options: '検索オプション', + searchIn: '検索対象:', + notAvailable: '統計ページでは検索は利用できません', + filters: { + filename: 'ファイル名', + modelname: 'モデル名', + tags: 'タグ', + creator: '作成者', + title: 'レシピタイトル', + loraName: 'LoRAファイル名', + loraModel: 'LoRAモデル名' + } + }, + filter: { + title: 'モデルをフィルター', + baseModel: 'ベースモデル', + modelTags: 'タグ(トップ20)', + clearAll: 'すべてのフィルターをクリア' + }, + theme: { + toggle: 'テーマ切り替え', + switchToLight: 'ライトテーマに切り替え', + switchToDark: 'ダークテーマに切り替え', + switchToAuto: 'オートテーマに切り替え' + } + }, + + // LoRAページ + loras: { + title: 'LoRAモデル', + controls: { + sort: { + title: 'モデルをソート...', + name: '名前', + nameAsc: 'A - Z', + nameDesc: 'Z - A', + date: '追加日', + dateDesc: '新しい順', + dateAsc: '古い順', + size: 'ファイルサイズ', + sizeDesc: '大きい順', + sizeAsc: '小さい順' + }, + refresh: { + title: 'モデルリストを更新', + quick: 'クイック更新(増分)', + full: '完全再構築(完全)' + }, + fetch: 'Civitaiから取得', + download: 'URLからダウンロード', + bulk: '一括操作', + duplicates: '重複を検索', + favorites: 'お気に入りのみ表示' + }, + bulkOperations: { + title: '一括操作', + selected: '{count}個選択中', + selectAll: '現在のページのすべてを選択', + deselectAll: 'すべての選択を解除', + actions: { + move: '選択項目を移動', + delete: '選択項目を削除', + setRating: 'コンテンツレーティングを設定', + export: '選択項目をエクスポート' + } + }, + card: { + actions: { + copyTriggerWords: 'トリガーワードをコピー', + copyLoraName: 'LoRA名をコピー', + sendToWorkflow: 'ワークフローに送信', + sendToWorkflowAppend: 'ワークフローに送信(追加)', + sendToWorkflowReplace: 'ワークフローに送信(置換)', + openExamples: 'サンプルフォルダを開く', + downloadExamples: 'サンプル画像をダウンロード', + replacePreview: 'プレビューを置換', + setContentRating: 'コンテンツレーティングを設定', + moveToFolder: 'フォルダに移動', + excludeModel: 'モデルを除外', + deleteModel: 'モデルを削除' + }, + modal: { + title: 'LoRA詳細', + tabs: { + examples: 'サンプル', + description: 'モデル説明', + recipes: 'レシピ' + }, + info: { + filename: 'ファイル名', + modelName: 'モデル名', + baseModel: 'ベースモデル', + fileSize: 'ファイルサイズ', + dateAdded: '追加日', + triggerWords: 'トリガーワード', + description: '説明', + tags: 'タグ', + rating: '評価', + downloads: 'ダウンロード数', + likes: 'いいね数', + version: 'バージョン' + }, + actions: { + copyTriggerWords: 'トリガーワードをコピー', + copyLoraName: 'LoRA名をコピー', + sendToWorkflow: 'ワークフローに送信', + viewOnCivitai: 'Civitaiで表示', + downloadExamples: 'サンプル画像をダウンロード' + } + } + } + }, + + // レシピページ + recipes: { + title: 'LoRAレシピ', + controls: { + import: 'レシピをインポート', + create: 'レシピを作成', + export: '選択項目をエクスポート', + downloadMissing: '不足しているLoRAをダウンロード' + }, + card: { + author: '作者', + loras: '{count}個のLoRA', + tags: 'タグ', + actions: { + sendToWorkflow: 'ワークフローに送信', + edit: 'レシピを編集', + duplicate: 'レシピを複製', + export: 'レシピをエクスポート', + delete: 'レシピを削除' + } + } + }, + + // チェックポイントページ + checkpoints: { + title: 'チェックポイントモデル', + info: { + filename: 'ファイル名', + modelName: 'モデル名', + baseModel: 'ベースモデル', + fileSize: 'ファイルサイズ', + dateAdded: '追加日' + } + }, + + // エンベディングページ + embeddings: { + title: 'エンベディングモデル', + info: { + filename: 'ファイル名', + modelName: 'モデル名', + triggerWords: 'トリガーワード', + fileSize: 'ファイルサイズ', + dateAdded: '追加日' + } + }, + + // 統計ページ + statistics: { + title: '統計', + overview: { + title: '概要', + totalModels: '総モデル数', + totalSize: '総サイズ', + avgFileSize: '平均ファイルサイズ', + newestModel: '最新モデル' + }, + charts: { + modelsByBaseModel: 'ベースモデル別', + modelsByMonth: '月別', + fileSizeDistribution: 'ファイルサイズ分布', + topTags: '人気タグ' + } + }, + + // モーダルダイアログ + modals: { + delete: { + title: '削除の確認', + message: 'このモデルを削除してもよろしいですか?この操作は元に戻せません。', + confirm: '削除', + cancel: 'キャンセル' + }, + exclude: { + title: 'モデルを除外', + message: 'このモデルをライブラリから除外してもよろしいですか?', + confirm: '除外', + cancel: 'キャンセル' + }, + download: { + title: 'モデルをダウンロード', + url: 'モデルURL', + placeholder: 'CivitaiモデルURLを入力...', + download: 'ダウンロード', + cancel: 'キャンセル' + }, + move: { + title: 'モデルを移動', + selectFolder: '移動先フォルダを選択', + createFolder: '新しいフォルダを作成', + folderName: 'フォルダ名', + move: '移動', + cancel: 'キャンセル' + }, + contentRating: { + title: 'コンテンツレーティングを設定', + current: '現在', + levels: { + pg: '全年齢', + pg13: '13歳以上', + r: '制限あり', + x: '成人向け', + xxx: '露骨' + } + } + }, + + // エラーメッセージ + errors: { + general: 'エラーが発生しました', + networkError: 'ネットワークエラー。接続を確認してください。', + serverError: 'サーバーエラー。後でもう一度試してください。', + fileNotFound: 'ファイルが見つかりません', + invalidFile: '無効なファイル形式', + uploadFailed: 'アップロードに失敗しました', + downloadFailed: 'ダウンロードに失敗しました', + saveFailed: '保存に失敗しました', + loadFailed: '読み込みに失敗しました', + deleteFailed: '削除に失敗しました', + moveFailed: '移動に失敗しました', + copyFailed: 'コピーに失敗しました', + fetchFailed: 'Civitaiからデータを取得できませんでした', + invalidUrl: '無効なURL形式', + missingPermissions: '権限が不足しています' + }, + + // 成功メッセージ + success: { + saved: '正常に保存されました', + deleted: '正常に削除されました', + moved: '正常に移動されました', + copied: '正常にコピーされました', + downloaded: '正常にダウンロードされました', + uploaded: '正常にアップロードされました', + refreshed: '正常に更新されました', + exported: '正常にエクスポートされました', + imported: '正常にインポートされました' + }, + + // キーボードショートカット + keyboard: { + navigation: 'キーボードナビゲーション:', + shortcuts: { + pageUp: '1ページ上にスクロール', + pageDown: '1ページ下にスクロール', + home: 'トップにジャンプ', + end: 'ボトムにジャンプ', + bulkMode: '一括モードを切り替え', + search: '検索にフォーカス', + escape: 'モーダル/パネルを閉じる' + } + }, + + // 初期化 + initialization: { + title: 'LoRAマネージャーを初期化中', + message: 'LoRAキャッシュをスキャンして構築中です。数分かかる場合があります...', + steps: { + scanning: 'モデルファイルをスキャン中...', + processing: 'メタデータを処理中...', + building: 'キャッシュを構築中...', + finalizing: '完了中...' + } + }, + + // ツールチップとヘルプテキスト + tooltips: { + refresh: 'モデルリストを更新', + bulkOperations: '複数のモデルを選択してバッチ操作', + favorites: 'お気に入りモデルのみ表示', + duplicates: '重複モデルを検索・管理', + search: '名前、タグ、その他の条件でモデルを検索', + filter: '様々な条件でモデルをフィルター', + sort: '異なる属性でモデルをソート', + backToTop: 'ページトップにスクロール' + } +}; diff --git a/static/js/i18n/locales/ko.js b/static/js/i18n/locales/ko.js new file mode 100644 index 00000000..7210ee6d --- /dev/null +++ b/static/js/i18n/locales/ko.js @@ -0,0 +1,397 @@ +/** + * Korean (ko) translations for LoRA Manager + */ +export const ko = { + // 애플리케이션 전체에서 사용되는 공통 용어 + common: { + // 파일 작업 + file: '파일', + folder: '폴더', + name: '이름', + size: '크기', + date: '날짜', + type: '유형', + path: '경로', + + // 파일 크기 + fileSize: { + zero: '0 바이트', + bytes: '바이트', + kb: 'KB', + mb: 'MB', + gb: 'GB', + tb: 'TB' + }, + + // 작업 + actions: { + save: '저장', + cancel: '취소', + delete: '삭제', + edit: '편집', + copy: '복사', + move: '이동', + refresh: '새로고침', + download: '다운로드', + upload: '업로드', + search: '검색', + filter: '필터', + sort: '정렬', + select: '선택', + selectAll: '모두 선택', + deselectAll: '선택 해제', + confirm: '확인', + close: '닫기', + back: '뒤로', + next: '다음', + previous: '이전', + view: '보기', + preview: '미리보기', + details: '세부정보', + settings: '설정', + help: '도움말', + about: '정보' + }, + + // 언어 설정 + language: { + current: '언어', + select: '언어 선택', + select_help: '선호하는 인터페이스 언어를 선택하세요', + english: '영어', + chinese_simplified: '중국어(간체)', + chinese_traditional: '중국어(번체)', + russian: '러시아어', + german: '독일어', + japanese: '일본어', + korean: '한국어', + french: '프랑스어', + spanish: '스페인어' + }, + + // 상태 메시지 + status: { + loading: '로딩 중...', + saving: '저장 중...', + saved: '저장됨', + error: '오류', + success: '성공', + warning: '경고', + info: '정보', + processing: '처리 중...', + completed: '완료', + failed: '실패', + cancelled: '취소됨', + pending: '대기 중', + ready: '준비 완료' + } + }, + + // 헤더 및 네비게이션 + header: { + appTitle: 'LoRA 매니저', + navigation: { + loras: 'LoRA', + recipes: '레시피', + checkpoints: '체크포인트', + embeddings: '임베딩', + statistics: '통계' + }, + search: { + placeholder: '검색...', + placeholders: { + loras: 'LoRA 검색...', + recipes: '레시피 검색...', + checkpoints: '체크포인트 검색...', + embeddings: '임베딩 검색...' + }, + options: '검색 옵션', + searchIn: '검색 범위:', + notAvailable: '통계 페이지에서는 검색이 불가능합니다', + filters: { + filename: '파일명', + modelname: '모델명', + tags: '태그', + creator: '제작자', + title: '레시피 제목', + loraName: 'LoRA 파일명', + loraModel: 'LoRA 모델명' + } + }, + filter: { + title: '모델 필터', + baseModel: '베이스 모델', + modelTags: '태그 (상위 20개)', + clearAll: '모든 필터 지우기' + }, + theme: { + toggle: '테마 전환', + switchToLight: '밝은 테마로 전환', + switchToDark: '어두운 테마로 전환', + switchToAuto: '자동 테마로 전환' + } + }, + + // LoRA 페이지 + loras: { + title: 'LoRA 모델', + controls: { + sort: { + title: '모델 정렬...', + name: '이름', + nameAsc: 'A - Z', + nameDesc: 'Z - A', + date: '추가 날짜', + dateDesc: '최신순', + dateAsc: '오래된순', + size: '파일 크기', + sizeDesc: '큰 순서', + sizeAsc: '작은 순서' + }, + refresh: { + title: '모델 목록 새로고침', + quick: '빠른 새로고침 (증분)', + full: '전체 재구축 (완전)' + }, + fetch: 'Civitai에서 가져오기', + download: 'URL에서 다운로드', + bulk: '일괄 작업', + duplicates: '중복 찾기', + favorites: '즐겨찾기만 표시' + }, + bulkOperations: { + title: '일괄 작업', + selected: '{count}개 선택됨', + selectAll: '현재 페이지 모두 선택', + deselectAll: '모든 선택 해제', + actions: { + move: '선택항목 이동', + delete: '선택항목 삭제', + setRating: '콘텐츠 등급 설정', + export: '선택항목 내보내기' + } + }, + card: { + actions: { + copyTriggerWords: '트리거 단어 복사', + copyLoraName: 'LoRA 이름 복사', + sendToWorkflow: '워크플로우로 전송', + sendToWorkflowAppend: '워크플로우로 전송 (추가)', + sendToWorkflowReplace: '워크플로우로 전송 (교체)', + openExamples: '예제 폴더 열기', + downloadExamples: '예제 이미지 다운로드', + replacePreview: '미리보기 교체', + setContentRating: '콘텐츠 등급 설정', + moveToFolder: '폴더로 이동', + excludeModel: '모델 제외', + deleteModel: '모델 삭제' + }, + modal: { + title: 'LoRA 세부정보', + tabs: { + examples: '예제', + description: '모델 설명', + recipes: '레시피' + }, + info: { + filename: '파일명', + modelName: '모델명', + baseModel: '베이스 모델', + fileSize: '파일 크기', + dateAdded: '추가 날짜', + triggerWords: '트리거 단어', + description: '설명', + tags: '태그', + rating: '평점', + downloads: '다운로드 수', + likes: '좋아요 수', + version: '버전' + }, + actions: { + copyTriggerWords: '트리거 단어 복사', + copyLoraName: 'LoRA 이름 복사', + sendToWorkflow: '워크플로우로 전송', + viewOnCivitai: 'Civitai에서 보기', + downloadExamples: '예제 이미지 다운로드' + } + } + } + }, + + // 레시피 페이지 + recipes: { + title: 'LoRA 레시피', + controls: { + import: '레시피 가져오기', + create: '레시피 만들기', + export: '선택항목 내보내기', + downloadMissing: '누락된 LoRA 다운로드' + }, + card: { + author: '저자', + loras: '{count}개의 LoRA', + tags: '태그', + actions: { + sendToWorkflow: '워크플로우로 전송', + edit: '레시피 편집', + duplicate: '레시피 복제', + export: '레시피 내보내기', + delete: '레시피 삭제' + } + } + }, + + // 체크포인트 페이지 + checkpoints: { + title: '체크포인트 모델', + info: { + filename: '파일명', + modelName: '모델명', + baseModel: '베이스 모델', + fileSize: '파일 크기', + dateAdded: '추가 날짜' + } + }, + + // 임베딩 페이지 + embeddings: { + title: '임베딩 모델', + info: { + filename: '파일명', + modelName: '모델명', + triggerWords: '트리거 단어', + fileSize: '파일 크기', + dateAdded: '추가 날짜' + } + }, + + // 통계 페이지 + statistics: { + title: '통계', + overview: { + title: '개요', + totalModels: '총 모델 수', + totalSize: '총 크기', + avgFileSize: '평균 파일 크기', + newestModel: '최신 모델' + }, + charts: { + modelsByBaseModel: '베이스 모델별', + modelsByMonth: '월별', + fileSizeDistribution: '파일 크기 분포', + topTags: '인기 태그' + } + }, + + // 모달 대화상자 + modals: { + delete: { + title: '삭제 확인', + message: '이 모델을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.', + confirm: '삭제', + cancel: '취소' + }, + exclude: { + title: '모델 제외', + message: '이 모델을 라이브러리에서 제외하시겠습니까?', + confirm: '제외', + cancel: '취소' + }, + download: { + title: '모델 다운로드', + url: '모델 URL', + placeholder: 'Civitai 모델 URL 입력...', + download: '다운로드', + cancel: '취소' + }, + move: { + title: '모델 이동', + selectFolder: '대상 폴더 선택', + createFolder: '새 폴더 만들기', + folderName: '폴더명', + move: '이동', + cancel: '취소' + }, + contentRating: { + title: '콘텐츠 등급 설정', + current: '현재', + levels: { + pg: '전체관람가', + pg13: '13세 이상', + r: '제한관람가', + x: '성인', + xxx: '노골적' + } + } + }, + + // 오류 메시지 + errors: { + general: '오류가 발생했습니다', + networkError: '네트워크 오류. 연결을 확인하세요.', + serverError: '서버 오류. 나중에 다시 시도하세요.', + fileNotFound: '파일을 찾을 수 없습니다', + invalidFile: '잘못된 파일 형식', + uploadFailed: '업로드 실패', + downloadFailed: '다운로드 실패', + saveFailed: '저장 실패', + loadFailed: '로드 실패', + deleteFailed: '삭제 실패', + moveFailed: '이동 실패', + copyFailed: '복사 실패', + fetchFailed: 'Civitai에서 데이터를 가져오지 못했습니다', + invalidUrl: '잘못된 URL 형식', + missingPermissions: '권한이 부족합니다' + }, + + // 성공 메시지 + success: { + saved: '성공적으로 저장되었습니다', + deleted: '성공적으로 삭제되었습니다', + moved: '성공적으로 이동되었습니다', + copied: '성공적으로 복사되었습니다', + downloaded: '성공적으로 다운로드되었습니다', + uploaded: '성공적으로 업로드되었습니다', + refreshed: '성공적으로 새로고침되었습니다', + exported: '성공적으로 내보내졌습니다', + imported: '성공적으로 가져왔습니다' + }, + + // 키보드 단축키 + keyboard: { + navigation: '키보드 내비게이션:', + shortcuts: { + pageUp: '한 페이지 위로 스크롤', + pageDown: '한 페이지 아래로 스크롤', + home: '맨 위로 이동', + end: '맨 아래로 이동', + bulkMode: '일괄 모드 전환', + search: '검색 포커스', + escape: '모달/패널 닫기' + } + }, + + // 초기화 + initialization: { + title: 'LoRA 매니저 초기화', + message: 'LoRA 캐시를 스캔하고 구축중입니다. 몇 분 정도 걸릴 수 있습니다...', + steps: { + scanning: '모델 파일 스캔 중...', + processing: '메타데이터 처리 중...', + building: '캐시 구축 중...', + finalizing: '마무리 중...' + } + }, + + // 툴팁 및 도움말 텍스트 + tooltips: { + refresh: '모델 목록 새로고침', + bulkOperations: '여러 모델을 선택하여 일괄 작업', + favorites: '즐겨찾기 모델만 표시', + duplicates: '중복 모델 찾기 및 관리', + search: '이름, 태그 또는 기타 기준으로 모델 검색', + filter: '다양한 기준으로 모델 필터링', + sort: '다른 속성으로 모델 정렬', + backToTop: '페이지 맨 위로 스크롤' + } +}; diff --git a/static/js/i18n/locales/ru.js b/static/js/i18n/locales/ru.js new file mode 100644 index 00000000..c0adeae8 --- /dev/null +++ b/static/js/i18n/locales/ru.js @@ -0,0 +1,397 @@ +/** + * Russian (ru) translations for LoRA Manager + */ +export const ru = { + // Общие термины, используемые в приложении + common: { + // Операции с файлами + file: 'Файл', + folder: 'Папка', + name: 'Имя', + size: 'Размер', + date: 'Дата', + type: 'Тип', + path: 'Путь', + + // Размеры файлов + fileSize: { + zero: '0 Байт', + bytes: 'Байт', + kb: 'КБ', + mb: 'МБ', + gb: 'ГБ', + tb: 'ТБ' + }, + + // Действия + actions: { + save: 'Сохранить', + cancel: 'Отмена', + delete: 'Удалить', + edit: 'Редактировать', + copy: 'Копировать', + move: 'Переместить', + refresh: 'Обновить', + download: 'Скачать', + upload: 'Загрузить', + search: 'Поиск', + filter: 'Фильтр', + sort: 'Сортировка', + select: 'Выбрать', + selectAll: 'Выбрать все', + deselectAll: 'Отменить выбор', + confirm: 'Подтвердить', + close: 'Закрыть', + back: 'Назад', + next: 'Далее', + previous: 'Предыдущий', + view: 'Просмотр', + preview: 'Предпросмотр', + details: 'Детали', + settings: 'Настройки', + help: 'Помощь', + about: 'О программе' + }, + + // Настройки языка + language: { + current: 'Язык', + select: 'Выберите язык', + select_help: 'Выберите предпочитаемый язык интерфейса', + english: 'Английский', + chinese_simplified: 'Китайский (упрощенный)', + chinese_traditional: 'Китайский (традиционный)', + russian: 'Русский', + german: 'Немецкий', + japanese: 'Японский', + korean: 'Корейский', + french: 'Французский', + spanish: 'Испанский' + }, + + // Сообщения о состоянии + status: { + loading: 'Загрузка...', + saving: 'Сохранение...', + saved: 'Сохранено', + error: 'Ошибка', + success: 'Успешно', + warning: 'Предупреждение', + info: 'Информация', + processing: 'Обработка...', + completed: 'Завершено', + failed: 'Не удалось', + cancelled: 'Отменено', + pending: 'Ожидание', + ready: 'Готово' + } + }, + + // Заголовок и навигация + header: { + appTitle: 'LoRA Менеджер', + navigation: { + loras: 'LoRA', + recipes: 'Рецепты', + checkpoints: 'Чекпоинты', + embeddings: 'Эмбеддинги', + statistics: 'Статистика' + }, + search: { + placeholder: 'Поиск...', + placeholders: { + loras: 'Поиск LoRA...', + recipes: 'Поиск рецептов...', + checkpoints: 'Поиск чекпоинтов...', + embeddings: 'Поиск эмбеддингов...' + }, + options: 'Опции поиска', + searchIn: 'Искать в:', + notAvailable: 'Поиск недоступен на странице статистики', + filters: { + filename: 'Имя файла', + modelname: 'Имя модели', + tags: 'Теги', + creator: 'Создатель', + title: 'Название рецепта', + loraName: 'Имя файла LoRA', + loraModel: 'Имя модели LoRA' + } + }, + filter: { + title: 'Фильтр моделей', + baseModel: 'Базовая модель', + modelTags: 'Теги (топ 20)', + clearAll: 'Очистить все фильтры' + }, + theme: { + toggle: 'Переключить тему', + switchToLight: 'Переключить на светлую тему', + switchToDark: 'Переключить на тёмную тему', + switchToAuto: 'Переключить на автоматическую тему' + } + }, + + // Страница LoRA + loras: { + title: 'LoRA Модели', + controls: { + sort: { + title: 'Сортировать модели по...', + name: 'Имя', + nameAsc: 'A - Z', + nameDesc: 'Z - A', + date: 'Дата добавления', + dateDesc: 'Новые', + dateAsc: 'Старые', + size: 'Размер файла', + sizeDesc: 'Большие', + sizeAsc: 'Маленькие' + }, + refresh: { + title: 'Обновить список моделей', + quick: 'Быстрое обновление (инкрементальное)', + full: 'Полная перестройка (полная)' + }, + fetch: 'Получить с Civitai', + download: 'Скачать по URL', + bulk: 'Массовые операции', + duplicates: 'Найти дубликаты', + favorites: 'Показать только избранные' + }, + bulkOperations: { + title: 'Массовые операции', + selected: 'Выбрано {count}', + selectAll: 'Выбрать все на текущей странице', + deselectAll: 'Отменить выбор всех', + actions: { + move: 'Переместить выбранные', + delete: 'Удалить выбранные', + setRating: 'Установить рейтинг контента', + export: 'Экспортировать выбранные' + } + }, + card: { + actions: { + copyTriggerWords: 'Копировать триггерные слова', + copyLoraName: 'Копировать имя LoRA', + sendToWorkflow: 'Отправить в рабочий процесс', + sendToWorkflowAppend: 'Отправить в рабочий процесс (добавить)', + sendToWorkflowReplace: 'Отправить в рабочий процесс (заменить)', + openExamples: 'Открыть папку с примерами', + downloadExamples: 'Скачать примеры изображений', + replacePreview: 'Заменить превью', + setContentRating: 'Установить рейтинг контента', + moveToFolder: 'Переместить в папку', + excludeModel: 'Исключить модель', + deleteModel: 'Удалить модель' + }, + modal: { + title: 'Детали LoRA', + tabs: { + examples: 'Примеры', + description: 'Описание модели', + recipes: 'Рецепты' + }, + info: { + filename: 'Имя файла', + modelName: 'Имя модели', + baseModel: 'Базовая модель', + fileSize: 'Размер файла', + dateAdded: 'Дата добавления', + triggerWords: 'Триггерные слова', + description: 'Описание', + tags: 'Теги', + rating: 'Рейтинг', + downloads: 'Скачивания', + likes: 'Лайки', + version: 'Версия' + }, + actions: { + copyTriggerWords: 'Копировать триггерные слова', + copyLoraName: 'Копировать имя LoRA', + sendToWorkflow: 'Отправить в рабочий процесс', + viewOnCivitai: 'Просмотреть на Civitai', + downloadExamples: 'Скачать примеры изображений' + } + } + } + }, + + // Страница рецептов + recipes: { + title: 'LoRA Рецепты', + controls: { + import: 'Импортировать рецепт', + create: 'Создать рецепт', + export: 'Экспортировать выбранные', + downloadMissing: 'Скачать отсутствующие LoRA' + }, + card: { + author: 'Автор', + loras: '{count} LoRA', + tags: 'Теги', + actions: { + sendToWorkflow: 'Отправить в рабочий процесс', + edit: 'Редактировать рецепт', + duplicate: 'Дублировать рецепт', + export: 'Экспортировать рецепт', + delete: 'Удалить рецепт' + } + } + }, + + // Страница чекпоинтов + checkpoints: { + title: 'Чекпоинты', + info: { + filename: 'Имя файла', + modelName: 'Имя модели', + baseModel: 'Базовая модель', + fileSize: 'Размер файла', + dateAdded: 'Дата добавления' + } + }, + + // Страница эмбеддингов + embeddings: { + title: 'Эмбеддинги', + info: { + filename: 'Имя файла', + modelName: 'Имя модели', + triggerWords: 'Триггерные слова', + fileSize: 'Размер файла', + dateAdded: 'Дата добавления' + } + }, + + // Страница статистики + statistics: { + title: 'Статистика', + overview: { + title: 'Обзор', + totalModels: 'Всего моделей', + totalSize: 'Общий размер', + avgFileSize: 'Средний размер файла', + newestModel: 'Новейшая модель' + }, + charts: { + modelsByBaseModel: 'По базовым моделям', + modelsByMonth: 'По месяцам', + fileSizeDistribution: 'Распределение размеров файлов', + topTags: 'Популярные теги' + } + }, + + // Модальные окна + modals: { + delete: { + title: 'Подтвердить удаление', + message: 'Вы уверены, что хотите удалить эту модель? Это действие нельзя отменить.', + confirm: 'Удалить', + cancel: 'Отмена' + }, + exclude: { + title: 'Исключить модель', + message: 'Вы уверены, что хотите исключить эту модель из библиотеки?', + confirm: 'Исключить', + cancel: 'Отмена' + }, + download: { + title: 'Скачать модель', + url: 'URL модели', + placeholder: 'Введите URL модели Civitai...', + download: 'Скачать', + cancel: 'Отмена' + }, + move: { + title: 'Переместить модель', + selectFolder: 'Выберите папку назначения', + createFolder: 'Создать новую папку', + folderName: 'Имя папки', + move: 'Переместить', + cancel: 'Отмена' + }, + contentRating: { + title: 'Установить рейтинг контента', + current: 'Текущий', + levels: { + pg: 'Для всех', + pg13: 'С 13 лет', + r: 'Ограниченный', + x: 'Для взрослых', + xxx: 'Эротический' + } + } + }, + + // Сообщения об ошибках + errors: { + general: 'Произошла ошибка', + networkError: 'Ошибка сети. Проверьте подключение.', + serverError: 'Ошибка сервера. Попробуйте позже.', + fileNotFound: 'Файл не найден', + invalidFile: 'Неверный формат файла', + uploadFailed: 'Загрузка не удалась', + downloadFailed: 'Скачивание не удалось', + saveFailed: 'Сохранение не удалось', + loadFailed: 'Загрузка не удалась', + deleteFailed: 'Удаление не удалось', + moveFailed: 'Перемещение не удалось', + copyFailed: 'Копирование не удалось', + fetchFailed: 'Не удалось получить данные с Civitai', + invalidUrl: 'Неверный формат URL', + missingPermissions: 'Недостаточно прав' + }, + + // Сообщения об успехе + success: { + saved: 'Успешно сохранено', + deleted: 'Успешно удалено', + moved: 'Успешно перемещено', + copied: 'Успешно скопировано', + downloaded: 'Успешно скачано', + uploaded: 'Успешно загружено', + refreshed: 'Успешно обновлено', + exported: 'Успешно экспортировано', + imported: 'Успешно импортировано' + }, + + // Горячие клавиши + keyboard: { + navigation: 'Навигация с клавиатуры:', + shortcuts: { + pageUp: 'Прокрутить вверх на одну страницу', + pageDown: 'Прокрутить вниз на одну страницу', + home: 'Перейти к началу', + end: 'Перейти к концу', + bulkMode: 'Переключить массовый режим', + search: 'Фокус на поиске', + escape: 'Закрыть модальное окно/панель' + } + }, + + // Инициализация + initialization: { + title: 'Инициализация LoRA Менеджера', + message: 'Сканирование и построение кэша LoRA. Это может занять несколько минут...', + steps: { + scanning: 'Сканирование файлов моделей...', + processing: 'Обработка метаданных...', + building: 'Построение кэша...', + finalizing: 'Завершение...' + } + }, + + // Подсказки и справочный текст + tooltips: { + refresh: 'Обновить список моделей', + bulkOperations: 'Выбрать несколько моделей для массовых операций', + favorites: 'Показать только избранные модели', + duplicates: 'Найти и управлять дублирующимися моделями', + search: 'Поиск моделей по имени, тегам или другим критериям', + filter: 'Фильтровать модели по различным критериям', + sort: 'Сортировать модели по разным атрибутам', + backToTop: 'Прокрутить обратно к верху страницы' + } +}; diff --git a/static/js/i18n/locales/zh-CN.js b/static/js/i18n/locales/zh-CN.js index c130ccac..9595a94c 100644 --- a/static/js/i18n/locales/zh-CN.js +++ b/static/js/i18n/locales/zh-CN.js @@ -53,6 +53,22 @@ export const zhCN = { about: '关于' }, + // 语言设置 + language: { + current: '语言', + select: '选择语言', + select_help: '选择您偏好的界面语言', + english: '英语', + chinese_simplified: '中文(简体)', + chinese_traditional: '中文(繁体)', + russian: '俄语', + german: '德语', + japanese: '日语', + korean: '韩语', + french: '法语', + spanish: '西班牙语' + }, + // 状态信息 status: { loading: '加载中...', @@ -73,7 +89,7 @@ export const zhCN = { // 头部和导航 header: { - appTitle: 'LoRA', + appTitle: 'LoRA 管理器', navigation: { loras: 'LoRA', recipes: '配方', diff --git a/static/js/i18n/locales/zh-TW.js b/static/js/i18n/locales/zh-TW.js new file mode 100644 index 00000000..48b3ab0e --- /dev/null +++ b/static/js/i18n/locales/zh-TW.js @@ -0,0 +1,397 @@ +/** + * Traditional Chinese (zh-TW) translations for LoRA Manager + */ +export const zhTW = { + // 應用中使用的通用術語 + common: { + // 檔案操作 + file: '檔案', + folder: '資料夾', + name: '名稱', + size: '大小', + date: '日期', + type: '類型', + path: '路徑', + + // 檔案大小 + fileSize: { + zero: '0 位元組', + bytes: '位元組', + kb: 'KB', + mb: 'MB', + gb: 'GB', + tb: 'TB' + }, + + // 操作 + actions: { + save: '儲存', + cancel: '取消', + delete: '刪除', + edit: '編輯', + copy: '複製', + move: '移動', + refresh: '重新整理', + download: '下載', + upload: '上傳', + search: '搜尋', + filter: '篩選', + sort: '排序', + select: '選擇', + selectAll: '全選', + deselectAll: '取消全選', + confirm: '確認', + close: '關閉', + back: '返回', + next: '下一步', + previous: '上一步', + view: '檢視', + preview: '預覽', + details: '詳情', + settings: '設定', + help: '說明', + about: '關於' + }, + + // 語言設定 + language: { + current: '語言', + select: '選擇語言', + select_help: '選擇您偏好的介面語言', + english: '英語', + chinese_simplified: '中文(簡體)', + chinese_traditional: '中文(繁體)', + russian: '俄語', + german: '德語', + japanese: '日語', + korean: '韓語', + french: '法語', + spanish: '西班牙語' + }, + + // 狀態資訊 + status: { + loading: '載入中...', + saving: '儲存中...', + saved: '已儲存', + error: '錯誤', + success: '成功', + warning: '警告', + info: '資訊', + processing: '處理中...', + completed: '已完成', + failed: '失敗', + cancelled: '已取消', + pending: '等待中', + ready: '就緒' + } + }, + + // 標題列和導覽 + header: { + appTitle: 'LoRA 管理器', + navigation: { + loras: 'LoRA', + recipes: '配方', + checkpoints: '大模型', + embeddings: 'Embedding', + statistics: '統計' + }, + search: { + placeholder: '搜尋...', + placeholders: { + loras: '搜尋 LoRA...', + recipes: '搜尋配方...', + checkpoints: '搜尋大模型...', + embeddings: '搜尋 Embedding...' + }, + options: '搜尋選項', + searchIn: '搜尋範圍:', + notAvailable: '統計頁面不支援搜尋', + filters: { + filename: '檔案名稱', + modelname: '模型名稱', + tags: '標籤', + creator: '創作者', + title: '配方標題', + loraName: 'LoRA 檔案名稱', + loraModel: 'LoRA 模型名稱' + } + }, + filter: { + title: '篩選模型', + baseModel: '基礎模型', + modelTags: '標籤(前20個)', + clearAll: '清除所有篩選' + }, + theme: { + toggle: '切換主題', + switchToLight: '切換到淺色主題', + switchToDark: '切換到深色主題', + switchToAuto: '切換到自動主題' + } + }, + + // LoRA 頁面 + loras: { + title: 'LoRA 模型', + controls: { + sort: { + title: '排序方式...', + name: '名稱', + nameAsc: 'A - Z', + nameDesc: 'Z - A', + date: '新增日期', + dateDesc: '最新', + dateAsc: '最舊', + size: '檔案大小', + sizeDesc: '最大', + sizeAsc: '最小' + }, + refresh: { + title: '重新整理模型清單', + quick: '快速重新整理(增量)', + full: '完整重建(完整)' + }, + fetch: '從 Civitai 獲取', + download: '從 URL 下載', + bulk: '批次操作', + duplicates: '尋找重複項', + favorites: '僅顯示收藏' + }, + bulkOperations: { + title: '批次操作', + selected: '已選擇 {count} 個', + selectAll: '選擇所有當前頁面', + deselectAll: '取消選擇所有', + actions: { + move: '移動選中項目', + delete: '刪除選中項目', + setRating: '設定內容評級', + export: '匯出選中項目' + } + }, + card: { + actions: { + copyTriggerWords: '複製觸發詞', + copyLoraName: '複製 LoRA 名稱', + sendToWorkflow: '傳送到工作流程', + sendToWorkflowAppend: '傳送到工作流程(附加)', + sendToWorkflowReplace: '傳送到工作流程(替換)', + openExamples: '開啟範例資料夾', + downloadExamples: '下載範例圖片', + replacePreview: '替換預覽圖', + setContentRating: '設定內容評級', + moveToFolder: '移動到資料夾', + excludeModel: '排除模型', + deleteModel: '刪除模型' + }, + modal: { + title: 'LoRA 詳情', + tabs: { + examples: '範例', + description: '模型描述', + recipes: '配方' + }, + info: { + filename: '檔案名稱', + modelName: '模型名稱', + baseModel: '基礎模型', + fileSize: '檔案大小', + dateAdded: '新增日期', + triggerWords: '觸發詞', + description: '描述', + tags: '標籤', + rating: '評級', + downloads: '下載次數', + likes: '按讚數', + version: '版本' + }, + actions: { + copyTriggerWords: '複製觸發詞', + copyLoraName: '複製 LoRA 名稱', + sendToWorkflow: '傳送到工作流程', + viewOnCivitai: '在 Civitai 檢視', + downloadExamples: '下載範例圖片' + } + } + } + }, + + // 配方頁面 + recipes: { + title: 'LoRA 配方', + controls: { + import: '匯入配方', + create: '建立配方', + export: '匯出選中項目', + downloadMissing: '下載缺少的 LoRA' + }, + card: { + author: '作者', + loras: '{count} 個 LoRA', + tags: '標籤', + actions: { + sendToWorkflow: '傳送到工作流程', + edit: '編輯配方', + duplicate: '複製配方', + export: '匯出配方', + delete: '刪除配方' + } + } + }, + + // 大模型頁面 + checkpoints: { + title: '大模型', + info: { + filename: '檔案名稱', + modelName: '模型名稱', + baseModel: '基礎模型', + fileSize: '檔案大小', + dateAdded: '新增日期' + } + }, + + // Embedding 頁面 + embeddings: { + title: 'Embedding 模型', + info: { + filename: '檔案名稱', + modelName: '模型名稱', + triggerWords: '觸發詞', + fileSize: '檔案大小', + dateAdded: '新增日期' + } + }, + + // 統計頁面 + statistics: { + title: '統計', + overview: { + title: '概覽', + totalModels: '總模型數', + totalSize: '總大小', + avgFileSize: '平均檔案大小', + newestModel: '最新模型' + }, + charts: { + modelsByBaseModel: '按基礎模型分類', + modelsByMonth: '按月份分類', + fileSizeDistribution: '檔案大小分佈', + topTags: '熱門標籤' + } + }, + + // 模態對話框 + modals: { + delete: { + title: '確認刪除', + message: '確定要刪除這個模型嗎?此操作無法復原。', + confirm: '刪除', + cancel: '取消' + }, + exclude: { + title: '排除模型', + message: '確定要從程式庫中排除這個模型嗎?', + confirm: '排除', + cancel: '取消' + }, + download: { + title: '下載模型', + url: '模型 URL', + placeholder: '輸入 Civitai 模型 URL...', + download: '下載', + cancel: '取消' + }, + move: { + title: '移動模型', + selectFolder: '選擇目標資料夾', + createFolder: '建立新資料夾', + folderName: '資料夾名稱', + move: '移動', + cancel: '取消' + }, + contentRating: { + title: '設定內容評級', + current: '目前', + levels: { + pg: '普通級', + pg13: '輔導級', + r: '限制級', + x: '成人級', + xxx: '重口級' + } + } + }, + + // 錯誤訊息 + errors: { + general: '發生錯誤', + networkError: '網路錯誤。請檢查您的連線。', + serverError: '伺服器錯誤。請稍後再試。', + fileNotFound: '找不到檔案', + invalidFile: '無效的檔案格式', + uploadFailed: '上傳失敗', + downloadFailed: '下載失敗', + saveFailed: '儲存失敗', + loadFailed: '載入失敗', + deleteFailed: '刪除失敗', + moveFailed: '移動失敗', + copyFailed: '複製失敗', + fetchFailed: '無法從 Civitai 獲取資料', + invalidUrl: '無效的 URL 格式', + missingPermissions: '權限不足' + }, + + // 成功訊息 + success: { + saved: '儲存成功', + deleted: '刪除成功', + moved: '移動成功', + copied: '複製成功', + downloaded: '下載成功', + uploaded: '上傳成功', + refreshed: '重新整理成功', + exported: '匯出成功', + imported: '匯入成功' + }, + + // 鍵盤快速鍵 + keyboard: { + navigation: '鍵盤導覽:', + shortcuts: { + pageUp: '向上捲動一頁', + pageDown: '向下捲動一頁', + home: '跳轉到頂部', + end: '跳轉到底部', + bulkMode: '切換批次模式', + search: '聚焦搜尋框', + escape: '關閉模態框/面板' + } + }, + + // 初始化 + initialization: { + title: '初始化 LoRA 管理器', + message: '正在掃描並建構 LoRA 快取,這可能需要幾分鐘時間...', + steps: { + scanning: '掃描模型檔案...', + processing: '處理中繼資料...', + building: '建構快取...', + finalizing: '完成中...' + } + }, + + // 工具提示和說明文字 + tooltips: { + refresh: '重新整理模型清單', + bulkOperations: '選擇多個模型進行批次操作', + favorites: '僅顯示收藏的模型', + duplicates: '尋找並管理重複的模型', + search: '按名稱、標籤或其他條件搜尋模型', + filter: '按各種條件篩選模型', + sort: '按不同屬性排序模型', + backToTop: '捲動回頁面頂部' + } +}; diff --git a/static/js/managers/SettingsManager.js b/static/js/managers/SettingsManager.js index 7953136c..2560b527 100644 --- a/static/js/managers/SettingsManager.js +++ b/static/js/managers/SettingsManager.js @@ -4,6 +4,7 @@ import { state } from '../state/index.js'; import { resetAndReload } from '../api/modelApiFactory.js'; import { setStorageItem, getStorageItem } from '../utils/storageHelpers.js'; import { DOWNLOAD_PATH_TEMPLATES, MAPPABLE_BASE_MODELS, PATH_TEMPLATE_PLACEHOLDERS, DEFAULT_PATH_TEMPLATES } from '../utils/constants.js'; +import { switchLanguage } from '../utils/i18nHelpers.js'; export class SettingsManager { constructor() { @@ -270,6 +271,13 @@ export class SettingsManager { // Load default embedding root await this.loadEmbeddingRoots(); + + // Load language setting + const languageSelect = document.getElementById('languageSelect'); + if (languageSelect) { + const currentLanguage = state.global.settings.language || 'en'; + languageSelect.value = currentLanguage; + } } async loadLoraRoots() { @@ -945,6 +953,44 @@ export class SettingsManager { } } + async saveLanguageSetting() { + const element = document.getElementById('languageSelect'); + if (!element) return; + + const selectedLanguage = element.value; + + try { + // Update local state + state.global.settings.language = selectedLanguage; + + // Save to localStorage + setStorageItem('settings', state.global.settings); + + // 保存到后端 + const response = await fetch('/api/settings', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + language: selectedLanguage + }) + }); + + if (!response.ok) { + throw new Error('Failed to save language setting to backend'); + } + + // Switch language immediately + switchLanguage(selectedLanguage); + + showToast('Language changed successfully.', 'success'); + + } catch (error) { + showToast('Failed to change language: ' + error.message, 'error'); + } + } + toggleInputVisibility(button) { const input = button.parentElement.querySelector('input'); const icon = button.querySelector('i'); diff --git a/static/js/utils/i18nHelpers.js b/static/js/utils/i18nHelpers.js index ee7a0212..91ff5aae 100644 --- a/static/js/utils/i18nHelpers.js +++ b/static/js/utils/i18nHelpers.js @@ -195,3 +195,36 @@ export function initializePageI18n() { export function t(key, params = {}) { return i18n.t(key, params); } + +/** + * Switch language and retranslate the page + * @param {string} languageCode - The language code to switch to + * @returns {boolean} True if language switch was successful + */ +export function switchLanguage(languageCode) { + if (i18n.setLanguage(languageCode)) { + // Retranslate the entire page + translateDOM(); + + // Update search placeholder based on current page + const currentPath = window.location.pathname; + updateSearchPlaceholder(currentPath); + + // Set document direction for RTL languages + if (i18n.isRTL()) { + document.documentElement.setAttribute('dir', 'rtl'); + document.body.classList.add('rtl'); + } else { + document.documentElement.setAttribute('dir', 'ltr'); + document.body.classList.remove('rtl'); + } + + // Dispatch a custom event for other components to react to language change + window.dispatchEvent(new CustomEvent('languageChanged', { + detail: { language: languageCode } + })); + + return true; + } + return false; +} diff --git a/templates/base.html b/templates/base.html index 7464ea1b..026aeb19 100644 --- a/templates/base.html +++ b/templates/base.html @@ -50,6 +50,12 @@