Compare commits
2 commits
300953a04d
...
e991f27cbf
| Author | SHA1 | Date | |
|---|---|---|---|
| e991f27cbf | |||
| 836280e00d |
6 changed files with 253 additions and 78 deletions
|
|
@ -3,12 +3,44 @@
|
|||
# Enable error handling
|
||||
set -e
|
||||
|
||||
# Same layout as GameUpdate.bat: patch assets live under ./gameupdate/ next to this file.
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
GAME_ROOT="$SCRIPT_DIR"
|
||||
PATCH_DIR="$SCRIPT_DIR/gameupdate"
|
||||
# Re-exec from a temp copy so applying the patch zip cannot overwrite/truncate
|
||||
# this launcher while bash is still reading it (same idea as patch.sh -> patch2.sh).
|
||||
if [ -z "${GAMEUPDATE_LINUX_REEXEC:-}" ]; then
|
||||
SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
|
||||
GAME_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TMP_SELF="$(mktemp)"
|
||||
cp -f "$SELF" "$TMP_SELF"
|
||||
chmod +x "$TMP_SELF"
|
||||
export GAMEUPDATE_LINUX_REEXEC=1
|
||||
# shellcheck disable=SC2093
|
||||
exec bash "$TMP_SELF" "$GAME_ROOT"
|
||||
fi
|
||||
|
||||
GAME_ROOT="$1"
|
||||
PATCH_DIR="$GAME_ROOT/gameupdate"
|
||||
REEXEC_PATH="$0"
|
||||
|
||||
cleanup_reexec() {
|
||||
rm -f "$REEXEC_PATH"
|
||||
}
|
||||
trap cleanup_reexec EXIT
|
||||
|
||||
cd "$GAME_ROOT"
|
||||
cp "$PATCH_DIR/patch.sh" "$PATCH_DIR/patch2.sh"
|
||||
set +e
|
||||
bash "$PATCH_DIR/patch2.sh"
|
||||
rm "$PATCH_DIR/patch2.sh"
|
||||
patch_exit=$?
|
||||
set -e
|
||||
rm -f "$PATCH_DIR/patch2.sh"
|
||||
|
||||
if [ "$patch_exit" -ne 0 ]; then
|
||||
echo "GameUpdate failed (exit $patch_exit)."
|
||||
fi
|
||||
|
||||
# Match Windows patch.ps1 pause when launched interactively (double-click / terminal).
|
||||
if [ -t 0 ] && [ -t 1 ]; then
|
||||
echo
|
||||
read -r -p "Press Enter to close..."
|
||||
fi
|
||||
|
||||
exit "$patch_exit"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,25 @@ check_dependency() {
|
|||
fi
|
||||
}
|
||||
|
||||
# Run a Windows .exe. On Linux always go through wine (never rely on binfmt,
|
||||
# which can hang waiting on wineserver / GUI). On Windows / Git Bash, exec directly.
|
||||
run_windows_exe() {
|
||||
local exe="$1"
|
||||
shift
|
||||
case "$(uname -s)" in
|
||||
Linux*)
|
||||
if ! command -v wine > /dev/null 2>&1; then
|
||||
echo "ERROR: wine is required to run $(basename "$exe") on Linux."
|
||||
return 1
|
||||
fi
|
||||
( cd "$ROOT_DIR" && WINEDEBUG=-all wine "$exe" "$@" )
|
||||
;;
|
||||
*)
|
||||
( cd "$ROOT_DIR" && "$exe" "$@" )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Check for jq, unzip, and curl
|
||||
check_dependency jq
|
||||
check_dependency unzip
|
||||
|
|
@ -263,12 +282,7 @@ invoke_wolf_pre_setup() {
|
|||
fi
|
||||
|
||||
echo "[Pre-Setup] Unpacking Data.wolf with UberWolfCli (one-time)..."
|
||||
# Native .exe works under Windows / Git Bash; wine covers Linux hosts.
|
||||
if ( cd "$ROOT_DIR" && "$cli" -o "$data_wolf" ); then
|
||||
:
|
||||
elif command -v wine > /dev/null 2>&1 && ( cd "$ROOT_DIR" && wine "$cli" -o "$data_wolf" ); then
|
||||
:
|
||||
else
|
||||
if ! run_windows_exe "$cli" -o "$data_wolf"; then
|
||||
echo "[Pre-Setup] ERROR: UberWolfCli unpack failed."
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -315,7 +329,7 @@ if [ -f "$ROOT_DIR/data.dts" ]; then
|
|||
if [ ! -d "$ROOT_DIR/data" ] || [ ! -f "$ROOT_DIR/data/project.dat" ]; then
|
||||
if [ -f "$ROOT_DIR/data.dts" ]; then
|
||||
echo "[Pre-Setup] Step 1: Unpacking data.dts -> data"
|
||||
( cd "$ROOT_DIR" && "$UNPACKER" -o data data.dts ) || echo "[Pre-Setup] ERROR: Unpack failed."
|
||||
run_windows_exe "$UNPACKER" -o data data.dts || echo "[Pre-Setup] ERROR: Unpack failed."
|
||||
else
|
||||
echo "[Pre-Setup] Step 1: Skipping unpack (no data folder and no data.dts found)."
|
||||
fi
|
||||
|
|
@ -327,7 +341,7 @@ if [ -f "$ROOT_DIR/data.dts" ]; then
|
|||
if [ ! -d "$ROOT_DIR/patch" ]; then
|
||||
if [ -f "$ROOT_DIR/data/project.dat" ]; then
|
||||
echo "[Pre-Setup] Step 2: Creating patch from data/project.dat"
|
||||
( cd "$ROOT_DIR" && "$UNPACKER" ./data/project.dat -c ) || echo "[Pre-Setup] ERROR: Create Patch failed."
|
||||
run_windows_exe "$UNPACKER" ./data/project.dat -c || echo "[Pre-Setup] ERROR: Create Patch failed."
|
||||
else
|
||||
echo "[Pre-Setup] Step 2: Skipping create patch (data/project.dat not found)."
|
||||
fi
|
||||
|
|
@ -385,20 +399,17 @@ download_extract() {
|
|||
invoke_wolf_pre_setup
|
||||
|
||||
echo "Applying patch..."
|
||||
if ! cp -r "$inner"/* "$ROOT_DIR/"; then
|
||||
if ! cp -rf "$inner"/* "$ROOT_DIR/"; then
|
||||
echo "Patch application failed!"
|
||||
rm -rf "$TMP_EX"
|
||||
rm -f "$ROOT_DIR/repo.zip"
|
||||
return 1
|
||||
fi
|
||||
|
||||
rm -rf "$TMP_EX"
|
||||
|
||||
echo "Cleaning up..."
|
||||
rm -f "$ROOT_DIR/repo.zip"
|
||||
|
||||
# --------------------------------------------------------
|
||||
# POST-APPLY: Run Steps 3 and 4 after patch files are merged
|
||||
# (Must finish before "Cleaning up" so a slow wine/SRPG step is not
|
||||
# mistaken for a hung cleanup - matches patch.ps1 order.)
|
||||
# --------------------------------------------------------
|
||||
UNPACKER="$ROOT_DIR/SRPG_Unpacker.exe"
|
||||
if [ -f "$ROOT_DIR/data.dts" ]; then
|
||||
|
|
@ -407,14 +418,14 @@ download_extract() {
|
|||
|
||||
if [ -f "$ROOT_DIR/data/project.dat" ]; then
|
||||
echo "Step 3: Applying patch to data/project.dat"
|
||||
( cd "$ROOT_DIR" && "$UNPACKER" ./data/project.dat -a ) || echo "ERROR: Apply Patch failed."
|
||||
run_windows_exe "$UNPACKER" ./data/project.dat -a || echo "ERROR: Apply Patch failed."
|
||||
else
|
||||
echo "ERROR: data/project.dat not found; cannot apply patch."
|
||||
fi
|
||||
|
||||
if [ -d "$ROOT_DIR/data" ]; then
|
||||
echo "Step 4: Packing data -> data.dts"
|
||||
( cd "$ROOT_DIR" && "$UNPACKER" -o data.dts data ) || echo "WARNING: Pack failed."
|
||||
run_windows_exe "$UNPACKER" -o data.dts data || echo "WARNING: Pack failed."
|
||||
else
|
||||
echo "Step 4: Skipping pack (data folder not found)."
|
||||
fi
|
||||
|
|
@ -426,7 +437,19 @@ download_extract() {
|
|||
# Retire Data.wolf if loose Data/ is ready after the patch copy.
|
||||
invoke_wolf_pre_setup
|
||||
|
||||
echo "Cleaning up..."
|
||||
rm -rf "$TMP_EX"
|
||||
rm -f "$ROOT_DIR/repo.zip"
|
||||
# Legacy / accidental leftovers from older patchers.
|
||||
rm -rf "$ROOT_DIR/_patch_extract_tmp"
|
||||
shopt -s nullglob
|
||||
for stale in "$ROOT_DIR"/dazedmtl_patch_*.zip; do
|
||||
rm -f "$stale"
|
||||
done
|
||||
shopt -u nullglob
|
||||
|
||||
echo "$latest_patch_sha" > "$STATE_FILE"
|
||||
echo "Done."
|
||||
}
|
||||
|
||||
# Check if previous_patch_sha.txt exists in gameupdate
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -74,6 +74,89 @@ BATCH_COLLECT_LIVE_CHARGE_NOTE = (
|
|||
)
|
||||
|
||||
|
||||
class _ShimLabel:
|
||||
"""Plain stand-in for QLabel used by Files-tab row helpers (no real Qt widget)."""
|
||||
|
||||
def __init__(self, text=""):
|
||||
self._text = text
|
||||
self._style = ""
|
||||
self._visible = False
|
||||
self._tooltip = ""
|
||||
|
||||
def setText(self, text):
|
||||
self._text = text or ""
|
||||
|
||||
def text(self):
|
||||
return self._text
|
||||
|
||||
def setStyleSheet(self, style):
|
||||
self._style = style or ""
|
||||
|
||||
def setVisible(self, visible):
|
||||
self._visible = bool(visible)
|
||||
|
||||
def setToolTip(self, tip):
|
||||
self._tooltip = tip or ""
|
||||
|
||||
def toolTip(self):
|
||||
return self._tooltip
|
||||
|
||||
|
||||
class _ShimCheckBox:
|
||||
def __init__(self):
|
||||
self._checked = False
|
||||
self._enabled = False
|
||||
self._visible = False
|
||||
|
||||
def setChecked(self, checked):
|
||||
self._checked = bool(checked)
|
||||
|
||||
def isChecked(self):
|
||||
return self._checked
|
||||
|
||||
def setEnabled(self, enabled):
|
||||
self._enabled = bool(enabled)
|
||||
|
||||
def setVisible(self, visible):
|
||||
self._visible = bool(visible)
|
||||
|
||||
|
||||
class _ShimProgressBar:
|
||||
def __init__(self):
|
||||
self._value = 0
|
||||
self._maximum = 100
|
||||
self._visible = False
|
||||
self._text_visible = True
|
||||
self._style = ""
|
||||
|
||||
def setValue(self, value):
|
||||
self._value = int(value)
|
||||
|
||||
def setMaximum(self, maximum):
|
||||
self._maximum = int(maximum)
|
||||
|
||||
def setVisible(self, visible):
|
||||
self._visible = bool(visible)
|
||||
|
||||
def setTextVisible(self, visible):
|
||||
self._text_visible = bool(visible)
|
||||
|
||||
def setStyleSheet(self, style):
|
||||
self._style = style or ""
|
||||
|
||||
|
||||
class _ShimWidget:
|
||||
def __init__(self):
|
||||
self._visible = False
|
||||
self._tooltip = ""
|
||||
|
||||
def setVisible(self, visible):
|
||||
self._visible = bool(visible)
|
||||
|
||||
def setToolTip(self, tip):
|
||||
self._tooltip = tip or ""
|
||||
|
||||
|
||||
class TranslationWorker(QThread):
|
||||
"""Worker thread for running translations without blocking the UI."""
|
||||
|
||||
|
|
@ -1162,31 +1245,12 @@ class TranslationTab(QWidget):
|
|||
self.batch_pipeline_stack.setStyleSheet("background: transparent;")
|
||||
self.batch_pipeline_stack.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
||||
self.progress_list = QListWidget()
|
||||
self.progress_list.setMinimumHeight(120)
|
||||
self.progress_list.setSelectionMode(QListWidget.NoSelection)
|
||||
self.progress_list.setFocusPolicy(Qt.NoFocus)
|
||||
self.progress_list.setSpacing(1) # Minimal spacing between items
|
||||
self.progress_list.setStyleSheet("""
|
||||
QListWidget {
|
||||
outline: none;
|
||||
border: 1px solid #555555;
|
||||
border-radius: 3px;
|
||||
background-color: #1e1e1e;
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
}
|
||||
QListWidget::item {
|
||||
padding: 0px;
|
||||
border-bottom: 1px solid #333333;
|
||||
}
|
||||
QListWidget::item:last {
|
||||
border-bottom: none;
|
||||
}
|
||||
""")
|
||||
# Prefer a Batch-history-style table for per-file status; keep the list
|
||||
# widget as a non-visible compatibility shim for any leftover references.
|
||||
# Prefer a Batch-history-style table for per-file status. Keep a hidden,
|
||||
# parented list as a compatibility shim so clear()/legacy refs stay safe
|
||||
# without creating an orphan top-level window (minimize/restore glitches).
|
||||
self.progress_list = QListWidget(self)
|
||||
self.progress_list.setVisible(False)
|
||||
self.progress_list.setMaximumSize(0, 0)
|
||||
|
||||
self.progress_files_summary = QLabel("No files in this run.")
|
||||
self.progress_files_summary.setStyleSheet("color:#9d9d9d;font-size:12px;padding:2px 0;")
|
||||
|
|
@ -1785,6 +1849,16 @@ class TranslationTab(QWidget):
|
|||
self.files_tab_btn.setText("Files")
|
||||
self._batch_tab_index = -1
|
||||
self.progress_content_stack.setCurrentIndex(1)
|
||||
# Per-file Time is useful for live translate; for batch, Anthropic owns
|
||||
# the wait and local write time is negligible.
|
||||
table = getattr(self, "progress_table", None)
|
||||
if table is not None and table.columnCount() > 5:
|
||||
table.setColumnHidden(5, bool(batch_mode))
|
||||
try:
|
||||
if hasattr(self, "totals_time_label"):
|
||||
self.totals_time_label.setVisible(not batch_mode)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _on_batch_submit_yes(self):
|
||||
if hasattr(self, "translation_worker") and self.translation_worker:
|
||||
|
|
@ -2456,11 +2530,10 @@ class TranslationTab(QWidget):
|
|||
self._refresh_files_summary()
|
||||
|
||||
def create_progress_item(self, filename):
|
||||
"""Add a Files-tab table row for a file and return a placeholder widget."""
|
||||
"""Add a Files-tab table row for a file and return a placeholder shim."""
|
||||
table = getattr(self, "progress_table", None)
|
||||
if table is None:
|
||||
# Fallback empty widget if table is unavailable
|
||||
return QWidget()
|
||||
return _ShimWidget()
|
||||
|
||||
row = table.rowCount()
|
||||
table.insertRow(row)
|
||||
|
|
@ -2473,25 +2546,16 @@ class TranslationTab(QWidget):
|
|||
item.setData(Qt.UserRole, filename)
|
||||
table.setItem(row, col, item)
|
||||
|
||||
# Keep a small compatibility widget so older helpers that expect
|
||||
# checkbox/label/progress_bar keys do not crash.
|
||||
checkbox = QCheckBox()
|
||||
# Non-Qt shims keep older helpers working without spawning orphan windows.
|
||||
checkbox = _ShimCheckBox()
|
||||
checkbox.setEnabled(False)
|
||||
checkbox.setVisible(False)
|
||||
progress_label = QLabel("Waiting...")
|
||||
progress_label.setVisible(False)
|
||||
progress_bar = QProgressBar()
|
||||
progress_bar.setVisible(False)
|
||||
tokens_label = QLabel("")
|
||||
tokens_label.setVisible(False)
|
||||
cost_label = QLabel("")
|
||||
cost_label.setVisible(False)
|
||||
time_label = QLabel("")
|
||||
time_label.setVisible(False)
|
||||
status_label = QLabel("")
|
||||
status_label.setVisible(False)
|
||||
shim = QWidget()
|
||||
shim.setVisible(False)
|
||||
progress_label = _ShimLabel("Waiting...")
|
||||
progress_bar = _ShimProgressBar()
|
||||
tokens_label = _ShimLabel("")
|
||||
cost_label = _ShimLabel("")
|
||||
time_label = _ShimLabel("")
|
||||
status_label = _ShimLabel("")
|
||||
shim = _ShimWidget()
|
||||
|
||||
self.file_progress_items[filename] = {
|
||||
"row": row,
|
||||
|
|
@ -2650,7 +2714,7 @@ class TranslationTab(QWidget):
|
|||
filename,
|
||||
tokens=tokens or None,
|
||||
cost=cost or None,
|
||||
time_s=time_s or None,
|
||||
time_s=None if getattr(self, "_batch_active", False) else (time_s or None),
|
||||
)
|
||||
|
||||
def reset_to_file_view(self):
|
||||
|
|
@ -3083,7 +3147,7 @@ class TranslationTab(QWidget):
|
|||
filename,
|
||||
tokens=tokens_text,
|
||||
cost=cost_text,
|
||||
time_s=time_text,
|
||||
time_s=None if getattr(self, "_batch_active", False) else time_text,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue