更改项目名为NebulaShell
This commit is contained in:
156
store/@{NebulaShell}/i18n/i18n.py
Normal file
156
store/@{NebulaShell}/i18n/i18n.py
Normal file
@@ -0,0 +1,156 @@
|
||||
"""i18n 核心引擎"""
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
class I18nEngine:
|
||||
"""国际化引擎"""
|
||||
|
||||
def __init__(self):
|
||||
self._translations: dict[str, dict[str, Any]] = {} # {locale: {key: value}}
|
||||
self._current_locale: str = "zh-CN"
|
||||
self._fallback_locale: str = "en-US"
|
||||
self._supported_locales: list[str] = []
|
||||
self._locales_dir: str = ""
|
||||
|
||||
def load_locales(self, locales_dir: str, locales: list[str]):
|
||||
"""加载语言文件
|
||||
|
||||
Args:
|
||||
locales_dir: 语言文件目录路径
|
||||
locales: 支持的语言列表
|
||||
"""
|
||||
self._locales_dir = locales_dir
|
||||
self._supported_locales = locales
|
||||
locales_path = Path(locales_dir)
|
||||
|
||||
if not locales_path.exists():
|
||||
locales_path.mkdir(parents=True, exist_ok=True)
|
||||
return
|
||||
|
||||
for locale in locales:
|
||||
locale_file = locales_path / f"{locale}.json"
|
||||
if locale_file.exists():
|
||||
try:
|
||||
content = locale_file.read_text(encoding="utf-8")
|
||||
self._translations[locale] = json.loads(content)
|
||||
except (json.JSONDecodeError, Exception) as e:
|
||||
print(f"[i18n] 加载语言文件失败 {locale_file}: {e}")
|
||||
self._translations[locale] = {}
|
||||
|
||||
def set_locale(self, locale: str):
|
||||
"""设置当前语言"""
|
||||
if locale in self._supported_locales:
|
||||
self._current_locale = locale
|
||||
|
||||
def get_locale(self) -> str:
|
||||
"""获取当前语言"""
|
||||
return self._current_locale
|
||||
|
||||
def set_fallback(self, locale: str):
|
||||
"""设置回退语言"""
|
||||
self._fallback_locale = locale
|
||||
|
||||
def t(self, key: str, locale: Optional[str] = None, **kwargs) -> str:
|
||||
"""翻译文本
|
||||
|
||||
Args:
|
||||
key: 翻译键 (支持点号分隔的嵌套路径,如 "user.greeting")
|
||||
locale: 指定语言 (默认使用当前语言)
|
||||
**kwargs: 插值参数
|
||||
|
||||
Returns:
|
||||
翻译后的文本
|
||||
"""
|
||||
target_locale = locale or self._current_locale
|
||||
|
||||
# 尝试从指定语言获取
|
||||
value = self._get_nested(key, self._translations.get(target_locale, {}))
|
||||
|
||||
# 如果未找到,尝试从回退语言获取
|
||||
if value is None and target_locale != self._fallback_locale:
|
||||
value = self._get_nested(key, self._translations.get(self._fallback_locale, {}))
|
||||
|
||||
# 仍未找到,返回键名
|
||||
if value is None:
|
||||
return key
|
||||
|
||||
# 插值处理: {{name}} 或 {name}
|
||||
return self._interpolate(value, kwargs)
|
||||
|
||||
def _get_nested(self, key: str, data: dict) -> Any:
|
||||
"""获取嵌套字典值"""
|
||||
keys = key.split(".")
|
||||
current = data
|
||||
for k in keys:
|
||||
if isinstance(current, dict) and k in current:
|
||||
current = current[k]
|
||||
else:
|
||||
return None
|
||||
return current
|
||||
|
||||
def _interpolate(self, text: str, kwargs: dict) -> str:
|
||||
"""插值替换: {{name}} 或 {name}"""
|
||||
# 支持 {{name}} 格式
|
||||
result = re.sub(r'\{\{(\w+)\}\}', lambda m: str(kwargs.get(m.group(1), m.group(0))), text)
|
||||
# 支持 {name} 格式 (如果未被 {{}} 替换)
|
||||
result = re.sub(r'\{(\w+)\}', lambda m: str(kwargs.get(m.group(1), m.group(0))), result)
|
||||
return result
|
||||
|
||||
def get_supported_locales(self) -> list[str]:
|
||||
"""获取支持的语言列表"""
|
||||
return self._supported_locales
|
||||
|
||||
def is_valid_locale(self, locale: str) -> bool:
|
||||
"""检查语言是否有效"""
|
||||
return locale in self._supported_locales
|
||||
|
||||
def detect_locale(self, accept_language: Optional[str] = None,
|
||||
query_lang: Optional[str] = None,
|
||||
cookie_lang: Optional[str] = None) -> str:
|
||||
"""检测语言优先级
|
||||
|
||||
Args:
|
||||
accept_language: HTTP Accept-Language 头
|
||||
query_lang: URL 查询参数 ?lang=xx
|
||||
cookie_lang: Cookie 中的语言
|
||||
|
||||
Returns:
|
||||
检测到的语言代码
|
||||
"""
|
||||
# 1. 查询参数优先级最高
|
||||
if query_lang and self.is_valid_locale(query_lang):
|
||||
return query_lang
|
||||
|
||||
# 2. Cookie 次之
|
||||
if cookie_lang and self.is_valid_locale(cookie_lang):
|
||||
return cookie_lang
|
||||
|
||||
# 3. Accept-Language 头
|
||||
if accept_language:
|
||||
# 解析 "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7"
|
||||
languages = []
|
||||
for part in accept_language.split(","):
|
||||
part = part.strip()
|
||||
if ";q=" in part:
|
||||
lang, q = part.split(";q=")
|
||||
languages.append((lang.strip(), float(q)))
|
||||
else:
|
||||
languages.append((part, 1.0))
|
||||
|
||||
# 按权重排序
|
||||
languages.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
for lang, _ in languages:
|
||||
# 精确匹配
|
||||
if self.is_valid_locale(lang):
|
||||
return lang
|
||||
# 前缀匹配 (zh 匹配 zh-CN, zh-TW)
|
||||
for supported in self._supported_locales:
|
||||
if supported.startswith(lang + "-") or lang.startswith(supported.split("-")[0] + "-"):
|
||||
return supported
|
||||
|
||||
# 4. 默认语言
|
||||
return self._current_locale
|
||||
Reference in New Issue
Block a user