diff --git a/data/help/00-welcome.md b/data/help/00-welcome.md new file mode 100644 index 0000000..9d26bba --- /dev/null +++ b/data/help/00-welcome.md @@ -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. diff --git a/data/help/01-requirements.md b/data/help/01-requirements.md new file mode 100644 index 0000000..6262fd1 --- /dev/null +++ b/data/help/01-requirements.md @@ -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 diff --git a/data/help/02-quickstart.md b/data/help/02-quickstart.md new file mode 100644 index 0000000..37a6f07 --- /dev/null +++ b/data/help/02-quickstart.md @@ -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. diff --git a/data/help/03-workflow-rpg.md b/data/help/03-workflow-rpg.md new file mode 100644 index 0000000..850ab7b --- /dev/null +++ b/data/help/03-workflow-rpg.md @@ -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. diff --git a/data/help/04-workflow-wolf.md b/data/help/04-workflow-wolf.md new file mode 100644 index 0000000..ccedf98 --- /dev/null +++ b/data/help/04-workflow-wolf.md @@ -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 6–8 - 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 diff --git a/data/help/05-other-tabs.md b/data/help/05-other-tabs.md new file mode 100644 index 0000000..0df3eb2 --- /dev/null +++ b/data/help/05-other-tabs.md @@ -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 `/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 | diff --git a/data/help/06-examples.md b/data/help/06-examples.md new file mode 100644 index 0000000..fb39de5 --- /dev/null +++ b/data/help/06-examples.md @@ -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. diff --git a/gui/batch_tab.py b/gui/batch_tab.py index 44d0681..3032917 100644 --- a/gui/batch_tab.py +++ b/gui/batch_tab.py @@ -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?", diff --git a/gui/config_tab.py b/gui/config_tab.py index 486d5e9..c63efd1 100644 --- a/gui/config_tab.py +++ b/gui/config_tab.py @@ -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): diff --git a/gui/guide_tab.py b/gui/guide_tab.py new file mode 100644 index 0000000..a628e68 --- /dev/null +++ b/gui/guide_tab.py @@ -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("&", "&") + .replace("<", "<") + .replace(">", ">") + ) + body = f"
{escaped}
" + return ( + "" + f"" + f"{body}" + ) + + +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) diff --git a/gui/main.py b/gui/main.py index 6f2fa69..0ac0b60 100644 --- a/gui/main.py +++ b/gui/main.py @@ -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 @@ -620,7 +621,14 @@ 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") @@ -838,37 +846,44 @@ 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) - # Workflow / Automation button (second) + # 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 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) @@ -1117,7 +1136,14 @@ 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) diff --git a/gui/wolf_workflow_tab.py b/gui/wolf_workflow_tab.py index f365686..3287747 100644 --- a/gui/wolf_workflow_tab.py +++ b/gui/wolf_workflow_tab.py @@ -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( diff --git a/gui/workflow_tab.py b/gui/workflow_tab.py index cd3ffaf..d8cba6c 100644 --- a/gui/workflow_tab.py +++ b/gui/workflow_tab.py @@ -469,13 +469,17 @@ _STEP_HELP: dict[int, str] = { "translating IDs used as logic keys will break the game." ), 5: ( - "Step 5 - Plugins & Export

" - "Plugins (MV/MZ; optional before shipping): copy vocab.txt into " + "Step 5 - Plugins / Scripts & Export

" + "MV/MZ - Plugins (optional before shipping): 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 projects are converted to JSON and translated like MV/MZ - skip this if there is " - "no plugins.js.

" + "plugin parameter keys or internal identifiers.

" + "Ace - Ruby scripts (instead of plugins.js): RV2JSON unpacks " + "Data/Scripts.rvdata2 into ace_json/scripts/*.rb. " + "Copy vocab + the Ace scripts prompt, edit those .rb files in Cursor/VS Code " + "the same way you would edit plugins.js (player-visible strings only). " + "Then use Export - the tool runs RV2JSON -u and packs " + "ace_json/ (including scripts) back into Data/*.rvdata2.

" "Export - copy finished translations from translated/ back " "into the game's data folder:
" "• Export Active Files - only names currently in files/
" @@ -1262,6 +1266,81 @@ class WorkflowTab(QWidget): "\n" ) + _ACE_SCRIPTS_TRANSLATE_PROMPT = ( + "You are an expert RPGMaker VX Ace (Ruby) localisation engineer.\n" + "\n" + "\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" + "\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" + "\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" + "\n" + "\n" + "\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: