Improvements

This commit is contained in:
dazedanon 2026-03-15 19:27:29 -05:00
parent 401f34fdb1
commit 145d5ddc0c
3 changed files with 96 additions and 13 deletions

View file

@ -672,11 +672,20 @@ class WorkflowTab(QWidget):
" - Speech register and personality notes — how they speak, their tone, any nicknames, "
"whether their name is player-chosen, etc.\n"
"\n"
"⚠️ IMPORTANT: In all descriptions, ALWAYS refer to other characters by their "
"ENGLISH name, never their Japanese name. "
"For example, write \"her sister Ruin was kidnapped\" not "
"\"her sister ルイン was kidnapped\". "
"The description text must be entirely in English with no Japanese characters "
"except inside the leading Japanese (English) name tag.\n"
"\n"
"Format — the header line, then one entry per line:\n"
"# Game Characters\n"
"\u30b7\u30ed (Shiro) - Female; protagonist; player-controlled (Actors.json ID 1); "
"speaks in a flustered, cute register with feminine speech markers; nickname \u30d0\u30ab\u732b\u3002\n"
"\u30af\u30ed\u30cd (Kurone) - Female; antagonist; cold and terse; speaks in short cutting sentences\n"
"speaks in a flustered, cute register with feminine speech markers; "
"her partner is Kurone\n"
"\u30af\u30ed\u30cd (Kurone) - Female; antagonist; cold and terse; speaks in short cutting sentences; "
"pursues Shiro throughout the story\n"
"\n"
"Rules:\n"
" - Named characters only — no generic enemy types or unnamed NPCs.\n"
@ -1212,6 +1221,10 @@ class WorkflowTab(QWidget):
self.import_btn.setEnabled(False)
self.import_btn.clicked.connect(self._import_files)
import_row.addWidget(self.import_btn)
clear_translated_btn = _make_btn("🗑 Clear translated/", "#8b0000")
clear_translated_btn.setToolTip("Delete all files inside the translated/ folder")
clear_translated_btn.clicked.connect(self._clear_translated)
import_row.addWidget(clear_translated_btn)
import_row.addStretch()
layout.addLayout(import_row)
@ -1820,6 +1833,43 @@ class WorkflowTab(QWidget):
self._worker = worker
worker.start()
def _clear_translated(self):
translated_dir = Path("translated")
items_to_delete = [
item for item in translated_dir.iterdir()
if item.name != ".gitkeep"
] if translated_dir.exists() else []
if not items_to_delete:
self._log(" translated/ is already empty — nothing to clear.")
return
reply = QMessageBox.warning(
self,
"Clear translated/ folder",
"This will permanently delete all files inside the translated/ folder.\n\nAre you sure?",
QMessageBox.Yes | QMessageBox.Cancel,
QMessageBox.Cancel,
)
if reply != QMessageBox.Yes:
return
deleted = 0
errors = []
for item in items_to_delete:
try:
if item.is_file():
item.unlink()
deleted += 1
elif item.is_dir():
import shutil
shutil.rmtree(item)
deleted += 1
except Exception as exc:
errors.append(f"{item.name}: {exc}")
if errors:
self._log(f"{len(errors)} error(s) while clearing translated/:")
for e in errors[:10]:
self._log(f" {e}")
self._log(f"✅ Cleared {deleted} item(s) from translated/")
def _on_import_done(self, count: int, errors: list):
self.import_btn.setEnabled(True)
if errors:
@ -2299,10 +2349,17 @@ class WorkflowTab(QWidget):
if not game_data:
return
translated_dir = Path("translated")
active_set = set(active)
exportable = [
fp for fp in translated_dir.glob("*.json")
if fp.name in active_set and fp.name != ".gitkeep"
] if translated_dir.exists() else []
reply = QMessageBox.question(
self,
"Export Active Files to Game",
f"Export {len(active)} file(s) matching files/ contents into:\n{game_data}\n\n"
f"Export {len(exportable)} file(s) into:\n{game_data}\n\n"
"Make a backup first if needed. Continue?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No,

View file

@ -79,7 +79,7 @@ LEAVE = False
# FIRSTLINESPEAKERS: Guess speaker from first line.
FIRSTLINESPEAKERS = False
# INLINE401SPEAKERS: Extract speaker from "Name「dialogue」" inline format on 401 lines.
INLINE401SPEAKERS = False
INLINE401SPEAKERS = True
# FACENAME101: Map face name -> speaker.
FACENAME101 = False
# Face name -> speaker mapping for FACENAME101.

View file

@ -57,7 +57,25 @@ def protect_script_codes(text):
"""
if not text or not isinstance(text, str):
return text, {}
# Normalize curly/smart quotes to ASCII equivalents BEFORE building the JSON
# payload. When these characters appear inside a JSON string value the AI
# tends to treat them as regular ASCII double-quotes, which makes the value
# appear empty (e.g. `"スキルを"リセットする` → AI sees empty + stray text).
# This mirrors the identical normalization already applied to the AI's OUTPUT
# inside extractTranslation's translation_table.
quote_norm_table = str.maketrans({
'\u201C': "'", # " left double quotation mark
'\u201D': "'", # " right double quotation mark
'\uFF02': "'", # fullwidth quotation mark
'\u2018': "'", # ' left single quotation mark
'\u2019': "'", # ' right single quotation mark
'\u201B': "'", # single high-reversed-9 quotation mark
'\u02BC': "'", # ʼ modifier letter apostrophe
'\uFF07': "'", # fullwidth apostrophe
})
text = text.translate(quote_norm_table)
replacements = {}
protected_text = text
counter = 0
@ -1042,8 +1060,9 @@ def cleanTranslatedText(translatedText, language):
"": "~",
"": "",
"": ".",
"": '\"',
"": '\"',
# Note: 「 and 」 are NOT replaced here — replacing them with ASCII " would
# corrupt raw JSON strings before extraction. They are handled per-line
# in _clean_extracted_line() after JSON parsing.
"": "",
"": "]",
"": "[",
@ -1069,8 +1088,11 @@ def cleanTranslatedText(translatedText, language):
def elongateCharacters(text):
"""Replace ー sequences with elongated characters"""
# Define a pattern to match one character followed by two or more ー characters
pattern = r"(?<=(.))ー{2,}"
# Define a pattern to match one character followed by two or more ー characters.
# The lookbehind is restricted to non-ー Japanese/CJK characters so that:
# - standalone ー separators (e.g. "ーーーーーーーーーー") are left untouched
# - ー sequences preceded by a JSON quote or other non-Japanese char are not corrupted
pattern = r"(?<=([\u3040-\u309F\u30A0-\u30FB\u30FD-\u30FF\u4E00-\u9FEF\uFF61-\uFF9F]))ー{2,}"
# Define a replacement function that elongates the captured character
def repl(match):
@ -1588,10 +1610,14 @@ def translateAI(text, history, fullPromptFlag, config, filename=None, pbar=None,
else:
# Set translations (line count matches, placeholders valid, and content is good)
# Strip "Placeholder Text" from individual lines (AI placeholder for untranslatable input)
final_translations = [
line.replace("Placeholder Text", "").strip() if isinstance(line, str) else line
for line in extracted
]
# Also apply the 「→" / 」→" replacements here per-line (safe now that JSON is parsed)
def _clean_extracted_line(line):
if not isinstance(line, str):
return line
line = line.replace("Placeholder Text", "").strip()
line = line.replace("", '"').replace("", '"')
return line
final_translations = [_clean_extracted_line(line) for line in extracted]
else:
# Single string: validate placeholders
placeholder_valid, missing, extra = validate_placeholders(protected_items, cleaned_text, all_replacements[0])