""" 런타임 i18n 재번역 — 재시작 없이 언어 전환. 사용법: from ui.i18n_runtime import register, retranslate_all, set_language_and_retranslate label = QLabel(tr('label.foo')) register(label, 'label.foo') # 약한 참조로 등록 button = QPushButton() register(button, 'btn.bar', kwargs={'name': 'X'}) # 그룹박스 제목 등 setter가 다른 경우 register(group, 'group.work_time', setter='setTitle') # 윈도우 제목 register(dialog, 'window.foo', setter='setWindowTitle') 언어 변경: set_language_and_retranslate('en') 각 widget은 weakref로 보관되므로 삭제되면 자동 정리. format placeholder가 있는 키는 kwargs를 함께 등록하면 retranslate 시 같은 인자로 재계산. """ from __future__ import annotations import weakref from typing import Any, Callable, List, Tuple, Optional from core.i18n import tr, set_language as _set_language # (weakref, key, setter_name, kwargs, post_format) 튜플 리스트 # weakref가 죽으면 다음 retranslate 시 정리. _registry: List[Tuple[weakref.ReferenceType, str, str, dict, Optional[Callable]]] = [] def register(widget: Any, key: str, *, setter: str = 'setText', kwargs: Optional[dict] = None, post: Optional[Callable[[str], str]] = None) -> None: """위젯을 retranslate 대상으로 등록. Args: widget: PyQt 위젯 (setText/setTitle/setWindowTitle 등 지원) key: i18n 키 setter: 호출할 메서드명 (기본 setText) kwargs: tr()에 전달할 format 인자 (정적인 경우만) post: 번역 후 한 번 더 가공할 콜백 — 예: 이모지 prefix """ # 약한 참조 — 위젯 삭제 시 자동 GC try: ref = weakref.ref(widget) except TypeError: # weakref 미지원 객체는 retranslate 불가 return _registry.append((ref, key, setter, kwargs or {}, post)) # 초기 적용 _apply(widget, key, setter, kwargs or {}, post) def retranslate_all() -> None: """모든 등록된 위젯에 현재 언어로 텍스트 재적용.""" global _registry alive = [] for ref, key, setter, kw, post in _registry: widget = ref() if widget is None: continue # 죽은 위젯은 빼버림 try: _apply(widget, key, setter, kw, post) alive.append((ref, key, setter, kw, post)) except RuntimeError: # Qt C++ 객체 삭제 후 호출 — 정리만 하고 패스 continue _registry = alive def set_language_and_retranslate(lang: str) -> None: """언어 전환 + 즉시 재번역.""" _set_language(lang) retranslate_all() def clear() -> None: """레지스트리 비우기 (테스트용).""" _registry.clear() def _apply(widget: Any, key: str, setter: str, kw: dict, post: Optional[Callable[[str], str]]) -> None: text = tr(key, **kw) if post: text = post(text) fn = getattr(widget, setter, None) if callable(fn): fn(text)