Surely cleaning up all this mess wont break anything. surely
This commit is contained in:
parent
563299ba21
commit
4dd9d2bbd4
38 changed files with 16162 additions and 15976 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
10
DazedMTLTool.desktop
Normal 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
|
||||
23
README.md
23
README.md
|
|
@ -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. |
|
||||
|
|
|
|||
25
START.bat
25
START.bat
|
|
@ -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 (
|
||||
|
|
|
|||
23
START.sh
23
START.sh
|
|
@ -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."
|
||||
|
|
|
|||
29
gui/main.py
29
gui/main.py
|
|
@ -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("""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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
13
scripts/launch.sh
Normal 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"
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
72
util/linux_desktop.py
Normal 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
45
util/paths.py
Normal 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")
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue