fix: 필수 팩 기본 활성화 + 저부하 모드 토글 반영 + 용어 겹침 수정

리뷰 지적 2건과, 그것을 고치다 드러난 용어집 버그 1건을 함께 처리했다.

1) 저부하 모드 토글이 모델에 반영되지 않던 버그
   ModelManager 는 생성 시점의 low_power 로 정밀도를 정하는데 설정에서
   토글해도 갱신되지 않았고, stop() 은 모델을 내리지 않아 이전 정밀도
   모델이 그대로 남았다. TranslationEngine.sync_performance() 를 추가해
   값이 바뀌면 언로드 후 갱신하고, _bootstrap 과 재시작 경로에서 부른다.

2) 필수 게임 팩이 기본으로 꺼져 있던 문제
   새 설치 기본값을 fps-common + 오버워치 + 배그 + R6 + 워독스로 켰다.

3) (1)(2)를 고치며 드러난 것: 팩 여러 개를 켜면 결과가 체크 순서에
   휘둘렸다. knocked 가 FPS공통 "기절" / 배그 "눕혔다" 로 갈리는 식으로
   19개 용어가 충돌하는데, 사용자는 그 순서를 볼 수도 바꿀 수도 없었다.
   팩에 scope(generic/game) 를 두고 범용 -> 게임 전용 -> 사용자 항목
   순으로 적용해 결정적으로 만들었다. 게임 팩끼리 갈리는 것은 사람이
   판단할 문제라 find_conflicts() 로 뽑아 용어집 화면에 표시한다.

4) 용어 겹침 버그: "the blue"(조각)가 "blue zone" 보다 앞 위치라는
   이유로 먼저 잡혀 "자기장 자기장" 이 됐다. 최장일치 정렬은 같은 시작
   위치에서만 통한다. 조각 항목을 제거하고, 모든 팩을 켠 상태로 각
   용어가 정확히 한 번만 치환되는지 검사하는 테스트를 추가했다.

검증: pytest 110개 통과 (저부하 6 + 팩 계층/충돌/겹침 12 신규), ruff clean

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
EJClaw
2026-09-21 18:05:22 +09:00
parent a7eafd03e1
commit e4a048c8d5
19 changed files with 4266 additions and 506 deletions

View File

@@ -238,6 +238,8 @@ class MainWindow(QMainWindow):
self.engine.start()
def _on_restart_needed(self) -> None:
# 정지 상태에서도 반영해 둬야 저부하 모드를 켠 즉시 VRAM이 반납된다.
self.engine.sync_performance()
if self.engine.running:
self.engine.stop()
self.engine.start()

View File

@@ -25,8 +25,8 @@ from PySide6.QtWidgets import (
from ...config import AppConfig
from ...constants import LANGUAGE_CODES, LANGUAGES, user_data_dir
from ...models import Glossary, GlossaryEntry
from ...models.packs import available_packs
from ..theme import SPACING
from ...models.packs import available_packs, find_conflicts
from ..theme import SPACING, palette
from ..widgets import Card, PageHeader
COLUMNS = ["원문 용어", *[f"{LANGUAGES[c]['label']} 역어" for c in LANGUAGE_CODES], "메모"]
@@ -71,14 +71,17 @@ class GlossaryPage(QWidget):
card.add(_hint("기본 제공 용어집을 찾지 못했습니다."))
return card
for pack in packs:
check = QCheckBox(f"{pack.name} ({len(pack)}개)")
# 범용 팩을 위에 두어 "게임 전용이 범용을 이긴다"는 규칙이 눈에 보이게 한다.
for pack in sorted(packs, key=lambda p: (not p.is_generic, p.name)):
suffix = " · 장르 공통" if pack.is_generic else ""
check = QCheckBox(f"{pack.name} ({len(pack)}개){suffix}")
check.setChecked(pack.key in enabled)
check.setToolTip(pack.description)
check.toggled.connect(self._on_packs_changed)
self._pack_checks[pack.key] = check
card.add(check)
self.conflict_label = card.add(_warning(""))
self.pack_total = card.add(_hint(""))
self._update_pack_total()
return card
@@ -94,6 +97,33 @@ class GlossaryPage(QWidget):
enabled = set(self.config.glossary.enabled_packs)
total = sum(len(p) for p in available_packs() if p.key in enabled)
self.pack_total.setText(f"켜둔 기본 용어 {total}개")
self._update_conflicts()
def _update_conflicts(self) -> None:
"""게임 전용 팩끼리 역어가 갈리는 단어를 알려준다.
예를 들어 오버워치와 워치독스를 같이 켜면 payload 가 '화물'과
'페이로드'로 갈린다. 조용히 아무거나 고르면 사용자는 영문도 모른 채
이상한 자막을 보게 되므로, 어떤 단어가 문제인지 그대로 보여준다.
"""
conflicts = find_conflicts(self.config.glossary.enabled_packs)
if not conflicts:
self.conflict_label.setText("")
self.conflict_label.setVisible(False)
return
shown = sorted(conflicts.items())[:4]
parts = [
f"{source} ({' / '.join(t for _, t in owners)})" for source, owners in shown
]
more = f" 외 {len(conflicts) - len(shown)}개" if len(conflicts) > len(shown) else ""
self.conflict_label.setText(
"게임 팩끼리 역어가 갈리는 단어가 있습니다 — "
+ ", ".join(parts)
+ more
+ ". 안 하는 게임은 꺼주세요. (직접 등록하면 그쪽이 이깁니다)"
)
self.conflict_label.setVisible(True)
# --- 구성 -----------------------------------------------------------
def _build_options_card(self) -> Card:
@@ -295,3 +325,12 @@ def _hint(text: str) -> QLabel:
label.setObjectName("Hint")
label.setAlignment(Qt.AlignmentFlag.AlignRight)
return label
def _warning(text: str) -> QLabel:
label = QLabel(text)
label.setObjectName("Hint")
label.setWordWrap(True)
label.setStyleSheet(f"color: {palette('dark').warning};")
label.setVisible(bool(text))
return label