]> danix's work - publisher.git/commitdiff
feat: translation view with QProcess streaming and post-translation preview
authorDanilo M. <redacted>
Sun, 3 May 2026 08:41:41 +0000 (10:41 +0200)
committerDanilo M. <redacted>
Sun, 3 May 2026 08:41:41 +0000 (10:41 +0200)
ui/main_window.py
ui/translation_view.py [new file with mode: 0644]

index 001b03a1b0b20925398fc773a205091387842b7f..407359124c487b5eefa43254f2bac3b4078a2240 100644 (file)
@@ -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 (file)
index 0000000..70e7ac2
--- /dev/null
@@ -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)