feat(gui): add in-app Guide tab and open it by default

- Reorder sidebar to Guide, Workflow, Translation, Batches, Skills, Config
- Load maintainable docs from data/help/ with a process walkthrough
- Restore Ace Step 5 scripts prompt and pack guidance via RV2JSON
- Recommend Claude Sonnet 4.6 for quality/batch and Mistral for free
This commit is contained in:
DazedAnon 2026-07-22 12:13:19 -05:00
parent a1af8fb985
commit cb90965f30
16 changed files with 939 additions and 54 deletions

29
data/help/00-welcome.md Normal file
View file

@ -0,0 +1,29 @@
# Welcome to DazedMTLTool
DazedMTLTool translates Japanese games into English with an AI API.
It has the strongest support for **RPG Maker** (MV / MZ / Ace) and **Wolf RPG** (WolfDawn),
plus modules for Ren'Py, Tyrano, Kirikiri, CSV, and more.
## How this Guide works
Use the section list on the left to jump around.
Update this Guide by editing markdown under `data/help/` and the entries in `data/help/index.json`.
You do not need to change the GUI code to add a new section.
## Recommended path
1. Read **Requirements** (API key + an AI agent in an IDE).
2. Follow **Quick Start** once.
3. Run your game through the **Workflow** tab (sidebar lightning icon).
4. Keep **Example Walkthrough** open when you want a full “day in the life” process story.
## Example: first five minutes
1. Open **Configuration** and confirm your API key / model.
Prefer **Claude Sonnet 4.6** for quality + Batch, or **Mistral** if you want free.
2. Click **Open Workflow** below (or the Workflow sidebar button).
3. Pick **RPG Maker** or **Wolf RPG (WolfDawn)** in the engine selector.
4. On Step 0, browse to the game folder that contains `Game.exe` (or the Wolf game root).
5. Open that same game folder in **Cursor** or **VS Code** so your agent can help with glossary and setup.
When you are ready, use the buttons at the bottom of this page.

View file

@ -0,0 +1,81 @@
# Requirements
You need three things working together: this tool, an AI translation API, and an IDE with an agent.
## 1. Python
Python **3.12 3.14**.
Launch with `START.bat` (Windows) or `START.sh` / the desktop launcher (Linux/macOS).
Those scripts create a venv and install dependencies for you.
## 2. AI API key
In **Configuration → General**, set:
- Provider / API base URL
- API key
- Model name
You can also edit the `.env` file next to the tool.
OpenAI-compatible endpoints and Gemini work too; pick a model based on budget:
| Goal | Recommendation |
|------|----------------|
| **Best cheap high-quality TLs + Batch mode** | **Claude Sonnet 4.6** (`claude-sonnet-4-6`) via Anthropic - Batch is ~50% off live price |
| **Free** | **Mistral** (`mistral-medium-3.5`) - free tier, no credit card; live only (no Batch discount) |
### Recommended: Claude Sonnet 4.6
Use this for most games when you can pay for API usage.
Strong translation quality at a good price, and it unlocks **Batch** mode for large maps / CommonEvents.
```plaintext
api=https://api.anthropic.com/v1
key=YOUR_ANTHROPIC_KEY_HERE
model=claude-sonnet-4-6
```
In Configuration, pick **Claude (Anthropic)** as the provider if you prefer the UI over `.env`.
### Free: Mistral
```plaintext
API_PROVIDER=mistral
api=https://api.mistral.ai/v1/
key=YOUR_MISTRAL_KEY_HERE
model=mistral-medium-3.5
```
Avoid `mistral-medium-latest` - it still points at an older release.
Mistral runs live translation with built-in rate limiting; use **Normal** mode (Batch is Anthropic/Claude only).
## 3. AI agent + IDE (required for a good workflow)
The Workflow tab expects you to use an AI coding agent for setup work that the translation API does not do alone:
- Building glossary / vocab from the game
- Choosing speaker flags
- Writing quirks and a Translation Frame
- Editing `plugins.js` UI strings (MV/MZ) or Ace `ace_json/scripts/*.rb`
- Searching for leftover Japanese after playtest
Use either:
| Tool | Notes |
|------|--------|
| **[Cursor](https://cursor.com/)** | Agent chat with the game folder open - preferred for Project Setup |
| **[VS Code](https://code.visualstudio.com/)** + Copilot (or another agent) | Same idea: open the game repo, paste Workflow prompts |
### Example: open the game in Cursor
1. In Cursor: **File → Open Folder** and select the game root (where `Game.exe` or Wolf `Data` lives).
2. In DazedMTLTool Workflow Step 2, click **Copy Project Setup**.
3. Paste into Cursor chat with the game files available (or `@`-mention key JSON files).
4. Paste the agent's labeled blocks back into the Workflow editors.
Without an IDE agent, you can still run raw Translation tab jobs, but glossary quality and plugin/UI cleanup will be much harder.
## Optional but strongly recommended
- **Git** in the game folder so you can diff and roll back bad edits
- **Windows Snipping Tool OCR** (or ShareX) for grabbing leftover Japanese while playtesting

View file

@ -0,0 +1,59 @@
# Quick Start
This is the happy path for a new game.
## 1. Configure the API
Open **Configuration** (sidebar gear).
Enter your key and model, then save.
- **Recommended:** `claude-sonnet-4-6` (cheap high-quality TLs; enables Batch at ~50% off).
- **Free:** `mistral-medium-3.5` (live only).
A wrong key shows up immediately when you try the first translate.
## 2. Open Workflow
Use the Workflow sidebar button (or **Open Workflow** on this Guide).
Choose the engine at the top:
- **RPG Maker MV / MZ / Ace**
- **Wolf RPG (WolfDawn)**
## 3. Step 0 - Project
Browse to the game folder.
Import the listed data files into the tool's `files/` folder (Workflow does this for you when you import or leave the step with files checked).
**Example:** for RPG Maker MV, you typically import `Actors.json`, `Items.json`, maps, `CommonEvents.json`, and so on into `files/`.
## 4. Step 1 - Pre-process (optional)
Run formatters / copy `gameupdate/` only if you want cleaner diffs or the updater scripts.
Skip if you just want to translate.
## 5. Step 2 - Setup with your IDE agent
1. Parse speakers into vocab when available.
2. Copy the **Project Setup** skill into Cursor / VS Code with the game open.
3. Paste the returned `glossary`, `speakers`, `translation_quirks`, and `game_skill` blocks into the Workflow editors and save.
See **Examples** for paste-ready prompts.
## 6. Translate in phases
Follow the Workflow steps (Phase 0 / 1 / 2 for RPG Maker, or Names → Database → Maps for Wolf).
Each action configures the Translation tab and can start a run for you.
**Example (RPG Maker):** Phase 0 = database names → Phase 1 = dialogue → Phase 2 = risky plugin / variable codes.
## 7. Export / inject and playtest
- RPG Maker: Export from `translated/` back into the game, then install TL Inspector / Forge if you want.
- Wolf: Precheck → Inject all → Package → playtest → Fix wrap.
## 8. Fix leftovers
Screenshot Japanese text → OCR → search in the IDE across the game / `translated/` → re-translate or hand-edit → export/inject again.
See **Example Walkthrough** for a full first-game process story.

View file

@ -0,0 +1,53 @@
# Workflow: RPG Maker
Open **Workflow** and select **RPG Maker** in the engine selector.
Use each step's **?** button for short in-context help.
This page is the overview.
## Steps at a glance
| Step | What it does |
|------|----------------|
| **0 Project** | Detect MV/MZ/Ace, list JSON, import into `files/` |
| **1 Pre-process** | Optional `dazedformat`, format `plugins.js` (MV/MZ), copy `gameupdate/` |
| **2 Setup** | Speaker flags, Parse Speakers, Project Setup → vocab / quirks / game skill |
| **3 TL Phase 1** | Phase 0 DB → Phase 1 dialogue → Phase 1b code 111 cache |
| **4 TL Phase 2** | Risky codes (122, plugins, etc.) after you audit |
| **5 Export** | MV/MZ: `plugins.js` helpers. Ace: Ruby `.rb` scripts. Then export / pack |
| **6 Playtest** | TL Inspector / Forge (MV/MZ; hidden for Ace) |
## Example: first RPG Maker run
1. Step 0 - select the game root that contains `www/` (MV) or `data/` (MZ).
2. Import core DB + one small map (for example `Map001.json`) before importing everything.
3. Step 2 - Parse Speakers, then run Project Setup in Cursor on the game folder.
4. Step 3 - run Phase 0, skim `translated/` and `log/`, then Phase 1 on the small map.
5. Play in-game. If it looks good, import the rest of the maps and continue.
## Speaker flags (Step 2)
Enable only what the game uses:
- **INLINE401** - name stuck to dialogue on the same line
- **FIRSTLINE** - first dialogue line is the speaker name
- **FACENAME** - last resort from face filenames
Project Setup's `speakers` block recommends ENABLE / SKIP with evidence.
## Phase tips
- Keep **vocab.txt** focused - huge glossaries raise cost and can hurt quality.
- Use **Batch** mode with **Claude Sonnet 4.6** for large map / CommonEvents runs (Anthropic Message Batches, ~50% off).
- On **Mistral** (free), stay on **Normal** mode - Batch is Claude-only.
- Use **Normal** mode when you want live iteration on a handful of files.
- Phase 2 can break games if you translate logic keys - audit with the Plugin Prompt first.
## Ace note (Ruby scripts, not plugins.js)
Ace has no `plugins.js`. UI / menu strings often live in **Ruby scripts** packed inside `Data/Scripts.rvdata2`.
1. **Unpack** - Step 0 runs **RV2JSON `-c`**, which writes `ace_json/` (JSON data + `ace_json/scripts/*.rb`).
2. **Edit like plugins.js** - In Step 5 the **Plugins** row becomes **Scripts**. Copy vocab → game, copy the Ace scripts prompt, open `ace_json/scripts/` in Cursor/VS Code, and translate only player-visible string literals (same safety rules as plugins.js: never keys, method names, or logic comparisons).
3. **Pack** - Click **Export Active** or **Export ALL**. The tool writes translated JSON into `ace_json/`, then runs **RV2JSON `-u`**, which packs `ace_json/` (including the edited `.rb` scripts) back into `Data/*.rvdata2` (including `Scripts.rvdata2`).
Dialogue/DB files still go through the normal translate pipeline; script UI text is an IDE pass on the `.rb` files, then Export packs them.

View file

@ -0,0 +1,43 @@
# Workflow: WolfDawn
Open **Workflow** and select **Wolf RPG (WolfDawn)**.
The bundled `wolf` CLI unpacks archives, extracts strings to JSON, translates with the same AI pipeline, then injects byte-exact results.
## Steps at a glance
| Step | What it does |
|------|----------------|
| **0 Project** | Select game root, unpack/extract to `wolf_json/`, import into `files/` |
| **1 Pre-process** | Optional format + copy `gameupdate/` |
| **2 Glossary** | Build vocab with your IDE agent (not item/skill names from `names.json`) |
| **3 Names** | Translate safe `names.json` entries (Phase 0) |
| **4 Database** | Foundation DB then narrative DB sheets |
| **5 Maps/Events** | Maps, CommonEvent, Game.dat, Evtext |
| **6 Precheck** | Safety dry-run before writing binaries |
| **7 Inject** | Inject all translated JSON |
| **8 Package** | Loose Data/ or repack `Data.wolf`, optional save rewrite |
| **9 Fix wrap** | Find overflowing lines, wrap, reinject |
## Example: first WolfDawn run
1. Step 0 - point at the folder with `Game.exe` / `Data.wolf`.
2. Let extract finish; import the listed JSON (or leave the step with files checked).
3. Step 2 - copy the Wolf glossary prompt into Cursor with extracted files available; paste vocab back.
4. Step 3 - translate **safe** names only.
5. Step 4 - read the discovery summary. For a classic RPG layout: foundation DB → maps/events.
6. Step 68 - Precheck → Inject all → Package → playtest.
## Layout order
After extract, the Database discovery report classifies the game:
| Layout | Order |
|--------|--------|
| **DB-heavy** | Names → foundation DB → narrative DB → maps/events |
| **Classic RPG** | Names → foundation DB → maps/events |
| **Hybrid** | Names → foundation DB → narrative DB → maps/events |
## Safety badges on names
- `safe` - Phase 0 will translate
- `refs` / `verify` - stay Japanese so inject skips them until you handle them carefully

View file

@ -0,0 +1,50 @@
# Other Tabs
Workflow is the guided path.
These tabs cover everything else.
## Translation
Manual control: pick a module, check files in `files/`, run Normal or Batch translate.
Workflow jumps here when you start a phase.
**When to use it directly**
- Re-running one stubborn file
- Engines without a full Workflow (Ren'Py, Tyrano, CSV, …)
- Resuming a batch from the Batches tab
**Example:** check only `Map003.json`, module = RPG Maker MV/MZ, click Translate, then export from Workflow Step 5 (or copy from `translated/` yourself).
## Batches
History for Anthropic Message Batch jobs.
Resume a batch back onto the Translation tab without submitting a new one.
**Example:** a long CommonEvents batch finished overnight - open Batches, pick the entry, resume consume / validation from here.
## Skills
Edit tool-level markdown:
- `data/skills/system.md` - base translation system prompt
- `data/skills/project_setup.md` - clipboard skill for IDE setup
- `data/translation_contexts.json` - per-call history templates
Per-game quirks / Translation Frame live under `<game>/skills/` and are edited in Workflow Step 2, not here.
## Configuration
API keys, wordwrap widths, and engine toggles (which RPG Maker codes to translate, Wolf options, CSV delimiters, SRPG, …).
**Example:** if dialogue overflows in MV, set wrap width near `60` and re-run wrap / re-translate the overflowing maps.
## Folder cheat sheet
| Path | Role |
|------|------|
| `files/` | Input JSON / text for the current run |
| `translated/` | Output from the translator |
| `log/` | Run logs and caches |
| `data/help/` | This Guide's markdown (edit to update) |
| `data/skills/` | Tool-level skills |

94
data/help/06-examples.md Normal file
View file

@ -0,0 +1,94 @@
# Example walkthrough
This page is a concrete story of translating one game, step by step.
Use it to see the *order* of work. Details for each Workflow step live in the other Guide sections and in each step's **?** button.
Assume: Claude Sonnet 4.6 for paid quality + Batch, or Mistral if you are on the free tier.
Also assume Cursor (or VS Code) has the **game folder** open for glossary / UI script work.
---
## Example A - First RPG Maker MV/MZ game
You just downloaded a small MV game. You want English dialogue, menus that make sense, and a playable build - without boiling the ocean on day one.
### 1. Get the tool ready
1. Launch DazedMTLTool (opens on this Guide).
2. Open **Configuration**, set your API key and model, save.
3. Open the game folder in Cursor so the agent can read JSON later.
### 2. Point Workflow at the game
1. Open **Workflow** → engine **RPG Maker**.
2. Step 0 - browse to the folder with `Game.exe` / `www/` (MV) or `data/` (MZ).
3. When the file list appears, start small: check core DB files (`Actors`, `Items`, `System`, …) plus **one** early map (`Map001.json`). Import those into `files/`.
You are not importing the whole game yet. A tiny slice proves the pipeline before you burn tokens on every map.
### 3. Set up names and voice (Step 2)
1. Run **Parse Speakers** if the game uses nameplates the tool can harvest.
2. **Copy Project Setup**, paste into Cursor with the game open, let the agent return glossary / speakers / quirks / game skill.
3. Paste each block into the Workflow editors and save.
Now the translation API has character genders, terms, and a short Translation Frame instead of guessing blind.
### 4. Translate a slice (Step 3)
1. Prefer **Normal** mode while you are learning.
2. Run **Phase 0** (database names/descriptions).
3. Run **Phase 1** on the checked map only.
4. Skim `translated/` and `log/` - do names look consistent? Does dialogue sound okay?
If something is wrong, fix vocab / quirks *now* before you translate fifty maps.
### 5. Put it in the game and play
1. Step 5 - **Export Active Files** (only what you imported).
2. Launch the game and walk that first map.
3. Note leftovers: overflow, wrong names, Japanese in a menu.
Fix leftovers next (re-translate a file, tweak wrap width in Config, or OCR → search in the IDE). Do not jump to Phase 2 yet.
### 6. Scale up
When the sample map looks good:
1. Step 0 - import the rest of the maps / CommonEvents (or the next chunk).
2. Step 3 Phase 1 again - use **Batch** with Claude if the set is large; stay on **Normal** with Mistral.
3. Export again and play further.
### 7. Risky codes and UI (only when needed)
1. Step 4 - copy the Plugin / risky-codes prompt, audit the game in your IDE, enable only codes that hold player-visible text, then run Phase 2 carefully.
2. Step 5 - for MV/MZ, copy vocab + the plugins.js prompt and edit `plugins.js` in the IDE (player-facing strings only). For **Ace**, edit `ace_json/scripts/*.rb` the same way, then Export so **RV2JSON** packs `Scripts.rvdata2`.
3. Step 6 (MV/MZ) - install TL Inspector / Forge if you want in-game inspection helpers.
### What “done enough” looks like
You can play through without walls of Japanese, menus are readable, and you know how to OCR → search → re-export when something slips through. Perfect first pass is rare; the loop is translate → play → fix → expand.
---
## Example B - Same idea on WolfDawn (short)
1. Workflow → **Wolf RPG (WolfDawn)** → Step 0 extract + import.
2. Step 2 - build vocab in Cursor from the extracted files (skip `names.json` value names).
3. Step 3 - translate **safe** names only.
4. Step 4 - foundation DB first; add narrative DB if the discovery report says the game is DB-heavy.
5. Step 5 - maps / events (Batch for huge CommonEvent files on Claude).
6. Step 6 Precheck → Step 7 **Inject all** → Step 8 Package → play.
7. Step 9 - fix wrap on overflowing lines → Inject → Package again.
Same philosophy as RPG Maker: prove quality on a small slice of content, then scale; pack/inject before you judge overflow in-game.
---
## Habits that save time
- **Git** the game folder before big Export / Inject passes so you can roll back.
- Start with **one map** (or one DB sheet), not the whole project.
- Fix glossary and speaker flags early - they multiply across every later file.
- Use Batch for bulk overnight work; Normal for quick iteration.
- Leftover Japanese: screenshot → OCR → search in the IDE → re-translate or hand-edit → export/inject again.

View file

@ -452,9 +452,10 @@ class BatchTab(QWidget):
file_set = entry.get("file_set") or []
if file_set and hasattr(tt, "select_files_by_name"):
tt.select_files_by_name(file_set)
# Switch to Translation page (index 0).
# Switch to Translation page.
if hasattr(parent, "switch_page"):
parent.switch_page(0)
page = getattr(parent, "PAGE_TRANSLATION", 2)
parent.switch_page(page)
reply = QMessageBox.question(
self,
"Start Resume?",

View file

@ -30,10 +30,10 @@ class ModelFetchThread(QThread):
DEFAULTS = [
"gpt-4.1-mini", "gpt-4.1", "gpt-4o", "gpt-4o-mini",
"o3", "o4-mini",
"claude-opus-4-5", "claude-sonnet-4-5", "claude-haiku-4-5",
"claude-opus-4-5", "claude-sonnet-4-6", "claude-sonnet-4-5", "claude-haiku-4-5",
"gemini-2.0-flash", "gemini-2.5-flash", "gemini-2.5-pro",
"deepseek-chat",
"mistral-medium-3.5", # best quality/cost Mistral for translation (don't use -latest, it points at the older 3.1)
"mistral-medium-3.5", # free-tier recommendation; avoid -latest (older 3.1)
]
def __init__(self, api_key, api_url, parent=None):

320
gui/guide_tab.py Normal file
View file

@ -0,0 +1,320 @@
"""Guide / Quickstart tab - markdown-driven in-app documentation."""
from __future__ import annotations
import json
from pathlib import Path
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QHBoxLayout,
QLabel,
QListWidget,
QListWidgetItem,
QPushButton,
QTextBrowser,
QVBoxLayout,
QWidget,
)
from util.paths import HELP_DIR
try:
import markdown as _markdown
except ImportError: # pragma: no cover - dependency should be installed via requirements
_markdown = None
_BROWSER_CSS = """
body {
color: #d4d4d4;
background-color: #1e1e1e;
font-family: Segoe UI, sans-serif;
font-size: 14px;
line-height: 1.45;
margin: 12px 16px;
}
h1, h2, h3, h4 {
color: #ffffff;
font-weight: 600;
margin-top: 1.1em;
margin-bottom: 0.4em;
}
h1 { font-size: 22px; }
h2 { font-size: 18px; border-bottom: 1px solid #3c3c3c; padding-bottom: 4px; }
h3 { font-size: 15px; color: #e0e0e0; }
p, li { color: #d4d4d4; }
a { color: #569cd6; }
code {
background-color: #2d2d30;
color: #ce9178;
padding: 1px 5px;
border-radius: 3px;
font-family: Consolas, monospace;
font-size: 13px;
}
pre {
background-color: #252526;
border: 1px solid #3c3c3c;
border-radius: 4px;
padding: 10px 12px;
overflow-x: auto;
}
pre code {
background: transparent;
color: #d4d4d4;
padding: 0;
}
table {
border-collapse: collapse;
margin: 10px 0;
width: 100%;
}
th, td {
border: 1px solid #3c3c3c;
padding: 6px 10px;
text-align: left;
vertical-align: top;
}
th { background-color: #2d2d30; color: #ffffff; }
strong { color: #ffffff; }
hr { border: none; border-top: 1px solid #3c3c3c; margin: 16px 0; }
blockquote {
border-left: 3px solid #007acc;
margin: 10px 0;
padding: 4px 12px;
color: #9d9d9d;
background-color: #252526;
}
"""
_LIST_STYLE = """
QListWidget {
background-color: #252526;
color: #cccccc;
border: 1px solid #3c3c3c;
border-radius: 4px;
outline: none;
font-size: 13px;
padding: 4px;
}
QListWidget::item {
padding: 8px 10px;
border-radius: 3px;
}
QListWidget::item:selected {
background-color: #094771;
color: #ffffff;
}
QListWidget::item:hover:!selected {
background-color: #2a2d2e;
}
"""
_BROWSER_STYLE = """
QTextBrowser {
background-color: #1e1e1e;
color: #d4d4d4;
border: 1px solid #3c3c3c;
border-radius: 4px;
padding: 0;
}
"""
_BTN_STYLE = """
QPushButton {
background-color: #0e639c;
color: #ffffff;
border: none;
border-radius: 4px;
padding: 8px 14px;
font-size: 13px;
}
QPushButton:hover { background-color: #1177bb; }
QPushButton:pressed { background-color: #0a4d78; }
"""
_SECONDARY_BTN_STYLE = """
QPushButton {
background-color: #3c3c3c;
color: #d4d4d4;
border: 1px solid #555555;
border-radius: 4px;
padding: 8px 14px;
font-size: 13px;
}
QPushButton:hover { background-color: #4a4a4a; }
"""
def _md_to_html(source: str) -> str:
if _markdown is not None:
body = _markdown.markdown(
source,
extensions=["fenced_code", "tables", "nl2br", "sane_lists"],
)
else:
escaped = (
source.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
)
body = f"<pre style='white-space:pre-wrap;font-family:Segoe UI,sans-serif;'>{escaped}</pre>"
return (
"<html><head><meta charset='utf-8'>"
f"<style>{_BROWSER_CSS}</style></head>"
f"<body>{body}</body></html>"
)
class GuideTab(QWidget):
"""Sidebar Guide page: section list + rendered markdown from data/help/."""
def __init__(self, parent=None, help_dir: Path | None = None):
super().__init__(parent)
self.parent_window = parent
self.help_dir = Path(help_dir) if help_dir else HELP_DIR
self._sections: list[dict] = []
self._build_ui()
self.reload()
def _build_ui(self) -> None:
root = QVBoxLayout(self)
root.setContentsMargins(20, 16, 20, 16)
root.setSpacing(10)
title = QLabel("Guide / Quickstart")
title.setStyleSheet("color:#ffffff;font-size:18px;font-weight:bold;")
root.addWidget(title)
intro = QLabel(
"In-app docs for requirements, Workflow, and copy-paste examples. "
"Edit markdown under data/help/ to keep this up to date."
)
intro.setWordWrap(True)
intro.setStyleSheet("color:#9d9d9d;font-size:13px;")
root.addWidget(intro)
body = QHBoxLayout()
body.setSpacing(12)
self.section_list = QListWidget()
self.section_list.setFixedWidth(200)
self.section_list.setStyleSheet(_LIST_STYLE)
self.section_list.currentRowChanged.connect(self._on_section_changed)
body.addWidget(self.section_list)
self.browser = QTextBrowser()
self.browser.setOpenExternalLinks(True)
self.browser.setStyleSheet(_BROWSER_STYLE)
body.addWidget(self.browser, 1)
root.addLayout(body, 1)
footer = QHBoxLayout()
footer.setSpacing(8)
self.btn_workflow = QPushButton("Open Workflow")
self.btn_workflow.setStyleSheet(_BTN_STYLE)
self.btn_workflow.setCursor(Qt.PointingHandCursor)
self.btn_workflow.clicked.connect(self._open_workflow)
footer.addWidget(self.btn_workflow)
self.btn_config = QPushButton("Open Configuration")
self.btn_config.setStyleSheet(_SECONDARY_BTN_STYLE)
self.btn_config.setCursor(Qt.PointingHandCursor)
self.btn_config.clicked.connect(self._open_config)
footer.addWidget(self.btn_config)
footer.addStretch()
reload_btn = QPushButton("Reload")
reload_btn.setFixedWidth(90)
reload_btn.setStyleSheet(_SECONDARY_BTN_STYLE)
reload_btn.setToolTip("Reload data/help/index.json and the current section")
reload_btn.clicked.connect(self.reload)
footer.addWidget(reload_btn)
root.addLayout(footer)
def reload(self) -> None:
"""Reload the section index and re-select the current (or first) section."""
previous_id = None
row = self.section_list.currentRow()
if 0 <= row < len(self._sections):
previous_id = self._sections[row].get("id")
self._sections = self._load_index()
self.section_list.blockSignals(True)
self.section_list.clear()
select_row = 0
for i, section in enumerate(self._sections):
title = section.get("title") or section.get("id") or f"Section {i + 1}"
item = QListWidgetItem(title)
item.setData(Qt.UserRole, section)
self.section_list.addItem(item)
if previous_id and section.get("id") == previous_id:
select_row = i
self.section_list.blockSignals(False)
if self._sections:
self.section_list.setCurrentRow(select_row)
else:
self.browser.setHtml(
_md_to_html(
"# Guide unavailable\n\n"
f"No sections found under `{self.help_dir}`.\n"
"Add `index.json` and markdown files to restore this page."
)
)
def _load_index(self) -> list[dict]:
index_path = self.help_dir / "index.json"
if not index_path.is_file():
return []
try:
data = json.loads(index_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return []
if not isinstance(data, list):
return []
return [s for s in data if isinstance(s, dict) and s.get("file")]
def _on_section_changed(self, row: int) -> None:
if row < 0 or row >= len(self._sections):
return
section = self._sections[row]
rel = section.get("file", "")
path = self.help_dir / rel
if not path.is_file():
self.browser.setHtml(
_md_to_html(f"# Missing file\n\nCould not find `{rel}` under `data/help/`.")
)
return
try:
source = path.read_text(encoding="utf-8")
except OSError as exc:
self.browser.setHtml(_md_to_html(f"# Read error\n\n{exc}"))
return
self.browser.setHtml(_md_to_html(source))
self.browser.verticalScrollBar().setValue(0)
def show_section(self, section_id: str) -> bool:
"""Select a section by id. Returns True if found."""
for i, section in enumerate(self._sections):
if section.get("id") == section_id:
self.section_list.setCurrentRow(i)
return True
return False
def _open_workflow(self) -> None:
pw = self.parent_window
if pw is not None and hasattr(pw, "switch_page"):
page = getattr(pw, "PAGE_WORKFLOW", 1)
pw.switch_page(page)
def _open_config(self) -> None:
pw = self.parent_window
if pw is not None and hasattr(pw, "switch_page"):
page = getattr(pw, "PAGE_CONFIG", 5)
pw.switch_page(page)

View file

@ -612,6 +612,7 @@ class UpdateDialog(QDialog):
# Import configuration widgets
from gui.config_tab import ConfigTab
from gui.guide_tab import GuideTab
from gui.translation_tab import TranslationTab
from gui.workflow_tab import WorkflowTab
from gui.wolf_workflow_tab import WolfWorkflowTab
@ -621,6 +622,13 @@ from gui.batch_tab import BatchTab
class DazedMTLGUI(QMainWindow):
"""Main GUI window for the DazedMTLTool."""
PAGE_GUIDE = 0
PAGE_WORKFLOW = 1
PAGE_TRANSLATION = 2
PAGE_BATCHES = 3
PAGE_SKILLS = 4
PAGE_CONFIG = 5
def __init__(self):
super().__init__()
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
@ -839,36 +847,43 @@ class DazedMTLGUI(QMainWindow):
# Create navigation buttons
self.nav_buttons = []
# Translation button (first)
btn_translation = self.create_nav_button("🌐", "Translation")
btn_translation.clicked.connect(lambda: self.switch_page(0))
sidebar_layout.addWidget(btn_translation)
self.nav_buttons.append(btn_translation)
# Guide / Quickstart (first - default on open)
btn_guide = self.create_nav_button("📖", "Guide")
btn_guide.setToolTip("Guide - quickstart, requirements, and examples")
btn_guide.clicked.connect(lambda: self.switch_page(self.PAGE_GUIDE))
sidebar_layout.addWidget(btn_guide)
self.nav_buttons.append(btn_guide)
# Workflow / Automation button (second)
# Workflow / Automation
btn_workflow = self.create_nav_button("", "Workflow")
btn_workflow.setToolTip("Workflow automated translation pipeline")
btn_workflow.clicked.connect(lambda: self.switch_page(1))
btn_workflow.setToolTip("Workflow - automated translation pipeline")
btn_workflow.clicked.connect(lambda: self.switch_page(self.PAGE_WORKFLOW))
sidebar_layout.addWidget(btn_workflow)
self.nav_buttons.append(btn_workflow)
# Batch history button (third)
# Translation
btn_translation = self.create_nav_button("🌐", "Translation")
btn_translation.clicked.connect(lambda: self.switch_page(self.PAGE_TRANSLATION))
sidebar_layout.addWidget(btn_translation)
self.nav_buttons.append(btn_translation)
# Batch history
btn_batches = self.create_nav_button("📦", "Batches")
btn_batches.setToolTip("Batches — Anthropic Message Batch history")
btn_batches.clicked.connect(lambda: self.switch_page(2))
btn_batches.setToolTip("Batches - Anthropic Message Batch history")
btn_batches.clicked.connect(lambda: self.switch_page(self.PAGE_BATCHES))
sidebar_layout.addWidget(btn_batches)
self.nav_buttons.append(btn_batches)
# Skills button (fourth)
# Skills
btn_skills = self.create_nav_button("📚", "Skills")
btn_skills.setToolTip("Skills edit system prompt, Project Setup, and translation contexts")
btn_skills.clicked.connect(lambda: self.switch_page(3))
btn_skills.setToolTip("Skills - edit system prompt, Project Setup, and translation contexts")
btn_skills.clicked.connect(lambda: self.switch_page(self.PAGE_SKILLS))
sidebar_layout.addWidget(btn_skills)
self.nav_buttons.append(btn_skills)
# Configuration button (fifth)
# Configuration
btn_config = self.create_nav_button("⚙️", "Configuration")
btn_config.clicked.connect(lambda: self.switch_page(4))
btn_config.clicked.connect(lambda: self.switch_page(self.PAGE_CONFIG))
sidebar_layout.addWidget(btn_config)
self.nav_buttons.append(btn_config)
@ -896,8 +911,8 @@ class DazedMTLGUI(QMainWindow):
# Create menu bar
self.create_menu_bar()
# Select first page by default
self.switch_page(0)
# Select Guide page by default
self.switch_page(self.PAGE_GUIDE)
def create_nav_button(self, icon_text, tooltip):
"""Create a navigation button for the sidebar."""
@ -922,23 +937,27 @@ class DazedMTLGUI(QMainWindow):
"""Set up all the tabs in the interface."""
self.project_root = PROJECT_ROOT
# Translation Execution Tab (index 0)
self.translation_tab = TranslationTab(self)
self.content_stack.addWidget(self.translation_tab)
# Guide / Quickstart (index 0)
self.guide_tab = GuideTab(self)
self.content_stack.addWidget(self.guide_tab)
# Workflow / Automation Tab (index 1) — engine selector swaps between the
# Workflow / Automation (index 1) - engine selector swaps between the
# RPGMaker and Wolf guided panels while keeping a single sidebar button.
self.content_stack.addWidget(self._create_workflow_container())
# Batch History Tab (index 2)
# Translation Execution Tab (index 2)
self.translation_tab = TranslationTab(self)
self.content_stack.addWidget(self.translation_tab)
# Batch History Tab (index 3)
self.batch_tab = BatchTab(self)
self.content_stack.addWidget(self.batch_tab)
# Skills Tab (index 3)
# Skills Tab (index 4)
self.skills_tab = SkillsTab(self)
self.content_stack.addWidget(self.skills_tab)
# Configuration Tab (index 4)
# Configuration Tab (index 5)
self.config_tab = ConfigTab()
self.config_tab.config_changed.connect(self.on_config_changed)
self.content_stack.addWidget(self.config_tab)
@ -1118,6 +1137,13 @@ class DazedMTLGUI(QMainWindow):
# Help menu
help_menu = menubar.addMenu('Help')
getting_started_action = help_menu.addAction('Getting Started')
getting_started_action.triggered.connect(
lambda: self.switch_page(self.PAGE_GUIDE)
)
help_menu.addSeparator()
# About action
about_action = help_menu.addAction('About')
about_action.triggered.connect(self.show_about)

View file

@ -2320,11 +2320,14 @@ class WolfWorkflowTab(QWidget):
except Exception:
pass
if pw and hasattr(pw, "content_stack"):
pw.content_stack.setCurrentIndex(0)
if pw and hasattr(pw, "switch_page"):
page = getattr(pw, "PAGE_TRANSLATION", 2)
pw.switch_page(page)
elif pw and hasattr(pw, "content_stack"):
pw.content_stack.setCurrentIndex(2)
if hasattr(pw, "nav_buttons"):
for i, btn in enumerate(pw.nav_buttons):
btn.setChecked(i == 0)
btn.setChecked(i == 2)
if auto_start:
QTimer.singleShot(

View file

@ -469,13 +469,17 @@ _STEP_HELP: dict[int, str] = {
"translating IDs used as logic keys will break the game."
),
5: (
"<b>Step 5 - Plugins & Export</b><br><br>"
"<b>Plugins</b> (MV/MZ; optional before shipping): copy <code>vocab.txt</code> into "
"<b>Step 5 - Plugins / Scripts & Export</b><br><br>"
"<b>MV/MZ - Plugins</b> (optional before shipping): copy <code>vocab.txt</code> into "
"the game folder, then copy the plugins.js prompt and run it in your IDE with "
"<code>plugins.js</code> attached. Only translate player-visible UI strings - never "
"plugin parameter keys or internal identifiers.<br>"
"Ace projects are converted to JSON and translated like MV/MZ - skip this if there is "
"no <code>plugins.js</code>.<br><br>"
"plugin parameter keys or internal identifiers.<br><br>"
"<b>Ace - Ruby scripts</b> (instead of plugins.js): RV2JSON unpacks "
"<code>Data/Scripts.rvdata2</code> into <code>ace_json/scripts/*.rb</code>. "
"Copy vocab + the Ace scripts prompt, edit those <code>.rb</code> files in Cursor/VS Code "
"the same way you would edit plugins.js (player-visible strings only). "
"Then use <b>Export</b> - the tool runs <code>RV2JSON -u</code> and packs "
"<code>ace_json/</code> (including scripts) back into <code>Data/*.rvdata2</code>.<br><br>"
"<b>Export</b> - copy finished translations from <code>translated/</code> back "
"into the game's data folder:<br>"
"• <b>Export Active Files</b> - only names currently in <code>files/</code><br>"
@ -1262,6 +1266,81 @@ class WorkflowTab(QWidget):
"</output_format>\n"
)
_ACE_SCRIPTS_TRANSLATE_PROMPT = (
"You are an expert RPGMaker VX Ace (Ruby) localisation engineer.\n"
"\n"
"<task>\n"
"Translate visible Japanese strings inside the game's Ruby scripts (ace_json/scripts/*.rb) "
"without breaking any game logic or script functionality. A vocab.txt glossary is attached - "
"use it as your primary reference. Any name or term in the glossary must be translated exactly as shown.\n"
"</task>\n"
"\n"
"--- attach the .rb script files and vocab.txt here before continuing ---\n"
"\n"
"\n"
"========================================\n"
"## WHAT TO TRANSLATE\n"
"========================================\n"
"\n"
"Only translate string literals that are directly shown to the player at runtime.\n"
"These typically appear as:\n"
" - Strings passed to msgbox, msgbox_p, print, p\n"
" - Labels and text in Window or Scene classes rendered to screen\n"
" - draw_text / draw_item calls with a Japanese string literal\n"
" - Default UI label text (menu names, button labels, status window text)\n"
" - Battle log messages, notifications, popup strings\n"
" - Help or description text shown in help windows\n"
"\n"
"========================================\n"
"## WHAT MUST NOT BE TRANSLATED\n"
"========================================\n"
"\n"
"CRITICAL - translating the following will break the game:\n"
"\n"
" 1. Strings used as hash keys, method names, or symbol equivalents.\n"
" Example: vocab[\"HP\"] = \"体力\" -> translate \"体力\" but NOT the key \"HP\"\n"
"\n"
" 2. Strings used as internal identifiers compared with == or used in case/when:\n"
" - Actor/class/skill/item names used as lookup strings\n"
" - Script-internal state names or flag strings\n"
" Example: if type == \"スキル\" -> do NOT translate \"スキル\"\n"
"\n"
" 3. File paths, filenames, font names, colour strings, URLs.\n"
"\n"
" 4. Regular expressions, format strings used with sprintf or % operator\n"
" where the placeholders must stay in the same position.\n"
" (You may translate the human-readable parts but keep %s / %d / %1 etc intact.)\n"
"\n"
" 5. Script class names, module names, method names, constants.\n"
"\n"
" 6. Any string that is read back elsewhere in the scripts with an exact match.\n"
"\n"
"<safety_check>\n"
"Before translating any string, confirm all three are true:\n"
"- It is displayed directly to the player as visible text\n"
"- It is purely a display string, not compared or looked up anywhere\n"
"- Changing it would break no conditional logic or data lookup\n"
"When in doubt, skip it - untranslated Japanese is better than a broken game.\n"
"</safety_check>\n"
"\n"
"<output_format>\n"
"For each .rb file that needed changes, provide the full translated file content inside its "
"own fenced code block (```ruby ... ```), preceded by the filename, so each file can be "
"copied in one click. Only change string values identified as safe. "
"Preserve all Ruby syntax, indentation, comments, and structure exactly.\n"
"\n"
"After all files, output a summary:\n"
"\n"
"### Translations Made\n"
" File: <script filename.rb>\n"
" Before: <original Japanese>\n"
" After: <English translation>\n"
"\n"
"### Skipped (Ambiguous or Internal)\n"
"List any Japanese strings you detected but did not translate, with a one-line reason.\n"
"</output_format>\n"
)
_PLUGIN_PROMPT = (
"You are an expert RPGMaker MV/MZ event system analyst.\n"
"\n"
@ -2323,12 +2402,12 @@ class WorkflowTab(QWidget):
self._step6_section_label = QLabel("Plugins")
vocab_btn = _make_btn("📄 Copy vocab.txt → Game", "#555")
vocab_btn.setToolTip(
self._step6_vocab_btn = _make_btn("📄 Copy vocab.txt → Game", "#555")
self._step6_vocab_btn.setToolTip(
"Copy vocab.txt to <game root>/vocab.txt so you can attach it "
"alongside plugins.js when running the AI prompt."
"alongside plugins.js (or Ace .rb scripts) when running the AI prompt."
)
vocab_btn.clicked.connect(self._copy_vocab_to_game)
self._step6_vocab_btn.clicked.connect(self._copy_vocab_to_game)
self._step6_copy_btn = _make_btn("📋 Copy Prompt for Copilot", "#555")
self._step6_copy_btn.setToolTip(
@ -2338,7 +2417,11 @@ class WorkflowTab(QWidget):
self._step6_copy_btn.clicked.connect(self._copy_plugins_js_translate_prompt)
inner.addLayout(
_labeled_row(self._step6_section_label, vocab_btn, self._step6_copy_btn)
_labeled_row(
self._step6_section_label,
self._step6_vocab_btn,
self._step6_copy_btn,
)
)
export_lbl = QLabel("Export")
@ -3018,14 +3101,43 @@ class WorkflowTab(QWidget):
self._log(f"⚠ Could not remove {err}")
def _update_step6_for_engine(self, is_ace: bool) -> None:
"""Hide MV/MZ-only preprocess + Playtest controls when the project is Ace."""
# Step 1 prettier / plugins.js format only relevant for MV/MZ
"""Adapt plugins/scripts controls for MV/MZ vs Ace; hide Playtest on Ace."""
# Step 1 prettier / plugins.js format - 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)
# Step 6 — TL Inspector (MV/MZ only; hidden for Ace)
# Step 5 plugins subsection title + prompt tooltip
lbl = getattr(self, "_step6_section_label", None)
if lbl is not None:
lbl.setText("Scripts" if is_ace else "Plugins")
vocab_btn = getattr(self, "_step6_vocab_btn", None)
if vocab_btn is not None:
if is_ace:
vocab_btn.setToolTip(
"Copy vocab.txt to <game root>/vocab.txt so you can attach it "
"alongside ace_json/scripts/*.rb when running the AI prompt."
)
else:
vocab_btn.setToolTip(
"Copy vocab.txt to <game root>/vocab.txt so you can attach it "
"alongside plugins.js when running the AI prompt."
)
btn = getattr(self, "_step6_copy_btn", None)
if btn is not None:
if is_ace:
btn.setToolTip(
"Copy a prompt that instructs Copilot/Cursor to translate only "
"visible player-facing strings in the Ace .rb script files "
"(ace_json/scripts/*.rb)."
)
else:
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."
)
# Step 6 - TL Inspector (MV/MZ only; hidden for Ace)
show_playtest = not is_ace
playtest_idx = 6
if hasattr(self, "_step_tabs") and self._step_tabs.count() > playtest_idx:
@ -3620,6 +3732,13 @@ class WorkflowTab(QWidget):
self._log(f"❌ Could not copy vocab.txt: {exc}")
def _copy_plugins_js_translate_prompt(self):
is_ace = bool(
getattr(self, "_ace_rvdata_dir", "") or getattr(self, "_ace_json_dir", "")
)
if is_ace:
QApplication.clipboard().setText(self._ACE_SCRIPTS_TRANSLATE_PROMPT)
self._log("Ace scripts translation prompt copied to clipboard.")
else:
QApplication.clipboard().setText(self._PLUGINS_JS_TRANSLATE_PROMPT)
self._log("plugins.js translation prompt copied to clipboard.")
@ -3889,12 +4008,15 @@ class WorkflowTab(QWidget):
except Exception:
pass
# 5. Navigate
if hasattr(pw, "content_stack"):
pw.content_stack.setCurrentIndex(0)
# 5. Navigate to Translation tab
if hasattr(pw, "switch_page"):
page = getattr(pw, "PAGE_TRANSLATION", 2)
pw.switch_page(page)
elif hasattr(pw, "content_stack"):
pw.content_stack.setCurrentIndex(2)
if hasattr(pw, "nav_buttons"):
for i, btn in enumerate(pw.nav_buttons):
btn.setChecked(i == 0)
btn.setChecked(i == 2)
# 6. Auto-start translation so the user doesn't need an extra click
if auto_start:

View file

@ -10,3 +10,4 @@ pillow>=12.1.0
pefile>=2023.2.7
PyQt5>=5.15.0
qtawesome>=1.3.0
markdown>=3.5.0

View file

@ -25,6 +25,8 @@ class UpdateThreadInstallFilterTests(unittest.TestCase):
"data/translation_contexts.json",
"data/skills/system.md",
"data/skills/project_setup.md",
"data/help/index.json",
"data/help/00-welcome.md",
"data/vocab_base.txt",
"gui/main.py",
"gameupdate/GameUpdate.bat",

View file

@ -9,6 +9,7 @@ DATA_DIR = PROJECT_ROOT / "data"
VOCAB_PATH = DATA_DIR / "vocab.txt"
VOCAB_BASE_PATH = DATA_DIR / "vocab_base.txt"
SKILLS_DIR = DATA_DIR / "skills"
HELP_DIR = DATA_DIR / "help"
# Runtime translation system skill (formerly data/prompt.txt).
PROMPT_PATH = SKILLS_DIR / "system.md"
LEGACY_PROMPT_PATH = DATA_DIR / "prompt.txt"