Surely cleaning up all this mess wont break anything. surely

This commit is contained in:
DazedAnon 2026-06-15 17:34:32 -05:00
parent 563299ba21
commit 4dd9d2bbd4
38 changed files with 16162 additions and 15976 deletions

4
.gitignore vendored
View file

@ -18,8 +18,8 @@ __pycache__
!assets/*.png
!.pre-commit-config.yaml
!prompt.txt
!vocab_base.txt
!data/prompt.txt
!data/vocab_base.txt
!requirements.txt
!util/tl_inspector/TLInspector.js
!util/forge/Forge_MZ.js

View file

@ -3,6 +3,6 @@
'hooks':
- 'id': 'set-defaults'
'name': 'Set Defaults in rpgmakermvmz.py'
'entry': 'python set_defaults.py'
'entry': 'python -m util.defaults'
'language': 'python'
'files': '^modules/rpgmakermvmz.py$'

10
DazedMTLTool.desktop Normal file
View file

@ -0,0 +1,10 @@
[Desktop Entry]
Type=Application
Name=DazedMTLTool
GenericName=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

View file

@ -124,12 +124,11 @@ This means Python wasn't added to your PATH. You have two options:
### 3. Launch the GUI
**Double-click `START.bat`**. It will:
- Create a virtual environment automatically.
- Install all dependencies.
- Launch the GUI.
**Windows:** Double-click `START.bat`. It will create a virtual environment, install dependencies, and open the GUI.
That's it! From now on, just double-click `START.bat` to open the tool.
**Linux/macOS:** Run `./START.sh`, or double-click `DazedMTLTool.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.
---
@ -160,10 +159,10 @@ Specialized tabs with extra options for those specific engines.
## Vocab & Prompt
### vocab.txt
### data/vocab.txt
This file gives the AI context about your game — character names, genders, recurring terms, etc. The better your vocab file, the more consistent the translation.
Open `vocab.txt` (or copy `vocab.txt.example` to `vocab.txt` if it doesn't exist) and add entries like:
On first run, `data/vocab.txt` is created automatically from `data/vocab_base.txt` if it does not exist yet. Add entries like:
```plaintext
# Game Characters
@ -176,14 +175,14 @@ Format: Japanese name, English name in parentheses, then gender.
> **Note:** A very large vocab file can increase API costs and potentially reduce quality. Focus on the most important characters and terms.
### prompt.txt
This is the system prompt sent to the AI. A default `prompt.txt` is included and works well for most games. You generally don't need to edit it unless you want to customize the translation style.
### data/prompt.txt
This is the system prompt sent to the AI. A default `data/prompt.txt` is included and works well for most games. You generally don't need to edit it unless you want to customize the translation style.
---
## Tips
- **Check `log/translations.txt`** after a run to see what was translated. You can copy useful terms from it into `vocab.txt` for consistency in future runs.
- **Check `log/translations.txt`** after a run to see what was translated. You can copy useful terms from it into `data/vocab.txt` for consistency in future runs.
- **Start small** — Translate a few files first to make sure the output looks good before doing the whole game.
- **Wordwrap** — If text overflows or looks awkward in-game, adjust the `width` setting in `.env` or the Config tab. `60` is a good default for most RPG Maker games.
- **Version control** — Using [Git](https://git-scm.com/) with the game folder is highly recommended. It lets you track every change the translation makes, compare with original files, and roll back if needed.
@ -300,8 +299,8 @@ Here's the recommended step-by-step process for translating an RPG Maker MV/MZ g
| Step | Action |
|------|--------|
| **1** | **Parse speakers → vocab.txt** — Use the Parse Speakers feature to pull character names from the game files into `vocab.txt`. |
| **2** | **Identify speaker genders** — Figure out which characters are male/female and update `vocab.txt` accordingly. This helps the AI use correct pronouns. |
| **1** | **Parse speakers → vocab** — Use the Parse Speakers feature to pull character names from the game files into `data/vocab.txt`. |
| **2** | **Identify speaker genders** — Figure out which characters are male/female and update `data/vocab.txt` accordingly. This helps the AI use correct pronouns. |
| **3** | **Translate Actors.json, MapInfos.json** — These are small files with character and map names. Good to do first. |
| **4** | **Translate Items, System, Weapons, etc.** — All the data files that aren't maps or events. Place them in `files/`, translate, then copy results back. |
| **5** | **Find speaker names** — Enable CODE 101 (Speakers), check for bracketed names, or use the "First Line = Speaker" option to capture speaker names properly. |

View file

@ -184,28 +184,29 @@ echo Launching DazedMTLTool GUI...
echo ==========================================
echo.
:: Ensure vocab.txt exists (create from example if available)
if not exist "vocab.txt" (
if exist "vocab.txt.example" (
echo vocab.txt not found - creating from vocab.txt.example...
copy /Y "vocab.txt.example" "vocab.txt" >nul 2>&1
:: Ensure data/vocab.txt exists (create from vocab_base.txt if available)
if not exist "data\\vocab.txt" (
if not exist "data" mkdir data
if exist "data\\vocab_base.txt" (
echo data\vocab.txt not found - creating from data\vocab_base.txt...
copy /Y "data\vocab_base.txt" "data\vocab.txt" >nul 2>&1
if errorlevel 1 (
echo ERROR: Failed to copy vocab.txt.example to vocab.txt.
echo ERROR: Failed to copy data\vocab_base.txt to data\vocab.txt.
) else (
echo Created vocab.txt from vocab.txt.example
echo Created data\vocab.txt from data\vocab_base.txt
)
) else (
echo vocab.txt and vocab.txt.example not found - creating empty vocab.txt to avoid import errors...
type NUL > "vocab.txt"
echo data\vocab.txt not found - creating empty file to avoid import errors...
type NUL > "data\vocab.txt"
if errorlevel 1 (
echo ERROR: Failed to create empty vocab.txt.
echo ERROR: Failed to create empty data\vocab.txt.
) else (
echo Created empty vocab.txt
echo Created empty data\vocab.txt
)
)
)
python start_gui.py
python scripts/start_gui.py
:: Check if GUI launched successfully
if errorlevel 1 (

View file

@ -127,23 +127,24 @@ activate_venv() {
}
ensure_vocab_file() {
if [[ -f "vocab.txt" ]]; then
mkdir -p data
if [[ -f "data/vocab.txt" ]]; then
return 0
fi
if [[ -f "vocab.txt.example" ]]; then
echo "vocab.txt not found - creating from vocab.txt.example..."
if cp "vocab.txt.example" "vocab.txt"; then
echo "Created vocab.txt from vocab.txt.example"
if [[ -f "data/vocab_base.txt" ]]; then
echo "data/vocab.txt not found - creating from data/vocab_base.txt..."
if cp "data/vocab_base.txt" "data/vocab.txt"; then
echo "Created data/vocab.txt from data/vocab_base.txt"
else
echo "ERROR: Failed to copy vocab.txt.example to vocab.txt."
echo "ERROR: Failed to copy data/vocab_base.txt to data/vocab.txt."
fi
else
echo "vocab.txt and vocab.txt.example not found - creating empty vocab.txt to avoid import errors..."
if : > "vocab.txt"; then
echo "Created empty vocab.txt"
echo "data/vocab.txt not found - creating empty file to avoid import errors..."
if : > "data/vocab.txt"; then
echo "Created empty data/vocab.txt"
else
echo "ERROR: Failed to create empty vocab.txt."
echo "ERROR: Failed to create empty data/vocab.txt."
fi
fi
}
@ -227,7 +228,7 @@ echo
ensure_vocab_file
if ! python start_gui.py; then
if ! exec python "$SCRIPT_DIR/scripts/start_gui.py"; then
echo
echo "ERROR: Failed to launch GUI."
echo "Check the error messages above."

View file

@ -19,20 +19,16 @@ from PyQt5.QtWidgets import (
QTextEdit, QSplitter, QGroupBox, QStatusBar, QStackedWidget, QToolButton,
QDialog
)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QSettings
from PyQt5.QtGui import QIcon, QFont, QPixmap, QScreen
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QSettings, QCoreApplication
from PyQt5.QtGui import QIcon, QFont, QPixmap, QScreen, QGuiApplication
def _project_root() -> Path:
"""Directory containing start_gui.py / requirements.txt (parent of gui/)."""
return Path(__file__).resolve().parent.parent
from util.paths import PROJECT_ROOT, ICON_PATH, LAST_UPDATE_SHA_PATH
def load_application_icon() -> QIcon:
"""Prefer ICO on Windows; PNG is fine for all platforms. Empty if missing."""
root = _project_root()
for rel in ("assets/icon.ico", "assets/icon.png"):
path = root / rel
for path in (PROJECT_ROOT / "assets/icon.ico", ICON_PATH):
if path.is_file():
icon = QIcon(str(path))
if not icon.isNull():
@ -126,14 +122,13 @@ class UpdateThread(QThread):
REPO_USER = "DazedAnon"
REPO_NAME = "DazedMTLTool"
REPO_BRANCH = "main"
SHA_FILE = "last_update_sha.txt"
SHA_FILE = str(LAST_UPDATE_SHA_PATH)
# Paths (relative, top-level) that should never be touched during update
PROTECTED = {".env", "venv", "log", "files", "translated",
"vocab.txt", "last_update_sha.txt"}
PROTECTED = {".env", "venv", "log", "files", "translated", "data"}
# GitLab zip archives do not preserve Unix execute bits; restore after apply.
EXECUTABLE_SUFFIXES = {".sh"}
EXECUTABLE_SUFFIXES = {".sh", ".desktop"}
progress = pyqtSignal(str) # status message
finished = pyqtSignal(bool, str) # (success, message)
@ -903,6 +898,14 @@ def main():
if hasattr(QApplication, 'setHighDpiScaleFactorRoundingPolicy'):
QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
if sys.platform.startswith("linux"):
from util.linux_desktop import ensure_linux_desktop_entry
ensure_linux_desktop_entry()
QGuiApplication.setDesktopFileName("DazedMTLTool")
QCoreApplication.setOrganizationName("DazedTranslations")
QCoreApplication.setApplicationName("DazedMTLTool")
app = QApplication(sys.argv)
# Windows taskbar groups by executable (python.exe) unless we set an explicit
@ -934,9 +937,7 @@ def main():
return 1
# Set application properties
app.setApplicationName("DazedMTLTool")
app.setApplicationVersion("1.0")
app.setOrganizationName("DazedTranslations")
# Apply dark theme with cleaner, more compact styling
app.setStyleSheet("""

View file

@ -19,8 +19,7 @@ except ImportError:
ConfigIntegration = None
try:
# Prefer importing the project's canonical defaults if available
import set_defaults
CANONICAL_DEFAULTS = getattr(set_defaults, 'DEFAULTS', None)
from util.defaults import DEFAULTS as CANONICAL_DEFAULTS
except Exception:
CANONICAL_DEFAULTS = None

View file

@ -12,8 +12,7 @@ import re
from gui.platform_glyph import platform_glyph
try:
import set_defaults
CANONICAL_DEFAULTS = getattr(set_defaults, 'DEFAULTS', None)
from util.defaults import DEFAULTS as CANONICAL_DEFAULTS
except Exception:
CANONICAL_DEFAULTS = None

View file

@ -11,8 +11,7 @@ from PyQt5.QtCore import pyqtSignal
import re
from gui.platform_glyph import platform_glyph
try:
import set_defaults
CANONICAL_DEFAULTS = getattr(set_defaults, 'DEFAULTS', None)
from util.defaults import DEFAULTS as CANONICAL_DEFAULTS
except Exception:
CANONICAL_DEFAULTS = None

View file

@ -22,6 +22,8 @@ import sys
import threading
from pathlib import Path
from util.paths import VOCAB_BASE_PATH, VOCAB_PATH
import jsbeautifier
from PyQt5.QtCore import Qt, QEvent, QSettings, QThread, QTimer, pyqtSignal
@ -3576,7 +3578,7 @@ class WorkflowTab(QWidget):
def _read_vocab_speakers(self) -> list[tuple[str, str]]:
"""Parse the '# Speakers' section from vocab.txt and return (orig, tl) pairs."""
vocab_path = Path("vocab.txt")
vocab_path = VOCAB_PATH
if not vocab_path.exists():
return []
try:
@ -3606,7 +3608,7 @@ class WorkflowTab(QWidget):
return results
def _reload_vocab(self):
vocab_path = Path("vocab.txt")
vocab_path = VOCAB_PATH
try:
if vocab_path.exists():
text = vocab_path.read_text(encoding="utf-8")
@ -3623,10 +3625,10 @@ class WorkflowTab(QWidget):
def _save_vocab(self):
try:
game_text = self.vocab_editor.toPlainText().rstrip("\n")
base_path = Path("vocab_base.txt")
base_path = VOCAB_BASE_PATH
base_text = base_path.read_text(encoding="utf-8") if base_path.exists() else ""
combined = game_text + "\n\n" + self._BASE_SEPARATOR + base_text
Path("vocab.txt").write_text(combined, encoding="utf-8")
VOCAB_PATH.write_text(combined, encoding="utf-8")
self._log("✅ vocab.txt saved (base terms from vocab_base.txt appended).")
except Exception as exc:
self._log(f"❌ Could not save vocab.txt: {exc}")
@ -3795,7 +3797,7 @@ class WorkflowTab(QWidget):
self._log("⚠ No game folder set. Complete Step 0 first.")
return
src = Path("vocab.txt")
src = VOCAB_PATH
if not src.exists():
self._log("⚠ vocab.txt not found — save it in Step 3 first.")
return

View file

@ -22,8 +22,10 @@ import tempfile
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
ESTIMATE = ""
TOKENS = [0, 0]

View file

@ -20,8 +20,10 @@ import tempfile
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
LISTWIDTH = int(os.getenv("listWidth"))

View file

@ -18,8 +18,10 @@ from util.translation import TranslationConfig, translateAI as sharedtranslateAI
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
PBAR = None
WIDTH = int(os.getenv("width"))

View file

@ -19,8 +19,10 @@ import tempfile
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
LISTWIDTH = int(os.getenv("listWidth"))

View file

@ -19,8 +19,10 @@ import tempfile
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
LISTWIDTH = int(os.getenv("listWidth"))

View file

@ -19,8 +19,10 @@ import tempfile
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
LISTWIDTH = int(os.getenv("listWidth"))

View file

@ -11,6 +11,10 @@ from dotenv import load_dotenv
# This needs to be before the module imports as some of them currently try to read and use some of these values
# upon import, in which case if they are unset the script will crash before we can output these messages.
load_dotenv()
from util.paths import migrate_root_data_files, ensure_vocab_file
migrate_root_data_files()
ensure_vocab_file()
_missing_envs = [
env for env in [
"api",

View file

@ -19,8 +19,10 @@ import tempfile
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
LISTWIDTH = int(os.getenv("listWidth"))

View file

@ -19,8 +19,10 @@ import tempfile
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
LISTWIDTH = int(os.getenv("listWidth"))

View file

@ -19,8 +19,10 @@ import tempfile
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
LISTWIDTH = int(os.getenv("listWidth"))

View file

@ -19,8 +19,10 @@ from util.translation import TranslationConfig, translateAI as sharedtranslateAI
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
THREAD_CTX = threading.local()
WIDTH = int(os.getenv("width"))
@ -803,7 +805,7 @@ def update_vocab_section(category: str, pairs: list[tuple[str, str]]):
The existing section is replaced entirely; other sections are preserved.
"""
try:
vocab_path = Path("vocab.txt")
vocab_path = VOCAB_PATH
# Helper: normalized comparison to detect no-op translations
def _norm(s: str) -> str:
@ -5071,7 +5073,7 @@ def finalizeSpeakerParse():
_speakerCache[orig] = norm
NAMESLIST.append([orig, norm])
vocab_path = Path("vocab.txt")
vocab_path = VOCAB_PATH
if not vocab_path.exists():
return
content = vocab_path.read_text(encoding="utf-8")

View file

@ -19,8 +19,10 @@ import tempfile
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
LISTWIDTH = int(os.getenv("listWidth"))

View file

@ -19,8 +19,10 @@ from util.translation import TranslationConfig, translateAI as sharedtranslateAI
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
VOCAB_LOCK = threading.Lock() # Dedicated lock for vocab.txt updates
WIDTH = int(os.getenv("width"))
@ -118,7 +120,7 @@ def update_vocab_section(category: str, pairs: list[tuple[str, str]]):
The existing section is replaced entirely; other sections are preserved.
"""
try:
vocab_path = Path("vocab.txt")
vocab_path = VOCAB_PATH
# Helper: normalized comparison to detect no-op translations
def _norm(s: str) -> str:

View file

@ -19,8 +19,10 @@ import tempfile
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
LISTWIDTH = int(os.getenv("listWidth"))

View file

@ -21,8 +21,10 @@ import tempfile
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
LISTWIDTH = int(os.getenv("listWidth"))

View file

@ -21,8 +21,10 @@ import tempfile
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
LISTWIDTH = int(os.getenv("listWidth"))

View file

@ -21,8 +21,10 @@ from util.translation import TranslationConfig, translateAI as sharedtranslateAI
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
LISTWIDTH = int(os.getenv("listWidth"))

View file

@ -22,8 +22,10 @@ import tempfile
MODEL = os.getenv("model")
TIMEOUT = int(os.getenv("timeout"))
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
LISTWIDTH = int(os.getenv("listWidth"))

View file

@ -21,8 +21,10 @@ from util.translation import (
MODEL = os.getenv("model")
LANGUAGE = os.getenv("language").capitalize()
PROMPT = Path("prompt.txt").read_text(encoding="utf-8")
VOCAB = Path("vocab.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
PROMPT = PROMPT_PATH.read_text(encoding="utf-8")
VOCAB = VOCAB_PATH.read_text(encoding="utf-8")
LOCK = threading.Lock()
WIDTH = int(os.getenv("width"))
MAXHISTORY = 10

13
scripts/launch.sh Normal file
View file

@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Launcher for DazedMTLTool.desktop (finds venv, then starts the GUI).
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT" || exit 1
PYTHON="python3"
if [[ -x "$ROOT/.venv/bin/python" ]]; then
PYTHON="$ROOT/.venv/bin/python"
elif [[ -x "$ROOT/venv/bin/python" ]]; then
PYTHON="$ROOT/venv/bin/python"
fi
exec "$PYTHON" "$ROOT/scripts/start_gui.py"

View file

@ -1,27 +1,24 @@
#!/usr/bin/env python3
"""
Launch script for DazedMTLTool GUI
"""
"""Launch script for DazedMTLTool GUI."""
import sys
import os
from pathlib import Path
# Add the project root to Python path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
PROJECT_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
def check_dependencies():
"""Check if required dependencies are installed."""
missing_deps = []
try:
import PyQt5
import PyQt5 # noqa: F401
except ImportError:
missing_deps.append("PyQt5")
try:
from dotenv import load_dotenv
from dotenv import load_dotenv # noqa: F401
except ImportError:
missing_deps.append("python-dotenv")
@ -30,17 +27,22 @@ def check_dependencies():
for dep in missing_deps:
print(f" - {dep}")
print("\nPlease install them using:")
print(" pip install -r requirements_gui.txt")
print(" pip install -r requirements.txt")
return False
return True
def main():
"""Main entry point."""
print("DazedMTLTool GUI Launcher")
print("=" * 40)
# Check dependencies
from util.paths import ensure_vocab_file, migrate_root_data_files
migrate_root_data_files()
ensure_vocab_file()
if not check_dependencies():
sys.exit(1)
@ -56,7 +58,6 @@ def main():
except Exception as exc:
print(f"Warning: Forge plugin setup failed ({exc}). Playtest Forge may be unavailable.")
# Import and run GUI
try:
from gui.main import main as gui_main
gui_main()
@ -68,5 +69,6 @@ def main():
print(f"Error starting GUI: {e}")
sys.exit(1)
if __name__ == "__main__":
main()

View file

@ -1,7 +1,9 @@
import re
from copy import deepcopy
# Canonical defaults used by the GUI and the `set_defaults` script.
from util.paths import PROJECT_ROOT
# Canonical defaults used by the GUI and the pre-commit hook.
# Stored as booleans for easier consumption by the GUI.
DEFAULTS = {
'FIRSTLINESPEAKERS': False,
@ -56,4 +58,4 @@ def set_defaults(file_path):
if __name__ == "__main__":
set_defaults('modules/rpgmakermvmz.py')
set_defaults(PROJECT_ROOT / "modules/rpgmakermvmz.py")

72
util/linux_desktop.py Normal file
View file

@ -0,0 +1,72 @@
"""Install/update the Linux .desktop entry for taskbar and window icons."""
from __future__ import annotations
import os
import subprocess
import sys
from pathlib import Path
from util.paths import ICON_PATH, PROJECT_ROOT
DESKTOP_ID = "DazedMTLTool"
LAUNCH_SCRIPT = PROJECT_ROOT / "scripts" / "launch.sh"
def installed_desktop_path() -> Path:
data_home = os.environ.get("XDG_DATA_HOME", "")
if data_home:
base = Path(data_home)
else:
base = Path.home() / ".local" / "share"
return base / "applications" / f"{DESKTOP_ID}.desktop"
def _desktop_content(root: Path, icon: Path, launch: Path) -> str:
return (
"[Desktop Entry]\n"
"Type=Application\n"
f"Name={DESKTOP_ID}\n"
"GenericName=Translation Tool\n"
"Comment=AI translation tool for visual novels and RPG games\n"
f"Exec={launch}\n"
f"Icon={icon}\n"
f"Path={root}\n"
"Terminal=false\n"
"Categories=Development;Utility;\n"
f"StartupWMClass={DESKTOP_ID}\n"
)
def ensure_linux_desktop_entry() -> None:
"""Write ~/.local/share/applications/DazedMTLTool.desktop with absolute paths."""
if not sys.platform.startswith("linux"):
return
if not LAUNCH_SCRIPT.is_file() or not ICON_PATH.is_file():
return
desktop_path = installed_desktop_path()
content = _desktop_content(PROJECT_ROOT, ICON_PATH, LAUNCH_SCRIPT)
try:
if desktop_path.is_file() and desktop_path.read_text(encoding="utf-8") == content:
return
desktop_path.parent.mkdir(parents=True, exist_ok=True)
desktop_path.write_text(content, encoding="utf-8")
desktop_path.chmod(0o755)
except OSError as exc:
print(f"Warning: could not install Linux desktop entry ({exc})")
return
_refresh_desktop_database(desktop_path.parent)
def _refresh_desktop_database(applications_dir: Path) -> None:
for cmd in (
["update-desktop-database", str(applications_dir)],
["kbuildsycoca5", "--noincremental"],
):
try:
subprocess.run(cmd, check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except OSError:
pass

45
util/paths.py Normal file
View file

@ -0,0 +1,45 @@
"""Canonical project paths (repo root, data files, config)."""
from __future__ import annotations
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent
DATA_DIR = PROJECT_ROOT / "data"
VOCAB_PATH = DATA_DIR / "vocab.txt"
VOCAB_BASE_PATH = DATA_DIR / "vocab_base.txt"
PROMPT_PATH = DATA_DIR / "prompt.txt"
LAST_UPDATE_SHA_PATH = DATA_DIR / "last_update_sha.txt"
ENV_PATH = PROJECT_ROOT / ".env"
ICON_PATH = PROJECT_ROOT / "assets" / "icon.png"
_ROOT_DATA_FILES = (
"vocab.txt",
"vocab_base.txt",
"prompt.txt",
"last_update_sha.txt",
)
def migrate_root_data_files() -> None:
"""Move legacy root-level data files into data/ on first run."""
DATA_DIR.mkdir(parents=True, exist_ok=True)
for name in _ROOT_DATA_FILES:
src = PROJECT_ROOT / name
dst = DATA_DIR / name
if src.is_file() and not dst.exists():
src.rename(dst)
migrate_root_data_files()
def ensure_vocab_file() -> None:
"""Create data/vocab.txt from vocab_base.txt when missing."""
migrate_root_data_files()
if VOCAB_PATH.is_file():
return
if VOCAB_BASE_PATH.is_file():
VOCAB_PATH.write_text(VOCAB_BASE_PATH.read_text(encoding="utf-8"), encoding="utf-8")
else:
VOCAB_PATH.write_text("", encoding="utf-8")

View file

@ -1316,7 +1316,8 @@ class TranslationConfig:
# Load prompt and vocab files if not provided
if prompt is None:
try:
self.prompt = Path("prompt.txt").read_text(encoding="utf-8")
from util.paths import PROMPT_PATH, VOCAB_PATH
self.prompt = PROMPT_PATH.read_text(encoding="utf-8")
except FileNotFoundError:
self.prompt = ""
else:
@ -1324,7 +1325,7 @@ class TranslationConfig:
if vocab is None:
try:
self.vocab = Path("vocab.txt").read_text(encoding="utf-8")
self.vocab = VOCAB_PATH.read_text(encoding="utf-8")
except FileNotFoundError:
self.vocab = ""
else: