fix(i18n): 修复 PROMPT_EXTENSIONS 元组声明、消除重复代码、优化锁策略
- fix: PROMPT_EXTENSIONS = (".prompt") 是字符串非元组,改为 (".prompt",)
- refactor: 将 extract_placeholders/format_template 统一到 loaders.py,
消除 formatting.py、prompt_i18n.py、i18n_validate.py 三处重复
- perf: _get_catalog 和 load_prompt 改为双重检查锁定,I/O 不再阻塞其他线程
- perf: _log_once 使用独立 _warning_lock,不再与 _cache_lock 竞争
- fix: _scan_legacy_prompt_directory 添加 prompts_root 参数,修正 relative_to 语义
- refactor: 合并 _supported_prompt_files 两个变体为单函数 + recursive 参数
- docs: i18n.md 强化 repository-specific 校验策略标注,修正时间表述冗余
- fix: 验证脚本错误消息移除 Crowdin 暗示,标注为仓库级校验策略
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,28 +2,14 @@ from __future__ import annotations
|
||||
|
||||
from datetime import date, datetime, time
|
||||
from decimal import Decimal
|
||||
from string import Formatter
|
||||
|
||||
from babel import Locale
|
||||
from babel.dates import format_datetime as babel_format_datetime
|
||||
from babel.numbers import format_decimal as babel_format_decimal
|
||||
|
||||
from .loaders import DEFAULT_LOCALE, to_babel_locale
|
||||
from .loaders import DEFAULT_LOCALE, extract_placeholders, format_template, to_babel_locale
|
||||
|
||||
FORMATTER = Formatter()
|
||||
|
||||
|
||||
def extract_placeholders(template: str) -> set[str]:
|
||||
placeholders: set[str] = set()
|
||||
for _, field_name, _, _ in FORMATTER.parse(template):
|
||||
if not field_name:
|
||||
continue
|
||||
placeholders.add(field_name.split(".", maxsplit=1)[0].split("[", maxsplit=1)[0])
|
||||
return placeholders
|
||||
|
||||
|
||||
def format_template(template: str, **kwargs: object) -> str:
|
||||
return template.format(**kwargs)
|
||||
__all__ = ["extract_placeholders", "format_template"]
|
||||
|
||||
|
||||
def select_plural_category(locale: str, count: int | float | Decimal) -> str:
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from string import Formatter
|
||||
|
||||
import json
|
||||
|
||||
_FORMATTER = Formatter()
|
||||
|
||||
from .exceptions import (
|
||||
DuplicateTranslationKeyError,
|
||||
InvalidLocaleError,
|
||||
@@ -122,3 +125,16 @@ def load_locale_catalog(locale: str, locales_root: Path | None = None) -> dict[s
|
||||
)
|
||||
merged_translations[key] = value
|
||||
return merged_translations
|
||||
|
||||
|
||||
def extract_placeholders(template: str) -> set[str]:
|
||||
placeholders: set[str] = set()
|
||||
for _, field_name, _, _ in _FORMATTER.parse(template):
|
||||
if not field_name:
|
||||
continue
|
||||
placeholders.add(field_name.split(".", maxsplit=1)[0].split("[", maxsplit=1)[0])
|
||||
return placeholders
|
||||
|
||||
|
||||
def format_template(template: str, **kwargs: object) -> str:
|
||||
return template.format(**kwargs)
|
||||
|
||||
@@ -26,6 +26,7 @@ class I18nManager:
|
||||
self._locale_override: ContextVar[str | None] = ContextVar("maibot_locale", default=None)
|
||||
self._warning_cache: set[tuple[str, str, str]] = set()
|
||||
self._cache_lock = threading.RLock()
|
||||
self._warning_lock = threading.Lock()
|
||||
|
||||
def set_locale(self, locale: str) -> str:
|
||||
self._default_locale = normalize_locale(locale)
|
||||
@@ -175,23 +176,26 @@ class I18nManager:
|
||||
if normalized_locale in self._catalog_cache:
|
||||
return self._catalog_cache[normalized_locale]
|
||||
|
||||
try:
|
||||
catalog = load_locale_catalog(normalized_locale, self._locales_root)
|
||||
except I18nError as exc:
|
||||
self._log_once(
|
||||
("load_failed", normalized_locale, exc.__class__.__name__),
|
||||
logging.WARNING,
|
||||
"加载 locale '%s' 失败: %s",
|
||||
normalized_locale,
|
||||
exc,
|
||||
)
|
||||
catalog = {}
|
||||
try:
|
||||
catalog = load_locale_catalog(normalized_locale, self._locales_root)
|
||||
except I18nError as exc:
|
||||
self._log_once(
|
||||
("load_failed", normalized_locale, exc.__class__.__name__),
|
||||
logging.WARNING,
|
||||
"加载 locale '%s' 失败: %s",
|
||||
normalized_locale,
|
||||
exc,
|
||||
)
|
||||
catalog = {}
|
||||
|
||||
with self._cache_lock:
|
||||
if normalized_locale in self._catalog_cache:
|
||||
return self._catalog_cache[normalized_locale]
|
||||
self._catalog_cache[normalized_locale] = catalog
|
||||
return catalog
|
||||
|
||||
def _log_once(self, cache_key: tuple[str, str, str], level: int, message: str, *args: object) -> None:
|
||||
with self._cache_lock:
|
||||
with self._warning_lock:
|
||||
if cache_key in self._warning_cache:
|
||||
return
|
||||
self._warning_cache.add(cache_key)
|
||||
|
||||
Reference in New Issue
Block a user