refactor!: rebrand product from DazedMTLTool to DazedTL
- Rename desktop launcher, window titles, About, and User-Agents - Point QSettings at DazedTL with one-time migration from legacy app key - Update README, Guide, skills, and launcher/script copy BREAKING CHANGE: QSettings and desktop id now use DazedTL (legacy keys migrated once)
This commit is contained in:
parent
41691879dc
commit
4d1b01c870
30 changed files with 4353 additions and 4311 deletions
|
|
@ -1,10 +1,10 @@
|
||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=DazedMTLTool
|
Name=DazedTL
|
||||||
GenericName=Translation Tool
|
GenericName=AI Translation Tool
|
||||||
Comment=AI translation tool for visual novels and RPG games
|
Comment=AI translation tool for visual novels and RPG games
|
||||||
Exec=bash -c 'exec "$(cd "$(dirname "%k")" && pwd)/scripts/launch.sh"'
|
Exec=bash -c 'exec "$(cd "$(dirname "%k")" && pwd)/scripts/launch.sh"'
|
||||||
Icon=%k/assets/icon.png
|
Icon=%k/assets/icon.png
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Development;Utility;
|
Categories=Development;Utility;
|
||||||
StartupWMClass=DazedMTLTool
|
StartupWMClass=DazedTL
|
||||||
12
README.md
12
README.md
|
|
@ -1,10 +1,10 @@
|
||||||
# DazedMTLTool
|
# DazedTL
|
||||||
|
|
||||||
An AI-powered game translation tool with a GUI. Translate RPG Maker, Ren'Py, Tyrano, Wolf RPG, Kirikiri, and other game engines from Japanese to English using GPT, Gemini, [Mistral](https://docs.mistral.ai/api), or other compatible AI models.
|
An AI-powered game translation tool with a GUI. Translate RPG Maker, Ren'Py, Tyrano, Wolf RPG, Kirikiri, and other game engines from Japanese to English using GPT, Gemini, [Mistral](https://docs.mistral.ai/api), or other compatible AI models.
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
- **[Sinflower](https://github.com/Sinflower)** — [RV2JSON](https://github.com/Sinflower/RV2JSON) — enables RPGMaker Ace games to be translated the same way as MV/MZ by converting rvdata2 files to JSON and back. A curated copy is bundled offline in `util/ace/offline/` and updates with DazedMTLTool.
|
- **[Sinflower](https://github.com/Sinflower)** — [RV2JSON](https://github.com/Sinflower/RV2JSON) — enables RPGMaker Ace games to be translated the same way as MV/MZ by converting rvdata2 files to JSON and back. A curated copy is bundled offline in `util/ace/offline/` and updates with DazedTL.
|
||||||
- **Sakura & Kao_SSS** — TL Inspector (`util/tl_inspector/`) — in-game translation source inspector and live-edit plugin for RPG Maker MV/MZ playtesting.
|
- **Sakura & Kao_SSS** — TL Inspector (`util/tl_inspector/`) — in-game translation source inspector and live-edit plugin for RPG Maker MV/MZ playtesting.
|
||||||
- **Len** — [Forge](https://gitgud.io/zero64801/forge-mvmz) MV/MZ playtest plugin (`util/forge/`), Mistral API support (provider integration and adaptive rate limiting), and batch translation mode (Anthropic Message Batches API).
|
- **Len** — [Forge](https://gitgud.io/zero64801/forge-mvmz) MV/MZ playtest plugin (`util/forge/`), Mistral API support (provider integration and adaptive rate limiting), and batch translation mode (Anthropic Message Batches API).
|
||||||
|
|
||||||
|
|
@ -108,7 +108,7 @@ This means Python wasn't added to your PATH. You have two options:
|
||||||
### 1. Download the Tool
|
### 1. Download the Tool
|
||||||
|
|
||||||
1. Click the green **Code** button at the top of this page and select **Download ZIP**.
|
1. Click the green **Code** button at the top of this page and select **Download ZIP**.
|
||||||
2. Extract the ZIP to a folder of your choice (e.g., `C:\DazedMTLTool`).
|
2. Extract the ZIP to a folder of your choice (e.g., `C:\DazedTL`).
|
||||||
|
|
||||||
### 2. Set Up Your API Key
|
### 2. Set Up Your API Key
|
||||||
|
|
||||||
|
|
@ -127,7 +127,7 @@ This means Python wasn't added to your PATH. You have two options:
|
||||||
|
|
||||||
**Windows:** Double-click `START.bat`. It will create a virtual environment, install dependencies, and open the GUI.
|
**Windows:** Double-click `START.bat`. It will create a virtual environment, install dependencies, and open the GUI.
|
||||||
|
|
||||||
**Linux/macOS:** Run `./START.sh`, or double-click `DazedMTLTool.desktop` (choose **Allow Launching** when your file manager asks). From then on, either method works.
|
**Linux/macOS:** Run `./START.sh`, or double-click `DazedTL.desktop` (choose **Allow Launching** when your file manager asks). From then on, either method works.
|
||||||
|
|
||||||
That's it! Use the same launcher each time you want to open the tool.
|
That's it! Use the same launcher each time you want to open the tool.
|
||||||
|
|
||||||
|
|
@ -350,7 +350,7 @@ After Step 0 extract, the **Database** tab discovery report classifies your game
|
||||||
| **Classic RPG** (most dialogue in maps/common events) | Names → foundation DB → maps/events (skip narrative DB if none) |
|
| **Classic RPG** (most dialogue in maps/common events) | Names → foundation DB → maps/events (skip narrative DB if none) |
|
||||||
| **Hybrid** | Names → foundation DB → narrative DB → maps/events |
|
| **Hybrid** | Names → foundation DB → narrative DB → maps/events |
|
||||||
|
|
||||||
> **`wolf` binary:** Prebuilt `wolf` CLIs for Windows and Linux are bundled offline under `util/wolfdawn/bin/<platform>/`, so no toolchain or build step is needed. They update when you update DazedMTLTool. If your platform's binary is missing, update the tool or ask the maintainer to refresh the bundled copy.
|
> **`wolf` binary:** Prebuilt `wolf` CLIs for Windows and Linux are bundled offline under `util/wolfdawn/bin/<platform>/`, so no toolchain or build step is needed. They update when you update DazedTL. If your platform's binary is missing, update the tool or ask the maintainer to refresh the bundled copy.
|
||||||
|
|
||||||
> **Legacy modules:** The older `Wolf RPG` / `Wolf RPG 2` modules (configured in the Engine Config tab) still exist for edge cases, but the WolfDawn workflow above is the recommended path.
|
> **Legacy modules:** The older `Wolf RPG` / `Wolf RPG 2` modules (configured in the Engine Config tab) still exist for edge cases, but the WolfDawn workflow above is the recommended path.
|
||||||
|
|
||||||
|
|
@ -378,7 +378,7 @@ You can also select a block of text, right-click, and choose **Copilot → Fix /
|
||||||
|
|
||||||
### Modifying Tool Modules
|
### Modifying Tool Modules
|
||||||
|
|
||||||
Open the DazedMTLTool folder in VSCode and ask Copilot to make changes to the translation modules. Examples:
|
Open the DazedTL folder in VSCode and ask Copilot to make changes to the translation modules. Examples:
|
||||||
|
|
||||||
- *"Add a new regex pattern to skip lines that start with //"*
|
- *"Add a new regex pattern to skip lines that start with //"*
|
||||||
- *"Change the wordwrap logic to break on full-width punctuation"*
|
- *"Change the wordwrap logic to break on full-width punctuation"*
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
setlocal EnableDelayedExpansion
|
setlocal EnableDelayedExpansion
|
||||||
|
|
||||||
echo ==========================================
|
echo ==========================================
|
||||||
echo DazedMTLTool Startup Script
|
echo DazedTL Startup Script
|
||||||
echo ==========================================
|
echo ==========================================
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
|
|
@ -180,7 +180,7 @@ echo.
|
||||||
|
|
||||||
:: Launch the GUI
|
:: Launch the GUI
|
||||||
echo ==========================================
|
echo ==========================================
|
||||||
echo Launching DazedMTLTool GUI...
|
echo Launching DazedTL GUI...
|
||||||
echo ==========================================
|
echo ==========================================
|
||||||
echo.
|
echo.
|
||||||
|
|
||||||
|
|
|
||||||
6
START.sh
6
START.sh
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# DazedMTLTool startup script for Linux/macOS
|
# DazedTL startup script for Linux/macOS
|
||||||
|
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
|
|
@ -150,7 +150,7 @@ ensure_vocab_file() {
|
||||||
}
|
}
|
||||||
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " DazedMTLTool Startup Script"
|
echo " DazedTL Startup Script"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
|
@ -222,7 +222,7 @@ fi
|
||||||
echo
|
echo
|
||||||
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo " Launching DazedMTLTool GUI..."
|
echo " Launching DazedTL GUI..."
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Welcome to DazedMTLTool
|
# Welcome to DazedTL
|
||||||
|
|
||||||
DazedMTLTool translates Japanese games into English with an AI API.
|
DazedTL translates Japanese games into English with an AI API.
|
||||||
It has the strongest support for **RPG Maker** (MV / MZ / Ace) and **Wolf RPG** (WolfDawn),
|
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.
|
plus modules for Ren'Py, Tyrano, Kirikiri, CSV, and more.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ Use either:
|
||||||
### Example: open the game in Cursor
|
### Example: open the game in Cursor
|
||||||
|
|
||||||
1. In Cursor: **File → Open Folder** and select the game root (where `Game.exe` or Wolf `Data` lives).
|
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**.
|
2. In DazedTL Workflow Step 2, click **Copy Project Setup**.
|
||||||
3. Paste into Cursor chat with the game files available (or `@`-mention key JSON files).
|
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.
|
4. Paste the agent's labeled blocks back into the Workflow editors.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ You just downloaded a small MV game. You want English dialogue, menus that make
|
||||||
|
|
||||||
### 1. Get the tool ready
|
### 1. Get the tool ready
|
||||||
|
|
||||||
1. Launch DazedMTLTool (opens on this Guide).
|
1. Launch DazedTL (opens on this Guide).
|
||||||
2. Open **Configuration**, set your API key and model, save.
|
2. Open **Configuration**, set your API key and model, save.
|
||||||
3. Open the game folder in Cursor so the agent can read JSON later.
|
3. Open the game folder in Cursor so the agent can read JSON later.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# DazedMTLTool — Project Setup
|
# DazedTL — Project Setup
|
||||||
|
|
||||||
You are analysing a Japanese game project to produce configuration artifacts for DazedMTLTool.
|
You are analysing a Japanese game project to produce configuration artifacts for DazedTL.
|
||||||
Work in the game repository. Scan files; do not invent content you did not see.
|
Work in the game repository. Scan files; do not invent content you did not see.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -99,7 +99,7 @@ Output as short imperative bullets suitable to paste into `skills/quirks.md`.
|
||||||
### Game skill rules (for `game_skill` block)
|
### Game skill rules (for `game_skill` block)
|
||||||
|
|
||||||
Produce the per-game translation skill saved at `skills/game.md`.
|
Produce the per-game translation skill saved at `skills/game.md`.
|
||||||
DazedMTLTool **merges this file into the translation system prompt** (before quirks).
|
DazedTL **merges this file into the translation system prompt** (before quirks).
|
||||||
|
|
||||||
**Translation Frame only** (one compact line per field; evidence-based):
|
**Translation Frame only** (one compact line per field; evidence-based):
|
||||||
- `世界観 (Theme / setting)` - genre, world type, core atmosphere
|
- `世界観 (Theme / setting)` - genre, world type, core atmosphere
|
||||||
|
|
@ -195,7 +195,7 @@ Output as short imperative bullets suitable to paste into `skills/quirks.md`.
|
||||||
### Game skill rules (for `game_skill` block)
|
### Game skill rules (for `game_skill` block)
|
||||||
|
|
||||||
Produce the per-game translation skill saved at `skills/game.md`.
|
Produce the per-game translation skill saved at `skills/game.md`.
|
||||||
DazedMTLTool **merges this file into the translation system prompt** (before quirks).
|
DazedTL **merges this file into the translation system prompt** (before quirks).
|
||||||
|
|
||||||
**Translation Frame only** (one compact line per field; evidence-based):
|
**Translation Frame only** (one compact line per field; evidence-based):
|
||||||
- `世界観 (Theme / setting)` - genre, world type, core atmosphere
|
- `世界観 (Theme / setting)` - genre, world type, core atmosphere
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
setlocal EnableExtensions EnableDelayedExpansion
|
setlocal EnableExtensions EnableDelayedExpansion
|
||||||
|
|
||||||
REM When deployed on a game: this file sits in the game root; patch scripts live in .\gameupdate\
|
REM When deployed on a game: this file sits in the game root; patch scripts live in .\gameupdate\
|
||||||
REM In this repo the same layout is kept for neatness: this bat is under DazedMTLTool\gameupdate\ next to a nested gameupdate\ folder with patch.ps1.
|
REM In this repo the same layout is kept for neatness: this bat is under gameupdate\ next to a nested gameupdate\ folder with patch.ps1.
|
||||||
set "GU_ROOT=%~dp0"
|
set "GU_ROOT=%~dp0"
|
||||||
REM Game root is this batch file's folder (not %%CD%%, so full-path launches still work).
|
REM Game root is this batch file's folder (not %%CD%%, so full-path launches still work).
|
||||||
set "GAME_ROOT=!GU_ROOT!"
|
set "GAME_ROOT=!GU_ROOT!"
|
||||||
|
|
|
||||||
|
|
@ -1,159 +1,159 @@
|
||||||
# Apply Patch
|
# Apply Patch
|
||||||
1. Click Code
|
1. Click Code
|
||||||
2. Click Download ZIP
|
2. Click Download ZIP
|
||||||
3. Extract to game folder and Replace All.
|
3. Extract to game folder and Replace All.
|
||||||
|
|
||||||
## Future Patching
|
## Future Patching
|
||||||
1. Run **`GameUpdate.bat`** to auto patch (Windows).
|
1. Run **`GameUpdate.bat`** to auto patch (Windows).
|
||||||
|
|
||||||
### Folder layout
|
### Folder layout
|
||||||
|
|
||||||
**In this translation-tool repo:** Patch payloads stay tidy inside **`gameupdate/gameupdate/`** (`patch.ps1`, `patch-config.txt`, etc.). **`GameUpdate.bat`** and **`GameUpdate_linux.sh`** sit one level up, under **`gameupdate/`**.
|
**In this translation-tool repo:** Patch payloads stay tidy inside **`gameupdate/gameupdate/`** (`patch.ps1`, `patch-config.txt`, etc.). **`GameUpdate.bat`** and **`GameUpdate_linux.sh`** sit one level up, under **`gameupdate/`**.
|
||||||
|
|
||||||
**On an installed game (what gets copied over):** Put **`GameUpdate.bat`** in the **game root**, next to the game exe. Put **`patch.ps1`** and friends inside **`gameupdate\`** under that same root (mirror names—still **`gameupdate\`**). **`GameUpdate.bat`** finds **`gameupdate\patch.ps1`** from its own folder, so **`GameRoot`** is correct even if the console cwd is somewhere else.
|
**On an installed game (what gets copied over):** Put **`GameUpdate.bat`** in the **game root**, next to the game exe. Put **`patch.ps1`** and friends inside **`gameupdate\`** under that same root (mirror names—still **`gameupdate\`**). **`GameUpdate.bat`** finds **`gameupdate\patch.ps1`** from its own folder, so **`GameRoot`** is correct even if the console cwd is somewhere else.
|
||||||
|
|
||||||
**Copy checklist:** From **`DazedMTLTool/gameupdate/`**, copy **`GameUpdate.bat`** to `<game>\`; copy everything inside **`gameupdate/gameupdate/`** into `<game>\gameupdate\`.
|
**Copy checklist:** From the tool repo's **`gameupdate/`**, copy **`GameUpdate.bat`** to `<game>\`; copy everything inside **`gameupdate/gameupdate/`** into `<game>\gameupdate\`.
|
||||||
|
|
||||||
2. Optional: set `GAMEUPDATE_PROMPT_PWSH=1` before running `GameUpdate.bat` if you want users to be prompted to install PowerShell 7 via winget.
|
2. Optional: set `GAMEUPDATE_PROMPT_PWSH=1` before running `GameUpdate.bat` if you want users to be prompted to install PowerShell 7 via winget.
|
||||||
3. Optional: set `GAMEUPDATE_DL_ATTEMPTS` (default `2`) to control retries for API checks/downloads. Lower values fail faster; higher values tolerate flaky networks.
|
3. Optional: set `GAMEUPDATE_DL_ATTEMPTS` (default `2`) to control retries for API checks/downloads. Lower values fail faster; higher values tolerate flaky networks.
|
||||||
|
|
||||||
### patch-config.txt
|
### patch-config.txt
|
||||||
|
|
||||||
Create `gameupdate/patch-config.txt` next to `patch.ps1` (see `patch-config.example.txt`).
|
Create `gameupdate/patch-config.txt` next to `patch.ps1` (see `patch-config.example.txt`).
|
||||||
|
|
||||||
In DazedMTLTool, set **Config → Game Update Defaults** (forge, host, org/username, branch) once. Step 1 **Copy gameupdate/** writes those into each game's `patch-config.txt`. You still set `repo=` per game.
|
In DazedTL, set **Config → Game Update Defaults** (forge, host, org/username, branch) once. Step 1 **Copy gameupdate/** writes those into each game's `patch-config.txt`. You still set `repo=` per game.
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
forge=gitlab
|
forge=gitlab
|
||||||
host=gitgud.io
|
host=gitgud.io
|
||||||
username=YOUR_ORG_OR_USER
|
username=YOUR_ORG_OR_USER
|
||||||
repo=YOUR_PATCH_REPO
|
repo=YOUR_PATCH_REPO
|
||||||
branch=main
|
branch=main
|
||||||
```
|
```
|
||||||
|
|
||||||
| Key | Meaning |
|
| Key | Meaning |
|
||||||
|-----|---------|
|
|-----|---------|
|
||||||
| `forge` | `gitlab` (default), `github`, or `forgejo` (`gitea` also accepted) |
|
| `forge` | `gitlab` (default), `github`, or `forgejo` (`gitea` also accepted) |
|
||||||
| `host` | Hostname only. Defaults: `gitgud.io` / `github.com` / `codeberg.org` |
|
| `host` | Hostname only. Defaults: `gitgud.io` / `github.com` / `codeberg.org` |
|
||||||
| `username` | Owner / org / group (`owner=` / `org=` aliases work) |
|
| `username` | Owner / org / group (`owner=` / `org=` aliases work) |
|
||||||
| `repo` | Repository name |
|
| `repo` | Repository name |
|
||||||
| `branch` | Branch to track |
|
| `branch` | Branch to track |
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
# GitLab / gitgud
|
# GitLab / gitgud
|
||||||
forge=gitlab
|
forge=gitlab
|
||||||
host=gitgud.io
|
host=gitgud.io
|
||||||
username=myorg
|
username=myorg
|
||||||
repo=cool-game-en
|
repo=cool-game-en
|
||||||
branch=main
|
branch=main
|
||||||
|
|
||||||
# GitHub
|
# GitHub
|
||||||
forge=github
|
forge=github
|
||||||
host=github.com
|
host=github.com
|
||||||
username=myorg
|
username=myorg
|
||||||
repo=cool-game-en
|
repo=cool-game-en
|
||||||
branch=main
|
branch=main
|
||||||
|
|
||||||
# Forgejo / Codeberg
|
# Forgejo / Codeberg
|
||||||
forge=forgejo
|
forge=forgejo
|
||||||
host=codeberg.org
|
host=codeberg.org
|
||||||
username=myorg
|
username=myorg
|
||||||
repo=cool-game-en
|
repo=cool-game-en
|
||||||
branch=main
|
branch=main
|
||||||
```
|
```
|
||||||
|
|
||||||
Older configs with only `username` / `repo` / `branch` still work (assumes GitLab on `gitgud.io`).
|
Older configs with only `username` / `repo` / `branch` still work (assumes GitLab on `gitgud.io`).
|
||||||
|
|
||||||
# Troubleshooting
|
# Troubleshooting
|
||||||
**GAMEUPDATE.bat doesn't update and closes immediately**
|
**GAMEUPDATE.bat doesn't update and closes immediately**
|
||||||
1. Make sure your path doesn't contain any Japanese characters or lots of whitespace.
|
1. Make sure your path doesn't contain any Japanese characters or lots of whitespace.
|
||||||
2. Make sure you actually have permissions in the folder
|
2. Make sure you actually have permissions in the folder
|
||||||
3. Auto-update calls the forge's public HTTP API (GitLab `/api/v4`, GitHub `/repos/...`, Forgejo `/api/v1`), not the web “Download ZIP” button - no account is required for public patch repos.
|
3. Auto-update calls the forge's public HTTP API (GitLab `/api/v4`, GitHub `/repos/...`, Forgejo `/api/v1`), not the web “Download ZIP” button - no account is required for public patch repos.
|
||||||
|
|
||||||
# Wolf Games
|
# Wolf Games
|
||||||
WOLF RPG installs from DLSite ship a master `Data.wolf` archive that takes priority over loose English patch files. `GameUpdate.bat` detects that and unpacks it automatically with the bundled `UberWolfCli.exe` (MIT, [Sinflower/UberWolf](https://github.com/Sinflower/UberWolf)), then renames `Data.wolf` to `Data.wolf.bak` so the patched loose `Data/` files load.
|
WOLF RPG installs from DLSite ship a master `Data.wolf` archive that takes priority over loose English patch files. `GameUpdate.bat` detects that and unpacks it automatically with the bundled `UberWolfCli.exe` (MIT, [Sinflower/UberWolf](https://github.com/Sinflower/UberWolf)), then renames `Data.wolf` to `Data.wolf.bak` so the patched loose `Data/` files load.
|
||||||
|
|
||||||
Just run **`GameUpdate.bat`**. No manual UberWolf download is required.
|
Just run **`GameUpdate.bat`**. No manual UberWolf download is required.
|
||||||
|
|
||||||
If unpack fails (rare Pro / protected builds), unpack once with [UberWolf](https://github.com/Sinflower/UberWolf/releases), ensure a loose `Data/` folder exists, rename or remove `Data.wolf`, delete `gameupdate/previous_patch_sha.txt`, and run `GameUpdate.bat` again.
|
If unpack fails (rare Pro / protected builds), unpack once with [UberWolf](https://github.com/Sinflower/UberWolf/releases), ensure a loose `Data/` folder exists, rename or remove `Data.wolf`, delete `gameupdate/previous_patch_sha.txt`, and run `GameUpdate.bat` again.
|
||||||
|
|
||||||
# Edit/Contribute
|
# Edit/Contribute
|
||||||
TLDR 3 steps.
|
TLDR 3 steps.
|
||||||
|
|
||||||
Fork the repository.
|
Fork the repository.
|
||||||
Make the changes.
|
Make the changes.
|
||||||
Submit a merge request.
|
Submit a merge request.
|
||||||
|
|
||||||
If everything looks good and doesn't break things I'll merge it in.
|
If everything looks good and doesn't break things I'll merge it in.
|
||||||
|
|
||||||
Longer Version:
|
Longer Version:
|
||||||
|
|
||||||
# Required Software:
|
# Required Software:
|
||||||
* [VSCode](https://code.visualstudio.com/) Make sure you check all the boxes for context menus.
|
* [VSCode](https://code.visualstudio.com/) Make sure you check all the boxes for context menus.
|
||||||
* [Git](https://git-scm.com/downloads) (Use the default for everything. Just keep clicking Next)
|
* [Git](https://git-scm.com/downloads) (Use the default for everything. Just keep clicking Next)
|
||||||
|
|
||||||
# Guide to contributing
|
# Guide to contributing
|
||||||
|
|
||||||
### 1. Fork the Repository
|
### 1. Fork the Repository
|
||||||
- Go to the repository you want to fork.
|
- Go to the repository you want to fork.
|
||||||
- Click the "Fork" button.
|
- Click the "Fork" button.
|
||||||
|
|
||||||
### 2. Clone Your Fork
|
### 2. Clone Your Fork
|
||||||
- Clone your forked repository to your local machine.
|
- Clone your forked repository to your local machine.
|
||||||
```sh
|
```sh
|
||||||
git clone https://gitgud.io/YOUR_USERNAME/REPO_NAME.git
|
git clone https://gitgud.io/YOUR_USERNAME/REPO_NAME.git
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Make Your Changes (In VSCode)
|
### 3. Make Your Changes (In VSCode)
|
||||||
- Edit the files locally on your new branch using VSCode.
|
- Edit the files locally on your new branch using VSCode.
|
||||||
- Add and commit your changes.
|
- Add and commit your changes.
|
||||||
```sh
|
```sh
|
||||||
git add .
|
git add .
|
||||||
git commit -m "Description of your changes"
|
git commit -m "Description of your changes"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Push Your Changes
|
### 4. Push Your Changes
|
||||||
- Push your changes to your fork on GitGud.io.
|
- Push your changes to your fork on GitGud.io.
|
||||||
```sh
|
```sh
|
||||||
git push origin your-feature-branch
|
git push origin your-feature-branch
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Create a Merge Request
|
### 5. Create a Merge Request
|
||||||
- Go to your fork on GitGud.io.
|
- Go to your fork on GitGud.io.
|
||||||
- Click on "Merge Requests" in the sidebar.
|
- Click on "Merge Requests" in the sidebar.
|
||||||
- Click the "New merge request" button.
|
- Click the "New merge request" button.
|
||||||
- Select the branch you made changes to and the target project (the original repo).
|
- Select the branch you made changes to and the target project (the original repo).
|
||||||
- Provide a title and description for your merge request and submit it.
|
- Provide a title and description for your merge request and submit it.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
Assuming you want to fork a repository named `example-project`:
|
Assuming you want to fork a repository named `example-project`:
|
||||||
|
|
||||||
### 1. Fork the Repo
|
### 1. Fork the Repo
|
||||||
- Navigate to `https://gitgud.io/original_user/example-project` and click "Fork".
|
- Navigate to `https://gitgud.io/original_user/example-project` and click "Fork".
|
||||||
|
|
||||||
### 2. Clone Your Fork
|
### 2. Clone Your Fork
|
||||||
```sh
|
```sh
|
||||||
git clone https://gitgud.io/YOUR_USERNAME/example-project.git
|
git clone https://gitgud.io/YOUR_USERNAME/example-project.git
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Make Changes and Commit
|
### 3. Make Changes and Commit
|
||||||
```sh
|
```sh
|
||||||
# Make changes to the files
|
# Make changes to the files
|
||||||
git add .
|
git add .
|
||||||
git commit -m "Add new feature to example project"
|
git commit -m "Add new feature to example project"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Push Changes
|
### 4. Push Changes
|
||||||
```sh
|
```sh
|
||||||
git push origin add-new-feature
|
git push origin add-new-feature
|
||||||
```
|
```
|
||||||
|
|
||||||
### 5. Create a Merge Request
|
### 5. Create a Merge Request
|
||||||
- Go to `https://gitgud.io/YOUR_USERNAME/example-project/merge_requests` and click on "New merge request"
|
- Go to `https://gitgud.io/YOUR_USERNAME/example-project/merge_requests` and click on "New merge request"
|
||||||
- Choose the source branch `add-new-feature` and target branch (default: `main` or `master`)
|
- Choose the source branch `add-new-feature` and target branch (default: `main` or `master`)
|
||||||
- Fill in the details and submit the merge request
|
- Fill in the details and submit the merge request
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# GameUpdate patch source (copy to patch-config.txt and edit).
|
# GameUpdate patch source (copy to patch-config.txt and edit).
|
||||||
# Lines starting with # are comments. Keys are case-insensitive.
|
# Lines starting with # are comments. Keys are case-insensitive.
|
||||||
#
|
#
|
||||||
# Tip: set forge / host / username / branch once in DazedMTLTool
|
# Tip: set forge / host / username / branch once in DazedTL
|
||||||
# Config → Game Update Defaults. Step 1 "Copy gameupdate/" writes
|
# Config → Game Update Defaults. Step 1 "Copy gameupdate/" writes
|
||||||
# those into this file automatically (you still set repo= per game).
|
# those into this file automatically (you still set repo= per game).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
"""
|
"""
|
||||||
DazedMTLTool GUI Package
|
DazedTL GUI Package
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "1.0.0"
|
__version__ = "1.0.0"
|
||||||
__author__ = "DazedTranslations"
|
__author__ = "DazedTranslations"
|
||||||
|
|
||||||
# Package imports
|
# Package imports
|
||||||
from .main import DazedMTLGUI
|
from .main import DazedMTLGUI
|
||||||
from .config_tab import ConfigTab
|
from .config_tab import ConfigTab
|
||||||
from .rpgmaker_tab import RPGMakerTab
|
from .rpgmaker_tab import RPGMakerTab
|
||||||
from .log_viewer import LogViewer
|
from .log_viewer import LogViewer
|
||||||
from .file_manager import FileManager
|
from .file_manager import FileManager
|
||||||
from .workflow_tab import WorkflowTab
|
from .workflow_tab import WorkflowTab
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"DazedMTLGUI",
|
"DazedMTLGUI",
|
||||||
"ConfigTab",
|
"ConfigTab",
|
||||||
"RPGMakerTab",
|
"RPGMakerTab",
|
||||||
"LogViewer",
|
"LogViewer",
|
||||||
"FileManager",
|
"FileManager",
|
||||||
"WorkflowTab",
|
"WorkflowTab",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,426 +1,428 @@
|
||||||
"""
|
"""
|
||||||
File Manager - Handle input and output files
|
File Manager - Handle input and output files
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QTreeWidget, QTreeWidgetItem,
|
QWidget, QVBoxLayout, QHBoxLayout, QTreeWidget, QTreeWidgetItem,
|
||||||
QPushButton, QLabel, QFileDialog, QMessageBox, QGroupBox,
|
QPushButton, QLabel, QFileDialog, QMessageBox, QGroupBox,
|
||||||
QSplitter, QTextEdit, QProgressBar, QCheckBox
|
QSplitter, QTextEdit, QProgressBar, QCheckBox
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings
|
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from util.paths import APP_NAME, ORG_NAME
|
||||||
class FileManager(QWidget):
|
|
||||||
"""Widget for managing input and output files."""
|
|
||||||
|
class FileManager(QWidget):
|
||||||
files_changed = pyqtSignal()
|
"""Widget for managing input and output files."""
|
||||||
|
|
||||||
def __init__(self):
|
files_changed = pyqtSignal()
|
||||||
super().__init__()
|
|
||||||
# Persistent settings for remembering last-open directory
|
def __init__(self):
|
||||||
try:
|
super().__init__()
|
||||||
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
|
# Persistent settings for remembering last-open directory
|
||||||
except Exception:
|
try:
|
||||||
self.settings = None
|
self.settings = QSettings(ORG_NAME, APP_NAME)
|
||||||
self.input_files = []
|
except Exception:
|
||||||
self.output_directory = Path("translated")
|
self.settings = None
|
||||||
self.init_ui()
|
self.input_files = []
|
||||||
self.refresh_files()
|
self.output_directory = Path("translated")
|
||||||
|
self.init_ui()
|
||||||
def init_ui(self):
|
self.refresh_files()
|
||||||
"""Initialize the user interface."""
|
|
||||||
layout = QVBoxLayout()
|
def init_ui(self):
|
||||||
|
"""Initialize the user interface."""
|
||||||
# Create splitter for input and output sections
|
layout = QVBoxLayout()
|
||||||
splitter = QSplitter(Qt.Horizontal)
|
|
||||||
|
# Create splitter for input and output sections
|
||||||
# Input files section
|
splitter = QSplitter(Qt.Horizontal)
|
||||||
input_group = self.create_input_section()
|
|
||||||
splitter.addWidget(input_group)
|
# Input files section
|
||||||
|
input_group = self.create_input_section()
|
||||||
# Output files section
|
splitter.addWidget(input_group)
|
||||||
output_group = self.create_output_section()
|
|
||||||
splitter.addWidget(output_group)
|
# Output files section
|
||||||
|
output_group = self.create_output_section()
|
||||||
splitter.setSizes([400, 400])
|
splitter.addWidget(output_group)
|
||||||
layout.addWidget(splitter)
|
|
||||||
|
splitter.setSizes([400, 400])
|
||||||
# Progress section
|
layout.addWidget(splitter)
|
||||||
progress_group = self.create_progress_section()
|
|
||||||
layout.addWidget(progress_group)
|
# Progress section
|
||||||
|
progress_group = self.create_progress_section()
|
||||||
self.setLayout(layout)
|
layout.addWidget(progress_group)
|
||||||
|
|
||||||
def create_input_section(self):
|
self.setLayout(layout)
|
||||||
"""Create the input files section."""
|
|
||||||
group = QGroupBox("Input Files")
|
def create_input_section(self):
|
||||||
layout = QVBoxLayout()
|
"""Create the input files section."""
|
||||||
|
group = QGroupBox("Input Files")
|
||||||
# Buttons
|
layout = QVBoxLayout()
|
||||||
button_layout = QHBoxLayout()
|
|
||||||
|
# Buttons
|
||||||
self.add_files_button = QPushButton("Add Files")
|
button_layout = QHBoxLayout()
|
||||||
self.add_files_button.clicked.connect(self.add_input_files)
|
|
||||||
|
self.add_files_button = QPushButton("Add Files")
|
||||||
self.add_folder_button = QPushButton("Add Folder")
|
self.add_files_button.clicked.connect(self.add_input_files)
|
||||||
self.add_folder_button.clicked.connect(self.add_input_folder)
|
|
||||||
|
self.add_folder_button = QPushButton("Add Folder")
|
||||||
self.remove_files_button = QPushButton("Remove Selected")
|
self.add_folder_button.clicked.connect(self.add_input_folder)
|
||||||
self.remove_files_button.clicked.connect(self.remove_selected_files)
|
|
||||||
|
self.remove_files_button = QPushButton("Remove Selected")
|
||||||
self.clear_files_button = QPushButton("Clear All")
|
self.remove_files_button.clicked.connect(self.remove_selected_files)
|
||||||
self.clear_files_button.clicked.connect(self.clear_input_files)
|
|
||||||
|
self.clear_files_button = QPushButton("Clear All")
|
||||||
button_layout.addWidget(self.add_files_button)
|
self.clear_files_button.clicked.connect(self.clear_input_files)
|
||||||
button_layout.addWidget(self.add_folder_button)
|
|
||||||
button_layout.addWidget(self.remove_files_button)
|
button_layout.addWidget(self.add_files_button)
|
||||||
button_layout.addWidget(self.clear_files_button)
|
button_layout.addWidget(self.add_folder_button)
|
||||||
|
button_layout.addWidget(self.remove_files_button)
|
||||||
layout.addLayout(button_layout)
|
button_layout.addWidget(self.clear_files_button)
|
||||||
|
|
||||||
# File tree
|
layout.addLayout(button_layout)
|
||||||
self.input_tree = QTreeWidget()
|
|
||||||
self.input_tree.setHeaderLabels(["File", "Size", "Type"])
|
# File tree
|
||||||
self.input_tree.itemSelectionChanged.connect(self.on_input_selection_changed)
|
self.input_tree = QTreeWidget()
|
||||||
layout.addWidget(self.input_tree)
|
self.input_tree.setHeaderLabels(["File", "Size", "Type"])
|
||||||
|
self.input_tree.itemSelectionChanged.connect(self.on_input_selection_changed)
|
||||||
# File info
|
layout.addWidget(self.input_tree)
|
||||||
self.input_info = QTextEdit()
|
|
||||||
self.input_info.setMaximumHeight(100)
|
# File info
|
||||||
self.input_info.setReadOnly(True)
|
self.input_info = QTextEdit()
|
||||||
layout.addWidget(self.input_info)
|
self.input_info.setMaximumHeight(100)
|
||||||
|
self.input_info.setReadOnly(True)
|
||||||
group.setLayout(layout)
|
layout.addWidget(self.input_info)
|
||||||
return group
|
|
||||||
|
group.setLayout(layout)
|
||||||
def create_output_section(self):
|
return group
|
||||||
"""Create the output files section."""
|
|
||||||
group = QGroupBox("Output Files")
|
def create_output_section(self):
|
||||||
layout = QVBoxLayout()
|
"""Create the output files section."""
|
||||||
|
group = QGroupBox("Output Files")
|
||||||
# Output directory selection
|
layout = QVBoxLayout()
|
||||||
dir_layout = QHBoxLayout()
|
|
||||||
|
# Output directory selection
|
||||||
self.output_dir_label = QLabel(str(self.output_directory))
|
dir_layout = QHBoxLayout()
|
||||||
self.output_dir_label.setStyleSheet("border: 1px solid #555; padding: 5px;")
|
|
||||||
|
self.output_dir_label = QLabel(str(self.output_directory))
|
||||||
self.browse_output_button = QPushButton("Browse")
|
self.output_dir_label.setStyleSheet("border: 1px solid #555; padding: 5px;")
|
||||||
self.browse_output_button.clicked.connect(self.browse_output_directory)
|
|
||||||
|
self.browse_output_button = QPushButton("Browse")
|
||||||
dir_layout.addWidget(QLabel("Output Directory:"))
|
self.browse_output_button.clicked.connect(self.browse_output_directory)
|
||||||
dir_layout.addWidget(self.output_dir_label)
|
|
||||||
dir_layout.addWidget(self.browse_output_button)
|
dir_layout.addWidget(QLabel("Output Directory:"))
|
||||||
|
dir_layout.addWidget(self.output_dir_label)
|
||||||
layout.addLayout(dir_layout)
|
dir_layout.addWidget(self.browse_output_button)
|
||||||
|
|
||||||
# Output file tree
|
layout.addLayout(dir_layout)
|
||||||
self.output_tree = QTreeWidget()
|
|
||||||
self.output_tree.setHeaderLabels(["File", "Size", "Modified"])
|
# Output file tree
|
||||||
self.output_tree.itemSelectionChanged.connect(self.on_output_selection_changed)
|
self.output_tree = QTreeWidget()
|
||||||
layout.addWidget(self.output_tree)
|
self.output_tree.setHeaderLabels(["File", "Size", "Modified"])
|
||||||
|
self.output_tree.itemSelectionChanged.connect(self.on_output_selection_changed)
|
||||||
# Output file preview
|
layout.addWidget(self.output_tree)
|
||||||
self.output_preview = QTextEdit()
|
|
||||||
self.output_preview.setMaximumHeight(100)
|
# Output file preview
|
||||||
self.output_preview.setReadOnly(True)
|
self.output_preview = QTextEdit()
|
||||||
layout.addWidget(self.output_preview)
|
self.output_preview.setMaximumHeight(100)
|
||||||
|
self.output_preview.setReadOnly(True)
|
||||||
# Output buttons
|
layout.addWidget(self.output_preview)
|
||||||
output_button_layout = QHBoxLayout()
|
|
||||||
|
# Output buttons
|
||||||
self.open_output_button = QPushButton("Open in Editor")
|
output_button_layout = QHBoxLayout()
|
||||||
self.open_output_button.clicked.connect(self.open_output_file)
|
|
||||||
|
self.open_output_button = QPushButton("Open in Editor")
|
||||||
self.delete_output_button = QPushButton("Delete Selected")
|
self.open_output_button.clicked.connect(self.open_output_file)
|
||||||
self.delete_output_button.clicked.connect(self.delete_output_file)
|
|
||||||
|
self.delete_output_button = QPushButton("Delete Selected")
|
||||||
output_button_layout.addWidget(self.open_output_button)
|
self.delete_output_button.clicked.connect(self.delete_output_file)
|
||||||
output_button_layout.addWidget(self.delete_output_button)
|
|
||||||
output_button_layout.addStretch()
|
output_button_layout.addWidget(self.open_output_button)
|
||||||
|
output_button_layout.addWidget(self.delete_output_button)
|
||||||
layout.addLayout(output_button_layout)
|
output_button_layout.addStretch()
|
||||||
|
|
||||||
group.setLayout(layout)
|
layout.addLayout(output_button_layout)
|
||||||
return group
|
|
||||||
|
group.setLayout(layout)
|
||||||
def create_progress_section(self):
|
return group
|
||||||
"""Create the progress monitoring section."""
|
|
||||||
group = QGroupBox("Translation Progress")
|
def create_progress_section(self):
|
||||||
layout = QVBoxLayout()
|
"""Create the progress monitoring section."""
|
||||||
|
group = QGroupBox("Translation Progress")
|
||||||
# Progress bar
|
layout = QVBoxLayout()
|
||||||
self.progress_bar = QProgressBar()
|
|
||||||
self.progress_bar.setVisible(False)
|
# Progress bar
|
||||||
layout.addWidget(self.progress_bar)
|
self.progress_bar = QProgressBar()
|
||||||
|
self.progress_bar.setVisible(False)
|
||||||
# Status and options
|
layout.addWidget(self.progress_bar)
|
||||||
status_layout = QHBoxLayout()
|
|
||||||
|
# Status and options
|
||||||
self.status_label = QLabel("Ready")
|
status_layout = QHBoxLayout()
|
||||||
self.backup_cb = QCheckBox("Create Backups")
|
|
||||||
self.backup_cb.setChecked(True)
|
self.status_label = QLabel("Ready")
|
||||||
|
self.backup_cb = QCheckBox("Create Backups")
|
||||||
status_layout.addWidget(self.status_label)
|
self.backup_cb.setChecked(True)
|
||||||
status_layout.addStretch()
|
|
||||||
status_layout.addWidget(self.backup_cb)
|
status_layout.addWidget(self.status_label)
|
||||||
|
status_layout.addStretch()
|
||||||
layout.addLayout(status_layout)
|
status_layout.addWidget(self.backup_cb)
|
||||||
|
|
||||||
group.setLayout(layout)
|
layout.addLayout(status_layout)
|
||||||
return group
|
|
||||||
|
group.setLayout(layout)
|
||||||
def add_input_files(self):
|
return group
|
||||||
"""Add input files via dialog."""
|
|
||||||
# Try to restore last used directory
|
def add_input_files(self):
|
||||||
start_dir = ""
|
"""Add input files via dialog."""
|
||||||
try:
|
# Try to restore last used directory
|
||||||
if self.settings:
|
start_dir = ""
|
||||||
start_dir = self.settings.value("last_open_dir", "") or ""
|
try:
|
||||||
except Exception:
|
if self.settings:
|
||||||
start_dir = ""
|
start_dir = self.settings.value("last_open_dir", "") or ""
|
||||||
|
except Exception:
|
||||||
files, _ = QFileDialog.getOpenFileNames(
|
start_dir = ""
|
||||||
self,
|
|
||||||
"Select Input Files",
|
files, _ = QFileDialog.getOpenFileNames(
|
||||||
start_dir,
|
self,
|
||||||
"JSON Files (*.json);;All Files (*)"
|
"Select Input Files",
|
||||||
)
|
start_dir,
|
||||||
|
"JSON Files (*.json);;All Files (*)"
|
||||||
for file_path in files:
|
)
|
||||||
if file_path not in self.input_files:
|
|
||||||
self.input_files.append(file_path)
|
for file_path in files:
|
||||||
|
if file_path not in self.input_files:
|
||||||
# Persist directory used
|
self.input_files.append(file_path)
|
||||||
try:
|
|
||||||
if self.settings and len(files) > 0:
|
# Persist directory used
|
||||||
import os
|
try:
|
||||||
self.settings.setValue("last_open_dir", os.path.dirname(files[0]))
|
if self.settings and len(files) > 0:
|
||||||
except Exception:
|
import os
|
||||||
pass
|
self.settings.setValue("last_open_dir", os.path.dirname(files[0]))
|
||||||
|
except Exception:
|
||||||
self.refresh_input_files()
|
pass
|
||||||
|
|
||||||
def add_input_folder(self):
|
self.refresh_input_files()
|
||||||
"""Add all files from a folder."""
|
|
||||||
folder = QFileDialog.getExistingDirectory(self, "Select Input Folder")
|
def add_input_folder(self):
|
||||||
|
"""Add all files from a folder."""
|
||||||
if folder:
|
folder = QFileDialog.getExistingDirectory(self, "Select Input Folder")
|
||||||
folder_path = Path(folder)
|
|
||||||
for file_path in folder_path.rglob("*.json"):
|
if folder:
|
||||||
file_str = str(file_path)
|
folder_path = Path(folder)
|
||||||
if file_str not in self.input_files:
|
for file_path in folder_path.rglob("*.json"):
|
||||||
self.input_files.append(file_str)
|
file_str = str(file_path)
|
||||||
|
if file_str not in self.input_files:
|
||||||
self.refresh_input_files()
|
self.input_files.append(file_str)
|
||||||
|
|
||||||
def remove_selected_files(self):
|
self.refresh_input_files()
|
||||||
"""Remove selected input files."""
|
|
||||||
selected_items = self.input_tree.selectedItems()
|
def remove_selected_files(self):
|
||||||
for item in selected_items:
|
"""Remove selected input files."""
|
||||||
file_path = item.text(0)
|
selected_items = self.input_tree.selectedItems()
|
||||||
if file_path in self.input_files:
|
for item in selected_items:
|
||||||
self.input_files.remove(file_path)
|
file_path = item.text(0)
|
||||||
|
if file_path in self.input_files:
|
||||||
self.refresh_input_files()
|
self.input_files.remove(file_path)
|
||||||
|
|
||||||
def clear_input_files(self):
|
self.refresh_input_files()
|
||||||
"""Clear all input files."""
|
|
||||||
self.input_files.clear()
|
def clear_input_files(self):
|
||||||
self.refresh_input_files()
|
"""Clear all input files."""
|
||||||
|
self.input_files.clear()
|
||||||
def refresh_input_files(self):
|
self.refresh_input_files()
|
||||||
"""Refresh the input files display."""
|
|
||||||
self.input_tree.clear()
|
def refresh_input_files(self):
|
||||||
|
"""Refresh the input files display."""
|
||||||
for file_path in self.input_files:
|
self.input_tree.clear()
|
||||||
path_obj = Path(file_path)
|
|
||||||
if path_obj.exists():
|
for file_path in self.input_files:
|
||||||
size = path_obj.stat().st_size
|
path_obj = Path(file_path)
|
||||||
file_type = path_obj.suffix
|
if path_obj.exists():
|
||||||
|
size = path_obj.stat().st_size
|
||||||
item = QTreeWidgetItem([
|
file_type = path_obj.suffix
|
||||||
path_obj.name,
|
|
||||||
f"{size:,} bytes",
|
item = QTreeWidgetItem([
|
||||||
file_type
|
path_obj.name,
|
||||||
])
|
f"{size:,} bytes",
|
||||||
item.setToolTip(0, str(path_obj))
|
file_type
|
||||||
self.input_tree.addTopLevelItem(item)
|
])
|
||||||
else:
|
item.setToolTip(0, str(path_obj))
|
||||||
# File doesn't exist - show in red
|
self.input_tree.addTopLevelItem(item)
|
||||||
item = QTreeWidgetItem([
|
else:
|
||||||
f"{path_obj.name} (Missing)",
|
# File doesn't exist - show in red
|
||||||
"0 bytes",
|
item = QTreeWidgetItem([
|
||||||
"N/A"
|
f"{path_obj.name} (Missing)",
|
||||||
])
|
"0 bytes",
|
||||||
item.setBackground(0, Qt.red)
|
"N/A"
|
||||||
self.input_tree.addTopLevelItem(item)
|
])
|
||||||
|
item.setBackground(0, Qt.red)
|
||||||
# Update info
|
self.input_tree.addTopLevelItem(item)
|
||||||
self.update_input_info()
|
|
||||||
|
# Update info
|
||||||
def refresh_output_files(self):
|
self.update_input_info()
|
||||||
"""Refresh the output files display."""
|
|
||||||
self.output_tree.clear()
|
def refresh_output_files(self):
|
||||||
|
"""Refresh the output files display."""
|
||||||
if self.output_directory.exists():
|
self.output_tree.clear()
|
||||||
for file_path in self.output_directory.iterdir():
|
|
||||||
if file_path.is_file():
|
if self.output_directory.exists():
|
||||||
size = file_path.stat().st_size
|
for file_path in self.output_directory.iterdir():
|
||||||
modified = file_path.stat().st_mtime
|
if file_path.is_file():
|
||||||
|
size = file_path.stat().st_size
|
||||||
import datetime
|
modified = file_path.stat().st_mtime
|
||||||
modified_str = datetime.datetime.fromtimestamp(modified).strftime("%Y-%m-%d %H:%M")
|
|
||||||
|
import datetime
|
||||||
item = QTreeWidgetItem([
|
modified_str = datetime.datetime.fromtimestamp(modified).strftime("%Y-%m-%d %H:%M")
|
||||||
file_path.name,
|
|
||||||
f"{size:,} bytes",
|
item = QTreeWidgetItem([
|
||||||
modified_str
|
file_path.name,
|
||||||
])
|
f"{size:,} bytes",
|
||||||
item.setToolTip(0, str(file_path))
|
modified_str
|
||||||
self.output_tree.addTopLevelItem(item)
|
])
|
||||||
|
item.setToolTip(0, str(file_path))
|
||||||
def browse_output_directory(self):
|
self.output_tree.addTopLevelItem(item)
|
||||||
"""Browse for output directory."""
|
|
||||||
directory = QFileDialog.getExistingDirectory(
|
def browse_output_directory(self):
|
||||||
self, "Select Output Directory", str(self.output_directory)
|
"""Browse for output directory."""
|
||||||
)
|
directory = QFileDialog.getExistingDirectory(
|
||||||
|
self, "Select Output Directory", str(self.output_directory)
|
||||||
if directory:
|
)
|
||||||
self.output_directory = Path(directory)
|
|
||||||
self.output_dir_label.setText(str(self.output_directory))
|
if directory:
|
||||||
self.refresh_output_files()
|
self.output_directory = Path(directory)
|
||||||
|
self.output_dir_label.setText(str(self.output_directory))
|
||||||
def refresh_files(self):
|
self.refresh_output_files()
|
||||||
"""Refresh both input and output file displays."""
|
|
||||||
self.refresh_input_files()
|
def refresh_files(self):
|
||||||
self.refresh_output_files()
|
"""Refresh both input and output file displays."""
|
||||||
|
self.refresh_input_files()
|
||||||
def on_input_selection_changed(self):
|
self.refresh_output_files()
|
||||||
"""Handle input file selection change."""
|
|
||||||
selected_items = self.input_tree.selectedItems()
|
def on_input_selection_changed(self):
|
||||||
if selected_items:
|
"""Handle input file selection change."""
|
||||||
item = selected_items[0]
|
selected_items = self.input_tree.selectedItems()
|
||||||
file_name = item.text(0)
|
if selected_items:
|
||||||
|
item = selected_items[0]
|
||||||
# Find the full path
|
file_name = item.text(0)
|
||||||
full_path = None
|
|
||||||
for file_path in self.input_files:
|
# Find the full path
|
||||||
if Path(file_path).name == file_name:
|
full_path = None
|
||||||
full_path = file_path
|
for file_path in self.input_files:
|
||||||
break
|
if Path(file_path).name == file_name:
|
||||||
|
full_path = file_path
|
||||||
if full_path and Path(full_path).exists():
|
break
|
||||||
try:
|
|
||||||
with open(full_path, 'r', encoding='utf-8') as f:
|
if full_path and Path(full_path).exists():
|
||||||
content = f.read()
|
try:
|
||||||
|
with open(full_path, 'r', encoding='utf-8') as f:
|
||||||
# Show preview of file structure
|
content = f.read()
|
||||||
if full_path.endswith('.json'):
|
|
||||||
data = json.loads(content)
|
# Show preview of file structure
|
||||||
if isinstance(data, dict):
|
if full_path.endswith('.json'):
|
||||||
keys = list(data.keys())[:10] # Show first 10 keys
|
data = json.loads(content)
|
||||||
preview = f"JSON Object with {len(data)} keys:\n"
|
if isinstance(data, dict):
|
||||||
preview += "\n".join(keys)
|
keys = list(data.keys())[:10] # Show first 10 keys
|
||||||
if len(data) > 10:
|
preview = f"JSON Object with {len(data)} keys:\n"
|
||||||
preview += f"\n... and {len(data) - 10} more"
|
preview += "\n".join(keys)
|
||||||
elif isinstance(data, list):
|
if len(data) > 10:
|
||||||
preview = f"JSON Array with {len(data)} items"
|
preview += f"\n... and {len(data) - 10} more"
|
||||||
else:
|
elif isinstance(data, list):
|
||||||
preview = f"JSON: {type(data).__name__}"
|
preview = f"JSON Array with {len(data)} items"
|
||||||
else:
|
else:
|
||||||
preview = content[:500] + "..." if len(content) > 500 else content
|
preview = f"JSON: {type(data).__name__}"
|
||||||
|
else:
|
||||||
self.input_info.setPlainText(preview)
|
preview = content[:500] + "..." if len(content) > 500 else content
|
||||||
except Exception as e:
|
|
||||||
self.input_info.setPlainText(f"Error reading file: {str(e)}")
|
self.input_info.setPlainText(preview)
|
||||||
|
except Exception as e:
|
||||||
def on_output_selection_changed(self):
|
self.input_info.setPlainText(f"Error reading file: {str(e)}")
|
||||||
"""Handle output file selection change."""
|
|
||||||
selected_items = self.output_tree.selectedItems()
|
def on_output_selection_changed(self):
|
||||||
if selected_items:
|
"""Handle output file selection change."""
|
||||||
item = selected_items[0]
|
selected_items = self.output_tree.selectedItems()
|
||||||
file_name = item.text(0)
|
if selected_items:
|
||||||
file_path = self.output_directory / file_name
|
item = selected_items[0]
|
||||||
|
file_name = item.text(0)
|
||||||
if file_path.exists():
|
file_path = self.output_directory / file_name
|
||||||
try:
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as f:
|
if file_path.exists():
|
||||||
content = f.read()
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
preview = content[:500] + "..." if len(content) > 500 else content
|
content = f.read()
|
||||||
self.output_preview.setPlainText(preview)
|
|
||||||
except Exception as e:
|
preview = content[:500] + "..." if len(content) > 500 else content
|
||||||
self.output_preview.setPlainText(f"Error reading file: {str(e)}")
|
self.output_preview.setPlainText(preview)
|
||||||
|
except Exception as e:
|
||||||
def open_output_file(self):
|
self.output_preview.setPlainText(f"Error reading file: {str(e)}")
|
||||||
"""Open selected output file in external editor."""
|
|
||||||
selected_items = self.output_tree.selectedItems()
|
def open_output_file(self):
|
||||||
if selected_items:
|
"""Open selected output file in external editor."""
|
||||||
item = selected_items[0]
|
selected_items = self.output_tree.selectedItems()
|
||||||
file_name = item.text(0)
|
if selected_items:
|
||||||
file_path = self.output_directory / file_name
|
item = selected_items[0]
|
||||||
|
file_name = item.text(0)
|
||||||
if file_path.exists():
|
file_path = self.output_directory / file_name
|
||||||
import subprocess
|
|
||||||
try:
|
if file_path.exists():
|
||||||
subprocess.run(['notepad.exe', str(file_path)], check=True)
|
import subprocess
|
||||||
except subprocess.CalledProcessError:
|
try:
|
||||||
QMessageBox.warning(self, "Warning", "Could not open file in external editor")
|
subprocess.run(['notepad.exe', str(file_path)], check=True)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
def delete_output_file(self):
|
QMessageBox.warning(self, "Warning", "Could not open file in external editor")
|
||||||
"""Delete selected output file."""
|
|
||||||
selected_items = self.output_tree.selectedItems()
|
def delete_output_file(self):
|
||||||
if selected_items:
|
"""Delete selected output file."""
|
||||||
item = selected_items[0]
|
selected_items = self.output_tree.selectedItems()
|
||||||
file_name = item.text(0)
|
if selected_items:
|
||||||
file_path = self.output_directory / file_name
|
item = selected_items[0]
|
||||||
|
file_name = item.text(0)
|
||||||
reply = QMessageBox.question(
|
file_path = self.output_directory / file_name
|
||||||
self,
|
|
||||||
"Confirm Deletion",
|
reply = QMessageBox.question(
|
||||||
f"Are you sure you want to delete '{file_name}'?",
|
self,
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
"Confirm Deletion",
|
||||||
QMessageBox.No
|
f"Are you sure you want to delete '{file_name}'?",
|
||||||
)
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
|
QMessageBox.No
|
||||||
if reply == QMessageBox.Yes and file_path.exists():
|
)
|
||||||
try:
|
|
||||||
file_path.unlink()
|
if reply == QMessageBox.Yes and file_path.exists():
|
||||||
self.refresh_output_files()
|
try:
|
||||||
except Exception as e:
|
file_path.unlink()
|
||||||
QMessageBox.critical(self, "Error", f"Failed to delete file:\n{str(e)}")
|
self.refresh_output_files()
|
||||||
|
except Exception as e:
|
||||||
def update_input_info(self):
|
QMessageBox.critical(self, "Error", f"Failed to delete file:\n{str(e)}")
|
||||||
"""Update the input files information."""
|
|
||||||
total_files = len(self.input_files)
|
def update_input_info(self):
|
||||||
total_size = 0
|
"""Update the input files information."""
|
||||||
|
total_files = len(self.input_files)
|
||||||
for file_path in self.input_files:
|
total_size = 0
|
||||||
path_obj = Path(file_path)
|
|
||||||
if path_obj.exists():
|
for file_path in self.input_files:
|
||||||
total_size += path_obj.stat().st_size
|
path_obj = Path(file_path)
|
||||||
|
if path_obj.exists():
|
||||||
info = f"Total Files: {total_files}\nTotal Size: {total_size:,} bytes"
|
total_size += path_obj.stat().st_size
|
||||||
|
|
||||||
# Count by file type
|
info = f"Total Files: {total_files}\nTotal Size: {total_size:,} bytes"
|
||||||
extensions = {}
|
|
||||||
for file_path in self.input_files:
|
# Count by file type
|
||||||
ext = Path(file_path).suffix
|
extensions = {}
|
||||||
extensions[ext] = extensions.get(ext, 0) + 1
|
for file_path in self.input_files:
|
||||||
|
ext = Path(file_path).suffix
|
||||||
if extensions:
|
extensions[ext] = extensions.get(ext, 0) + 1
|
||||||
info += "\n\nFile Types:"
|
|
||||||
for ext, count in sorted(extensions.items()):
|
if extensions:
|
||||||
info += f"\n{ext or 'No extension'}: {count}"
|
info += "\n\nFile Types:"
|
||||||
|
for ext, count in sorted(extensions.items()):
|
||||||
self.input_info.setPlainText(info)
|
info += f"\n{ext or 'No extension'}: {count}"
|
||||||
|
|
||||||
def get_input_files(self):
|
self.input_info.setPlainText(info)
|
||||||
"""Get list of input files."""
|
|
||||||
return self.input_files.copy()
|
def get_input_files(self):
|
||||||
|
"""Get list of input files."""
|
||||||
def get_output_directory(self):
|
return self.input_files.copy()
|
||||||
"""Get output directory path."""
|
|
||||||
return self.output_directory
|
def get_output_directory(self):
|
||||||
|
"""Get output directory path."""
|
||||||
|
return self.output_directory
|
||||||
|
|
|
||||||
55
gui/main.py
55
gui/main.py
|
|
@ -1,7 +1,7 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
DazedMTLTool GUI - Main Application
|
DazedTL GUI - Main Application
|
||||||
A PyQt-based graphical user interface for the DazedMTLTool translation system.
|
A PyQt-based graphical user interface for the DazedTL translation system.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
@ -29,7 +29,14 @@ from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QSettings, QCoreApplic
|
||||||
from PyQt5.QtGui import QIcon, QFont, QPixmap, QScreen, QGuiApplication
|
from PyQt5.QtGui import QIcon, QFont, QPixmap, QScreen, QGuiApplication
|
||||||
|
|
||||||
|
|
||||||
from util.paths import PROJECT_ROOT, ICON_PATH, LAST_UPDATE_SHA_PATH
|
from util.paths import (
|
||||||
|
APP_NAME,
|
||||||
|
ICON_PATH,
|
||||||
|
LAST_UPDATE_SHA_PATH,
|
||||||
|
ORG_NAME,
|
||||||
|
PROJECT_ROOT,
|
||||||
|
migrate_app_settings,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def load_application_icon() -> QIcon:
|
def load_application_icon() -> QIcon:
|
||||||
|
|
@ -56,7 +63,7 @@ def check_tool_update() -> str | None:
|
||||||
|
|
||||||
|
|
||||||
class BackgroundUpdateCheckThread(QThread):
|
class BackgroundUpdateCheckThread(QThread):
|
||||||
"""Checks for DazedMTLTool updates without blocking the UI."""
|
"""Checks for DazedTL updates without blocking the UI."""
|
||||||
|
|
||||||
finished = pyqtSignal(object) # str | None — pending tool SHA when available
|
finished = pyqtSignal(object) # str | None — pending tool SHA when available
|
||||||
|
|
||||||
|
|
@ -151,7 +158,7 @@ class UpdateThread(QThread):
|
||||||
@classmethod
|
@classmethod
|
||||||
def fetch_latest_sha(cls) -> str:
|
def fetch_latest_sha(cls) -> str:
|
||||||
req = urllib.request.Request(
|
req = urllib.request.Request(
|
||||||
cls.branch_api_url(), headers={"User-Agent": "DazedMTLTool"}
|
cls.branch_api_url(), headers={"User-Agent": APP_NAME}
|
||||||
)
|
)
|
||||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||||
return json.loads(resp.read())["commit"]["id"]
|
return json.loads(resp.read())["commit"]["id"]
|
||||||
|
|
@ -167,7 +174,7 @@ class UpdateThread(QThread):
|
||||||
|
|
||||||
def _download_archive(self, zip_path: Path):
|
def _download_archive(self, zip_path: Path):
|
||||||
req = urllib.request.Request(
|
req = urllib.request.Request(
|
||||||
self.archive_zip_url(), headers={"User-Agent": "DazedMTLTool"}
|
self.archive_zip_url(), headers={"User-Agent": APP_NAME}
|
||||||
)
|
)
|
||||||
with urllib.request.urlopen(req, timeout=120) as resp, open(zip_path, "wb") as fh:
|
with urllib.request.urlopen(req, timeout=120) as resp, open(zip_path, "wb") as fh:
|
||||||
total = int(resp.headers.get("Content-Length", 0) or 0)
|
total = int(resp.headers.get("Content-Length", 0) or 0)
|
||||||
|
|
@ -367,7 +374,7 @@ class UpdateDialog(QDialog):
|
||||||
|
|
||||||
title_col = QVBoxLayout()
|
title_col = QVBoxLayout()
|
||||||
title_col.setSpacing(2)
|
title_col.setSpacing(2)
|
||||||
self.title_label = QLabel("DazedMTLTool Update")
|
self.title_label = QLabel(f"{APP_NAME} Update")
|
||||||
self.title_label.setObjectName("updateTitle")
|
self.title_label.setObjectName("updateTitle")
|
||||||
self.subtitle_label = QLabel("Stay current with the latest fixes and features.")
|
self.subtitle_label = QLabel("Stay current with the latest fixes and features.")
|
||||||
self.subtitle_label.setObjectName("updateDetail")
|
self.subtitle_label.setObjectName("updateDetail")
|
||||||
|
|
@ -577,7 +584,7 @@ class UpdateDialog(QDialog):
|
||||||
self.current_version_label.setText(sha)
|
self.current_version_label.setText(sha)
|
||||||
self.headline_label.setText("Update installed successfully")
|
self.headline_label.setText("Update installed successfully")
|
||||||
self.detail_label.setText(
|
self.detail_label.setText(
|
||||||
"Restart DazedMTLTool to load the new version."
|
f"Restart {APP_NAME} to load the new version."
|
||||||
)
|
)
|
||||||
self.subtitle_label.setText("Update complete.")
|
self.subtitle_label.setText("Update complete.")
|
||||||
if isinstance(self.parent(), DazedMTLGUI):
|
if isinstance(self.parent(), DazedMTLGUI):
|
||||||
|
|
@ -620,7 +627,7 @@ from gui.skills_tab import SkillsTab
|
||||||
from gui.batch_tab import BatchTab
|
from gui.batch_tab import BatchTab
|
||||||
|
|
||||||
class DazedMTLGUI(QMainWindow):
|
class DazedMTLGUI(QMainWindow):
|
||||||
"""Main GUI window for the DazedMTLTool."""
|
"""Main GUI window for DazedTL."""
|
||||||
|
|
||||||
PAGE_GUIDE = 0
|
PAGE_GUIDE = 0
|
||||||
PAGE_WORKFLOW = 1
|
PAGE_WORKFLOW = 1
|
||||||
|
|
@ -631,7 +638,7 @@ class DazedMTLGUI(QMainWindow):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
|
self.settings = QSettings(ORG_NAME, APP_NAME)
|
||||||
self._pending_tool_sha: str | None = None
|
self._pending_tool_sha: str | None = None
|
||||||
self._update_check_thread = None
|
self._update_check_thread = None
|
||||||
self._shutdown_started = False
|
self._shutdown_started = False
|
||||||
|
|
@ -791,16 +798,16 @@ class DazedMTLGUI(QMainWindow):
|
||||||
|
|
||||||
# Update window title only when non-default scale is active
|
# Update window title only when non-default scale is active
|
||||||
if scale_factor != 1.0:
|
if scale_factor != 1.0:
|
||||||
self.setWindowTitle(f"DazedMTLTool - Visual Translation Interface (Font: {scale_factor:.1f}x)")
|
self.setWindowTitle(f"{APP_NAME} - Visual Translation Interface (Font: {scale_factor:.1f}x)")
|
||||||
else:
|
else:
|
||||||
self.setWindowTitle("DazedMTLTool - Visual Translation Interface")
|
self.setWindowTitle(f"{APP_NAME} - Visual Translation Interface")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Could not apply font scaling: {e}")
|
print(f"Warning: Could not apply font scaling: {e}")
|
||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
"""Initialize the user interface."""
|
"""Initialize the user interface."""
|
||||||
self.setWindowTitle("DazedMTLTool - Visual Translation Interface")
|
self.setWindowTitle(f"{APP_NAME} - Visual Translation Interface")
|
||||||
|
|
||||||
# Get screen geometry and set window size more responsively
|
# Get screen geometry and set window size more responsively
|
||||||
screen = QApplication.primaryScreen()
|
screen = QApplication.primaryScreen()
|
||||||
|
|
@ -1012,7 +1019,7 @@ class DazedMTLGUI(QMainWindow):
|
||||||
return container
|
return container
|
||||||
|
|
||||||
def start_background_update_check(self):
|
def start_background_update_check(self):
|
||||||
"""Check for DazedMTLTool updates after the GUI is visible."""
|
"""Check for DazedTL updates after the GUI is visible."""
|
||||||
if self._update_check_thread and self._update_check_thread.isRunning():
|
if self._update_check_thread and self._update_check_thread.isRunning():
|
||||||
return
|
return
|
||||||
self._update_check_thread = BackgroundUpdateCheckThread(self)
|
self._update_check_thread = BackgroundUpdateCheckThread(self)
|
||||||
|
|
@ -1221,15 +1228,14 @@ class DazedMTLGUI(QMainWindow):
|
||||||
"""Show the about dialog."""
|
"""Show the about dialog."""
|
||||||
QMessageBox.about(
|
QMessageBox.about(
|
||||||
self,
|
self,
|
||||||
"About DazedMTLTool GUI",
|
f"About {APP_NAME}",
|
||||||
"""
|
f"""
|
||||||
<h3>DazedMTLTool GUI</h3>
|
<h3>{APP_NAME}</h3>
|
||||||
<p>A visual interface for the DazedMTLTool translation system.</p>
|
<p>An AI translation tool for visual novels, RPG games, and other text-based content.</p>
|
||||||
<p>This tool helps translate visual novels, RPG games, and other text-based content using AI translation services.</p>
|
|
||||||
<p><b>Features:</b></p>
|
<p><b>Features:</b></p>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>Guided Workflow for RPG Maker and WolfDawn</li>
|
||||||
<li>Visual configuration management</li>
|
<li>Visual configuration management</li>
|
||||||
<li>Module-specific settings</li>
|
|
||||||
<li>Real-time translation monitoring</li>
|
<li>Real-time translation monitoring</li>
|
||||||
<li>File management and organization</li>
|
<li>File management and organization</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -1269,10 +1275,11 @@ def main():
|
||||||
|
|
||||||
install_qt_message_filter()
|
install_qt_message_filter()
|
||||||
ensure_linux_desktop_entry()
|
ensure_linux_desktop_entry()
|
||||||
QGuiApplication.setDesktopFileName("DazedMTLTool")
|
QGuiApplication.setDesktopFileName(APP_NAME)
|
||||||
|
|
||||||
QCoreApplication.setOrganizationName("DazedTranslations")
|
QCoreApplication.setOrganizationName(ORG_NAME)
|
||||||
QCoreApplication.setApplicationName("DazedMTLTool")
|
QCoreApplication.setApplicationName(APP_NAME)
|
||||||
|
migrate_app_settings()
|
||||||
|
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
|
|
@ -1283,7 +1290,7 @@ def main():
|
||||||
try:
|
try:
|
||||||
import ctypes
|
import ctypes
|
||||||
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
|
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
|
||||||
"DazedTranslations.DazedMTLTool.1"
|
f"{ORG_NAME}.{APP_NAME}.1"
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -101,7 +101,7 @@ from util.wolfdawn import wrap_search as wolf_ws
|
||||||
import util.dazedwrap as dazedwrap
|
import util.dazedwrap as dazedwrap
|
||||||
|
|
||||||
from gui.setup_skills_editors import SetupSkillsEditors
|
from gui.setup_skills_editors import SetupSkillsEditors
|
||||||
from util.paths import PROJECT_ROOT, VOCAB_PATH
|
from util.paths import PROJECT_ROOT, VOCAB_PATH, APP_NAME, ORG_NAME
|
||||||
from util.project_scanner import (
|
from util.project_scanner import (
|
||||||
detect_wolf_layout,
|
detect_wolf_layout,
|
||||||
find_wolf_text_archives,
|
find_wolf_text_archives,
|
||||||
|
|
@ -208,7 +208,7 @@ class WolfWorkflowTab(QWidget):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.parent_window = parent
|
self.parent_window = parent
|
||||||
try:
|
try:
|
||||||
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
|
self.settings = QSettings(ORG_NAME, APP_NAME)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.settings = None
|
self.settings = None
|
||||||
|
|
||||||
|
|
@ -3670,7 +3670,7 @@ class WolfWorkflowTab(QWidget):
|
||||||
self._run_task(task, on_done=_after)
|
self._run_task(task, on_done=_after)
|
||||||
|
|
||||||
def _tool_root(self) -> Path:
|
def _tool_root(self) -> Path:
|
||||||
"""DazedMTLTool project root (``files/``, ``translated/``, etc.)."""
|
"""DazedTL project root (``files/``, ``translated/``, etc.)."""
|
||||||
return Path(__file__).resolve().parent.parent
|
return Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
def _translated_path(self, json_name: str) -> Path | None:
|
def _translated_path(self, json_name: str) -> Path | None:
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import sys
|
||||||
import threading
|
import threading
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from util.paths import VOCAB_PATH
|
from util.paths import VOCAB_PATH, APP_NAME, ORG_NAME
|
||||||
from util.skills import load_project_setup
|
from util.skills import load_project_setup
|
||||||
from util.vocab import BASE_SEPARATOR as _SHARED_BASE_SEPARATOR
|
from util.vocab import BASE_SEPARATOR as _SHARED_BASE_SEPARATOR
|
||||||
|
|
||||||
|
|
@ -638,7 +638,7 @@ class WorkflowTab(QWidget):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.parent_window = parent
|
self.parent_window = parent
|
||||||
try:
|
try:
|
||||||
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
|
self.settings = QSettings(ORG_NAME, APP_NAME)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.settings = None
|
self.settings = None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Launcher for DazedMTLTool.desktop (finds venv, then starts the GUI).
|
# Launcher for DazedTL.desktop (finds venv, then starts the GUI).
|
||||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
cd "$ROOT" || exit 1
|
cd "$ROOT" || exit 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Launch script for DazedMTLTool GUI."""
|
"""Launch script for DazedTL GUI."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -40,7 +40,7 @@ def check_dependencies():
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Main entry point."""
|
"""Main entry point."""
|
||||||
print("DazedMTLTool GUI Launcher")
|
print("DazedTL GUI Launcher")
|
||||||
print("=" * 40)
|
print("=" * 40)
|
||||||
|
|
||||||
from util.paths import ensure_vocab_file, migrate_root_data_files
|
from util.paths import ensure_vocab_file, migrate_root_data_files
|
||||||
|
|
|
||||||
2
tests/fixtures/README.md
vendored
2
tests/fixtures/README.md
vendored
|
|
@ -35,7 +35,7 @@ Or explicitly:
|
||||||
Manual equivalent (must run from project root, with venv activated):
|
Manual equivalent (must run from project root, with venv activated):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /path/to/DazedMTLTool
|
cd /path/to/DazedTL
|
||||||
source .venv/bin/activate # or: source venv/bin/activate
|
source .venv/bin/activate # or: source venv/bin/activate
|
||||||
python -m unittest tests.test_mvmz_source_original -v
|
python -m unittest tests.test_mvmz_source_original -v
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Run DazedMTLTool unit tests using the project venv (cwd = project root).
|
# Run DazedTL unit tests using the project venv (cwd = project root).
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -320,7 +320,7 @@ class WolfBundledOnlyTests(unittest.TestCase):
|
||||||
with patch("util.wolfdawn.bundled_binary_path", return_value=missing):
|
with patch("util.wolfdawn.bundled_binary_path", return_value=missing):
|
||||||
with self.assertRaises(WolfDawnError) as ctx:
|
with self.assertRaises(WolfDawnError) as ctx:
|
||||||
ensure_wolf_binary()
|
ensure_wolf_binary()
|
||||||
self.assertIn("Update DazedMTLTool", str(ctx.exception))
|
self.assertIn("Update DazedTL", str(ctx.exception))
|
||||||
|
|
||||||
def test_ensure_wolf_binary_never_downloads(self):
|
def test_ensure_wolf_binary_never_downloads(self):
|
||||||
missing = Path("/nonexistent/wolf/missing")
|
missing = Path("/nonexistent/wolf/missing")
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ RV2JSON.exe — https://github.com/Sinflower/RV2JSON (bin/RV2JSON.exe)
|
||||||
Decrypter CLI — https://github.com/uuksu/RPGMakerDecrypter (release asset)
|
Decrypter CLI — https://github.com/uuksu/RPGMakerDecrypter (release asset)
|
||||||
|
|
||||||
End users receive curated copies via the offline bundle (``util/ace/offline/``)
|
End users receive curated copies via the offline bundle (``util/ace/offline/``)
|
||||||
shipped with DazedMTLTool updates. Upstream fetches are maintainer-only
|
shipped with DazedTL updates. Upstream fetches are maintainer-only
|
||||||
(``--refresh-offline`` or ``--force``).
|
(``--refresh-offline`` or ``--force``).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ DECRYPTER_LOCAL = ACE_DIR / "RPGMakerDecrypter-cli.exe"
|
||||||
# Legacy name shipped in older DazedMTLTool commits (local only, not in git).
|
# Legacy name shipped in older DazedMTLTool commits (local only, not in git).
|
||||||
DECRYPTER_LEGACY = ACE_DIR / "RPGMakerDecrypter.exe"
|
DECRYPTER_LEGACY = ACE_DIR / "RPGMakerDecrypter.exe"
|
||||||
|
|
||||||
USER_AGENT = "DazedMTLTool"
|
USER_AGENT = "DazedTL"
|
||||||
|
|
||||||
|
|
||||||
def _load_versions() -> dict:
|
def _load_versions() -> dict:
|
||||||
|
|
@ -184,7 +184,7 @@ def ensure_rv2json(force: bool = False, log_fn=print) -> bool:
|
||||||
if RV2JSON_LOCAL.is_file():
|
if RV2JSON_LOCAL.is_file():
|
||||||
return True
|
return True
|
||||||
_log(
|
_log(
|
||||||
f"ERROR: RV2JSON.exe not found. Update DazedMTLTool or ask the maintainer "
|
f"ERROR: RV2JSON.exe not found. Update DazedTL or ask the maintainer "
|
||||||
f"to refresh util/ace/offline/RV2JSON.exe.",
|
f"to refresh util/ace/offline/RV2JSON.exe.",
|
||||||
log_fn,
|
log_fn,
|
||||||
)
|
)
|
||||||
|
|
@ -207,7 +207,7 @@ def ensure_decrypter(force: bool = False, log_fn=print) -> bool:
|
||||||
if DECRYPTER_LOCAL.is_file() or DECRYPTER_LEGACY.is_file():
|
if DECRYPTER_LOCAL.is_file() or DECRYPTER_LEGACY.is_file():
|
||||||
return True
|
return True
|
||||||
_log(
|
_log(
|
||||||
f"ERROR: {DECRYPTER_ASSET} not found. Update DazedMTLTool or ask the maintainer "
|
f"ERROR: {DECRYPTER_ASSET} not found. Update DazedTL or ask the maintainer "
|
||||||
f"to refresh util/ace/offline/{DECRYPTER_ASSET}.",
|
f"to refresh util/ace/offline/{DECRYPTER_ASSET}.",
|
||||||
log_fn,
|
log_fn,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,217 +1,217 @@
|
||||||
"""Download / update Forge plugins from len's upstream repo (gitgud.io).
|
"""Download / update Forge plugins from len's upstream repo (gitgud.io).
|
||||||
|
|
||||||
Upstream: https://gitgud.io/zero64801/forge-mvmz
|
Upstream: https://gitgud.io/zero64801/forge-mvmz
|
||||||
CI builds a unified forge.js plugin (master branch artifacts) for MZ.
|
CI builds a unified forge.js plugin (master branch artifacts) for MZ.
|
||||||
|
|
||||||
RPG Maker MV ships with NW.js / Chrome ~65, which cannot run the rewritten
|
RPG Maker MV ships with NW.js / Chrome ~65, which cannot run the rewritten
|
||||||
Svelte Forge bundle. MV therefore keeps the pre-rewrite legacy plugin
|
Svelte Forge bundle. MV therefore keeps the pre-rewrite legacy plugin
|
||||||
(``Forge_MV.js`` / ``upstream/Forge_MV.js`` / ``legacy/Forge_MV.js``).
|
(``Forge_MV.js`` / ``upstream/Forge_MV.js`` / ``legacy/Forge_MV.js``).
|
||||||
|
|
||||||
Offline copies: util/forge/upstream/
|
Offline copies: util/forge/upstream/
|
||||||
Active plugins: util/forge/Forge_MZ.js (modern), util/forge/Forge_MV.js (legacy)
|
Active plugins: util/forge/Forge_MZ.js (modern), util/forge/Forge_MV.js (legacy)
|
||||||
|
|
||||||
End users receive curated copies shipped with DazedMTLTool updates.
|
End users receive curated copies shipped with DazedTL updates.
|
||||||
Upstream fetches are maintainer-only (``--refresh-offline`` or ``--force``)
|
Upstream fetches are maintainer-only (``--refresh-offline`` or ``--force``)
|
||||||
and only refresh the MZ modern plugin.
|
and only refresh the MZ modern plugin.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.request
|
import urllib.request
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
_PKG_ROOT = Path(__file__).resolve().parent
|
_PKG_ROOT = Path(__file__).resolve().parent
|
||||||
UPSTREAM_DIR = _PKG_ROOT / "upstream"
|
UPSTREAM_DIR = _PKG_ROOT / "upstream"
|
||||||
|
|
||||||
PLUGIN_BY_ENGINE = {
|
PLUGIN_BY_ENGINE = {
|
||||||
"MV": "Forge_MV",
|
"MV": "Forge_MV",
|
||||||
"MZ": "Forge_MZ",
|
"MZ": "Forge_MZ",
|
||||||
}
|
}
|
||||||
|
|
||||||
FORGE_PROJECT = "zero64801/forge-mvmz"
|
FORGE_PROJECT = "zero64801/forge-mvmz"
|
||||||
FORGE_BRANCH = "master"
|
FORGE_BRANCH = "master"
|
||||||
GITGUD_API = "https://gitgud.io/api/v4"
|
GITGUD_API = "https://gitgud.io/api/v4"
|
||||||
VERSION_FILE = _PKG_ROOT / ".forge_version.json"
|
VERSION_FILE = _PKG_ROOT / ".forge_version.json"
|
||||||
USER_AGENT = "DazedMTLTool"
|
USER_AGENT = "DazedTL"
|
||||||
|
|
||||||
|
|
||||||
def bundled_plugin_path(engine: str) -> Path:
|
def bundled_plugin_path(engine: str) -> Path:
|
||||||
name = PLUGIN_BY_ENGINE.get(engine)
|
name = PLUGIN_BY_ENGINE.get(engine)
|
||||||
if not name:
|
if not name:
|
||||||
raise ValueError(f"Unsupported engine: {engine}")
|
raise ValueError(f"Unsupported engine: {engine}")
|
||||||
return _PKG_ROOT / f"{name}.js"
|
return _PKG_ROOT / f"{name}.js"
|
||||||
|
|
||||||
|
|
||||||
def upstream_plugin_path(engine: str) -> Path:
|
def upstream_plugin_path(engine: str) -> Path:
|
||||||
return UPSTREAM_DIR / f"{PLUGIN_BY_ENGINE[engine]}.js"
|
return UPSTREAM_DIR / f"{PLUGIN_BY_ENGINE[engine]}.js"
|
||||||
|
|
||||||
|
|
||||||
def _load_versions() -> dict:
|
def _load_versions() -> dict:
|
||||||
if not VERSION_FILE.is_file():
|
if not VERSION_FILE.is_file():
|
||||||
return {}
|
return {}
|
||||||
try:
|
try:
|
||||||
return json.loads(VERSION_FILE.read_text(encoding="utf-8"))
|
return json.loads(VERSION_FILE.read_text(encoding="utf-8"))
|
||||||
except Exception:
|
except Exception:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def _save_versions(data: dict) -> None:
|
def _save_versions(data: dict) -> None:
|
||||||
VERSION_FILE.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
VERSION_FILE.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
def _api_json(url: str) -> dict | list:
|
def _api_json(url: str) -> dict | list:
|
||||||
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
||||||
with urllib.request.urlopen(req, timeout=60) as resp:
|
with urllib.request.urlopen(req, timeout=60) as resp:
|
||||||
return json.loads(resp.read().decode("utf-8"))
|
return json.loads(resp.read().decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
def _download(url: str, dest: Path) -> None:
|
def _download(url: str, dest: Path) -> None:
|
||||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||||
tmp = dest.with_suffix(dest.suffix + ".part")
|
tmp = dest.with_suffix(dest.suffix + ".part")
|
||||||
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
||||||
with urllib.request.urlopen(req, timeout=600) as resp, open(tmp, "wb") as fh:
|
with urllib.request.urlopen(req, timeout=600) as resp, open(tmp, "wb") as fh:
|
||||||
shutil.copyfileobj(resp, fh)
|
shutil.copyfileobj(resp, fh)
|
||||||
tmp.replace(dest)
|
tmp.replace(dest)
|
||||||
|
|
||||||
|
|
||||||
def _project_id() -> str:
|
def _project_id() -> str:
|
||||||
return urllib.parse.quote(FORGE_PROJECT, safe="")
|
return urllib.parse.quote(FORGE_PROJECT, safe="")
|
||||||
|
|
||||||
|
|
||||||
def _upstream_commit() -> str:
|
def _upstream_commit() -> str:
|
||||||
url = f"{GITGUD_API}/projects/{_project_id()}/repository/commits/{FORGE_BRANCH}"
|
url = f"{GITGUD_API}/projects/{_project_id()}/repository/commits/{FORGE_BRANCH}"
|
||||||
data = _api_json(url)
|
data = _api_json(url)
|
||||||
commit = data.get("id") or data.get("sha") or ""
|
commit = data.get("id") or data.get("sha") or ""
|
||||||
if not commit:
|
if not commit:
|
||||||
raise RuntimeError(f"Could not resolve upstream commit for {FORGE_PROJECT}")
|
raise RuntimeError(f"Could not resolve upstream commit for {FORGE_PROJECT}")
|
||||||
return commit
|
return commit
|
||||||
|
|
||||||
|
|
||||||
def _artifact_url(filename: str) -> str:
|
def _artifact_url(filename: str) -> str:
|
||||||
"""Latest successful build_job artifact on the tracked branch."""
|
"""Latest successful build_job artifact on the tracked branch."""
|
||||||
return (
|
return (
|
||||||
f"https://gitgud.io/{FORGE_PROJECT}/-/jobs/artifacts/{FORGE_BRANCH}"
|
f"https://gitgud.io/{FORGE_PROJECT}/-/jobs/artifacts/{FORGE_BRANCH}"
|
||||||
f"/raw/{filename}?job=build_job"
|
f"/raw/{filename}?job=build_job"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _log(msg: str, log_fn) -> None:
|
def _log(msg: str, log_fn) -> None:
|
||||||
if log_fn:
|
if log_fn:
|
||||||
log_fn(msg)
|
log_fn(msg)
|
||||||
else:
|
else:
|
||||||
print(msg, flush=True)
|
print(msg, flush=True)
|
||||||
|
|
||||||
|
|
||||||
def _seed_from_offline(engine: str, log_fn) -> bool:
|
def _seed_from_offline(engine: str, log_fn) -> bool:
|
||||||
"""Copy bundled offline copy into the active plugin path if missing."""
|
"""Copy bundled offline copy into the active plugin path if missing."""
|
||||||
dest = bundled_plugin_path(engine)
|
dest = bundled_plugin_path(engine)
|
||||||
if dest.is_file():
|
if dest.is_file():
|
||||||
return True
|
return True
|
||||||
offline = upstream_plugin_path(engine)
|
offline = upstream_plugin_path(engine)
|
||||||
if not offline.is_file():
|
if not offline.is_file():
|
||||||
return False
|
return False
|
||||||
shutil.copy2(offline, dest)
|
shutil.copy2(offline, dest)
|
||||||
if log_fn:
|
if log_fn:
|
||||||
_log(f"Using offline bundled {dest.name}", log_fn)
|
_log(f"Using offline bundled {dest.name}", log_fn)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _fetch_plugins(log_fn) -> bytes:
|
def _fetch_plugins(log_fn) -> bytes:
|
||||||
"""Download the unified forge.js CI artifact."""
|
"""Download the unified forge.js CI artifact."""
|
||||||
_log(f"Downloading forge.js from {FORGE_PROJECT} ({FORGE_BRANCH} CI)...", log_fn)
|
_log(f"Downloading forge.js from {FORGE_PROJECT} ({FORGE_BRANCH} CI)...", log_fn)
|
||||||
return _download_bytes(_artifact_url("forge.js"))
|
return _download_bytes(_artifact_url("forge.js"))
|
||||||
|
|
||||||
|
|
||||||
def _install_modern_mz_bytes(data: bytes) -> None:
|
def _install_modern_mz_bytes(data: bytes) -> None:
|
||||||
"""Write modern forge.js into MZ active + offline paths only (never MV)."""
|
"""Write modern forge.js into MZ active + offline paths only (never MV)."""
|
||||||
UPSTREAM_DIR.mkdir(parents=True, exist_ok=True)
|
UPSTREAM_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
bundled_plugin_path("MZ").write_bytes(data)
|
bundled_plugin_path("MZ").write_bytes(data)
|
||||||
upstream_plugin_path("MZ").write_bytes(data)
|
upstream_plugin_path("MZ").write_bytes(data)
|
||||||
|
|
||||||
|
|
||||||
def _download_bytes(url: str) -> bytes:
|
def _download_bytes(url: str) -> bytes:
|
||||||
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
|
||||||
with urllib.request.urlopen(req, timeout=600) as resp:
|
with urllib.request.urlopen(req, timeout=600) as resp:
|
||||||
return resp.read()
|
return resp.read()
|
||||||
|
|
||||||
|
|
||||||
def refresh_forge_plugins(log_fn=print) -> bool:
|
def refresh_forge_plugins(log_fn=print) -> bool:
|
||||||
"""Download len's latest forge.js and refresh the MZ modern plugin only."""
|
"""Download len's latest forge.js and refresh the MZ modern plugin only."""
|
||||||
try:
|
try:
|
||||||
commit = _upstream_commit()
|
commit = _upstream_commit()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
_log(f"ERROR: could not contact upstream ({exc})", log_fn)
|
_log(f"ERROR: could not contact upstream ({exc})", log_fn)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = _fetch_plugins(log_fn)
|
data = _fetch_plugins(log_fn)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
_log(f"ERROR: download failed for forge.js ({exc})", log_fn)
|
_log(f"ERROR: download failed for forge.js ({exc})", log_fn)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not data.strip().startswith(b"//"):
|
if not data.strip().startswith(b"//"):
|
||||||
_log("ERROR: forge.js download did not look like a plugin file.", log_fn)
|
_log("ERROR: forge.js download did not look like a plugin file.", log_fn)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
_install_modern_mz_bytes(data)
|
_install_modern_mz_bytes(data)
|
||||||
|
|
||||||
versions = _load_versions()
|
versions = _load_versions()
|
||||||
versions["commit"] = commit
|
versions["commit"] = commit
|
||||||
versions["branch"] = FORGE_BRANCH
|
versions["branch"] = FORGE_BRANCH
|
||||||
_save_versions(versions)
|
_save_versions(versions)
|
||||||
_log(
|
_log(
|
||||||
f"Forge MZ plugin updated ({commit[:12]}). "
|
f"Forge MZ plugin updated ({commit[:12]}). "
|
||||||
"MV keeps the legacy Chrome-65-compatible plugin.",
|
"MV keeps the legacy Chrome-65-compatible plugin.",
|
||||||
log_fn,
|
log_fn,
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def seed_forge_plugins(log_fn=None) -> None:
|
def seed_forge_plugins(log_fn=None) -> None:
|
||||||
"""Copy offline-bundled Forge plugins if missing (no network)."""
|
"""Copy offline-bundled Forge plugins if missing (no network)."""
|
||||||
for engine in PLUGIN_BY_ENGINE:
|
for engine in PLUGIN_BY_ENGINE:
|
||||||
_seed_from_offline(engine, log_fn)
|
_seed_from_offline(engine, log_fn)
|
||||||
|
|
||||||
|
|
||||||
def ensure_forge_plugins(force: bool = False, log_fn=print) -> bool:
|
def ensure_forge_plugins(force: bool = False, log_fn=print) -> bool:
|
||||||
"""Ensure Forge plugins are present from the offline bundle (no upstream fetch)."""
|
"""Ensure Forge plugins are present from the offline bundle (no upstream fetch)."""
|
||||||
for engine in PLUGIN_BY_ENGINE:
|
for engine in PLUGIN_BY_ENGINE:
|
||||||
_seed_from_offline(engine, log_fn)
|
_seed_from_offline(engine, log_fn)
|
||||||
|
|
||||||
missing = [e for e in PLUGIN_BY_ENGINE if not bundled_plugin_path(e).is_file()]
|
missing = [e for e in PLUGIN_BY_ENGINE if not bundled_plugin_path(e).is_file()]
|
||||||
if missing and not force:
|
if missing and not force:
|
||||||
names = ", ".join(PLUGIN_BY_ENGINE[e] + ".js" for e in missing)
|
names = ", ".join(PLUGIN_BY_ENGINE[e] + ".js" for e in missing)
|
||||||
_log(
|
_log(
|
||||||
f"ERROR: Forge plugin(s) missing ({names}). "
|
f"ERROR: Forge plugin(s) missing ({names}). "
|
||||||
"Update DazedMTLTool or ask the maintainer to refresh util/forge/upstream/.",
|
"Update DazedTL or ask the maintainer to refresh util/forge/upstream/.",
|
||||||
log_fn,
|
log_fn,
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if force:
|
if force:
|
||||||
if refresh_forge_plugins(log_fn=log_fn):
|
if refresh_forge_plugins(log_fn=log_fn):
|
||||||
return True
|
return True
|
||||||
have_local = all(bundled_plugin_path(e).is_file() for e in PLUGIN_BY_ENGINE)
|
have_local = all(bundled_plugin_path(e).is_file() for e in PLUGIN_BY_ENGINE)
|
||||||
if not have_local:
|
if not have_local:
|
||||||
_log("ERROR: Forge plugin update failed.", log_fn)
|
_log("ERROR: Forge plugin update failed.", log_fn)
|
||||||
return False
|
return False
|
||||||
_log("Warning: could not update Forge plugins; using local copy.", log_fn)
|
_log("Warning: could not update Forge plugins; using local copy.", log_fn)
|
||||||
|
|
||||||
return all(bundled_plugin_path(e).is_file() for e in PLUGIN_BY_ENGINE)
|
return all(bundled_plugin_path(e).is_file() for e in PLUGIN_BY_ENGINE)
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
if "--refresh-offline" in sys.argv:
|
if "--refresh-offline" in sys.argv:
|
||||||
UPSTREAM_DIR.mkdir(parents=True, exist_ok=True)
|
UPSTREAM_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
ok = refresh_forge_plugins()
|
ok = refresh_forge_plugins()
|
||||||
return 0 if ok else 1
|
return 0 if ok else 1
|
||||||
force = "--force" in sys.argv or "-f" in sys.argv
|
force = "--force" in sys.argv or "-f" in sys.argv
|
||||||
return 0 if ensure_forge_plugins(force=force) else 1
|
return 0 if ensure_forge_plugins(force=force) else 1
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
raise SystemExit(main())
|
raise SystemExit(main())
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ def format_patch_config(
|
||||||
branch = (branch or "").strip() or DEFAULT_BRANCH
|
branch = (branch or "").strip() or DEFAULT_BRANCH
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
"# Generated by DazedMTLTool from Config → Game Update defaults.",
|
"# Generated by DazedTL from Config → Game Update defaults.",
|
||||||
"# Edit repo= per game. forge/host/username usually stay the same.",
|
"# Edit repo= per game. forge/host/username usually stay the same.",
|
||||||
f"forge={forge}",
|
f"forge={forge}",
|
||||||
f"host={host}",
|
f"host={host}",
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from util.paths import ICON_PATH, PROJECT_ROOT
|
from util.paths import APP_NAME, ICON_PATH, LEGACY_APP_NAME, PROJECT_ROOT
|
||||||
|
|
||||||
DESKTOP_ID = "DazedMTLTool"
|
DESKTOP_ID = APP_NAME
|
||||||
LAUNCH_SCRIPT = PROJECT_ROOT / "scripts" / "launch.sh"
|
LAUNCH_SCRIPT = PROJECT_ROOT / "scripts" / "launch.sh"
|
||||||
|
|
||||||
# Qt logs this on Wayland whenever a dialog tries to steal focus (harmless noise).
|
# Qt logs this on Wayland whenever a dialog tries to steal focus (harmless noise).
|
||||||
|
|
@ -63,7 +63,7 @@ def _desktop_content(root: Path, icon: Path, launch: Path) -> str:
|
||||||
"[Desktop Entry]\n"
|
"[Desktop Entry]\n"
|
||||||
"Type=Application\n"
|
"Type=Application\n"
|
||||||
f"Name={DESKTOP_ID}\n"
|
f"Name={DESKTOP_ID}\n"
|
||||||
"GenericName=Translation Tool\n"
|
"GenericName=AI Translation Tool\n"
|
||||||
"Comment=AI translation tool for visual novels and RPG games\n"
|
"Comment=AI translation tool for visual novels and RPG games\n"
|
||||||
f"Exec={launch}\n"
|
f"Exec={launch}\n"
|
||||||
f"Icon={icon}\n"
|
f"Icon={icon}\n"
|
||||||
|
|
@ -75,7 +75,7 @@ def _desktop_content(root: Path, icon: Path, launch: Path) -> str:
|
||||||
|
|
||||||
|
|
||||||
def ensure_linux_desktop_entry() -> None:
|
def ensure_linux_desktop_entry() -> None:
|
||||||
"""Write ~/.local/share/applications/DazedMTLTool.desktop with absolute paths."""
|
"""Write ~/.local/share/applications/DazedTL.desktop with absolute paths."""
|
||||||
if not sys.platform.startswith("linux"):
|
if not sys.platform.startswith("linux"):
|
||||||
return
|
return
|
||||||
if not LAUNCH_SCRIPT.is_file() or not ICON_PATH.is_file():
|
if not LAUNCH_SCRIPT.is_file() or not ICON_PATH.is_file():
|
||||||
|
|
@ -85,6 +85,13 @@ def ensure_linux_desktop_entry() -> None:
|
||||||
content = _desktop_content(PROJECT_ROOT, ICON_PATH, LAUNCH_SCRIPT)
|
content = _desktop_content(PROJECT_ROOT, ICON_PATH, LAUNCH_SCRIPT)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
legacy = desktop_path.parent / f"{LEGACY_APP_NAME}.desktop"
|
||||||
|
if legacy.is_file() and legacy.resolve() != desktop_path.resolve():
|
||||||
|
try:
|
||||||
|
legacy.unlink()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
if desktop_path.is_file() and desktop_path.read_text(encoding="utf-8") == content:
|
if desktop_path.is_file() and desktop_path.read_text(encoding="utf-8") == content:
|
||||||
return
|
return
|
||||||
desktop_path.parent.mkdir(parents=True, exist_ok=True)
|
desktop_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,11 @@ from __future__ import annotations
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Product identity (QSettings / desktop / window titles).
|
||||||
|
ORG_NAME = "DazedTranslations"
|
||||||
|
APP_NAME = "DazedTL"
|
||||||
|
LEGACY_APP_NAME = "DazedMTLTool"
|
||||||
|
|
||||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
DATA_DIR = PROJECT_ROOT / "data"
|
DATA_DIR = PROJECT_ROOT / "data"
|
||||||
VOCAB_PATH = DATA_DIR / "vocab.txt"
|
VOCAB_PATH = DATA_DIR / "vocab.txt"
|
||||||
|
|
@ -55,6 +60,26 @@ def migrate_prompt_to_skills() -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_app_settings() -> None:
|
||||||
|
"""Copy QSettings from the legacy app name into DazedTL once."""
|
||||||
|
try:
|
||||||
|
from PyQt5.QtCore import QSettings
|
||||||
|
except ImportError:
|
||||||
|
return
|
||||||
|
|
||||||
|
new = QSettings(ORG_NAME, APP_NAME)
|
||||||
|
if str(new.value("_migrated_from_legacy_app", "")) == "1":
|
||||||
|
return
|
||||||
|
|
||||||
|
old = QSettings(ORG_NAME, LEGACY_APP_NAME)
|
||||||
|
old_keys = list(old.allKeys())
|
||||||
|
new_keys = [k for k in new.allKeys() if k != "_migrated_from_legacy_app"]
|
||||||
|
if old_keys and not new_keys:
|
||||||
|
for key in old_keys:
|
||||||
|
new.setValue(key, old.value(key))
|
||||||
|
new.setValue("_migrated_from_legacy_app", "1")
|
||||||
|
|
||||||
|
|
||||||
migrate_root_data_files()
|
migrate_root_data_files()
|
||||||
migrate_prompt_to_skills()
|
migrate_prompt_to_skills()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
Shared translation utilities for DazedMTLTool.
|
Shared translation utilities for DazedTL.
|
||||||
Centralized translation function used across all modules.
|
Centralized translation function used across all modules.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
"""WolfDawn CLI bootstrap and thin subprocess wrappers.
|
"""WolfDawn CLI bootstrap and thin subprocess wrappers.
|
||||||
|
|
||||||
WolfDawn is the Rust toolchain (https://gitgud.io/zero64801/wolfdawn) that
|
WolfDawn is the Rust toolchain (https://gitgud.io/zero64801/wolfdawn) that
|
||||||
unpacks, extracts, injects, and repacks WOLF RPG Editor game data. DazedMTLTool
|
unpacks, extracts, injects, and repacks WOLF RPG Editor game data. DazedTL
|
||||||
ships prebuilt ``wolf`` binaries offline under ``util/wolfdawn/bin/<platform>/``
|
ships prebuilt ``wolf`` binaries offline under ``util/wolfdawn/bin/<platform>/``
|
||||||
so end users never need a Rust toolchain or a live upstream fetch. Binaries are
|
so end users never need a Rust toolchain or a live upstream fetch. Binaries are
|
||||||
updated when DazedMTLTool itself is updated. Everything the Wolf workflow needs
|
updated when DazedTL itself is updated. Everything the Wolf workflow needs
|
||||||
goes through the helpers here so command syntax and exit-code handling live in
|
goes through the helpers here so command syntax and exit-code handling live in
|
||||||
one place.
|
one place.
|
||||||
|
|
||||||
|
|
@ -215,7 +215,7 @@ _RELEASE_ASSET_MATCH = {
|
||||||
}
|
}
|
||||||
_DOWNLOAD_UA = (
|
_DOWNLOAD_UA = (
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) "
|
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||||
"DazedMTLTool WolfDawn-fetch"
|
"DazedTL WolfDawn-fetch"
|
||||||
)
|
)
|
||||||
|
|
||||||
PathLike = Union[str, Path]
|
PathLike = Union[str, Path]
|
||||||
|
|
@ -394,7 +394,7 @@ def ensure_wolf_binary(log_fn=print) -> Path:
|
||||||
return bundled
|
return bundled
|
||||||
raise WolfDawnError(
|
raise WolfDawnError(
|
||||||
f"No bundled WolfDawn binary for '{_platform_dir()}' at {bundled}. "
|
f"No bundled WolfDawn binary for '{_platform_dir()}' at {bundled}. "
|
||||||
"Update DazedMTLTool to receive a prebuilt wolf binary."
|
"Update DazedTL to receive a prebuilt wolf binary."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"""Check for and apply WolfDawn ``wolf`` binary updates (maintainer-only upstream fetch).
|
"""Check for and apply WolfDawn ``wolf`` binary updates (maintainer-only upstream fetch).
|
||||||
|
|
||||||
End users receive prebuilt binaries under ``util/wolfdawn/bin/<platform>/`` via
|
End users receive prebuilt binaries under ``util/wolfdawn/bin/<platform>/`` via
|
||||||
DazedMTLTool updates. Maintainers refresh them with ``--refresh-all`` or ``--force``.
|
DazedTL updates. Maintainers refresh them with ``--refresh-all`` or ``--force``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
@ -150,7 +150,7 @@ def ensure_wolfdawn_binary(force: bool = False, log_fn=print) -> bool:
|
||||||
return True
|
return True
|
||||||
_log(
|
_log(
|
||||||
f"ERROR: no bundled WolfDawn binary for '{platform}' at {bundled}. "
|
f"ERROR: no bundled WolfDawn binary for '{platform}' at {bundled}. "
|
||||||
"Update DazedMTLTool to receive a prebuilt wolf binary.",
|
"Update DazedTL to receive a prebuilt wolf binary.",
|
||||||
log_fn,
|
log_fn,
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue