- i18n 사전 100% (break/overtime/leave/clockin) — 50+ 신규 키 - 런타임 재번역 인프라 (ui/i18n_runtime.py) — 재시작 없이 메인 UI 적용 - MealController 분리 — 점심/저녁 토글을 컨트롤러로 추출 - 통합 테스트 +15 (S36-S52: 온보딩/salary/CSV/notification dedupe 등) - pytest 신규 4종 + i18n_runtime 테스트 (총 122 케이스, 90→122) - README/INSTALL/CLAUDE/AGENTS v2.6+ 아키텍처 반영
95 lines
3.0 KiB
Python
95 lines
3.0 KiB
Python
"""
|
|
런타임 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)
|