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:
DazedAnon 2026-07-22 12:27:22 -05:00
parent 41691879dc
commit 4d1b01c870
30 changed files with 4353 additions and 4311 deletions

View file

@ -1,10 +1,10 @@
[Desktop Entry]
Type=Application
Name=DazedMTLTool
GenericName=Translation Tool
Name=DazedTL
GenericName=AI Translation Tool
Comment=AI translation tool for visual novels and RPG games
Exec=bash -c 'exec "$(cd "$(dirname "%k")" && pwd)/scripts/launch.sh"'
Icon=%k/assets/icon.png
Terminal=false
Categories=Development;Utility;
StartupWMClass=DazedMTLTool
StartupWMClass=DazedTL

View file

@ -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.
## 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.
- **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. 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
@ -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.
**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.
@ -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) |
| **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.
@ -378,7 +378,7 @@ You can also select a block of text, right-click, and choose **Copilot → Fix /
### 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 //"*
- *"Change the wordwrap logic to break on full-width punctuation"*

View file

@ -2,7 +2,7 @@
setlocal EnableDelayedExpansion
echo ==========================================
echo DazedMTLTool Startup Script
echo DazedTL Startup Script
echo ==========================================
echo.
@ -180,7 +180,7 @@ echo.
:: Launch the GUI
echo ==========================================
echo Launching DazedMTLTool GUI...
echo Launching DazedTL GUI...
echo ==========================================
echo.

View file

@ -1,5 +1,5 @@
#!/usr/bin/env bash
# DazedMTLTool startup script for Linux/macOS
# DazedTL startup script for Linux/macOS
set -o pipefail
@ -150,7 +150,7 @@ ensure_vocab_file() {
}
echo "=========================================="
echo " DazedMTLTool Startup Script"
echo " DazedTL Startup Script"
echo "=========================================="
echo
@ -222,7 +222,7 @@ fi
echo
echo "=========================================="
echo " Launching DazedMTLTool GUI..."
echo " Launching DazedTL GUI..."
echo "=========================================="
echo

View file

@ -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),
plus modules for Ren'Py, Tyrano, Kirikiri, CSV, and more.

View file

@ -69,7 +69,7 @@ Use either:
### Example: open the game in Cursor
1. In Cursor: **File → Open Folder** and select the game root (where `Game.exe` or Wolf `Data` lives).
2. In DazedMTLTool Workflow Step 2, click **Copy Project Setup**.
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).
4. Paste the agent's labeled blocks back into the Workflow editors.

View file

@ -14,7 +14,7 @@ You just downloaded a small MV game. You want English dialogue, menus that make
### 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.
3. Open the game folder in Cursor so the agent can read JSON later.

View file

@ -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.
---
@ -99,7 +99,7 @@ Output as short imperative bullets suitable to paste into `skills/quirks.md`.
### Game skill rules (for `game_skill` block)
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):
- `世界観 (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)
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):
- `世界観 (Theme / setting)` - genre, world type, core atmosphere

View file

@ -2,7 +2,7 @@
setlocal EnableExtensions EnableDelayedExpansion
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"
REM Game root is this batch file's folder (not %%CD%%, so full-path launches still work).
set "GAME_ROOT=!GU_ROOT!"

View file

@ -1,159 +1,159 @@
# Apply Patch
1. Click Code
2. Click Download ZIP
3. Extract to game folder and Replace All.
## Future Patching
1. Run **`GameUpdate.bat`** to auto patch (Windows).
### 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/`**.
**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\`.
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.
### patch-config.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.
```txt
forge=gitlab
host=gitgud.io
username=YOUR_ORG_OR_USER
repo=YOUR_PATCH_REPO
branch=main
```
| Key | Meaning |
|-----|---------|
| `forge` | `gitlab` (default), `github`, or `forgejo` (`gitea` also accepted) |
| `host` | Hostname only. Defaults: `gitgud.io` / `github.com` / `codeberg.org` |
| `username` | Owner / org / group (`owner=` / `org=` aliases work) |
| `repo` | Repository name |
| `branch` | Branch to track |
Examples:
```txt
# GitLab / gitgud
forge=gitlab
host=gitgud.io
username=myorg
repo=cool-game-en
branch=main
# GitHub
forge=github
host=github.com
username=myorg
repo=cool-game-en
branch=main
# Forgejo / Codeberg
forge=forgejo
host=codeberg.org
username=myorg
repo=cool-game-en
branch=main
```
Older configs with only `username` / `repo` / `branch` still work (assumes GitLab on `gitgud.io`).
# Troubleshooting
**GAMEUPDATE.bat doesn't update and closes immediately**
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
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 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.
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
TLDR 3 steps.
Fork the repository.
Make the changes.
Submit a merge request.
If everything looks good and doesn't break things I'll merge it in.
Longer Version:
# Required Software:
* [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)
# Guide to contributing
### 1. Fork the Repository
- Go to the repository you want to fork.
- Click the "Fork" button.
### 2. Clone Your Fork
- Clone your forked repository to your local machine.
```sh
git clone https://gitgud.io/YOUR_USERNAME/REPO_NAME.git
```
### 3. Make Your Changes (In VSCode)
- Edit the files locally on your new branch using VSCode.
- Add and commit your changes.
```sh
git add .
git commit -m "Description of your changes"
```
### 4. Push Your Changes
- Push your changes to your fork on GitGud.io.
```sh
git push origin your-feature-branch
```
### 5. Create a Merge Request
- Go to your fork on GitGud.io.
- Click on "Merge Requests" in the sidebar.
- Click the "New merge request" button.
- 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.
---
## Example
Assuming you want to fork a repository named `example-project`:
### 1. Fork the Repo
- Navigate to `https://gitgud.io/original_user/example-project` and click "Fork".
### 2. Clone Your Fork
```sh
git clone https://gitgud.io/YOUR_USERNAME/example-project.git
```
### 3. Make Changes and Commit
```sh
# Make changes to the files
git add .
git commit -m "Add new feature to example project"
```
### 4. Push Changes
```sh
git push origin add-new-feature
```
### 5. Create a 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`)
# Apply Patch
1. Click Code
2. Click Download ZIP
3. Extract to game folder and Replace All.
## Future Patching
1. Run **`GameUpdate.bat`** to auto patch (Windows).
### 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/`**.
**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 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.
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
Create `gameupdate/patch-config.txt` next to `patch.ps1` (see `patch-config.example.txt`).
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
forge=gitlab
host=gitgud.io
username=YOUR_ORG_OR_USER
repo=YOUR_PATCH_REPO
branch=main
```
| Key | Meaning |
|-----|---------|
| `forge` | `gitlab` (default), `github`, or `forgejo` (`gitea` also accepted) |
| `host` | Hostname only. Defaults: `gitgud.io` / `github.com` / `codeberg.org` |
| `username` | Owner / org / group (`owner=` / `org=` aliases work) |
| `repo` | Repository name |
| `branch` | Branch to track |
Examples:
```txt
# GitLab / gitgud
forge=gitlab
host=gitgud.io
username=myorg
repo=cool-game-en
branch=main
# GitHub
forge=github
host=github.com
username=myorg
repo=cool-game-en
branch=main
# Forgejo / Codeberg
forge=forgejo
host=codeberg.org
username=myorg
repo=cool-game-en
branch=main
```
Older configs with only `username` / `repo` / `branch` still work (assumes GitLab on `gitgud.io`).
# Troubleshooting
**GAMEUPDATE.bat doesn't update and closes immediately**
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
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 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.
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
TLDR 3 steps.
Fork the repository.
Make the changes.
Submit a merge request.
If everything looks good and doesn't break things I'll merge it in.
Longer Version:
# Required Software:
* [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)
# Guide to contributing
### 1. Fork the Repository
- Go to the repository you want to fork.
- Click the "Fork" button.
### 2. Clone Your Fork
- Clone your forked repository to your local machine.
```sh
git clone https://gitgud.io/YOUR_USERNAME/REPO_NAME.git
```
### 3. Make Your Changes (In VSCode)
- Edit the files locally on your new branch using VSCode.
- Add and commit your changes.
```sh
git add .
git commit -m "Description of your changes"
```
### 4. Push Your Changes
- Push your changes to your fork on GitGud.io.
```sh
git push origin your-feature-branch
```
### 5. Create a Merge Request
- Go to your fork on GitGud.io.
- Click on "Merge Requests" in the sidebar.
- Click the "New merge request" button.
- 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.
---
## Example
Assuming you want to fork a repository named `example-project`:
### 1. Fork the Repo
- Navigate to `https://gitgud.io/original_user/example-project` and click "Fork".
### 2. Clone Your Fork
```sh
git clone https://gitgud.io/YOUR_USERNAME/example-project.git
```
### 3. Make Changes and Commit
```sh
# Make changes to the files
git add .
git commit -m "Add new feature to example project"
```
### 4. Push Changes
```sh
git push origin add-new-feature
```
### 5. Create a 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`)
- Fill in the details and submit the merge request

View file

@ -1,7 +1,7 @@
# GameUpdate patch source (copy to patch-config.txt and edit).
# 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
# those into this file automatically (you still set repo= per game).

View file

@ -1,23 +1,23 @@
"""
DazedMTLTool GUI Package
"""
__version__ = "1.0.0"
__author__ = "DazedTranslations"
# Package imports
from .main import DazedMTLGUI
from .config_tab import ConfigTab
from .rpgmaker_tab import RPGMakerTab
from .log_viewer import LogViewer
from .file_manager import FileManager
from .workflow_tab import WorkflowTab
__all__ = [
"DazedMTLGUI",
"ConfigTab",
"RPGMakerTab",
"LogViewer",
"FileManager",
"WorkflowTab",
]
"""
DazedTL GUI Package
"""
__version__ = "1.0.0"
__author__ = "DazedTranslations"
# Package imports
from .main import DazedMTLGUI
from .config_tab import ConfigTab
from .rpgmaker_tab import RPGMakerTab
from .log_viewer import LogViewer
from .file_manager import FileManager
from .workflow_tab import WorkflowTab
__all__ = [
"DazedMTLGUI",
"ConfigTab",
"RPGMakerTab",
"LogViewer",
"FileManager",
"WorkflowTab",
]

View file

@ -1,426 +1,428 @@
"""
File Manager - Handle input and output files
"""
from pathlib import Path
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QTreeWidget, QTreeWidgetItem,
QPushButton, QLabel, QFileDialog, QMessageBox, QGroupBox,
QSplitter, QTextEdit, QProgressBar, QCheckBox
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings
import json
class FileManager(QWidget):
"""Widget for managing input and output files."""
files_changed = pyqtSignal()
def __init__(self):
super().__init__()
# Persistent settings for remembering last-open directory
try:
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
except Exception:
self.settings = None
self.input_files = []
self.output_directory = Path("translated")
self.init_ui()
self.refresh_files()
def init_ui(self):
"""Initialize the user interface."""
layout = QVBoxLayout()
# Create splitter for input and output sections
splitter = QSplitter(Qt.Horizontal)
# Input files section
input_group = self.create_input_section()
splitter.addWidget(input_group)
# Output files section
output_group = self.create_output_section()
splitter.addWidget(output_group)
splitter.setSizes([400, 400])
layout.addWidget(splitter)
# Progress section
progress_group = self.create_progress_section()
layout.addWidget(progress_group)
self.setLayout(layout)
def create_input_section(self):
"""Create the input files section."""
group = QGroupBox("Input Files")
layout = QVBoxLayout()
# Buttons
button_layout = QHBoxLayout()
self.add_files_button = QPushButton("Add Files")
self.add_files_button.clicked.connect(self.add_input_files)
self.add_folder_button = QPushButton("Add Folder")
self.add_folder_button.clicked.connect(self.add_input_folder)
self.remove_files_button = QPushButton("Remove Selected")
self.remove_files_button.clicked.connect(self.remove_selected_files)
self.clear_files_button = QPushButton("Clear All")
self.clear_files_button.clicked.connect(self.clear_input_files)
button_layout.addWidget(self.add_files_button)
button_layout.addWidget(self.add_folder_button)
button_layout.addWidget(self.remove_files_button)
button_layout.addWidget(self.clear_files_button)
layout.addLayout(button_layout)
# File tree
self.input_tree = QTreeWidget()
self.input_tree.setHeaderLabels(["File", "Size", "Type"])
self.input_tree.itemSelectionChanged.connect(self.on_input_selection_changed)
layout.addWidget(self.input_tree)
# File info
self.input_info = QTextEdit()
self.input_info.setMaximumHeight(100)
self.input_info.setReadOnly(True)
layout.addWidget(self.input_info)
group.setLayout(layout)
return group
def create_output_section(self):
"""Create the output files section."""
group = QGroupBox("Output Files")
layout = QVBoxLayout()
# Output directory selection
dir_layout = QHBoxLayout()
self.output_dir_label = QLabel(str(self.output_directory))
self.output_dir_label.setStyleSheet("border: 1px solid #555; padding: 5px;")
self.browse_output_button = QPushButton("Browse")
self.browse_output_button.clicked.connect(self.browse_output_directory)
dir_layout.addWidget(QLabel("Output Directory:"))
dir_layout.addWidget(self.output_dir_label)
dir_layout.addWidget(self.browse_output_button)
layout.addLayout(dir_layout)
# Output file tree
self.output_tree = QTreeWidget()
self.output_tree.setHeaderLabels(["File", "Size", "Modified"])
self.output_tree.itemSelectionChanged.connect(self.on_output_selection_changed)
layout.addWidget(self.output_tree)
# Output file preview
self.output_preview = QTextEdit()
self.output_preview.setMaximumHeight(100)
self.output_preview.setReadOnly(True)
layout.addWidget(self.output_preview)
# Output buttons
output_button_layout = QHBoxLayout()
self.open_output_button = QPushButton("Open in Editor")
self.open_output_button.clicked.connect(self.open_output_file)
self.delete_output_button = QPushButton("Delete Selected")
self.delete_output_button.clicked.connect(self.delete_output_file)
output_button_layout.addWidget(self.open_output_button)
output_button_layout.addWidget(self.delete_output_button)
output_button_layout.addStretch()
layout.addLayout(output_button_layout)
group.setLayout(layout)
return group
def create_progress_section(self):
"""Create the progress monitoring section."""
group = QGroupBox("Translation Progress")
layout = QVBoxLayout()
# Progress bar
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
layout.addWidget(self.progress_bar)
# Status and options
status_layout = QHBoxLayout()
self.status_label = QLabel("Ready")
self.backup_cb = QCheckBox("Create Backups")
self.backup_cb.setChecked(True)
status_layout.addWidget(self.status_label)
status_layout.addStretch()
status_layout.addWidget(self.backup_cb)
layout.addLayout(status_layout)
group.setLayout(layout)
return group
def add_input_files(self):
"""Add input files via dialog."""
# Try to restore last used directory
start_dir = ""
try:
if self.settings:
start_dir = self.settings.value("last_open_dir", "") or ""
except Exception:
start_dir = ""
files, _ = QFileDialog.getOpenFileNames(
self,
"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)
# Persist directory used
try:
if self.settings and len(files) > 0:
import os
self.settings.setValue("last_open_dir", os.path.dirname(files[0]))
except Exception:
pass
self.refresh_input_files()
def add_input_folder(self):
"""Add all files from a folder."""
folder = QFileDialog.getExistingDirectory(self, "Select Input Folder")
if folder:
folder_path = Path(folder)
for file_path in folder_path.rglob("*.json"):
file_str = str(file_path)
if file_str not in self.input_files:
self.input_files.append(file_str)
self.refresh_input_files()
def remove_selected_files(self):
"""Remove selected input files."""
selected_items = self.input_tree.selectedItems()
for item in selected_items:
file_path = item.text(0)
if file_path in self.input_files:
self.input_files.remove(file_path)
self.refresh_input_files()
def clear_input_files(self):
"""Clear all input files."""
self.input_files.clear()
self.refresh_input_files()
def refresh_input_files(self):
"""Refresh the input files display."""
self.input_tree.clear()
for file_path in self.input_files:
path_obj = Path(file_path)
if path_obj.exists():
size = path_obj.stat().st_size
file_type = path_obj.suffix
item = QTreeWidgetItem([
path_obj.name,
f"{size:,} bytes",
file_type
])
item.setToolTip(0, str(path_obj))
self.input_tree.addTopLevelItem(item)
else:
# File doesn't exist - show in red
item = QTreeWidgetItem([
f"{path_obj.name} (Missing)",
"0 bytes",
"N/A"
])
item.setBackground(0, Qt.red)
self.input_tree.addTopLevelItem(item)
# Update info
self.update_input_info()
def refresh_output_files(self):
"""Refresh the output files display."""
self.output_tree.clear()
if self.output_directory.exists():
for file_path in self.output_directory.iterdir():
if file_path.is_file():
size = file_path.stat().st_size
modified = file_path.stat().st_mtime
import datetime
modified_str = datetime.datetime.fromtimestamp(modified).strftime("%Y-%m-%d %H:%M")
item = QTreeWidgetItem([
file_path.name,
f"{size:,} bytes",
modified_str
])
item.setToolTip(0, str(file_path))
self.output_tree.addTopLevelItem(item)
def browse_output_directory(self):
"""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))
self.refresh_output_files()
def refresh_files(self):
"""Refresh both input and output file displays."""
self.refresh_input_files()
self.refresh_output_files()
def on_input_selection_changed(self):
"""Handle input file selection change."""
selected_items = self.input_tree.selectedItems()
if selected_items:
item = selected_items[0]
file_name = item.text(0)
# Find the full path
full_path = None
for file_path in self.input_files:
if Path(file_path).name == file_name:
full_path = file_path
break
if full_path and Path(full_path).exists():
try:
with open(full_path, 'r', encoding='utf-8') as f:
content = f.read()
# Show preview of file structure
if full_path.endswith('.json'):
data = json.loads(content)
if isinstance(data, dict):
keys = list(data.keys())[:10] # Show first 10 keys
preview = f"JSON Object with {len(data)} keys:\n"
preview += "\n".join(keys)
if len(data) > 10:
preview += f"\n... and {len(data) - 10} more"
elif isinstance(data, list):
preview = f"JSON Array with {len(data)} items"
else:
preview = f"JSON: {type(data).__name__}"
else:
preview = content[:500] + "..." if len(content) > 500 else content
self.input_info.setPlainText(preview)
except Exception as e:
self.input_info.setPlainText(f"Error reading file: {str(e)}")
def on_output_selection_changed(self):
"""Handle output file selection change."""
selected_items = self.output_tree.selectedItems()
if selected_items:
item = selected_items[0]
file_name = item.text(0)
file_path = self.output_directory / file_name
if file_path.exists():
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
preview = content[:500] + "..." if len(content) > 500 else content
self.output_preview.setPlainText(preview)
except Exception as e:
self.output_preview.setPlainText(f"Error reading file: {str(e)}")
def open_output_file(self):
"""Open selected output file in external editor."""
selected_items = self.output_tree.selectedItems()
if selected_items:
item = selected_items[0]
file_name = item.text(0)
file_path = self.output_directory / file_name
if file_path.exists():
import subprocess
try:
subprocess.run(['notepad.exe', str(file_path)], check=True)
except subprocess.CalledProcessError:
QMessageBox.warning(self, "Warning", "Could not open file in external editor")
def delete_output_file(self):
"""Delete selected output file."""
selected_items = self.output_tree.selectedItems()
if selected_items:
item = selected_items[0]
file_name = item.text(0)
file_path = self.output_directory / file_name
reply = QMessageBox.question(
self,
"Confirm Deletion",
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()
self.refresh_output_files()
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to delete file:\n{str(e)}")
def update_input_info(self):
"""Update the input files information."""
total_files = len(self.input_files)
total_size = 0
for file_path in self.input_files:
path_obj = Path(file_path)
if path_obj.exists():
total_size += path_obj.stat().st_size
info = f"Total Files: {total_files}\nTotal Size: {total_size:,} bytes"
# Count by file type
extensions = {}
for file_path in self.input_files:
ext = Path(file_path).suffix
extensions[ext] = extensions.get(ext, 0) + 1
if extensions:
info += "\n\nFile Types:"
for ext, count in sorted(extensions.items()):
info += f"\n{ext or 'No extension'}: {count}"
self.input_info.setPlainText(info)
def get_input_files(self):
"""Get list of input files."""
return self.input_files.copy()
def get_output_directory(self):
"""Get output directory path."""
return self.output_directory
"""
File Manager - Handle input and output files
"""
from pathlib import Path
from PyQt5.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QTreeWidget, QTreeWidgetItem,
QPushButton, QLabel, QFileDialog, QMessageBox, QGroupBox,
QSplitter, QTextEdit, QProgressBar, QCheckBox
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QSettings
import json
from util.paths import APP_NAME, ORG_NAME
class FileManager(QWidget):
"""Widget for managing input and output files."""
files_changed = pyqtSignal()
def __init__(self):
super().__init__()
# Persistent settings for remembering last-open directory
try:
self.settings = QSettings(ORG_NAME, APP_NAME)
except Exception:
self.settings = None
self.input_files = []
self.output_directory = Path("translated")
self.init_ui()
self.refresh_files()
def init_ui(self):
"""Initialize the user interface."""
layout = QVBoxLayout()
# Create splitter for input and output sections
splitter = QSplitter(Qt.Horizontal)
# Input files section
input_group = self.create_input_section()
splitter.addWidget(input_group)
# Output files section
output_group = self.create_output_section()
splitter.addWidget(output_group)
splitter.setSizes([400, 400])
layout.addWidget(splitter)
# Progress section
progress_group = self.create_progress_section()
layout.addWidget(progress_group)
self.setLayout(layout)
def create_input_section(self):
"""Create the input files section."""
group = QGroupBox("Input Files")
layout = QVBoxLayout()
# Buttons
button_layout = QHBoxLayout()
self.add_files_button = QPushButton("Add Files")
self.add_files_button.clicked.connect(self.add_input_files)
self.add_folder_button = QPushButton("Add Folder")
self.add_folder_button.clicked.connect(self.add_input_folder)
self.remove_files_button = QPushButton("Remove Selected")
self.remove_files_button.clicked.connect(self.remove_selected_files)
self.clear_files_button = QPushButton("Clear All")
self.clear_files_button.clicked.connect(self.clear_input_files)
button_layout.addWidget(self.add_files_button)
button_layout.addWidget(self.add_folder_button)
button_layout.addWidget(self.remove_files_button)
button_layout.addWidget(self.clear_files_button)
layout.addLayout(button_layout)
# File tree
self.input_tree = QTreeWidget()
self.input_tree.setHeaderLabels(["File", "Size", "Type"])
self.input_tree.itemSelectionChanged.connect(self.on_input_selection_changed)
layout.addWidget(self.input_tree)
# File info
self.input_info = QTextEdit()
self.input_info.setMaximumHeight(100)
self.input_info.setReadOnly(True)
layout.addWidget(self.input_info)
group.setLayout(layout)
return group
def create_output_section(self):
"""Create the output files section."""
group = QGroupBox("Output Files")
layout = QVBoxLayout()
# Output directory selection
dir_layout = QHBoxLayout()
self.output_dir_label = QLabel(str(self.output_directory))
self.output_dir_label.setStyleSheet("border: 1px solid #555; padding: 5px;")
self.browse_output_button = QPushButton("Browse")
self.browse_output_button.clicked.connect(self.browse_output_directory)
dir_layout.addWidget(QLabel("Output Directory:"))
dir_layout.addWidget(self.output_dir_label)
dir_layout.addWidget(self.browse_output_button)
layout.addLayout(dir_layout)
# Output file tree
self.output_tree = QTreeWidget()
self.output_tree.setHeaderLabels(["File", "Size", "Modified"])
self.output_tree.itemSelectionChanged.connect(self.on_output_selection_changed)
layout.addWidget(self.output_tree)
# Output file preview
self.output_preview = QTextEdit()
self.output_preview.setMaximumHeight(100)
self.output_preview.setReadOnly(True)
layout.addWidget(self.output_preview)
# Output buttons
output_button_layout = QHBoxLayout()
self.open_output_button = QPushButton("Open in Editor")
self.open_output_button.clicked.connect(self.open_output_file)
self.delete_output_button = QPushButton("Delete Selected")
self.delete_output_button.clicked.connect(self.delete_output_file)
output_button_layout.addWidget(self.open_output_button)
output_button_layout.addWidget(self.delete_output_button)
output_button_layout.addStretch()
layout.addLayout(output_button_layout)
group.setLayout(layout)
return group
def create_progress_section(self):
"""Create the progress monitoring section."""
group = QGroupBox("Translation Progress")
layout = QVBoxLayout()
# Progress bar
self.progress_bar = QProgressBar()
self.progress_bar.setVisible(False)
layout.addWidget(self.progress_bar)
# Status and options
status_layout = QHBoxLayout()
self.status_label = QLabel("Ready")
self.backup_cb = QCheckBox("Create Backups")
self.backup_cb.setChecked(True)
status_layout.addWidget(self.status_label)
status_layout.addStretch()
status_layout.addWidget(self.backup_cb)
layout.addLayout(status_layout)
group.setLayout(layout)
return group
def add_input_files(self):
"""Add input files via dialog."""
# Try to restore last used directory
start_dir = ""
try:
if self.settings:
start_dir = self.settings.value("last_open_dir", "") or ""
except Exception:
start_dir = ""
files, _ = QFileDialog.getOpenFileNames(
self,
"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)
# Persist directory used
try:
if self.settings and len(files) > 0:
import os
self.settings.setValue("last_open_dir", os.path.dirname(files[0]))
except Exception:
pass
self.refresh_input_files()
def add_input_folder(self):
"""Add all files from a folder."""
folder = QFileDialog.getExistingDirectory(self, "Select Input Folder")
if folder:
folder_path = Path(folder)
for file_path in folder_path.rglob("*.json"):
file_str = str(file_path)
if file_str not in self.input_files:
self.input_files.append(file_str)
self.refresh_input_files()
def remove_selected_files(self):
"""Remove selected input files."""
selected_items = self.input_tree.selectedItems()
for item in selected_items:
file_path = item.text(0)
if file_path in self.input_files:
self.input_files.remove(file_path)
self.refresh_input_files()
def clear_input_files(self):
"""Clear all input files."""
self.input_files.clear()
self.refresh_input_files()
def refresh_input_files(self):
"""Refresh the input files display."""
self.input_tree.clear()
for file_path in self.input_files:
path_obj = Path(file_path)
if path_obj.exists():
size = path_obj.stat().st_size
file_type = path_obj.suffix
item = QTreeWidgetItem([
path_obj.name,
f"{size:,} bytes",
file_type
])
item.setToolTip(0, str(path_obj))
self.input_tree.addTopLevelItem(item)
else:
# File doesn't exist - show in red
item = QTreeWidgetItem([
f"{path_obj.name} (Missing)",
"0 bytes",
"N/A"
])
item.setBackground(0, Qt.red)
self.input_tree.addTopLevelItem(item)
# Update info
self.update_input_info()
def refresh_output_files(self):
"""Refresh the output files display."""
self.output_tree.clear()
if self.output_directory.exists():
for file_path in self.output_directory.iterdir():
if file_path.is_file():
size = file_path.stat().st_size
modified = file_path.stat().st_mtime
import datetime
modified_str = datetime.datetime.fromtimestamp(modified).strftime("%Y-%m-%d %H:%M")
item = QTreeWidgetItem([
file_path.name,
f"{size:,} bytes",
modified_str
])
item.setToolTip(0, str(file_path))
self.output_tree.addTopLevelItem(item)
def browse_output_directory(self):
"""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))
self.refresh_output_files()
def refresh_files(self):
"""Refresh both input and output file displays."""
self.refresh_input_files()
self.refresh_output_files()
def on_input_selection_changed(self):
"""Handle input file selection change."""
selected_items = self.input_tree.selectedItems()
if selected_items:
item = selected_items[0]
file_name = item.text(0)
# Find the full path
full_path = None
for file_path in self.input_files:
if Path(file_path).name == file_name:
full_path = file_path
break
if full_path and Path(full_path).exists():
try:
with open(full_path, 'r', encoding='utf-8') as f:
content = f.read()
# Show preview of file structure
if full_path.endswith('.json'):
data = json.loads(content)
if isinstance(data, dict):
keys = list(data.keys())[:10] # Show first 10 keys
preview = f"JSON Object with {len(data)} keys:\n"
preview += "\n".join(keys)
if len(data) > 10:
preview += f"\n... and {len(data) - 10} more"
elif isinstance(data, list):
preview = f"JSON Array with {len(data)} items"
else:
preview = f"JSON: {type(data).__name__}"
else:
preview = content[:500] + "..." if len(content) > 500 else content
self.input_info.setPlainText(preview)
except Exception as e:
self.input_info.setPlainText(f"Error reading file: {str(e)}")
def on_output_selection_changed(self):
"""Handle output file selection change."""
selected_items = self.output_tree.selectedItems()
if selected_items:
item = selected_items[0]
file_name = item.text(0)
file_path = self.output_directory / file_name
if file_path.exists():
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
preview = content[:500] + "..." if len(content) > 500 else content
self.output_preview.setPlainText(preview)
except Exception as e:
self.output_preview.setPlainText(f"Error reading file: {str(e)}")
def open_output_file(self):
"""Open selected output file in external editor."""
selected_items = self.output_tree.selectedItems()
if selected_items:
item = selected_items[0]
file_name = item.text(0)
file_path = self.output_directory / file_name
if file_path.exists():
import subprocess
try:
subprocess.run(['notepad.exe', str(file_path)], check=True)
except subprocess.CalledProcessError:
QMessageBox.warning(self, "Warning", "Could not open file in external editor")
def delete_output_file(self):
"""Delete selected output file."""
selected_items = self.output_tree.selectedItems()
if selected_items:
item = selected_items[0]
file_name = item.text(0)
file_path = self.output_directory / file_name
reply = QMessageBox.question(
self,
"Confirm Deletion",
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()
self.refresh_output_files()
except Exception as e:
QMessageBox.critical(self, "Error", f"Failed to delete file:\n{str(e)}")
def update_input_info(self):
"""Update the input files information."""
total_files = len(self.input_files)
total_size = 0
for file_path in self.input_files:
path_obj = Path(file_path)
if path_obj.exists():
total_size += path_obj.stat().st_size
info = f"Total Files: {total_files}\nTotal Size: {total_size:,} bytes"
# Count by file type
extensions = {}
for file_path in self.input_files:
ext = Path(file_path).suffix
extensions[ext] = extensions.get(ext, 0) + 1
if extensions:
info += "\n\nFile Types:"
for ext, count in sorted(extensions.items()):
info += f"\n{ext or 'No extension'}: {count}"
self.input_info.setPlainText(info)
def get_input_files(self):
"""Get list of input files."""
return self.input_files.copy()
def get_output_directory(self):
"""Get output directory path."""
return self.output_directory

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""
DazedMTLTool GUI - Main Application
A PyQt-based graphical user interface for the DazedMTLTool translation system.
DazedTL GUI - Main Application
A PyQt-based graphical user interface for the DazedTL translation system.
"""
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 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:
@ -56,7 +63,7 @@ def check_tool_update() -> str | None:
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
@ -151,7 +158,7 @@ class UpdateThread(QThread):
@classmethod
def fetch_latest_sha(cls) -> str:
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:
return json.loads(resp.read())["commit"]["id"]
@ -167,7 +174,7 @@ class UpdateThread(QThread):
def _download_archive(self, zip_path: Path):
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:
total = int(resp.headers.get("Content-Length", 0) or 0)
@ -367,7 +374,7 @@ class UpdateDialog(QDialog):
title_col = QVBoxLayout()
title_col.setSpacing(2)
self.title_label = QLabel("DazedMTLTool Update")
self.title_label = QLabel(f"{APP_NAME} Update")
self.title_label.setObjectName("updateTitle")
self.subtitle_label = QLabel("Stay current with the latest fixes and features.")
self.subtitle_label.setObjectName("updateDetail")
@ -577,7 +584,7 @@ class UpdateDialog(QDialog):
self.current_version_label.setText(sha)
self.headline_label.setText("Update installed successfully")
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.")
if isinstance(self.parent(), DazedMTLGUI):
@ -620,7 +627,7 @@ from gui.skills_tab import SkillsTab
from gui.batch_tab import BatchTab
class DazedMTLGUI(QMainWindow):
"""Main GUI window for the DazedMTLTool."""
"""Main GUI window for DazedTL."""
PAGE_GUIDE = 0
PAGE_WORKFLOW = 1
@ -631,7 +638,7 @@ class DazedMTLGUI(QMainWindow):
def __init__(self):
super().__init__()
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
self.settings = QSettings(ORG_NAME, APP_NAME)
self._pending_tool_sha: str | None = None
self._update_check_thread = None
self._shutdown_started = False
@ -791,16 +798,16 @@ class DazedMTLGUI(QMainWindow):
# Update window title only when non-default scale is active
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:
self.setWindowTitle("DazedMTLTool - Visual Translation Interface")
self.setWindowTitle(f"{APP_NAME} - Visual Translation Interface")
except Exception as e:
print(f"Warning: Could not apply font scaling: {e}")
def init_ui(self):
"""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
screen = QApplication.primaryScreen()
@ -1012,7 +1019,7 @@ class DazedMTLGUI(QMainWindow):
return container
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():
return
self._update_check_thread = BackgroundUpdateCheckThread(self)
@ -1221,15 +1228,14 @@ class DazedMTLGUI(QMainWindow):
"""Show the about dialog."""
QMessageBox.about(
self,
"About DazedMTLTool GUI",
"""
<h3>DazedMTLTool GUI</h3>
<p>A visual interface for the DazedMTLTool translation system.</p>
<p>This tool helps translate visual novels, RPG games, and other text-based content using AI translation services.</p>
f"About {APP_NAME}",
f"""
<h3>{APP_NAME}</h3>
<p>An AI translation tool for visual novels, RPG games, and other text-based content.</p>
<p><b>Features:</b></p>
<ul>
<li>Guided Workflow for RPG Maker and WolfDawn</li>
<li>Visual configuration management</li>
<li>Module-specific settings</li>
<li>Real-time translation monitoring</li>
<li>File management and organization</li>
</ul>
@ -1269,10 +1275,11 @@ def main():
install_qt_message_filter()
ensure_linux_desktop_entry()
QGuiApplication.setDesktopFileName("DazedMTLTool")
QGuiApplication.setDesktopFileName(APP_NAME)
QCoreApplication.setOrganizationName("DazedTranslations")
QCoreApplication.setApplicationName("DazedMTLTool")
QCoreApplication.setOrganizationName(ORG_NAME)
QCoreApplication.setApplicationName(APP_NAME)
migrate_app_settings()
app = QApplication(sys.argv)
@ -1283,7 +1290,7 @@ def main():
try:
import ctypes
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
"DazedTranslations.DazedMTLTool.1"
f"{ORG_NAME}.{APP_NAME}.1"
)
except Exception:
pass

File diff suppressed because it is too large Load diff

View file

@ -101,7 +101,7 @@ from util.wolfdawn import wrap_search as wolf_ws
import util.dazedwrap as dazedwrap
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 (
detect_wolf_layout,
find_wolf_text_archives,
@ -208,7 +208,7 @@ class WolfWorkflowTab(QWidget):
super().__init__(parent)
self.parent_window = parent
try:
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
self.settings = QSettings(ORG_NAME, APP_NAME)
except Exception:
self.settings = None
@ -3670,7 +3670,7 @@ class WolfWorkflowTab(QWidget):
self._run_task(task, on_done=_after)
def _tool_root(self) -> Path:
"""DazedMTLTool project root (``files/``, ``translated/``, etc.)."""
"""DazedTL project root (``files/``, ``translated/``, etc.)."""
return Path(__file__).resolve().parent.parent
def _translated_path(self, json_name: str) -> Path | None:

View file

@ -20,7 +20,7 @@ import sys
import threading
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.vocab import BASE_SEPARATOR as _SHARED_BASE_SEPARATOR
@ -638,7 +638,7 @@ class WorkflowTab(QWidget):
super().__init__(parent)
self.parent_window = parent
try:
self.settings = QSettings("DazedTranslations", "DazedMTLTool")
self.settings = QSettings(ORG_NAME, APP_NAME)
except Exception:
self.settings = None

View file

@ -1,5 +1,5 @@
#!/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)"
cd "$ROOT" || exit 1

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Launch script for DazedMTLTool GUI."""
"""Launch script for DazedTL GUI."""
import sys
from pathlib import Path
@ -40,7 +40,7 @@ def check_dependencies():
def main():
"""Main entry point."""
print("DazedMTLTool GUI Launcher")
print("DazedTL GUI Launcher")
print("=" * 40)
from util.paths import ensure_vocab_file, migrate_root_data_files

View file

@ -35,7 +35,7 @@ Or explicitly:
Manual equivalent (must run from project root, with venv activated):
```bash
cd /path/to/DazedMTLTool
cd /path/to/DazedTL
source .venv/bin/activate # or: source venv/bin/activate
python -m unittest tests.test_mvmz_source_original -v
```

View file

@ -1,5 +1,5 @@
#!/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

View file

@ -320,7 +320,7 @@ class WolfBundledOnlyTests(unittest.TestCase):
with patch("util.wolfdawn.bundled_binary_path", return_value=missing):
with self.assertRaises(WolfDawnError) as ctx:
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):
missing = Path("/nonexistent/wolf/missing")

View file

@ -4,7 +4,7 @@ RV2JSON.exe — https://github.com/Sinflower/RV2JSON (bin/RV2JSON.exe)
Decrypter CLI https://github.com/uuksu/RPGMakerDecrypter (release asset)
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``).
"""
@ -31,7 +31,7 @@ DECRYPTER_LOCAL = ACE_DIR / "RPGMakerDecrypter-cli.exe"
# Legacy name shipped in older DazedMTLTool commits (local only, not in git).
DECRYPTER_LEGACY = ACE_DIR / "RPGMakerDecrypter.exe"
USER_AGENT = "DazedMTLTool"
USER_AGENT = "DazedTL"
def _load_versions() -> dict:
@ -184,7 +184,7 @@ def ensure_rv2json(force: bool = False, log_fn=print) -> bool:
if RV2JSON_LOCAL.is_file():
return True
_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.",
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():
return True
_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}.",
log_fn,
)

View file

@ -1,217 +1,217 @@
"""Download / update Forge plugins from len's upstream repo (gitgud.io).
Upstream: https://gitgud.io/zero64801/forge-mvmz
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
Svelte Forge bundle. MV therefore keeps the pre-rewrite legacy plugin
(``Forge_MV.js`` / ``upstream/Forge_MV.js`` / ``legacy/Forge_MV.js``).
Offline copies: util/forge/upstream/
Active plugins: util/forge/Forge_MZ.js (modern), util/forge/Forge_MV.js (legacy)
End users receive curated copies shipped with DazedMTLTool updates.
Upstream fetches are maintainer-only (``--refresh-offline`` or ``--force``)
and only refresh the MZ modern plugin.
"""
from __future__ import annotations
import json
import shutil
import sys
import urllib.parse
import urllib.request
from pathlib import Path
_PKG_ROOT = Path(__file__).resolve().parent
UPSTREAM_DIR = _PKG_ROOT / "upstream"
PLUGIN_BY_ENGINE = {
"MV": "Forge_MV",
"MZ": "Forge_MZ",
}
FORGE_PROJECT = "zero64801/forge-mvmz"
FORGE_BRANCH = "master"
GITGUD_API = "https://gitgud.io/api/v4"
VERSION_FILE = _PKG_ROOT / ".forge_version.json"
USER_AGENT = "DazedMTLTool"
def bundled_plugin_path(engine: str) -> Path:
name = PLUGIN_BY_ENGINE.get(engine)
if not name:
raise ValueError(f"Unsupported engine: {engine}")
return _PKG_ROOT / f"{name}.js"
def upstream_plugin_path(engine: str) -> Path:
return UPSTREAM_DIR / f"{PLUGIN_BY_ENGINE[engine]}.js"
def _load_versions() -> dict:
if not VERSION_FILE.is_file():
return {}
try:
return json.loads(VERSION_FILE.read_text(encoding="utf-8"))
except Exception:
return {}
def _save_versions(data: dict) -> None:
VERSION_FILE.write_text(json.dumps(data, indent=2), encoding="utf-8")
def _api_json(url: str) -> dict | list:
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req, timeout=60) as resp:
return json.loads(resp.read().decode("utf-8"))
def _download(url: str, dest: Path) -> None:
dest.parent.mkdir(parents=True, exist_ok=True)
tmp = dest.with_suffix(dest.suffix + ".part")
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req, timeout=600) as resp, open(tmp, "wb") as fh:
shutil.copyfileobj(resp, fh)
tmp.replace(dest)
def _project_id() -> str:
return urllib.parse.quote(FORGE_PROJECT, safe="")
def _upstream_commit() -> str:
url = f"{GITGUD_API}/projects/{_project_id()}/repository/commits/{FORGE_BRANCH}"
data = _api_json(url)
commit = data.get("id") or data.get("sha") or ""
if not commit:
raise RuntimeError(f"Could not resolve upstream commit for {FORGE_PROJECT}")
return commit
def _artifact_url(filename: str) -> str:
"""Latest successful build_job artifact on the tracked branch."""
return (
f"https://gitgud.io/{FORGE_PROJECT}/-/jobs/artifacts/{FORGE_BRANCH}"
f"/raw/{filename}?job=build_job"
)
def _log(msg: str, log_fn) -> None:
if log_fn:
log_fn(msg)
else:
print(msg, flush=True)
def _seed_from_offline(engine: str, log_fn) -> bool:
"""Copy bundled offline copy into the active plugin path if missing."""
dest = bundled_plugin_path(engine)
if dest.is_file():
return True
offline = upstream_plugin_path(engine)
if not offline.is_file():
return False
shutil.copy2(offline, dest)
if log_fn:
_log(f"Using offline bundled {dest.name}", log_fn)
return True
def _fetch_plugins(log_fn) -> bytes:
"""Download the unified forge.js CI artifact."""
_log(f"Downloading forge.js from {FORGE_PROJECT} ({FORGE_BRANCH} CI)...", log_fn)
return _download_bytes(_artifact_url("forge.js"))
def _install_modern_mz_bytes(data: bytes) -> None:
"""Write modern forge.js into MZ active + offline paths only (never MV)."""
UPSTREAM_DIR.mkdir(parents=True, exist_ok=True)
bundled_plugin_path("MZ").write_bytes(data)
upstream_plugin_path("MZ").write_bytes(data)
def _download_bytes(url: str) -> bytes:
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req, timeout=600) as resp:
return resp.read()
def refresh_forge_plugins(log_fn=print) -> bool:
"""Download len's latest forge.js and refresh the MZ modern plugin only."""
try:
commit = _upstream_commit()
except Exception as exc:
_log(f"ERROR: could not contact upstream ({exc})", log_fn)
return False
try:
data = _fetch_plugins(log_fn)
except Exception as exc:
_log(f"ERROR: download failed for forge.js ({exc})", log_fn)
return False
if not data.strip().startswith(b"//"):
_log("ERROR: forge.js download did not look like a plugin file.", log_fn)
return False
_install_modern_mz_bytes(data)
versions = _load_versions()
versions["commit"] = commit
versions["branch"] = FORGE_BRANCH
_save_versions(versions)
_log(
f"Forge MZ plugin updated ({commit[:12]}). "
"MV keeps the legacy Chrome-65-compatible plugin.",
log_fn,
)
return True
def seed_forge_plugins(log_fn=None) -> None:
"""Copy offline-bundled Forge plugins if missing (no network)."""
for engine in PLUGIN_BY_ENGINE:
_seed_from_offline(engine, log_fn)
def ensure_forge_plugins(force: bool = False, log_fn=print) -> bool:
"""Ensure Forge plugins are present from the offline bundle (no upstream fetch)."""
for engine in PLUGIN_BY_ENGINE:
_seed_from_offline(engine, log_fn)
missing = [e for e in PLUGIN_BY_ENGINE if not bundled_plugin_path(e).is_file()]
if missing and not force:
names = ", ".join(PLUGIN_BY_ENGINE[e] + ".js" for e in missing)
_log(
f"ERROR: Forge plugin(s) missing ({names}). "
"Update DazedMTLTool or ask the maintainer to refresh util/forge/upstream/.",
log_fn,
)
return False
if force:
if refresh_forge_plugins(log_fn=log_fn):
return True
have_local = all(bundled_plugin_path(e).is_file() for e in PLUGIN_BY_ENGINE)
if not have_local:
_log("ERROR: Forge plugin update failed.", log_fn)
return False
_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)
def main() -> int:
if "--refresh-offline" in sys.argv:
UPSTREAM_DIR.mkdir(parents=True, exist_ok=True)
ok = refresh_forge_plugins()
return 0 if ok else 1
force = "--force" in sys.argv or "-f" in sys.argv
return 0 if ensure_forge_plugins(force=force) else 1
if __name__ == "__main__":
raise SystemExit(main())
"""Download / update Forge plugins from len's upstream repo (gitgud.io).
Upstream: https://gitgud.io/zero64801/forge-mvmz
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
Svelte Forge bundle. MV therefore keeps the pre-rewrite legacy plugin
(``Forge_MV.js`` / ``upstream/Forge_MV.js`` / ``legacy/Forge_MV.js``).
Offline copies: util/forge/upstream/
Active plugins: util/forge/Forge_MZ.js (modern), util/forge/Forge_MV.js (legacy)
End users receive curated copies shipped with DazedTL updates.
Upstream fetches are maintainer-only (``--refresh-offline`` or ``--force``)
and only refresh the MZ modern plugin.
"""
from __future__ import annotations
import json
import shutil
import sys
import urllib.parse
import urllib.request
from pathlib import Path
_PKG_ROOT = Path(__file__).resolve().parent
UPSTREAM_DIR = _PKG_ROOT / "upstream"
PLUGIN_BY_ENGINE = {
"MV": "Forge_MV",
"MZ": "Forge_MZ",
}
FORGE_PROJECT = "zero64801/forge-mvmz"
FORGE_BRANCH = "master"
GITGUD_API = "https://gitgud.io/api/v4"
VERSION_FILE = _PKG_ROOT / ".forge_version.json"
USER_AGENT = "DazedTL"
def bundled_plugin_path(engine: str) -> Path:
name = PLUGIN_BY_ENGINE.get(engine)
if not name:
raise ValueError(f"Unsupported engine: {engine}")
return _PKG_ROOT / f"{name}.js"
def upstream_plugin_path(engine: str) -> Path:
return UPSTREAM_DIR / f"{PLUGIN_BY_ENGINE[engine]}.js"
def _load_versions() -> dict:
if not VERSION_FILE.is_file():
return {}
try:
return json.loads(VERSION_FILE.read_text(encoding="utf-8"))
except Exception:
return {}
def _save_versions(data: dict) -> None:
VERSION_FILE.write_text(json.dumps(data, indent=2), encoding="utf-8")
def _api_json(url: str) -> dict | list:
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req, timeout=60) as resp:
return json.loads(resp.read().decode("utf-8"))
def _download(url: str, dest: Path) -> None:
dest.parent.mkdir(parents=True, exist_ok=True)
tmp = dest.with_suffix(dest.suffix + ".part")
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req, timeout=600) as resp, open(tmp, "wb") as fh:
shutil.copyfileobj(resp, fh)
tmp.replace(dest)
def _project_id() -> str:
return urllib.parse.quote(FORGE_PROJECT, safe="")
def _upstream_commit() -> str:
url = f"{GITGUD_API}/projects/{_project_id()}/repository/commits/{FORGE_BRANCH}"
data = _api_json(url)
commit = data.get("id") or data.get("sha") or ""
if not commit:
raise RuntimeError(f"Could not resolve upstream commit for {FORGE_PROJECT}")
return commit
def _artifact_url(filename: str) -> str:
"""Latest successful build_job artifact on the tracked branch."""
return (
f"https://gitgud.io/{FORGE_PROJECT}/-/jobs/artifacts/{FORGE_BRANCH}"
f"/raw/{filename}?job=build_job"
)
def _log(msg: str, log_fn) -> None:
if log_fn:
log_fn(msg)
else:
print(msg, flush=True)
def _seed_from_offline(engine: str, log_fn) -> bool:
"""Copy bundled offline copy into the active plugin path if missing."""
dest = bundled_plugin_path(engine)
if dest.is_file():
return True
offline = upstream_plugin_path(engine)
if not offline.is_file():
return False
shutil.copy2(offline, dest)
if log_fn:
_log(f"Using offline bundled {dest.name}", log_fn)
return True
def _fetch_plugins(log_fn) -> bytes:
"""Download the unified forge.js CI artifact."""
_log(f"Downloading forge.js from {FORGE_PROJECT} ({FORGE_BRANCH} CI)...", log_fn)
return _download_bytes(_artifact_url("forge.js"))
def _install_modern_mz_bytes(data: bytes) -> None:
"""Write modern forge.js into MZ active + offline paths only (never MV)."""
UPSTREAM_DIR.mkdir(parents=True, exist_ok=True)
bundled_plugin_path("MZ").write_bytes(data)
upstream_plugin_path("MZ").write_bytes(data)
def _download_bytes(url: str) -> bytes:
req = urllib.request.Request(url, headers={"User-Agent": USER_AGENT})
with urllib.request.urlopen(req, timeout=600) as resp:
return resp.read()
def refresh_forge_plugins(log_fn=print) -> bool:
"""Download len's latest forge.js and refresh the MZ modern plugin only."""
try:
commit = _upstream_commit()
except Exception as exc:
_log(f"ERROR: could not contact upstream ({exc})", log_fn)
return False
try:
data = _fetch_plugins(log_fn)
except Exception as exc:
_log(f"ERROR: download failed for forge.js ({exc})", log_fn)
return False
if not data.strip().startswith(b"//"):
_log("ERROR: forge.js download did not look like a plugin file.", log_fn)
return False
_install_modern_mz_bytes(data)
versions = _load_versions()
versions["commit"] = commit
versions["branch"] = FORGE_BRANCH
_save_versions(versions)
_log(
f"Forge MZ plugin updated ({commit[:12]}). "
"MV keeps the legacy Chrome-65-compatible plugin.",
log_fn,
)
return True
def seed_forge_plugins(log_fn=None) -> None:
"""Copy offline-bundled Forge plugins if missing (no network)."""
for engine in PLUGIN_BY_ENGINE:
_seed_from_offline(engine, log_fn)
def ensure_forge_plugins(force: bool = False, log_fn=print) -> bool:
"""Ensure Forge plugins are present from the offline bundle (no upstream fetch)."""
for engine in PLUGIN_BY_ENGINE:
_seed_from_offline(engine, log_fn)
missing = [e for e in PLUGIN_BY_ENGINE if not bundled_plugin_path(e).is_file()]
if missing and not force:
names = ", ".join(PLUGIN_BY_ENGINE[e] + ".js" for e in missing)
_log(
f"ERROR: Forge plugin(s) missing ({names}). "
"Update DazedTL or ask the maintainer to refresh util/forge/upstream/.",
log_fn,
)
return False
if force:
if refresh_forge_plugins(log_fn=log_fn):
return True
have_local = all(bundled_plugin_path(e).is_file() for e in PLUGIN_BY_ENGINE)
if not have_local:
_log("ERROR: Forge plugin update failed.", log_fn)
return False
_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)
def main() -> int:
if "--refresh-offline" in sys.argv:
UPSTREAM_DIR.mkdir(parents=True, exist_ok=True)
ok = refresh_forge_plugins()
return 0 if ok else 1
force = "--force" in sys.argv or "-f" in sys.argv
return 0 if ensure_forge_plugins(force=force) else 1
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -82,7 +82,7 @@ def format_patch_config(
branch = (branch or "").strip() or DEFAULT_BRANCH
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.",
f"forge={forge}",
f"host={host}",

View file

@ -7,9 +7,9 @@ import subprocess
import sys
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"
# 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"
"Type=Application\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"
f"Exec={launch}\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:
"""Write ~/.local/share/applications/DazedMTLTool.desktop with absolute paths."""
"""Write ~/.local/share/applications/DazedTL.desktop with absolute paths."""
if not sys.platform.startswith("linux"):
return
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)
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:
return
desktop_path.parent.mkdir(parents=True, exist_ok=True)

View file

@ -4,6 +4,11 @@ from __future__ import annotations
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
DATA_DIR = PROJECT_ROOT / "data"
VOCAB_PATH = DATA_DIR / "vocab.txt"
@ -55,6 +60,26 @@ def migrate_prompt_to_skills() -> None:
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_prompt_to_skills()

View file

@ -1,5 +1,5 @@
"""
Shared translation utilities for DazedMTLTool.
Shared translation utilities for DazedTL.
Centralized translation function used across all modules.
"""

View file

@ -1,10 +1,10 @@
"""WolfDawn CLI bootstrap and thin subprocess wrappers.
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>/``
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
one place.
@ -215,7 +215,7 @@ _RELEASE_ASSET_MATCH = {
}
_DOWNLOAD_UA = (
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) "
"DazedMTLTool WolfDawn-fetch"
"DazedTL WolfDawn-fetch"
)
PathLike = Union[str, Path]
@ -394,7 +394,7 @@ def ensure_wolf_binary(log_fn=print) -> Path:
return bundled
raise WolfDawnError(
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."
)

View file

@ -1,7 +1,7 @@
"""Check for and apply WolfDawn ``wolf`` binary updates (maintainer-only upstream fetch).
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
@ -150,7 +150,7 @@ def ensure_wolfdawn_binary(force: bool = False, log_fn=print) -> bool:
return True
_log(
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,
)
return False