diff --git a/py/routes/base_model_routes.py b/py/routes/base_model_routes.py index 38c44152..3a832c9b 100644 --- a/py/routes/base_model_routes.py +++ b/py/routes/base_model_routes.py @@ -130,18 +130,10 @@ class BaseModelRoutes(ABC): 'is_initializing': is_initializing, 'settings': settings, 'request': request, - 'user_language': user_language, # 传递语言设置到模板 'folders': [], # 添加服务端翻译函数 't': server_i18n.get_translation, 'server_i18n': server_i18n, - # 添加一些常用的翻译到上下文,避免在模板中频繁调用 - 'common_translations': { - 'loading': server_i18n.get_translation('common.status.loading'), - 'error': server_i18n.get_translation('common.status.error'), - 'refresh': server_i18n.get_translation('common.actions.refresh'), - 'search': server_i18n.get_translation('common.actions.search'), - } } if not is_initializing: diff --git a/py/routes/recipe_routes.py b/py/routes/recipe_routes.py index f392c250..f23e0fd7 100644 --- a/py/routes/recipe_routes.py +++ b/py/routes/recipe_routes.py @@ -149,17 +149,9 @@ class RecipeRoutes: is_initializing=False, settings=settings, request=request, - user_language=user_language, # 添加服务端翻译函数 t=server_i18n.get_translation, server_i18n=server_i18n, - # 添加一些常用的翻译到上下文 - common_translations={ - 'loading': server_i18n.get_translation('common.status.loading'), - 'error': server_i18n.get_translation('common.status.error'), - 'refresh': server_i18n.get_translation('common.actions.refresh'), - 'search': server_i18n.get_translation('common.actions.search'), - } ) except Exception as cache_error: logger.error(f"Error loading recipe cache data: {cache_error}") @@ -169,15 +161,9 @@ class RecipeRoutes: is_initializing=True, settings=settings, request=request, - user_language=user_language, # 添加服务端翻译函数 t=server_i18n.get_translation, server_i18n=server_i18n, - # 添加一些常用的翻译到上下文 - common_translations={ - 'loading': server_i18n.get_translation('common.status.loading'), - 'error': server_i18n.get_translation('common.status.error'), - } ) logger.info("Recipe cache error, returning initialization page") diff --git a/py/routes/stats_routes.py b/py/routes/stats_routes.py index 4b1c76a6..36392e2e 100644 --- a/py/routes/stats_routes.py +++ b/py/routes/stats_routes.py @@ -75,16 +75,9 @@ class StatsRoutes: is_initializing=is_initializing, settings=settings, request=request, - user_language=user_language, # 添加服务端翻译函数 t=server_i18n.get_translation, server_i18n=server_i18n, - # 添加一些常用的翻译到上下文 - common_translations={ - 'loading': server_i18n.get_translation('common.status.loading'), - 'error': server_i18n.get_translation('common.status.error'), - 'refresh': server_i18n.get_translation('common.actions.refresh'), - } ) return web.Response( diff --git a/static/js/i18n/index.js b/static/js/i18n/index.js index 5c3e697c..a480afac 100644 --- a/static/js/i18n/index.js +++ b/static/js/i18n/index.js @@ -33,20 +33,10 @@ class I18nManager { } /** - * Get language from user settings with fallback to browser detection + * Get language from user settings with fallback to English * @returns {string} Language code */ getLanguageFromSettings() { - // 优先使用后端传递的初始语言 - if (window.__INITIAL_LANGUAGE__ && this.locales[window.__INITIAL_LANGUAGE__]) { - return window.__INITIAL_LANGUAGE__; - } - - // 检查服务端传递的翻译数据 - if (window.__SERVER_TRANSLATIONS__ && window.__SERVER_TRANSLATIONS__.language && this.locales[window.__SERVER_TRANSLATIONS__.language]) { - return window.__SERVER_TRANSLATIONS__.language; - } - // Check localStorage for user-selected language const STORAGE_PREFIX = 'lora_manager_'; let userLanguage = null; @@ -66,8 +56,8 @@ class I18nManager { return userLanguage; } - // Fallback to browser language detection for first-time users - return this.detectLanguage(); + // Fallback to English + return 'en'; } /** @@ -123,29 +113,6 @@ class I18nManager { ]; } - /** - * Detect browser language with fallback to English (for first-time users) - * @returns {string} Language code - */ - detectLanguage() { - // Get browser language - const browserLang = navigator.language || navigator.languages[0] || 'en'; - - // Check if we have exact match - if (this.locales[browserLang]) { - return browserLang; - } - - // Check for language without region (e.g., 'zh' from 'zh-CN') - const langCode = browserLang.split('-')[0]; - if (this.locales[langCode]) { - return langCode; - } - - // Fallback to English - return 'en'; - } - /** * Get translation for a key with optional parameters * @param {string} key - Translation key (supports dot notation) @@ -255,7 +222,7 @@ class I18nManager { } /** - * Initialize i18n from user settings instead of browser detection + * Initialize i18n from user settings * This prevents language flashing on page load */ async initializeFromSettings() { diff --git a/static/js/utils/i18nHelpers.js b/static/js/utils/i18nHelpers.js index 14a8a745..582775e5 100644 --- a/static/js/utils/i18nHelpers.js +++ b/static/js/utils/i18nHelpers.js @@ -9,14 +9,14 @@ import { i18n } from '../i18n/index.js'; */ export function translateDOM() { if (!window.i18n) return; - + // Select all elements with data-i18n attributes, including optgroups and options const elements = document.querySelectorAll('[data-i18n]'); - + elements.forEach(element => { const key = element.getAttribute('data-i18n'); const target = element.getAttribute('data-i18n-target') || 'textContent'; - + if (key) { const translation = window.i18n.t(key); @@ -162,31 +162,23 @@ export function formatNumber(number, options = {}) { * This should be called after DOM content is loaded */ export function initializePageI18n() { - // 优先使用服务端传递的翻译数据,避免闪烁 - if (window.__SERVER_TRANSLATIONS__ && window.__SERVER_TRANSLATIONS__.language) { - // 设置客户端i18n的语言为服务端传递的语言 - if (window.i18n && window.i18n.setLanguage) { - window.i18n.setLanguage(window.__SERVER_TRANSLATIONS__.language); - } + // Always use the client-side i18n with user settings + if (window.i18n) { + // Translate DOM elements + translateDOM(); - // 对于剩余的需要动态翻译的元素,仍使用客户端翻译 - translateDOM(); - } else { - // 回退到完整的客户端翻译 - 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'); + // 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'); + } } } diff --git a/static/js/utils/mixedI18n.js b/static/js/utils/mixedI18n.js deleted file mode 100644 index e38a0d26..00000000 --- a/static/js/utils/mixedI18n.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Mixed i18n handler - coordinates server-side and client-side translations - * Reduces language flashing by using server-rendered content initially - */ - -class MixedI18nHandler { - constructor() { - this.serverTranslations = window.__SERVER_TRANSLATIONS__ || {}; - this.currentLanguage = this.serverTranslations.language || 'en'; - this.initialized = false; - } - - /** - * Initialize mixed i18n system - */ - async initialize() { - if (this.initialized) return; - - // Import the main i18n module - const { i18n } = await import('/loras_static/js/i18n/index.js'); - this.clientI18n = i18n; - - // Ensure client i18n uses the same language as server - if (this.currentLanguage && this.clientI18n.getCurrentLocale() !== this.currentLanguage) { - this.clientI18n.setLanguage(this.currentLanguage); - } - - // Translate any remaining elements that need client-side translation - this.translateRemainingElements(); - - this.initialized = true; - - // Dispatch event to notify that mixed i18n is ready - window.dispatchEvent(new CustomEvent('mixedI18nReady', { - detail: { language: this.currentLanguage } - })); - } - - /** - * Translate elements that still need client-side translation - * (primarily dynamic content and complex components) - */ - translateRemainingElements() { - if (!this.clientI18n) return; - - // Find all elements with data-i18n attribute that haven't been server-rendered - const elements = document.querySelectorAll('[data-i18n]'); - - elements.forEach(element => { - // Skip if already translated by server (check if content matches key pattern) - const key = element.getAttribute('data-i18n'); - const currentContent = element.textContent || element.value || element.placeholder; - - // If the current content looks like a translation key, translate it - if (currentContent === key || currentContent.includes('.') || currentContent === '') { - this.translateElement(element, key); - } - }); - } - - /** - * Translate a single element using client-side i18n - */ - translateElement(element, key) { - if (!this.clientI18n) return; - - const params = element.getAttribute('data-i18n-params'); - let parsedParams = {}; - - if (params) { - try { - parsedParams = JSON.parse(params); - } catch (e) { - console.warn(`Invalid JSON in data-i18n-params for key ${key}:`, params); - } - } - - // Get translated text - const translatedText = this.clientI18n.t(key, parsedParams); - - // Handle different translation targets - const target = element.getAttribute('data-i18n-target') || 'textContent'; - - switch (target) { - case 'placeholder': - element.placeholder = translatedText; - break; - case 'title': - element.title = translatedText; - break; - case 'alt': - element.alt = translatedText; - break; - case 'innerHTML': - element.innerHTML = translatedText; - break; - case 'textContent': - default: - element.textContent = translatedText; - break; - } - } - - /** - * Get current language - */ - getCurrentLanguage() { - return this.currentLanguage; - } - - /** - * Get translation using client-side i18n (for dynamic content) - */ - t(key, params = {}) { - if (this.clientI18n) { - return this.clientI18n.t(key, params); - } - - // Fallback: check server translations - if (this.serverTranslations.common && key.startsWith('common.')) { - const subKey = key.substring(7); // Remove 'common.' prefix - return this.serverTranslations.common[subKey] || key; - } - - return key; - } - - /** - * Format file size using client-side i18n - */ - formatFileSize(bytes, decimals = 2) { - if (this.clientI18n) { - return this.clientI18n.formatFileSize(bytes, decimals); - } - - // Simple fallback - if (bytes === 0) return '0 Bytes'; - 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)); - return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; - } - - /** - * Format date using client-side i18n - */ - formatDate(date, options = {}) { - if (this.clientI18n) { - return this.clientI18n.formatDate(date, options); - } - - // Simple fallback - const dateObj = date instanceof Date ? date : new Date(date); - return dateObj.toLocaleDateString(); - } -} - -// Create global instance -window.mixedI18n = new MixedI18nHandler(); - -// Auto-initialize when DOM is ready -if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', () => { - window.mixedI18n.initialize(); - }); -} else { - window.mixedI18n.initialize(); -} - -// Export for module usage -export default window.mixedI18n; diff --git a/templates/base.html b/templates/base.html index 2404b4b8..7464ea1b 100644 --- a/templates/base.html +++ b/templates/base.html @@ -50,23 +50,6 @@ {% else %} - - {% block main_script %}{% endblock %} {% endif %}