From: Danilo M. Date: Sun, 3 May 2026 08:41:41 +0000 (+0200) Subject: feat: translation view with QProcess streaming and post-translation preview X-Git-Tag: v1.0~12 X-Git-Url: https://git.danix.xyz/?a=commitdiff_plain;h=1786c672753a74b876f415dd5f32742c0e24f8c9;p=publisher.git feat: translation view with QProcess streaming and post-translation preview --- diff --git a/ui/main_window.py b/ui/main_window.py index 001b03a..4073591 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -79,8 +79,20 @@ class MainWindow(QMainWindow): self._stack.addWidget(self._detail_view) self._page_detail = self._stack.count() - 1 + from ui.translation_view import TranslationView + + self._translation_view = TranslationView( + self.config.transart_script, + self.config.typora_bin, + parent=self, + ) + self._translation_view.push_master.connect(lambda: self._do_git_push("master")) + self._translation_view.publish.connect(lambda: self._do_git_push("production")) + self._stack.addWidget(self._translation_view) + self._page_translations = self._stack.count() - 1 + # Remaining placeholders - for name in ["taxonomy", "media", "translations", "git", "hugo"]: + for name in ["taxonomy", "media", "git", "hugo"]: w = QLabel(f"[{name}]") w.setAlignment(Qt.AlignmentFlag.AlignCenter) self._stack.addWidget(w) @@ -189,7 +201,8 @@ class MainWindow(QMainWindow): subprocess.Popen([self.config.typora_bin, str(dlg.created_path)]) def _do_translate(self, article: Article): - pass # implemented in Task 15 + self._translation_view.start_translation(article) + self._stack.setCurrentIndex(self._page_translations) def _do_git_push(self, branch: str): pass # implemented in Task 16 diff --git a/ui/translation_view.py b/ui/translation_view.py new file mode 100644 index 0000000..70e7ac2 --- /dev/null +++ b/ui/translation_view.py @@ -0,0 +1,109 @@ +from __future__ import annotations +import subprocess +from pathlib import Path +from PyQt6.QtWidgets import ( + QWidget, QVBoxLayout, QHBoxLayout, QLabel, + QPushButton, QPlainTextEdit, QTextBrowser, +) +from PyQt6.QtCore import pyqtSignal +from core.models import Article +from core.frontmatter import parse_frontmatter +from workers.translation_worker import TranslationWorker +import re + +def _strip_shortcodes(text: str) -> str: + return re.sub(r'\{\{[^}]+\}\}', '[shortcode]', text) + +class TranslationView(QWidget): + push_master = pyqtSignal() + publish = pyqtSignal() + + def __init__(self, transart_script: str, typora_bin: str, parent=None): + super().__init__(parent) + self._transart_script = transart_script + self._typora_bin = typora_bin + self._article: Article | None = None + self._worker: TranslationWorker | None = None + self._output_path: str = "" + self._build_ui() + + def _build_ui(self): + layout = QVBoxLayout(self) + + self._header = QLabel("") + self._header.setStyleSheet("color:#fff;font-weight:bold;font-size:13px;padding:10px;") + layout.addWidget(self._header) + + log_label = QLabel("Output traduzione:") + log_label.setStyleSheet("color:#a855f7;font-size:9px;text-transform:uppercase;letter-spacing:1px;padding:0 10px;") + layout.addWidget(log_label) + self._log = QPlainTextEdit() + self._log.setReadOnly(True) + self._log.setStyleSheet("background:#0a0a12;color:#00ff88;font-family:monospace;font-size:11px;") + self._log.setMaximumHeight(180) + layout.addWidget(self._log) + + preview_label = QLabel("Preview traduzione:") + preview_label.setStyleSheet("color:#a855f7;font-size:9px;text-transform:uppercase;letter-spacing:1px;padding:0 10px;") + layout.addWidget(preview_label) + self._preview = QTextBrowser() + self._preview.setStyleSheet("background:#0d0d1a;color:#ccc;border:none;") + layout.addWidget(self._preview, stretch=1) + + self._action_bar = QWidget() + btns = QHBoxLayout(self._action_bar) + btns.setContentsMargins(10, 6, 10, 6) + + self._btn_typora = QPushButton("✏️ Apri in Typora") + self._btn_typora.clicked.connect(self._open_typora) + self._btn_retry = QPushButton("🔄 Rigenera") + self._btn_retry.clicked.connect(self._retry) + self._btn_master = QPushButton("🧪 Push master") + self._btn_master.clicked.connect(self.push_master.emit) + self._btn_publish = QPushButton("🚢 Pubblica") + self._btn_publish.clicked.connect(self.publish.emit) + + for btn in (self._btn_typora, self._btn_retry, self._btn_master, self._btn_publish): + btns.addWidget(btn) + btns.addStretch() + + self._action_bar.setVisible(False) + layout.addWidget(self._action_bar) + + def start_translation(self, article: Article): + self._article = article + self._log.clear() + self._preview.clear() + self._action_bar.setVisible(False) + + direction = "it-en" if article.lang == "it" else "en-it" + self._header.setText(f"Traduzione: {article.slug} ({article.lang.upper()} → {('EN' if article.lang == 'it' else 'IT')})") + + self._worker = TranslationWorker( + Path(self._transart_script), + article.path, + direction, + parent=self, + ) + self._worker.log_line.connect(self._log.appendPlainText) + self._worker.finished.connect(self._on_finished) + self._worker.error.connect(lambda e: self._log.appendPlainText(f"[ERRORE] {e}")) + self._worker.start() + + def _on_finished(self, success: bool, output_path: str): + self._output_path = output_path + if success and Path(output_path).exists(): + try: + _, body = parse_frontmatter(Path(output_path)) + self._preview.setMarkdown(_strip_shortcodes(body)) + except Exception as e: + self._preview.setPlainText(f"[Errore preview: {e}]") + self._action_bar.setVisible(True) + + def _open_typora(self): + if self._output_path: + subprocess.Popen([self._typora_bin, self._output_path]) + + def _retry(self): + if self._article: + self.start_translation(self._article)