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):