返回 Pixelle-Video
__init__.py
根目录 / web / i18n / __init__.py
1 # Copyright (C) 2025 AIDC-AI
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 # http://www.apache.org/licenses/LICENSE-2.0
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS,
9 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 # See the License for the specific language governing permissions and
11 # limitations under the License.
12
13 """
14 International language support for Pixelle-Video Web UI
15 """
16
17 import json
18 import locale
19 from pathlib import Path
20 from typing import Dict, Optional
21
22 from loguru import logger
23
24 _locales: Dict[str, dict] = {}
25 _current_language: str = "en_US" # Default fallback to English
26
27
28 def load_locales() -> Dict[str, dict]:
29 """Load all locale files from locales directory"""
30 global _locales
31
32 locales_dir = Path(__file__).parent / "locales"
33
34 if not locales_dir.exists():
35 logger.warning(f"Locales directory not found: {locales_dir}")
36 return _locales
37
38 for json_file in locales_dir.glob("*.json"):
39 lang_code = json_file.stem
40 try:
41 with open(json_file, "r", encoding="utf-8") as f:
42 _locales[lang_code] = json.load(f)
43 logger.debug(f"Loaded locale: {lang_code}")
44 except Exception as e:
45 logger.error(f"Failed to load locale {lang_code}: {e}")
46
47 logger.info(f"Loaded {len(_locales)} locales: {list(_locales.keys())}")
48 return _locales
49
50
51 def set_language(lang_code: str):
52 """Set current language"""
53 global _current_language
54 if lang_code in _locales:
55 _current_language = lang_code
56 logger.debug(f"Language set to: {lang_code}")
57 else:
58 logger.warning(f"Language {lang_code} not found, keeping {_current_language}")
59
60
61 def get_language() -> str:
62 """Get current language"""
63 return _current_language
64
65
66 def tr(key: str, fallback: Optional[str] = None, **kwargs) -> str:
67 """
68 Translate a key to current language
69
70 Args:
71 key: Translation key (e.g., "app.title")
72 fallback: Fallback text if key not found
73 **kwargs: Format parameters for string interpolation
74
75 Returns:
76 Translated text
77
78 Example:
79 tr("app.title") # => "Pixelle-Video"
80 tr("error.missing_field", field="API Key") # => "请填写 API Key"
81 """
82 locale = _locales.get(_current_language, {})
83 translations = locale.get("t", {})
84
85 result = translations.get(key)
86
87 if result is None:
88 # Try fallback parameter
89 if fallback is not None:
90 result = fallback
91 # Try English fallback
92 elif _current_language != "en_US" and "en_US" in _locales:
93 en_locale = _locales["en_US"]
94 result = en_locale.get("t", {}).get(key)
95
96 # Last resort: return the key itself
97 if result is None:
98 result = key
99 logger.debug(f"Translation missing: {key}")
100
101 # Apply string interpolation if kwargs provided
102 if kwargs:
103 try:
104 result = result.format(**kwargs)
105 except (KeyError, ValueError) as e:
106 logger.warning(f"Failed to format translation '{key}': {e}")
107
108 return result
109
110
111 def get_language_name(lang_code: Optional[str] = None) -> str:
112 """Get display name of a language"""
113 if lang_code is None:
114 lang_code = _current_language
115
116 locale = _locales.get(lang_code, {})
117 return locale.get("language_name", lang_code)
118
119
120 def get_available_languages() -> Dict[str, str]:
121 """Get all available languages with their display names"""
122 return {
123 code: locale.get("language_name", code)
124 for code, locale in _locales.items()
125 }
126
127
128 def detect_system_language() -> str:
129 """
130 Detect system/OS language and return the best matching locale code.
131 Falls back to English if no match found.
132
133 This is designed for self-hosted scenarios where the server and browser
134 are typically on the same machine.
135
136 Returns:
137 Language code (e.g., "zh_CN", "en_US")
138 """
139 try:
140 import os
141 import platform
142 import subprocess
143
144 system_locale = None
145
146 # Method 1: macOS-specific detection (most reliable for macOS)
147 if platform.system() == "Darwin": # macOS
148 try:
149 # Get AppleLocale which reflects system language preference
150 result = subprocess.run(
151 ["defaults", "read", "-g", "AppleLocale"],
152 capture_output=True,
153 text=True,
154 timeout=2
155 )
156 if result.returncode == 0:
157 system_locale = result.stdout.strip()
158 logger.debug(f"System locale from macOS AppleLocale: {system_locale}")
159 except Exception as e:
160 logger.debug(f"macOS AppleLocale detection failed: {e}")
161
162 # Fallback: try AppleLanguages
163 if not system_locale:
164 try:
165 result = subprocess.run(
166 ["defaults", "read", "-g", "AppleLanguages"],
167 capture_output=True,
168 text=True,
169 timeout=2
170 )
171 if result.returncode == 0:
172 # Parse array output like: ( "zh-Hans-CN", "en-CN" )
173 output = result.stdout.strip()
174 # Extract first language
175 import re
176 match = re.search(r'"([^"]+)"', output)
177 if match:
178 lang = match.group(1)
179 # Convert zh-Hans-CN to zh_CN
180 if lang.startswith("zh-Hans"):
181 system_locale = "zh_CN"
182 elif lang.startswith("zh-Hant"):
183 system_locale = "zh_TW"
184 else:
185 system_locale = lang.replace("-", "_")
186 logger.debug(f"System locale from macOS AppleLanguages: {system_locale}")
187 except Exception as e:
188 logger.debug(f"macOS AppleLanguages detection failed: {e}")
189
190 # Method 2: Get from environment locale (cross-platform)
191 if not system_locale:
192 try:
193 system_locale = locale.getdefaultlocale()[0]
194 logger.debug(f"System locale from getdefaultlocale(): {system_locale}")
195 except Exception as e:
196 logger.debug(f"getdefaultlocale() failed: {e}")
197
198 # Method 3: Get from current locale
199 if not system_locale:
200 try:
201 system_locale = locale.getlocale()[0]
202 logger.debug(f"System locale from getlocale(): {system_locale}")
203 except Exception as e:
204 logger.debug(f"getlocale() failed: {e}")
205
206 # Method 4: Try to get from environment variables
207 if not system_locale:
208 for env_var in ['LC_ALL', 'LC_MESSAGES', 'LANG', 'LANGUAGE']:
209 env_value = os.environ.get(env_var)
210 if env_value:
211 # Extract language code from formats like "zh_CN.UTF-8"
212 system_locale = env_value.split('.')[0]
213 logger.debug(f"System locale from {env_var}: {system_locale}")
214 break
215
216 if system_locale:
217 # Normalize the locale string
218 # Handle formats: zh_CN, zh-CN, zh_CN.UTF-8, etc.
219 system_locale = system_locale.replace('-', '_').split('.')[0]
220
221 # Direct match (e.g., "zh_CN")
222 for locale_code in _locales.keys():
223 if locale_code.lower() == system_locale.lower():
224 logger.info(f"System language matched: {locale_code}")
225 return locale_code
226
227 # Partial match (e.g., "zh" matches "zh_CN")
228 lang_prefix = system_locale.split('_')[0].lower()
229 for locale_code in _locales.keys():
230 if locale_code.lower().startswith(lang_prefix):
231 logger.info(f"System language partially matched: {locale_code} (from {system_locale})")
232 return locale_code
233
234 logger.info("No system language detected, using fallback")
235 except Exception as e:
236 logger.warning(f"Failed to detect system language: {e}")
237
238 # Fallback to English
239 return "en_US"
240
241
242 # Auto-load locales on import
243 load_locales()
244
245 # Auto-detect and set system language
246 _detected_language = detect_system_language()
247 _current_language = _detected_language
248 logger.info(f"Default language initialized to: {_current_language}")
249
250
250 lines PYTHON