import sys import logging import keyboard import pyperclip from PySide6.QtCore import QTimer, QEvent, Qt, QSettings from PySide6.QtWidgets import ( QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QCheckBox, QPushButton, QMessageBox, QWidget, QLineEdit ) from translatepy.translators.bing import BingTranslate from translatepy.translators.deepl import DeeplTranslate from translatepy.translators.google import GoogleTranslate from translatepy.translators.libre import LibreTranslate from translatepy.translators.mymemory import MyMemoryTranslate from translatepy.translators.reverso import ReversoTranslate from translatepy.translators.yandex import YandexTranslate from translatepy.translators.microsoft import MicrosoftTranslate # 로깅 설정 logging.basicConfig( level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s", handlers=[ logging.FileHandler("translator_app.log"), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) class MultiTranslatorApp(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("다중 번역기 설정") self.setGeometry(100, 100, 500, 600) # QSettings 초기화 self.settings = QSettings("MultiTranslatorApp", "TranslatorSettings") # 번역 요청 카운터 self.request_count = { "Bing": 0, "DeepL": 0, "Google": 0, "LibreTranslate": 0, "MyMemory": 0, "Reverso": 0, "Yandex": 0, "Microsoft": 0 } # GUI 레이아웃 self.central_widget = QWidget() self.setCentralWidget(self.central_widget) self.layout = QVBoxLayout(self.central_widget) # 번역기 체크박스 및 API 키 필드 생성 self.translator_settings = { "Bing": {"checkbox": QCheckBox("Bing Translate"), "api_key": QLineEdit()}, "DeepL": {"checkbox": QCheckBox("DeepL Translate"), "api_key": None}, "Google": {"checkbox": QCheckBox("Google Translate"), "api_key": None}, "LibreTranslate": {"checkbox": QCheckBox("LibreTranslate"), "api_key": None}, "MyMemory": {"checkbox": QCheckBox("MyMemory Translate"), "api_key": None}, "Reverso": {"checkbox": QCheckBox("Reverso Translate"), "api_key": None}, "Yandex": {"checkbox": QCheckBox("Yandex Translate"), "api_key": QLineEdit()}, "Microsoft": {"checkbox": QCheckBox("Microsoft Translate"), "api_key": QLineEdit()} } # 번역기 옵션 UI 구성 for name, widgets in self.translator_settings.items(): checkbox = widgets["checkbox"] api_key_field = widgets["api_key"] # 수평 레이아웃으로 배치 row_layout = QHBoxLayout() row_layout.addWidget(checkbox) if api_key_field: api_key_field.setPlaceholderText(f"{name} API 키 입력") api_key_field.setEnabled(False) # 처음에는 비활성화 row_layout.addWidget(api_key_field) # 체크박스와 API 키 활성화 상태를 연결 checkbox.stateChanged.connect(self.create_checkbox_handler(api_key_field)) self.layout.addLayout(row_layout) # 자동 번역 토글 self.auto_translate_toggle = QCheckBox("클립보드 변경 시 자동 번역") self.layout.addWidget(self.auto_translate_toggle) self.auto_translate_toggle.stateChanged.connect(self.toggle_auto_translate) # 적용 버튼 self.apply_button = QPushButton("설정 적용") self.apply_button.clicked.connect(self.apply_settings) self.layout.addWidget(self.apply_button) # 번역기 초기화 self.selected_services = [] # 클립보드 감지 관련 self.auto_translate_enabled = False self.last_clipboard_content = "" self.clipboard_timer = QTimer(self) self.clipboard_timer.timeout.connect(self.monitor_clipboard) # 단축키 등록 keyboard.add_hotkey("ctrl+shift+t", self.schedule_translation) # ESC 키로 창 최소화 self.installEventFilter(self) # 이전 설정 복원 self.load_settings() def create_checkbox_handler(self, api_key_field): """체크박스 상태 변경에 따라 API 키 입력 필드 활성화/비활성화.""" def handler(state): api_key_field.setEnabled(state == Qt.Checked) logger.debug(f"API 키 필드 {'활성화' if state == Qt.Checked else '비활성화'}됨") return handler def load_settings(self): """QSettings에서 체크박스 상태 및 API 키를 불러옵니다.""" logger.debug("QSettings에서 상태 복원 중...") for name, widgets in self.translator_settings.items(): checkbox = widgets["checkbox"] api_key_field = widgets["api_key"] # 체크박스 상태 복원 state = self.settings.value(f"{name}_checked", False, type=bool) checkbox.setChecked(state) logger.debug(f"{name} 체크박스 상태: {state}") # API 키 복원 if api_key_field: api_key = self.settings.value(f"{name}_api_key", "", type=str) api_key_field.setText(api_key) api_key_field.setEnabled(state) # 체크박스 상태에 따라 활성화 logger.debug(f"{name} API 키: {api_key}") def save_settings(self): """QSettings에 체크박스 상태 및 API 키를 저장합니다.""" logger.debug("QSettings에 상태 저장 중...") for name, widgets in self.translator_settings.items(): checkbox = widgets["checkbox"] api_key_field = widgets["api_key"] # 체크박스 상태 저장 self.settings.setValue(f"{name}_checked", checkbox.isChecked()) logger.debug(f"{name} 체크박스 상태 저장: {checkbox.isChecked()}") # API 키 저장 if api_key_field: self.settings.setValue(f"{name}_api_key", api_key_field.text()) logger.debug(f"{name} API 키 저장: {api_key_field.text()}") def apply_settings(self): """선택된 번역기를 업데이트하고 설정을 저장합니다.""" self.selected_services = [] for name, widgets in self.translator_settings.items(): checkbox = widgets["checkbox"] api_key_field = widgets["api_key"] if checkbox.isChecked(): logger.debug(f"{name} 번역기 활성화 및 추가 중...") try: if name == "Bing": self.selected_services.append(BingTranslate()) elif name == "DeepL": self.selected_services.append(DeeplTranslate()) elif name == "Google": self.selected_services.append(GoogleTranslate()) elif name == "LibreTranslate": self.selected_services.append(LibreTranslate()) elif name == "MyMemory": self.selected_services.append(MyMemoryTranslate()) elif name == "Reverso": self.selected_services.append(ReversoTranslate()) elif name == "Yandex": self.selected_services.append(YandexTranslate(api_key=api_key_field.text())) elif name == "Microsoft": self.selected_services.append(MicrosoftTranslate(api_key=api_key_field.text())) except Exception as e: logger.error(f"{name} 번역기 추가 실패: {e}") # 설정 저장 self.save_settings() # 창 최소화 self.showMinimized() def toggle_auto_translate(self): if self.auto_translate_toggle.isChecked(): self.auto_translate_enabled = True self.last_clipboard_content = pyperclip.paste() self.clipboard_timer.start(500) # 0.5초마다 클립보드 감지 else: self.auto_translate_enabled = False self.clipboard_timer.stop() def monitor_clipboard(self): if not self.auto_translate_enabled: return clipboard_text = pyperclip.paste() if clipboard_text != self.last_clipboard_content: self.last_clipboard_content = clipboard_text self.schedule_translation() def schedule_translation(self): """메인 스레드에서 번역 실행 예약.""" QTimer.singleShot(0, self.run_translation) def run_translation(self): clipboard_text = pyperclip.paste() if not clipboard_text.strip(): self.show_popup("클립보드에 텍스트가 없습니다.") return results = [] for service in self.selected_services: try: result = service.translate(clipboard_text, "Korean") service_name = service.__class__.__name__.replace("Translate", "") if service_name in self.request_count: self.request_count[service_name] += 1 results.append(f"[{service_name}]\n{result.result}\n\n") except Exception as e: results.append(f"[{service_name}] 번역 실패: {str(e)}") self.show_popup("\n\n".join(results)) def show_popup(self, message): popup = QMessageBox(self) popup.setWindowTitle("번역 결과") popup.setText(message) popup.addButton("닫기(ESC)", QMessageBox.AcceptRole) popup.show() def eventFilter(self, source, event): if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Escape: self.close() return super().eventFilter(source, event) if __name__ == "__main__": app = QApplication(sys.argv) window = MultiTranslatorApp() window.show() sys.exit(app.exec())