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:
parent
a1af8fb985
commit
cb90965f30
16 changed files with 939 additions and 54 deletions
29
data/help/00-welcome.md
Normal file
29
data/help/00-welcome.md
Normal 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.
|
||||||
81
data/help/01-requirements.md
Normal file
81
data/help/01-requirements.md
Normal 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
|
||||||
59
data/help/02-quickstart.md
Normal file
59
data/help/02-quickstart.md
Normal 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.
|
||||||
53
data/help/03-workflow-rpg.md
Normal file
53
data/help/03-workflow-rpg.md
Normal 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.
|
||||||
43
data/help/04-workflow-wolf.md
Normal file
43
data/help/04-workflow-wolf.md
Normal 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 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
|
||||||
50
data/help/05-other-tabs.md
Normal file
50
data/help/05-other-tabs.md
Normal 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
94
data/help/06-examples.md
Normal 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.
|
||||||
|
|
@ -452,9 +452,10 @@ class BatchTab(QWidget):
|
||||||
file_set = entry.get("file_set") or []
|
file_set = entry.get("file_set") or []
|
||||||
if file_set and hasattr(tt, "select_files_by_name"):
|
if file_set and hasattr(tt, "select_files_by_name"):
|
||||||
tt.select_files_by_name(file_set)
|
tt.select_files_by_name(file_set)
|
||||||
# Switch to Translation page (index 0).
|
# Switch to Translation page.
|
||||||
if hasattr(parent, "switch_page"):
|
if hasattr(parent, "switch_page"):
|
||||||
parent.switch_page(0)
|
page = getattr(parent, "PAGE_TRANSLATION", 2)
|
||||||
|
parent.switch_page(page)
|
||||||
reply = QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
self,
|
self,
|
||||||
"Start Resume?",
|
"Start Resume?",
|
||||||
|
|
|
||||||
|
|
@ -30,10 +30,10 @@ class ModelFetchThread(QThread):
|
||||||
DEFAULTS = [
|
DEFAULTS = [
|
||||||
"gpt-4.1-mini", "gpt-4.1", "gpt-4o", "gpt-4o-mini",
|
"gpt-4.1-mini", "gpt-4.1", "gpt-4o", "gpt-4o-mini",
|
||||||
"o3", "o4-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",
|
"gemini-2.0-flash", "gemini-2.5-flash", "gemini-2.5-pro",
|
||||||
"deepseek-chat",
|
"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):
|
def __init__(self, api_key, api_url, parent=None):
|
||||||
|
|
|
||||||
320
gui/guide_tab.py
Normal file
320
gui/guide_tab.py
Normal 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("&", "&")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
)
|
||||||
|
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)
|
||||||
76
gui/main.py
76
gui/main.py
|
|
@ -612,6 +612,7 @@ class UpdateDialog(QDialog):
|
||||||
|
|
||||||
# Import configuration widgets
|
# Import configuration widgets
|
||||||
from gui.config_tab import ConfigTab
|
from gui.config_tab import ConfigTab
|
||||||
|
from gui.guide_tab import GuideTab
|
||||||
from gui.translation_tab import TranslationTab
|
from gui.translation_tab import TranslationTab
|
||||||
from gui.workflow_tab import WorkflowTab
|
from gui.workflow_tab import WorkflowTab
|
||||||
from gui.wolf_workflow_tab import WolfWorkflowTab
|
from gui.wolf_workflow_tab import WolfWorkflowTab
|
||||||
|
|
@ -621,6 +622,13 @@ from gui.batch_tab import BatchTab
|
||||||
class DazedMTLGUI(QMainWindow):
|
class DazedMTLGUI(QMainWindow):
|
||||||
"""Main GUI window for the DazedMTLTool."""
|
"""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):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
|
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
|
||||||
|
|
@ -839,36 +847,43 @@ class DazedMTLGUI(QMainWindow):
|
||||||
# Create navigation buttons
|
# Create navigation buttons
|
||||||
self.nav_buttons = []
|
self.nav_buttons = []
|
||||||
|
|
||||||
# Translation button (first)
|
# Guide / Quickstart (first - default on open)
|
||||||
btn_translation = self.create_nav_button("🌐", "Translation")
|
btn_guide = self.create_nav_button("📖", "Guide")
|
||||||
btn_translation.clicked.connect(lambda: self.switch_page(0))
|
btn_guide.setToolTip("Guide - quickstart, requirements, and examples")
|
||||||
sidebar_layout.addWidget(btn_translation)
|
btn_guide.clicked.connect(lambda: self.switch_page(self.PAGE_GUIDE))
|
||||||
self.nav_buttons.append(btn_translation)
|
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 = self.create_nav_button("⚡", "Workflow")
|
||||||
btn_workflow.setToolTip("Workflow — automated translation pipeline")
|
btn_workflow.setToolTip("Workflow - automated translation pipeline")
|
||||||
btn_workflow.clicked.connect(lambda: self.switch_page(1))
|
btn_workflow.clicked.connect(lambda: self.switch_page(self.PAGE_WORKFLOW))
|
||||||
sidebar_layout.addWidget(btn_workflow)
|
sidebar_layout.addWidget(btn_workflow)
|
||||||
self.nav_buttons.append(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 = self.create_nav_button("📦", "Batches")
|
||||||
btn_batches.setToolTip("Batches — Anthropic Message Batch history")
|
btn_batches.setToolTip("Batches - Anthropic Message Batch history")
|
||||||
btn_batches.clicked.connect(lambda: self.switch_page(2))
|
btn_batches.clicked.connect(lambda: self.switch_page(self.PAGE_BATCHES))
|
||||||
sidebar_layout.addWidget(btn_batches)
|
sidebar_layout.addWidget(btn_batches)
|
||||||
self.nav_buttons.append(btn_batches)
|
self.nav_buttons.append(btn_batches)
|
||||||
|
|
||||||
# Skills button (fourth)
|
# Skills
|
||||||
btn_skills = self.create_nav_button("📚", "Skills")
|
btn_skills = self.create_nav_button("📚", "Skills")
|
||||||
btn_skills.setToolTip("Skills — edit system prompt, Project Setup, and translation contexts")
|
btn_skills.setToolTip("Skills - edit system prompt, Project Setup, and translation contexts")
|
||||||
btn_skills.clicked.connect(lambda: self.switch_page(3))
|
btn_skills.clicked.connect(lambda: self.switch_page(self.PAGE_SKILLS))
|
||||||
sidebar_layout.addWidget(btn_skills)
|
sidebar_layout.addWidget(btn_skills)
|
||||||
self.nav_buttons.append(btn_skills)
|
self.nav_buttons.append(btn_skills)
|
||||||
|
|
||||||
# Configuration button (fifth)
|
# Configuration
|
||||||
btn_config = self.create_nav_button("⚙️", "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)
|
sidebar_layout.addWidget(btn_config)
|
||||||
self.nav_buttons.append(btn_config)
|
self.nav_buttons.append(btn_config)
|
||||||
|
|
||||||
|
|
@ -896,8 +911,8 @@ class DazedMTLGUI(QMainWindow):
|
||||||
# Create menu bar
|
# Create menu bar
|
||||||
self.create_menu_bar()
|
self.create_menu_bar()
|
||||||
|
|
||||||
# Select first page by default
|
# Select Guide page by default
|
||||||
self.switch_page(0)
|
self.switch_page(self.PAGE_GUIDE)
|
||||||
|
|
||||||
def create_nav_button(self, icon_text, tooltip):
|
def create_nav_button(self, icon_text, tooltip):
|
||||||
"""Create a navigation button for the sidebar."""
|
"""Create a navigation button for the sidebar."""
|
||||||
|
|
@ -922,23 +937,27 @@ class DazedMTLGUI(QMainWindow):
|
||||||
"""Set up all the tabs in the interface."""
|
"""Set up all the tabs in the interface."""
|
||||||
self.project_root = PROJECT_ROOT
|
self.project_root = PROJECT_ROOT
|
||||||
|
|
||||||
# Translation Execution Tab (index 0)
|
# Guide / Quickstart (index 0)
|
||||||
self.translation_tab = TranslationTab(self)
|
self.guide_tab = GuideTab(self)
|
||||||
self.content_stack.addWidget(self.translation_tab)
|
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.
|
# RPGMaker and Wolf guided panels while keeping a single sidebar button.
|
||||||
self.content_stack.addWidget(self._create_workflow_container())
|
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.batch_tab = BatchTab(self)
|
||||||
self.content_stack.addWidget(self.batch_tab)
|
self.content_stack.addWidget(self.batch_tab)
|
||||||
|
|
||||||
# Skills Tab (index 3)
|
# Skills Tab (index 4)
|
||||||
self.skills_tab = SkillsTab(self)
|
self.skills_tab = SkillsTab(self)
|
||||||
self.content_stack.addWidget(self.skills_tab)
|
self.content_stack.addWidget(self.skills_tab)
|
||||||
|
|
||||||
# Configuration Tab (index 4)
|
# Configuration Tab (index 5)
|
||||||
self.config_tab = ConfigTab()
|
self.config_tab = ConfigTab()
|
||||||
self.config_tab.config_changed.connect(self.on_config_changed)
|
self.config_tab.config_changed.connect(self.on_config_changed)
|
||||||
self.content_stack.addWidget(self.config_tab)
|
self.content_stack.addWidget(self.config_tab)
|
||||||
|
|
@ -1118,6 +1137,13 @@ class DazedMTLGUI(QMainWindow):
|
||||||
# Help menu
|
# Help menu
|
||||||
help_menu = menubar.addMenu('Help')
|
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
|
||||||
about_action = help_menu.addAction('About')
|
about_action = help_menu.addAction('About')
|
||||||
about_action.triggered.connect(self.show_about)
|
about_action.triggered.connect(self.show_about)
|
||||||
|
|
|
||||||
|
|
@ -2320,11 +2320,14 @@ class WolfWorkflowTab(QWidget):
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if pw and hasattr(pw, "content_stack"):
|
if pw and hasattr(pw, "switch_page"):
|
||||||
pw.content_stack.setCurrentIndex(0)
|
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"):
|
if hasattr(pw, "nav_buttons"):
|
||||||
for i, btn in enumerate(pw.nav_buttons):
|
for i, btn in enumerate(pw.nav_buttons):
|
||||||
btn.setChecked(i == 0)
|
btn.setChecked(i == 2)
|
||||||
|
|
||||||
if auto_start:
|
if auto_start:
|
||||||
QTimer.singleShot(
|
QTimer.singleShot(
|
||||||
|
|
|
||||||
|
|
@ -469,13 +469,17 @@ _STEP_HELP: dict[int, str] = {
|
||||||
"translating IDs used as logic keys will break the game."
|
"translating IDs used as logic keys will break the game."
|
||||||
),
|
),
|
||||||
5: (
|
5: (
|
||||||
"<b>Step 5 - Plugins & Export</b><br><br>"
|
"<b>Step 5 - Plugins / Scripts & Export</b><br><br>"
|
||||||
"<b>Plugins</b> (MV/MZ; optional before shipping): copy <code>vocab.txt</code> into "
|
"<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 "
|
"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 "
|
"<code>plugins.js</code> attached. Only translate player-visible UI strings - never "
|
||||||
"plugin parameter keys or internal identifiers.<br>"
|
"plugin parameter keys or internal identifiers.<br><br>"
|
||||||
"Ace projects are converted to JSON and translated like MV/MZ - skip this if there is "
|
"<b>Ace - Ruby scripts</b> (instead of plugins.js): RV2JSON unpacks "
|
||||||
"no <code>plugins.js</code>.<br><br>"
|
"<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 "
|
"<b>Export</b> - copy finished translations from <code>translated/</code> back "
|
||||||
"into the game's data folder:<br>"
|
"into the game's data folder:<br>"
|
||||||
"• <b>Export Active Files</b> - only names currently in <code>files/</code><br>"
|
"• <b>Export Active Files</b> - only names currently in <code>files/</code><br>"
|
||||||
|
|
@ -1262,6 +1266,81 @@ class WorkflowTab(QWidget):
|
||||||
"</output_format>\n"
|
"</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 = (
|
_PLUGIN_PROMPT = (
|
||||||
"You are an expert RPGMaker MV/MZ event system analyst.\n"
|
"You are an expert RPGMaker MV/MZ event system analyst.\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
|
@ -2323,12 +2402,12 @@ class WorkflowTab(QWidget):
|
||||||
|
|
||||||
self._step6_section_label = QLabel("Plugins")
|
self._step6_section_label = QLabel("Plugins")
|
||||||
|
|
||||||
vocab_btn = _make_btn("📄 Copy vocab.txt → Game", "#555")
|
self._step6_vocab_btn = _make_btn("📄 Copy vocab.txt → Game", "#555")
|
||||||
vocab_btn.setToolTip(
|
self._step6_vocab_btn.setToolTip(
|
||||||
"Copy vocab.txt to <game root>/vocab.txt so you can attach it "
|
"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 = _make_btn("📋 Copy Prompt for Copilot", "#555")
|
||||||
self._step6_copy_btn.setToolTip(
|
self._step6_copy_btn.setToolTip(
|
||||||
|
|
@ -2338,7 +2417,11 @@ class WorkflowTab(QWidget):
|
||||||
self._step6_copy_btn.clicked.connect(self._copy_plugins_js_translate_prompt)
|
self._step6_copy_btn.clicked.connect(self._copy_plugins_js_translate_prompt)
|
||||||
|
|
||||||
inner.addLayout(
|
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")
|
export_lbl = QLabel("Export")
|
||||||
|
|
@ -3018,14 +3101,43 @@ class WorkflowTab(QWidget):
|
||||||
self._log(f"⚠ Could not remove {err}")
|
self._log(f"⚠ Could not remove {err}")
|
||||||
|
|
||||||
def _update_step6_for_engine(self, is_ace: bool) -> None:
|
def _update_step6_for_engine(self, is_ace: bool) -> None:
|
||||||
"""Hide MV/MZ-only preprocess + Playtest controls when the project is Ace."""
|
"""Adapt plugins/scripts controls for MV/MZ vs Ace; hide Playtest on Ace."""
|
||||||
# Step 1 prettier / plugins.js format — only relevant for MV/MZ
|
# Step 1 prettier / plugins.js format - only relevant for MV/MZ
|
||||||
for attr in ("_pp_dazedformat_title", "_pp_dazedformat_box",
|
for attr in ("_pp_dazedformat_title", "_pp_dazedformat_box",
|
||||||
"_pp_plugins_js_title", "_pp_plugins_js_box"):
|
"_pp_plugins_js_title", "_pp_plugins_js_box"):
|
||||||
w = getattr(self, attr, None)
|
w = getattr(self, attr, None)
|
||||||
if w is not None:
|
if w is not None:
|
||||||
w.setVisible(not is_ace)
|
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
|
show_playtest = not is_ace
|
||||||
playtest_idx = 6
|
playtest_idx = 6
|
||||||
if hasattr(self, "_step_tabs") and self._step_tabs.count() > playtest_idx:
|
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}")
|
self._log(f"❌ Could not copy vocab.txt: {exc}")
|
||||||
|
|
||||||
def _copy_plugins_js_translate_prompt(self):
|
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)
|
QApplication.clipboard().setText(self._PLUGINS_JS_TRANSLATE_PROMPT)
|
||||||
self._log("plugins.js translation prompt copied to clipboard.")
|
self._log("plugins.js translation prompt copied to clipboard.")
|
||||||
|
|
||||||
|
|
@ -3889,12 +4008,15 @@ class WorkflowTab(QWidget):
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# 5. Navigate
|
# 5. Navigate to Translation tab
|
||||||
if hasattr(pw, "content_stack"):
|
if hasattr(pw, "switch_page"):
|
||||||
pw.content_stack.setCurrentIndex(0)
|
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"):
|
if hasattr(pw, "nav_buttons"):
|
||||||
for i, btn in enumerate(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
|
# 6. Auto-start translation so the user doesn't need an extra click
|
||||||
if auto_start:
|
if auto_start:
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@ pillow>=12.1.0
|
||||||
pefile>=2023.2.7
|
pefile>=2023.2.7
|
||||||
PyQt5>=5.15.0
|
PyQt5>=5.15.0
|
||||||
qtawesome>=1.3.0
|
qtawesome>=1.3.0
|
||||||
|
markdown>=3.5.0
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ class UpdateThreadInstallFilterTests(unittest.TestCase):
|
||||||
"data/translation_contexts.json",
|
"data/translation_contexts.json",
|
||||||
"data/skills/system.md",
|
"data/skills/system.md",
|
||||||
"data/skills/project_setup.md",
|
"data/skills/project_setup.md",
|
||||||
|
"data/help/index.json",
|
||||||
|
"data/help/00-welcome.md",
|
||||||
"data/vocab_base.txt",
|
"data/vocab_base.txt",
|
||||||
"gui/main.py",
|
"gui/main.py",
|
||||||
"gameupdate/GameUpdate.bat",
|
"gameupdate/GameUpdate.bat",
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ DATA_DIR = PROJECT_ROOT / "data"
|
||||||
VOCAB_PATH = DATA_DIR / "vocab.txt"
|
VOCAB_PATH = DATA_DIR / "vocab.txt"
|
||||||
VOCAB_BASE_PATH = DATA_DIR / "vocab_base.txt"
|
VOCAB_BASE_PATH = DATA_DIR / "vocab_base.txt"
|
||||||
SKILLS_DIR = DATA_DIR / "skills"
|
SKILLS_DIR = DATA_DIR / "skills"
|
||||||
|
HELP_DIR = DATA_DIR / "help"
|
||||||
# Runtime translation system skill (formerly data/prompt.txt).
|
# Runtime translation system skill (formerly data/prompt.txt).
|
||||||
PROMPT_PATH = SKILLS_DIR / "system.md"
|
PROMPT_PATH = SKILLS_DIR / "system.md"
|
||||||
LEGACY_PROMPT_PATH = DATA_DIR / "prompt.txt"
|
LEGACY_PROMPT_PATH = DATA_DIR / "prompt.txt"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue