mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 07:05:43 -03:00
feat(i18n): Implement server-side internationalization support
- Added ServerI18nManager to handle translations and locale settings on the server. - Integrated server-side translations into templates, reducing language flashing on initial load. - Created API endpoints for setting and getting user language preferences. - Enhanced client-side i18n handling to work seamlessly with server-rendered content. - Updated various templates to utilize the new translation system. - Added mixed i18n handler to coordinate server and client translations, improving user experience. - Expanded translation files to include initialization messages for various components.
This commit is contained in:
154
py/services/server_i18n.py
Normal file
154
py/services/server_i18n.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ServerI18nManager:
|
||||
"""Server-side internationalization manager for template rendering"""
|
||||
|
||||
def __init__(self):
|
||||
self.translations = {}
|
||||
self.current_locale = 'en'
|
||||
self._load_translations()
|
||||
|
||||
def _load_translations(self):
|
||||
"""Load all translation files from the static/js/i18n directory"""
|
||||
i18n_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
|
||||
'static', 'js', 'i18n', 'locales'
|
||||
)
|
||||
|
||||
if not os.path.exists(i18n_path):
|
||||
logger.warning(f"I18n directory not found: {i18n_path}")
|
||||
return
|
||||
|
||||
# Load all available locale files
|
||||
for filename in os.listdir(i18n_path):
|
||||
if filename.endswith('.js'):
|
||||
locale_code = filename[:-3] # Remove .js extension
|
||||
try:
|
||||
self._load_locale_file(i18n_path, filename, locale_code)
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading locale file {filename}: {e}")
|
||||
|
||||
def _load_locale_file(self, path: str, filename: str, locale_code: str):
|
||||
"""Load a single locale file and extract translation data"""
|
||||
file_path = os.path.join(path, filename)
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Look for export const pattern like: export const en = { ... }
|
||||
import re
|
||||
|
||||
# Extract the variable name and object
|
||||
export_pattern = r'export\s+const\s+(\w+)\s*=\s*(\{.*\});?\s*$'
|
||||
match = re.search(export_pattern, content, re.DOTALL | re.MULTILINE)
|
||||
|
||||
if not match:
|
||||
logger.warning(f"No export const found in {filename}")
|
||||
return
|
||||
|
||||
var_name = match.group(1)
|
||||
js_object = match.group(2)
|
||||
|
||||
# Convert JS object to JSON
|
||||
json_str = self._js_object_to_json(js_object)
|
||||
|
||||
# Parse as JSON
|
||||
translations = json.loads(json_str)
|
||||
self.translations[locale_code] = translations
|
||||
|
||||
logger.debug(f"Loaded translations for {locale_code} (variable: {var_name})")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing locale file {filename}: {e}")
|
||||
|
||||
def _js_object_to_json(self, js_obj: str) -> str:
|
||||
"""Convert JavaScript object to JSON string"""
|
||||
import re
|
||||
|
||||
# Remove comments (single line and multi-line)
|
||||
js_obj = re.sub(r'//.*?$', '', js_obj, flags=re.MULTILINE)
|
||||
js_obj = re.sub(r'/\*.*?\*/', '', js_obj, flags=re.DOTALL)
|
||||
|
||||
# Replace unquoted object keys with quoted keys
|
||||
js_obj = re.sub(r'(\s*)([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:', r'\1"\2":', js_obj)
|
||||
|
||||
# Handle strings more robustly using regex
|
||||
# First, find all single-quoted strings and replace them with double-quoted ones
|
||||
def replace_single_quotes(match):
|
||||
content = match.group(1)
|
||||
# Escape any double quotes in the content
|
||||
content = content.replace('"', '\\"')
|
||||
# Handle escaped single quotes
|
||||
content = content.replace("\\'", "'")
|
||||
return f'"{content}"'
|
||||
|
||||
# Replace single-quoted strings with double-quoted strings
|
||||
js_obj = re.sub(r"'([^'\\]*(?:\\.[^'\\]*)*)'", replace_single_quotes, js_obj)
|
||||
|
||||
return js_obj
|
||||
|
||||
def set_locale(self, locale: str):
|
||||
"""Set the current locale"""
|
||||
if locale in self.translations:
|
||||
self.current_locale = locale
|
||||
else:
|
||||
logger.warning(f"Locale {locale} not found, using 'en'")
|
||||
self.current_locale = 'en'
|
||||
|
||||
def get_translation(self, key: str, params: Dict[str, Any] = None) -> str:
|
||||
"""Get translation for a key with optional parameters"""
|
||||
if self.current_locale not in self.translations:
|
||||
return key
|
||||
|
||||
# Navigate through nested object using dot notation
|
||||
keys = key.split('.')
|
||||
value = self.translations[self.current_locale]
|
||||
|
||||
for k in keys:
|
||||
if isinstance(value, dict) and k in value:
|
||||
value = value[k]
|
||||
else:
|
||||
# Fallback to English if current locale doesn't have the key
|
||||
if self.current_locale != 'en' and 'en' in self.translations:
|
||||
en_value = self.translations['en']
|
||||
for k in keys:
|
||||
if isinstance(en_value, dict) and k in en_value:
|
||||
en_value = en_value[k]
|
||||
else:
|
||||
return key
|
||||
value = en_value
|
||||
else:
|
||||
return key
|
||||
break
|
||||
|
||||
if not isinstance(value, str):
|
||||
return key
|
||||
|
||||
# Replace parameters if provided
|
||||
if params:
|
||||
for param_key, param_value in params.items():
|
||||
placeholder = f"{{{param_key}}}"
|
||||
double_placeholder = f"{{{{{param_key}}}}}"
|
||||
value = value.replace(placeholder, str(param_value))
|
||||
value = value.replace(double_placeholder, str(param_value))
|
||||
|
||||
return value
|
||||
|
||||
def get_available_locales(self) -> list:
|
||||
"""Get list of available locales"""
|
||||
return list(self.translations.keys())
|
||||
|
||||
def create_template_filter(self):
|
||||
"""Create a Jinja2 filter function for templates"""
|
||||
def t_filter(key: str, **params) -> str:
|
||||
return self.get_translation(key, params)
|
||||
return t_filter
|
||||
|
||||
# Create global instance
|
||||
server_i18n = ServerI18nManager()
|
||||
Reference in New Issue
Block a user