Make convert quotes optional.

This commit is contained in:
DazedAnon 2026-07-11 09:41:05 -05:00
parent 836280e00d
commit e991f27cbf
3 changed files with 66 additions and 10 deletions

View file

@ -511,6 +511,20 @@ class ConfigTab(QWidget):
self.note_width_spin.setSuffix(" chars")
self.note_width_spin.setFixedWidth(120) # Small
format_form.addRow(note_label, self.note_width_spin)
quotes_label = QLabel("Convert Quotes:")
quotes_label.setFixedWidth(150)
quotes_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.convert_quotes_cb = QCheckBox('Convert 「」 / 『』 to ""')
self.convert_quotes_cb.setChecked(True)
self.convert_quotes_cb.setToolTip(
"When enabled, Japanese corner brackets 「」 and 『』 are replaced "
'with ASCII double quotes "" in translated output (and on RPG Maker '
"source text before translation).\n\n"
"Leaving this on is recommended - the AI often fails to keep 「」 "
"consistent across lines."
)
format_form.addRow(quotes_label, self.convert_quotes_cb)
right_column.addLayout(format_form)
right_column.addWidget(create_horizontal_line())
@ -774,6 +788,9 @@ class ConfigTab(QWidget):
self.width_spin.setValue(int(_get("width", "60")))
self.list_width_spin.setValue(int(_get("listWidth", "100")))
self.note_width_spin.setValue(int(_get("noteWidth", "75")))
self.convert_quotes_cb.setChecked(
_get("convertQuotes", "true").strip().lower() in ("true", "1", "yes")
)
# Custom API pricing
self.input_cost_spin.setValue(float(_get("input_cost", "2.0")))
@ -835,6 +852,7 @@ class ConfigTab(QWidget):
self.width_spin.editingFinished.connect(self.auto_save)
self.list_width_spin.editingFinished.connect(self.auto_save)
self.note_width_spin.editingFinished.connect(self.auto_save)
self.convert_quotes_cb.stateChanged.connect(self._on_convert_quotes_changed)
self.input_cost_spin.editingFinished.connect(self.auto_save)
self.output_cost_spin.editingFinished.connect(self.auto_save)
self.font_scale_spin.editingFinished.connect(self.auto_save)
@ -859,6 +877,7 @@ class ConfigTab(QWidget):
self.width_spin.editingFinished.disconnect(self.auto_save)
self.list_width_spin.editingFinished.disconnect(self.auto_save)
self.note_width_spin.editingFinished.disconnect(self.auto_save)
self.convert_quotes_cb.stateChanged.disconnect(self._on_convert_quotes_changed)
self.input_cost_spin.editingFinished.disconnect(self.auto_save)
self.output_cost_spin.editingFinished.disconnect(self.auto_save)
self.font_scale_spin.editingFinished.disconnect(self.auto_save)
@ -869,6 +888,19 @@ class ConfigTab(QWidget):
except (TypeError, RuntimeError):
pass
def _on_convert_quotes_changed(self, state):
"""Warn when quote conversion is disabled, then auto-save."""
if state == Qt.Unchecked:
QMessageBox.warning(
self,
"Convert Quotes Disabled",
"The AI often struggles to keep 「」 and 『』 consistent "
"across translated lines.\n\n"
"Leaving conversion enabled is recommended unless you "
"specifically need the Japanese brackets in the output.",
)
self.auto_save()
def auto_save(self):
"""Auto-save configuration without showing message."""
self.save_to_env(show_message=False)
@ -901,6 +933,7 @@ class ConfigTab(QWidget):
"width": str(self.width_spin.value()),
"listWidth": str(self.list_width_spin.value()),
"noteWidth": str(self.note_width_spin.value()),
"convertQuotes": "true" if self.convert_quotes_cb.isChecked() else "false",
"input_cost": str(self.input_cost_spin.value()),
"output_cost": str(self.output_cost_spin.value()),
"font_scale": str(self.font_scale_spin.value()),
@ -964,6 +997,7 @@ class ConfigTab(QWidget):
self.width_spin.setValue(60)
self.list_width_spin.setValue(100)
self.note_width_spin.setValue(75)
self.convert_quotes_cb.setChecked(True)
# UI settings
self.font_scale_spin.setValue(1.0)
@ -1008,6 +1042,7 @@ class ConfigTab(QWidget):
"width": self.width_spin.value(),
"listWidth": self.list_width_spin.value(),
"noteWidth": self.note_width_spin.value(),
"convertQuotes": self.convert_quotes_cb.isChecked(),
"input_cost": self.input_cost_spin.value(),
"output_cost": self.output_cost_spin.value(),
"font_scale": self.font_scale_spin.value(),

View file

@ -13,7 +13,7 @@ from colorama import Fore
from dotenv import load_dotenv
from retry import retry
from tqdm import tqdm
from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost, getPricingConfig, calculateCost, get_var_translation, set_var_translations_batch
from util.translation import TranslationConfig, translateAI as sharedtranslateAI, getPricingConfig, calculateCost, getPricingConfig, calculateCost, get_var_translation, set_var_translations_batch, convert_corner_brackets
from util.speakers import SPEAKER_BRACKET_INNER, strip_speaker_prefix
# Globals
@ -2470,8 +2470,9 @@ def searchCodes(page, pbar, jobList, filename):
# Remove Extra Stuff bad for translation.
finalJAString = finalJAString.replace("", "")
finalJAString = finalJAString.replace(" ", "")
finalJAString = finalJAString.replace("", '"')
finalJAString = finalJAString.replace("", '"')
finalJAString = convert_corner_brackets(
finalJAString, TRANSLATION_CONFIG.convertQuotes
)
finalJAString = finalJAString.replace("\\,", ',')
### Remove format codes

View file

@ -1467,7 +1467,8 @@ class TranslationConfig:
maxHistory=10,
estimateMode=False,
logFilePath="log/translationHistory.txt",
mismatchLogPath="log/mismatchHistory.txt"):
mismatchLogPath="log/mismatchHistory.txt",
convertQuotes=None):
# Load from environment if not provided
self.model = model or os.getenv("model")
@ -1504,6 +1505,24 @@ class TranslationConfig:
self.estimateMode = estimateMode
self.logFilePath = logFilePath
self.mismatchLogPath = mismatchLogPath
if convertQuotes is None:
self.convertQuotes = os.getenv("convertQuotes", "true").strip().lower() in (
"true", "1", "yes",
)
else:
self.convertQuotes = bool(convertQuotes)
def convert_corner_brackets(text, enabled=True):
"""Replace Japanese corner brackets 「」『』 with ASCII double quotes when enabled."""
if not enabled or not isinstance(text, str):
return text
return (
text.replace("", '"')
.replace("", '"')
.replace("", '"')
.replace("", '"')
)
_LITELLM_PRICING_URL = (
@ -2295,7 +2314,7 @@ 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.
# in _clean_extracted_line() after JSON parsing (when convertQuotes is on).
"": "",
"": "]",
"": "[",
@ -2640,7 +2659,7 @@ def translateAI(text, history, config, filename=None, pbar=None, lock=None, mism
return bool(inner) and all(c in '\u2026\u30FC' for c in inner)
def _convert_ellipsis(s):
return str(s).replace('', '"').replace('', '"').replace('', '"').replace('', '"')
return convert_corner_brackets(str(s), config.convertQuotes)
if isinstance(tItem, list):
if all(_is_ellipsis_only(s) for s in tItem):
@ -2700,7 +2719,7 @@ def translateAI(text, history, config, filename=None, pbar=None, lock=None, mism
item_str = str(tItem[j]).strip() if tItem[j] else ""
if item_str and item_str != "Placeholder Text" and not re.search(config.langRegex, item_str):
cleaned = cleanTranslatedText(tItem[j], config.language)
cleaned = cleaned.replace("", '"').replace("", '"').strip()
cleaned = convert_corner_brackets(cleaned, config.convertQuotes).strip()
no_japanese_map[j] = cleaned
# Combine skip sets and rebuild protected_items / all_replacements
@ -3047,9 +3066,7 @@ def translateAI(text, history, config, filename=None, pbar=None, lock=None, mism
if not isinstance(line, str):
return line
line = line.replace("Placeholder Text", "").strip()
line = line.replace("", '"').replace("", '"')
line = line.replace("", '"').replace("", '"')
return line
return convert_corner_brackets(line, config.convertQuotes)
final_translations = [_clean_extracted_line(line) for line in extracted]
else:
# Single string: extract from JSON schema response
@ -3084,6 +3101,9 @@ def translateAI(text, history, config, filename=None, pbar=None, lock=None, mism
pbar.write(f" - {reason}")
else:
# Accept output - all validations passed
final_cleaned = convert_corner_brackets(
final_cleaned, config.convertQuotes
)
final_translations = final_cleaned
else:
is_valid = False