Improvements
This commit is contained in:
parent
401f34fdb1
commit
145d5ddc0c
3 changed files with 96 additions and 13 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
Loading…
Reference in a new issue