diff --git a/gui/workflow_tab.py b/gui/workflow_tab.py index 1c548f9..a80d5ea 100644 --- a/gui/workflow_tab.py +++ b/gui/workflow_tab.py @@ -8,9 +8,8 @@ Provides a guided, step-by-step interface: Step 2 – Setup: speaker flags, Project Setup skill, vocab / quirks / game skill Step 3 – Translation: Phase 0 (DB), Phase 1 (dialogue), Phase 1b (111 cache) Step 4 – Translation Phase 2 (risky codes) - Step 5 – Translate visible strings in js/plugins.js (or Ace scripts) - Step 6 – Export translated/ back to the game folder - Step 7 – Install TL Inspector and/or Forge playtest plugins + Step 5 – Plugins/scripts prompt helpers + export translated/ to the game + Step 6 – Install TL Inspector and/or Forge playtest plugins """ from __future__ import annotations @@ -389,6 +388,114 @@ def _make_section_label(text: str) -> QLabel: return lbl +def _make_help_button() -> QToolButton: + """Small circular ? control for step help dialogs.""" + btn = QToolButton() + btn.setText("?") + btn.setFixedSize(24, 24) + btn.setCursor(Qt.PointingHandCursor) + btn.setToolTip("How to use this step") + btn.setStyleSheet( + "QToolButton{" + "background-color:#2d2d30;color:#9d9d9d;border:1px solid #555;" + "border-radius:12px;font-size:12px;font-weight:bold;padding:0;}" + "QToolButton:hover{background-color:#3a3a3a;color:#ffffff;border-color:#007acc;}" + "QToolButton:pressed{background-color:#1e1e1e;}" + ) + return btn + + +# Per-step help copy shown by the header ? button (keeps step UIs compact). +_STEP_HELP: dict[int, str] = { + 0: ( + "Step 0 - Project

" + "Select the game root folder (the folder that contains the game's data files).

" + "The tool detects MV/MZ/Ace layout, lists JSON files, and lets you import them into " + "files/ for translation.

" + "Tip: when switching games, clear files/ and translated/ " + "so old project data does not mix in." + ), + 1: ( + "Step 1 - Pre-process (optional)

" + "Optional prep against the game folder before importing:
" + "• Format data JSON (dazedformat)
" + "• Format plugins.js (MV/MZ) for easier editing
" + "• Copy a gameupdate/ folder into the project

" + "Paths auto-fill when a project folder is detected. " + "Skip this step if you do not need those tasks." + ), + 2: ( + "Step 2 - Setup (Speakers + Glossary)

" + "1. Speaker flags - enable the formats this game uses " + "(INLINE401 / FIRSTLINE / FACENAME). Tooltips explain each flag. " + "Project Setup's speakers block can recommend ENABLE/SKIP.

" + "2. Parse Speakers - collect speaker names from event files into " + "vocab.txt (# Speakers). Does not translate dialogue.

" + "3. Copy Project Setup - paste into Cursor/Copilot with the game repo open. " + "The AI returns labeled code blocks:
" + "• glossary → Vocab tab
" + "• speakers → apply flags above
" + "• translation_quirks → Quirks tab " + "(skills/quirks.md)
" + "• game_skill → Game skill tab " + "(skills/translation.md)

" + "Use one game folder per translation run so quirks stay in the prompt cache." + ), + 3: ( + "Step 3 - TL Phase 1

" + "Safe dialogue / database translation.

" + "• Set text-wrap widths if needed (or copy the wrap analysis prompt)
" + "• Phase 0 - database names/descriptions
" + "• Phase 1 - dialogue / choices
" + "• Phase 1b - build the code 111 variable-translation cache

" + "Choose Normal or Batch mode at the top. Run each phase from the Translation tab " + "after the workflow applies the phase profile." + ), + 4: ( + "Step 4 - TL Phase 2

" + "Risky / plugin-related codes (variables, plugin commands, etc.).

" + "Copy the Plugin Prompt and audit the game first, then enable only codes that " + "contain player-visible text. Set code 122 variable ID ranges carefully - " + "translating IDs used as logic keys will break the game." + ), + 5: ( + "Step 5 - Plugins & Export

" + "Plugins / scripts (optional but recommended before shipping):
" + "MV/MZ: copy vocab.txt into the game folder, then copy the " + "plugins.js prompt and run it in your IDE with plugins.js attached. " + "Only translate player-visible UI strings - never plugin parameter keys or " + "internal identifiers.
" + "Ace: same idea for ace_json/scripts/*.rb.

" + "Export - copy finished translations from translated/ back " + "into the game's data folder:
" + "• Export Active Files - only names currently in files/
" + "• Export ALL - everything under translated/" + ), + 6: ( + "Step 6 - Playtest

" + "Install playtest plugins into the MV/MZ game:
" + "• TL Inspector - inspect translated text in-game
" + "• Forge - additional playtest helpers

" + "Configure hotkeys/UI scale, then Install / Uninstall as needed. " + "Not available for Ace projects." + ), +} + + +def _show_step_help(parent: QWidget | None, title: str, html: str) -> None: + box = QMessageBox(parent) + box.setWindowTitle(title) + box.setIcon(QMessageBox.Information) + box.setTextFormat(Qt.RichText) + box.setText(html) + box.setStandardButtons(QMessageBox.Ok) + box.setStyleSheet( + "QMessageBox{background-color:#252526;}" + "QLabel{color:#d4d4d4;font-size:13px;min-width:420px;}" + ) + box.exec_() + + def _make_hr() -> QFrame: hr = QFrame() hr.setFrameShape(QFrame.HLine) @@ -626,9 +733,8 @@ class WorkflowTab(QWidget): ("2 Setup", self._build_step2_setup), ("3 TL Phase 1", self._build_step4_translation), ("4 TL Phase 2", self._build_step5_tl_phase2), - ("5 Plugins.js", self._build_step6_plugins_js), - ("6 Export", self._build_step7_export), - ("7 Playtest", self._build_step8_playtest), + ("5 Export", self._build_step5_finish), + ("6 Playtest", self._build_step8_playtest), ] self._step_labels = [label for label, _ in _tab_defs] @@ -799,19 +905,10 @@ class WorkflowTab(QWidget): "Setup", "Phase1", "Phase2", - "Plugins", "Export", "Playtest", ) - # Step 5 label swaps for Ace scripts. - if idx == 5 and len(self._step_labels) > 5: - raw = self._step_labels[5] - if "Script" in raw: - name = "Scripts" - else: - name = short_names[5] - else: - name = short_names[idx] if 0 <= idx < len(short_names) else str(idx) + name = short_names[idx] if 0 <= idx < len(short_names) else str(idx) mark = "✓" if done else "" return f"{mark}{idx}\n{name}" @@ -872,7 +969,7 @@ class WorkflowTab(QWidget): if index == 4: self._populate_p2_checkboxes() - if index == 7: + if index == 6: self._refresh_playtest_status() self._load_playtest_settings() @@ -950,8 +1047,35 @@ class WorkflowTab(QWidget): # ── Step 0: Project Folder ────────────────────────────────────────────── + def _add_step_header( + self, + layout: QVBoxLayout, + title: str, + step_idx: int, + *, + extra_widgets: list | None = None, + ) -> QLabel: + """Title row with optional trailing widgets and a ? help button on the right.""" + row = QHBoxLayout() + row.setContentsMargins(0, 0, 0, 0) + row.setSpacing(8) + lbl = _make_section_label(title) + row.addWidget(lbl, 1) + for w in extra_widgets or []: + row.addWidget(w) + help_btn = _make_help_button() + help_html = _STEP_HELP.get(step_idx, "No help is available for this step.") + help_btn.clicked.connect( + lambda _=False, label=lbl, h=help_html: _show_step_help( + self, label.text(), h + ) + ) + row.addWidget(help_btn, 0, Qt.AlignTop) + layout.addLayout(row) + return lbl + def _build_step0(self, layout: QVBoxLayout): - layout.addWidget(_make_section_label("Step 0 — Project Folder")) + self._add_step_header(layout, "Step 0 — Project Folder", 0) # Folder picker row row0 = QHBoxLayout() @@ -1367,16 +1491,12 @@ class WorkflowTab(QWidget): # ── Step 1 (Optional): Pre-process ──────────────────────────────── def _build_step1_preprocess(self, layout: QVBoxLayout): - header_row = QHBoxLayout() - header_row.addWidget(_make_section_label("Step 1 (Optional) — Pre-process")) opt_badge = QLabel("optional") opt_badge.setStyleSheet( "color:#7a7a7a;font-size:11px;border:1px solid #3c3c3c;" - "padding:1px 8px;border-radius:8px;margin-left:8px;" + "padding:1px 8px;border-radius:8px;" "background-color:#252526;" ) - header_row.addWidget(opt_badge) - header_row.addStretch() # Collapse/expand toggle toggle_btn = QPushButton("▼") toggle_btn.setCheckable(True) @@ -1386,23 +1506,19 @@ class WorkflowTab(QWidget): "QPushButton{background:transparent;color:#888;border:none;font-size:11px;}" "QPushButton:hover{color:#ccc;}" ) - header_row.addWidget(toggle_btn) - layout.addLayout(header_row) + self._add_step_header( + layout, + "Step 1 (Optional) — Pre-process", + 1, + extra_widgets=[opt_badge, toggle_btn], + ) - # Collapsible container — wraps hint + tasks_box + run-all row + # Collapsible container — wraps tasks_box + run-all row collapse_widget = QWidget() collapse_layout = QVBoxLayout(collapse_widget) collapse_layout.setContentsMargins(0, 0, 0, 0) collapse_layout.setSpacing(4) - hint = QLabel( - "Three optional preparation tasks to run against the game folder before " - "importing files. Paths are auto-filled when a project folder is detected." - ) - hint.setWordWrap(True) - hint.setStyleSheet("color:#9d9d9d;font-size:13px;padding-bottom:6px;") - collapse_layout.addWidget(hint) - tasks_box = QGroupBox() tasks_box.setStyleSheet("QGroupBox{border:none;margin:0;padding:4px 0;}") tb = QVBoxLayout(tasks_box) @@ -1574,7 +1690,7 @@ class WorkflowTab(QWidget): def _build_step2_setup(self, layout: QVBoxLayout): """Combined speaker flags + Project Setup + vocab/quirks/game-skill editors.""" - layout.addWidget(_make_section_label("Step 2 — Setup (Speakers + Glossary)")) + self._add_step_header(layout, "Step 2 — Setup (Speakers + Glossary)", 2) # ---- Compact action bar -------------------------------------------- top = QWidget() @@ -1584,14 +1700,6 @@ class WorkflowTab(QWidget): top_l.setContentsMargins(10, 8, 10, 8) top_l.setSpacing(6) - hint = QLabel( - "1) Set speaker flags · 2) Parse Speakers into vocab · " - "3) Copy Project Setup → IDE → paste glossary / quirks / game skill below" - ) - hint.setWordWrap(True) - hint.setStyleSheet("color:#9d9d9d;font-size:12px;") - top_l.addWidget(hint) - from gui import qt_icons actions = QHBoxLayout() actions.setSpacing(8) @@ -1884,7 +1992,7 @@ class WorkflowTab(QWidget): def _build_step4_translation(self, layout: QVBoxLayout): - layout.addWidget(_make_section_label("Step 3 — TL Phase 1")) + self._add_step_header(layout, "Step 3 — TL Phase 1", 3) self._add_tl_mode_selector(layout) @@ -2036,7 +2144,7 @@ class WorkflowTab(QWidget): def _build_step5_tl_phase2(self, layout: QVBoxLayout): - layout.addWidget(_make_section_label("Step 4 — TL Phase 2")) + self._add_step_header(layout, "Step 4 — TL Phase 2", 4) self._step5_mode_hint = QLabel(f"Translation mode: {BATCH_MODE_LABEL}") self._step5_mode_hint.setStyleSheet("color:#8fbc8f;font-size:12px;margin-bottom:4px;") @@ -2339,26 +2447,39 @@ class WorkflowTab(QWidget): # Pre-populate all Phase 2 checkboxes from current module state self._populate_p2_checkboxes() - # ── Step 5: plugins.js / Ace scripts Translation ─────────────────────── + # ── Step 5: Plugins/scripts + Export ─────────────────────────────────── - def _build_step6_plugins_js(self, layout: QVBoxLayout): - self._step6_section_label = _make_section_label("Step 5 — Translate plugins.js") - layout.addWidget(self._step6_section_label) + def _build_step5_finish(self, layout: QVBoxLayout): + self._add_step_header(layout, "Step 5 — Plugins & Export", 5) - self._step6_hint = QLabel( - "Translate the visible Japanese strings in js/plugins.js without breaking " - "game logic. First copy vocab.txt into the game folder so the AI can use it " - "as a glossary, then copy the prompt and paste it into Copilot or Cursor with " - "plugins.js, vocab.txt, and optionally System.json attached." - ) - self._step6_hint.setWordWrap(True) - self._step6_hint.setStyleSheet("color:#9d9d9d;font-size:13px;padding-bottom:6px;") - layout.addWidget(self._step6_hint) + box = QWidget() + box.setObjectName("tbox") + box.setStyleSheet(self._task_box_style()) + box.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed) + inner = QVBoxLayout(box) + inner.setContentsMargins(14, 12, 14, 12) + inner.setSpacing(10) - _BTN_WIDTH = 240 + _LBL = "color:#4ec9b0;font-size:12px;font-weight:bold;" + _LBL_W = 70 + _BTN_H = 32 + _BTN_W = 230 - vocab_btn = _make_btn("📄 Copy vocab.txt → Game Folder", "#555") - vocab_btn.setFixedWidth(_BTN_WIDTH) + def _labeled_row(label: QLabel, *buttons: QPushButton) -> QHBoxLayout: + row = QHBoxLayout() + row.setSpacing(10) + label.setStyleSheet(_LBL) + label.setFixedWidth(_LBL_W) + label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + row.addWidget(label) + for btn in buttons: + btn.setFixedSize(_BTN_W, _BTN_H) + row.addWidget(btn) + return row + + self._step6_section_label = QLabel("Plugins") + + vocab_btn = _make_btn("📄 Copy vocab.txt → Game", "#555") vocab_btn.setToolTip( "Copy vocab.txt to /vocab.txt so you can attach it " "alongside plugins.js when running the AI prompt." @@ -2366,66 +2487,39 @@ class WorkflowTab(QWidget): vocab_btn.clicked.connect(self._copy_vocab_to_game) self._step6_copy_btn = _make_btn("📋 Copy Prompt for Copilot", "#555") - self._step6_copy_btn.setFixedWidth(_BTN_WIDTH) self._step6_copy_btn.setToolTip( "Copy a prompt that instructs Copilot/Cursor to translate only " "visible player-facing strings in plugins.js, using vocab.txt as a glossary." ) self._step6_copy_btn.clicked.connect(self._copy_plugins_js_translate_prompt) - btn_row = QHBoxLayout() - btn_row.setSpacing(8) - btn_row.addWidget(vocab_btn) - btn_row.addWidget(self._step6_copy_btn) - btn_row.addStretch() - layout.addLayout(btn_row) - - # ── Step 6: Export ────────────────────────────────────────────────────── - - def _build_step7_export(self, layout: QVBoxLayout): - layout.addWidget(_make_section_label("Step 6 — Export to Game")) - hint = QLabel( - "Copy translated files back into the game's data folder to patch the game in-place." + inner.addLayout( + _labeled_row(self._step6_section_label, vocab_btn, self._step6_copy_btn) ) - hint.setWordWrap(True) - hint.setStyleSheet("color:#9d9d9d;font-size:13px;padding-bottom:4px;") - layout.addWidget(hint) - _EXP_W = 260 - row = QHBoxLayout() - row.setSpacing(8) + export_lbl = QLabel("Export") export_active_btn = _make_btn("📤 Export Active Files", "#3a7a3a") - export_active_btn.setFixedWidth(_EXP_W) export_active_btn.setToolTip( "Only export files whose names match those currently in files/\n" "(i.e. the files you imported for this project)" ) export_active_btn.clicked.connect(self._export_active_files) - row.addWidget(export_active_btn) export_all_btn = _make_btn("📤 Export ALL translated/", "#555") - export_all_btn.setFixedWidth(_EXP_W) - export_all_btn.setToolTip("Export every file in translated/ regardless of what is in files/") + export_all_btn.setToolTip( + "Export every file in translated/ regardless of what is in files/" + ) export_all_btn.clicked.connect(self._export_to_game) - row.addWidget(export_all_btn) - row.addStretch() - layout.addLayout(row) - # ── Step 8: Playtest (TL Inspector) ───────────────────────────────────── + inner.addLayout(_labeled_row(export_lbl, export_active_btn, export_all_btn)) + layout.addWidget(box, 0, Qt.AlignLeft) + + # ── Step 6: Playtest (TL Inspector) ───────────────────────────────────── def _build_step8_playtest(self, layout: QVBoxLayout): - self._step8_section_label = _make_section_label("Step 7 — Playtest Tools") - layout.addWidget(self._step8_section_label) - - self._step8_main_hint = QLabel( - "Install playtest plugins into your RPG Maker game. " - "TL Inspector and Forge work on MV and MZ. " - "Use Install Both to add both plugins at once." + self._step8_section_label = self._add_step_header( + layout, "Step 6 — Playtest Tools", 6 ) - self._step8_main_hint.setWordWrap(True) - self._step8_main_hint.setTextFormat(Qt.RichText) - self._step8_main_hint.setStyleSheet("color:#9d9d9d;font-size:13px;padding-bottom:4px;") - layout.addWidget(self._step8_main_hint) settings_box = QWidget() settings_box.setObjectName("tbox") @@ -3076,43 +3170,17 @@ class WorkflowTab(QWidget): self._log(f"⚠ Could not remove {err}") def _update_step6_for_engine(self, is_ace: bool) -> None: - """Adapt the Step 6 tab label and content for MV/MZ (plugins.js) vs Ace (.rb scripts).""" + """Adapt plugins/scripts controls for MV/MZ vs Ace; hide Playtest on Ace.""" # Step 1 prettier section — only relevant for MV/MZ for attr in ("_pp_dazedformat_title", "_pp_dazedformat_box", "_pp_plugins_js_title", "_pp_plugins_js_box"): w = getattr(self, attr, None) if w is not None: w.setVisible(not is_ace) - # Tab / strip label (Plugins step is index 5 after Setup merge) - step6_label = "5 Scripts" if is_ace else "5 Plugins.js" - self._step_tabs.setTabText(5, step6_label) - if hasattr(self, "_step_labels") and len(self._step_labels) > 5: - self._step_labels[5] = step6_label - if hasattr(self, "_step_buttons") and len(self._step_buttons) > 5: - self._step_buttons[5].setToolTip(step6_label) - # Step 5 section header + # Step 5 plugins subsection title + prompt tooltip lbl = getattr(self, "_step6_section_label", None) if lbl is not None: - lbl.setText("Step 5 — Translate Ace Scripts (.rb)" if is_ace - else "Step 5 — Translate plugins.js") - # Hint text - hint = getattr(self, "_step6_hint", None) - if hint is not None: - if is_ace: - hint.setText( - "Translate visible Japanese strings in the Ace script files " - "(ace_json/scripts/*.rb) without breaking game logic. " - "Attach the .rb files and vocab.txt to Copilot or Cursor, " - "then paste the copied prompt." - ) - else: - hint.setText( - "Translate the visible Japanese strings in js/plugins.js without breaking " - "game logic. First copy vocab.txt into the game folder so the AI can use it " - "as a glossary, then copy the prompt and paste it into Copilot or Cursor with " - "plugins.js, vocab.txt, and optionally System.json attached." - ) - # Prompt button tooltip + lbl.setText("Scripts" if is_ace else "Plugins") btn = getattr(self, "_step6_copy_btn", None) if btn is not None: if is_ace: @@ -3125,18 +3193,19 @@ class WorkflowTab(QWidget): "Copy a prompt that instructs Copilot/Cursor to translate only " "visible player-facing strings in plugins.js, using vocab.txt as a glossary." ) - # Step 7 — TL Inspector (MV/MZ only; hidden for Ace) + # Step 6 — TL Inspector (MV/MZ only; hidden for Ace) show_playtest = not is_ace - if hasattr(self, "_step_tabs") and self._step_tabs.count() > 7: + playtest_idx = 6 + if hasattr(self, "_step_tabs") and self._step_tabs.count() > playtest_idx: if hasattr(self._step_tabs, "setTabVisible"): - self._step_tabs.setTabVisible(7, show_playtest) + self._step_tabs.setTabVisible(playtest_idx, show_playtest) else: - self._step_tabs.setTabEnabled(7, show_playtest) - if is_ace and self._step_tabs.currentIndex() == 7: - self._goto_step(6) - if hasattr(self, "_step_buttons") and len(self._step_buttons) > 7: - self._step_buttons[7].setVisible(show_playtest) - self._step_buttons[7].setEnabled(show_playtest) + self._step_tabs.setTabEnabled(playtest_idx, show_playtest) + if is_ace and self._step_tabs.currentIndex() == playtest_idx: + self._goto_step(5) + if hasattr(self, "_step_buttons") and len(self._step_buttons) > playtest_idx: + self._step_buttons[playtest_idx].setVisible(show_playtest) + self._step_buttons[playtest_idx].setEnabled(show_playtest) self._refresh_step_strip() box = getattr(self, "_step8_playtest_box", None) install_both_btn = getattr(self, "_install_both_btn", None) @@ -4081,7 +4150,7 @@ class WorkflowTab(QWidget): pass # ───────────────────────────────────────────────────────────────────────── - # Step 6 – Export to game + # Step 5 – Export to game # ───────────────────────────────────────────────────────────────────────── def _do_copy_translated_to_files(self):