Update CSV to work better with GUI and more compact
This commit is contained in:
parent
c18c2a257c
commit
3c771a2b32
4 changed files with 656 additions and 267 deletions
|
|
@ -16,6 +16,7 @@ from dotenv import load_dotenv, set_key
|
||||||
|
|
||||||
from gui.rpgmaker_tab import RPGMakerTab
|
from gui.rpgmaker_tab import RPGMakerTab
|
||||||
from gui.wolf_tab import WolfTab
|
from gui.wolf_tab import WolfTab
|
||||||
|
from gui.csv_tab import CSVTab
|
||||||
|
|
||||||
|
|
||||||
def create_section_header(title):
|
def create_section_header(title):
|
||||||
|
|
@ -104,6 +105,12 @@ class ConfigTab(QWidget):
|
||||||
nav_layout.addWidget(btn_wolf)
|
nav_layout.addWidget(btn_wolf)
|
||||||
self.nav_buttons.append(btn_wolf)
|
self.nav_buttons.append(btn_wolf)
|
||||||
|
|
||||||
|
# CSV button
|
||||||
|
btn_csv = self.create_nav_button("📄", "CSV")
|
||||||
|
btn_csv.clicked.connect(lambda: self.switch_page(4))
|
||||||
|
nav_layout.addWidget(btn_csv)
|
||||||
|
self.nav_buttons.append(btn_csv)
|
||||||
|
|
||||||
nav_layout.addStretch()
|
nav_layout.addStretch()
|
||||||
nav_bar.setLayout(nav_layout)
|
nav_bar.setLayout(nav_layout)
|
||||||
|
|
||||||
|
|
@ -126,6 +133,10 @@ class ConfigTab(QWidget):
|
||||||
self.wolf_tab = WolfTab()
|
self.wolf_tab = WolfTab()
|
||||||
self.content_stack.addWidget(self.wolf_tab)
|
self.content_stack.addWidget(self.wolf_tab)
|
||||||
|
|
||||||
|
# Page 5: CSV Settings
|
||||||
|
self.csv_tab = CSVTab()
|
||||||
|
self.content_stack.addWidget(self.csv_tab)
|
||||||
|
|
||||||
# Add navigation bar and content to main layout
|
# Add navigation bar and content to main layout
|
||||||
main_layout.addWidget(nav_bar)
|
main_layout.addWidget(nav_bar)
|
||||||
main_layout.addWidget(self.content_stack)
|
main_layout.addWidget(self.content_stack)
|
||||||
|
|
@ -589,6 +600,7 @@ class ConfigTab(QWidget):
|
||||||
self.mvmz_tab.reset_to_defaults()
|
self.mvmz_tab.reset_to_defaults()
|
||||||
self.ace_tab.reset_to_defaults()
|
self.ace_tab.reset_to_defaults()
|
||||||
self.wolf_tab.reset_to_defaults()
|
self.wolf_tab.reset_to_defaults()
|
||||||
|
self.csv_tab.reset_to_defaults()
|
||||||
|
|
||||||
def get_config(self):
|
def get_config(self):
|
||||||
"""Get current configuration as dictionary."""
|
"""Get current configuration as dictionary."""
|
||||||
|
|
|
||||||
498
gui/csv_tab.py
Normal file
498
gui/csv_tab.py
Normal file
|
|
@ -0,0 +1,498 @@
|
||||||
|
"""
|
||||||
|
CSV Tab - Configuration for CSV translation settings
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from PyQt5.QtWidgets import (
|
||||||
|
QWidget, QVBoxLayout, QHBoxLayout, QFormLayout, QCheckBox,
|
||||||
|
QPushButton, QLabel, QMessageBox, QSpinBox, QFrame, QComboBox
|
||||||
|
)
|
||||||
|
from PyQt5.QtCore import Qt, pyqtSignal
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .config_integration import ConfigIntegration
|
||||||
|
except ImportError:
|
||||||
|
ConfigIntegration = None
|
||||||
|
|
||||||
|
|
||||||
|
def create_section_label(text):
|
||||||
|
"""Create a section label for grouping settings."""
|
||||||
|
label = QLabel(text)
|
||||||
|
label.setStyleSheet("""
|
||||||
|
QLabel {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #007acc;
|
||||||
|
padding: 5px 0px 3px 0px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
return label
|
||||||
|
|
||||||
|
|
||||||
|
def create_horizontal_line():
|
||||||
|
"""Create a horizontal separator line."""
|
||||||
|
line = QFrame()
|
||||||
|
line.setFrameShape(QFrame.HLine)
|
||||||
|
line.setFrameShadow(QFrame.Sunken)
|
||||||
|
line.setStyleSheet("QFrame { color: #555555; margin: 5px 0px; }")
|
||||||
|
return line
|
||||||
|
|
||||||
|
|
||||||
|
class CSVTab(QWidget):
|
||||||
|
"""CSV configuration tab for managing CSV translation settings."""
|
||||||
|
|
||||||
|
# Default configuration values (0-indexed internally)
|
||||||
|
DEFAULT_CONFIG = {
|
||||||
|
# Column settings (stored as 0-indexed)
|
||||||
|
"SOURCE_COLUMN": 0,
|
||||||
|
"TARGET_COLUMN": 1,
|
||||||
|
"SPEAKER_COLUMN": -1, # -1 means no speaker column
|
||||||
|
|
||||||
|
# Row settings
|
||||||
|
"SKIP_HEADER_ROW": True,
|
||||||
|
"USE_TARGET_IF_NOT_EMPTY": False,
|
||||||
|
|
||||||
|
# Output settings
|
||||||
|
"WRITE_TO_NEXT_COLUMN": False,
|
||||||
|
|
||||||
|
# Special parsing
|
||||||
|
"PARSE_NAME_TAGS": False,
|
||||||
|
"PARSE_M_MARKERS": False,
|
||||||
|
"REMOVE_FURIGANA": False,
|
||||||
|
"SKIP_COMMENT_ROWS": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
config_changed = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.config_integration = ConfigIntegration() if ConfigIntegration else None
|
||||||
|
self.init_ui()
|
||||||
|
|
||||||
|
# Load configuration from module
|
||||||
|
try:
|
||||||
|
self.disconnect_auto_apply()
|
||||||
|
loaded_config = None
|
||||||
|
if self.config_integration:
|
||||||
|
module_path = Path("modules") / "csv.py"
|
||||||
|
loaded_config = self.read_csv_config(module_path)
|
||||||
|
|
||||||
|
if loaded_config:
|
||||||
|
self.set_config(loaded_config)
|
||||||
|
else:
|
||||||
|
self.set_config(self.DEFAULT_CONFIG)
|
||||||
|
except Exception:
|
||||||
|
self.set_config(self.DEFAULT_CONFIG)
|
||||||
|
|
||||||
|
self.connect_auto_apply()
|
||||||
|
|
||||||
|
def init_ui(self):
|
||||||
|
"""Initialize the user interface."""
|
||||||
|
main_layout = QVBoxLayout()
|
||||||
|
main_layout.setContentsMargins(15, 15, 15, 15)
|
||||||
|
main_layout.setSpacing(10)
|
||||||
|
|
||||||
|
# Title and description
|
||||||
|
title_label = QLabel("CSV Translation Settings")
|
||||||
|
title_label.setStyleSheet("font-size: 15px; font-weight: bold; color: #007acc;")
|
||||||
|
main_layout.addWidget(title_label)
|
||||||
|
|
||||||
|
description_label = QLabel(
|
||||||
|
"Configure how CSV files are parsed and translated. These settings replace the old format presets."
|
||||||
|
)
|
||||||
|
description_label.setWordWrap(True)
|
||||||
|
description_label.setStyleSheet("color: #888888; font-size: 10px; margin-bottom: 5px;")
|
||||||
|
main_layout.addWidget(description_label)
|
||||||
|
|
||||||
|
# Two-column layout
|
||||||
|
columns_layout = QHBoxLayout()
|
||||||
|
columns_layout.setSpacing(40)
|
||||||
|
|
||||||
|
# LEFT COLUMN
|
||||||
|
left_column = QVBoxLayout()
|
||||||
|
left_column.setSpacing(5)
|
||||||
|
|
||||||
|
# Column Settings
|
||||||
|
left_column.addWidget(create_section_label("📊 Column Settings"))
|
||||||
|
|
||||||
|
column_form = QFormLayout()
|
||||||
|
column_form.setSpacing(8)
|
||||||
|
column_form.setContentsMargins(0, 0, 0, 10)
|
||||||
|
|
||||||
|
# Source Column
|
||||||
|
source_label = QLabel("Source Column:")
|
||||||
|
source_label.setToolTip("Which column contains the source text to translate (1 = first column)")
|
||||||
|
self.source_column_spin = QSpinBox()
|
||||||
|
self.source_column_spin.setRange(1, 100)
|
||||||
|
self.source_column_spin.setValue(1)
|
||||||
|
self.source_column_spin.setFixedWidth(80)
|
||||||
|
column_form.addRow(source_label, self.source_column_spin)
|
||||||
|
|
||||||
|
# Target Column
|
||||||
|
target_label = QLabel("Target Column:")
|
||||||
|
target_label.setToolTip("Which column to write translations to (1 = first column)")
|
||||||
|
self.target_column_spin = QSpinBox()
|
||||||
|
self.target_column_spin.setRange(1, 100)
|
||||||
|
self.target_column_spin.setValue(2)
|
||||||
|
self.target_column_spin.setFixedWidth(80)
|
||||||
|
column_form.addRow(target_label, self.target_column_spin)
|
||||||
|
|
||||||
|
# Speaker Column
|
||||||
|
speaker_label = QLabel("Speaker Column:")
|
||||||
|
speaker_label.setToolTip("Which column contains speaker names (0 = none)")
|
||||||
|
self.speaker_column_spin = QSpinBox()
|
||||||
|
self.speaker_column_spin.setRange(0, 100)
|
||||||
|
self.speaker_column_spin.setValue(0)
|
||||||
|
self.speaker_column_spin.setSpecialValueText("None")
|
||||||
|
self.speaker_column_spin.setFixedWidth(80)
|
||||||
|
column_form.addRow(speaker_label, self.speaker_column_spin)
|
||||||
|
|
||||||
|
left_column.addLayout(column_form)
|
||||||
|
left_column.addWidget(create_horizontal_line())
|
||||||
|
|
||||||
|
# Row Settings
|
||||||
|
left_column.addWidget(create_section_label("📝 Row Settings"))
|
||||||
|
|
||||||
|
self.skip_header_cb = QCheckBox("Skip Header Row")
|
||||||
|
self.skip_header_cb.setToolTip("Skip the first row (usually contains column headers)")
|
||||||
|
left_column.addWidget(self.skip_header_cb)
|
||||||
|
|
||||||
|
self.use_target_if_not_empty_cb = QCheckBox("Use Target if Not Empty")
|
||||||
|
self.use_target_if_not_empty_cb.setToolTip("If target column already has text, use that instead of source (T++ style)")
|
||||||
|
left_column.addWidget(self.use_target_if_not_empty_cb)
|
||||||
|
|
||||||
|
left_column.addStretch()
|
||||||
|
|
||||||
|
# RIGHT COLUMN
|
||||||
|
right_column = QVBoxLayout()
|
||||||
|
right_column.setSpacing(5)
|
||||||
|
|
||||||
|
# Output Settings
|
||||||
|
right_column.addWidget(create_section_label("📤 Output Settings"))
|
||||||
|
|
||||||
|
self.write_next_column_cb = QCheckBox("Write to Next Column")
|
||||||
|
self.write_next_column_cb.setToolTip("Write translation to the column after target instead of overwriting")
|
||||||
|
right_column.addWidget(self.write_next_column_cb)
|
||||||
|
|
||||||
|
right_column.addWidget(create_horizontal_line())
|
||||||
|
|
||||||
|
# Special Parsing
|
||||||
|
right_column.addWidget(create_section_label("🔧 Special Parsing"))
|
||||||
|
|
||||||
|
self.parse_name_tags_cb = QCheckBox("Parse :name[] Tags")
|
||||||
|
self.parse_name_tags_cb.setToolTip("Extract speaker names from :name[Speaker] tags in text")
|
||||||
|
right_column.addWidget(self.parse_name_tags_cb)
|
||||||
|
|
||||||
|
self.parse_m_markers_cb = QCheckBox("Parse \\M Markers")
|
||||||
|
self.parse_m_markers_cb.setToolTip("Handle \\M escape sequences in text")
|
||||||
|
right_column.addWidget(self.parse_m_markers_cb)
|
||||||
|
|
||||||
|
self.remove_furigana_cb = QCheckBox("Remove Furigana <=>")
|
||||||
|
self.remove_furigana_cb.setToolTip("Remove furigana annotations like <漢字=かんじ>")
|
||||||
|
right_column.addWidget(self.remove_furigana_cb)
|
||||||
|
|
||||||
|
self.skip_comment_rows_cb = QCheckBox("Skip Comment Rows")
|
||||||
|
self.skip_comment_rows_cb.setToolTip("Skip rows where first column contains 'comment'")
|
||||||
|
right_column.addWidget(self.skip_comment_rows_cb)
|
||||||
|
|
||||||
|
right_column.addStretch()
|
||||||
|
|
||||||
|
# Add columns
|
||||||
|
columns_layout.addLayout(left_column, 1)
|
||||||
|
columns_layout.addLayout(right_column, 1)
|
||||||
|
main_layout.addLayout(columns_layout)
|
||||||
|
|
||||||
|
# Preset buttons
|
||||||
|
main_layout.addWidget(create_horizontal_line())
|
||||||
|
main_layout.addWidget(create_section_label("⚡ Quick Presets"))
|
||||||
|
|
||||||
|
preset_layout = QHBoxLayout()
|
||||||
|
preset_layout.setSpacing(10)
|
||||||
|
|
||||||
|
preset_tpp = QPushButton("Translator++")
|
||||||
|
preset_tpp.setToolTip("Source col 1, Target col 2, skip header, use target if not empty")
|
||||||
|
preset_tpp.clicked.connect(self.apply_preset_tpp)
|
||||||
|
preset_tpp.setMaximumWidth(120)
|
||||||
|
preset_layout.addWidget(preset_tpp)
|
||||||
|
|
||||||
|
preset_simple = QPushButton("Simple")
|
||||||
|
preset_simple.setToolTip("Source col 1, Target col 2, no special processing")
|
||||||
|
preset_simple.clicked.connect(self.apply_preset_simple)
|
||||||
|
preset_simple.setMaximumWidth(120)
|
||||||
|
preset_layout.addWidget(preset_simple)
|
||||||
|
|
||||||
|
preset_speaker = QPushButton("Speaker & Text")
|
||||||
|
preset_speaker.setToolTip("Speaker col 3, Text col 10, with furigana removal")
|
||||||
|
preset_speaker.clicked.connect(self.apply_preset_speaker)
|
||||||
|
preset_speaker.setMaximumWidth(120)
|
||||||
|
preset_layout.addWidget(preset_speaker)
|
||||||
|
|
||||||
|
preset_layout.addStretch()
|
||||||
|
main_layout.addLayout(preset_layout)
|
||||||
|
|
||||||
|
# Bottom buttons
|
||||||
|
main_layout.addSpacing(12)
|
||||||
|
button_layout = QHBoxLayout()
|
||||||
|
button_layout.setSpacing(10)
|
||||||
|
|
||||||
|
self.reset_button = QPushButton("🔄 Reset to Defaults")
|
||||||
|
self.reset_button.clicked.connect(self.reset_to_defaults_with_message)
|
||||||
|
self.reset_button.setMaximumWidth(180)
|
||||||
|
self.reset_button.setMinimumHeight(32)
|
||||||
|
|
||||||
|
button_layout.addWidget(self.reset_button)
|
||||||
|
button_layout.addStretch()
|
||||||
|
|
||||||
|
main_layout.addLayout(button_layout)
|
||||||
|
self.setLayout(main_layout)
|
||||||
|
|
||||||
|
def apply_preset_tpp(self):
|
||||||
|
"""Apply Translator++ preset."""
|
||||||
|
self.disconnect_auto_apply()
|
||||||
|
self.source_column_spin.setValue(1) # Display as 1-based
|
||||||
|
self.target_column_spin.setValue(2)
|
||||||
|
self.speaker_column_spin.setValue(0) # 0 = None
|
||||||
|
self.skip_header_cb.setChecked(True)
|
||||||
|
self.use_target_if_not_empty_cb.setChecked(True)
|
||||||
|
self.write_next_column_cb.setChecked(False)
|
||||||
|
self.parse_name_tags_cb.setChecked(False)
|
||||||
|
self.parse_m_markers_cb.setChecked(False)
|
||||||
|
self.remove_furigana_cb.setChecked(False)
|
||||||
|
self.skip_comment_rows_cb.setChecked(False)
|
||||||
|
self.connect_auto_apply()
|
||||||
|
self.apply_to_module(show_messages=False)
|
||||||
|
|
||||||
|
def apply_preset_simple(self):
|
||||||
|
"""Apply simple two-column preset."""
|
||||||
|
self.disconnect_auto_apply()
|
||||||
|
self.source_column_spin.setValue(1) # Display as 1-based
|
||||||
|
self.target_column_spin.setValue(2)
|
||||||
|
self.speaker_column_spin.setValue(0) # 0 = None
|
||||||
|
self.skip_header_cb.setChecked(False)
|
||||||
|
self.use_target_if_not_empty_cb.setChecked(False)
|
||||||
|
self.write_next_column_cb.setChecked(False)
|
||||||
|
self.parse_name_tags_cb.setChecked(False)
|
||||||
|
self.parse_m_markers_cb.setChecked(False)
|
||||||
|
self.remove_furigana_cb.setChecked(False)
|
||||||
|
self.skip_comment_rows_cb.setChecked(False)
|
||||||
|
self.connect_auto_apply()
|
||||||
|
self.apply_to_module(show_messages=False)
|
||||||
|
|
||||||
|
def apply_preset_speaker(self):
|
||||||
|
"""Apply speaker & text preset (like old format 4)."""
|
||||||
|
self.disconnect_auto_apply()
|
||||||
|
self.source_column_spin.setValue(10) # Display as 1-based (was 9)
|
||||||
|
self.target_column_spin.setValue(10)
|
||||||
|
self.speaker_column_spin.setValue(3) # Display as 1-based (was 2, 0=None so 3=col2)
|
||||||
|
self.skip_header_cb.setChecked(False)
|
||||||
|
self.use_target_if_not_empty_cb.setChecked(False)
|
||||||
|
self.write_next_column_cb.setChecked(False)
|
||||||
|
self.parse_name_tags_cb.setChecked(False)
|
||||||
|
self.parse_m_markers_cb.setChecked(False)
|
||||||
|
self.remove_furigana_cb.setChecked(True)
|
||||||
|
self.skip_comment_rows_cb.setChecked(False)
|
||||||
|
self.connect_auto_apply()
|
||||||
|
self.apply_to_module(show_messages=False)
|
||||||
|
|
||||||
|
def reset_to_defaults(self):
|
||||||
|
"""Reset all settings to default values."""
|
||||||
|
self.disconnect_auto_apply()
|
||||||
|
self.set_config(self.DEFAULT_CONFIG)
|
||||||
|
self.connect_auto_apply()
|
||||||
|
self.apply_to_module(show_messages=False)
|
||||||
|
|
||||||
|
def reset_to_defaults_with_message(self):
|
||||||
|
"""Reset to defaults and show confirmation."""
|
||||||
|
self.reset_to_defaults()
|
||||||
|
QMessageBox.information(
|
||||||
|
self,
|
||||||
|
"Reset Complete",
|
||||||
|
"All CSV settings have been reset to their default values."
|
||||||
|
)
|
||||||
|
|
||||||
|
def disconnect_auto_apply(self):
|
||||||
|
"""Disconnect all widgets from auto-apply."""
|
||||||
|
try:
|
||||||
|
self.source_column_spin.valueChanged.disconnect()
|
||||||
|
self.target_column_spin.valueChanged.disconnect()
|
||||||
|
self.speaker_column_spin.valueChanged.disconnect()
|
||||||
|
self.skip_header_cb.stateChanged.disconnect()
|
||||||
|
self.use_target_if_not_empty_cb.stateChanged.disconnect()
|
||||||
|
self.write_next_column_cb.stateChanged.disconnect()
|
||||||
|
self.parse_name_tags_cb.stateChanged.disconnect()
|
||||||
|
self.parse_m_markers_cb.stateChanged.disconnect()
|
||||||
|
self.remove_furigana_cb.stateChanged.disconnect()
|
||||||
|
self.skip_comment_rows_cb.stateChanged.disconnect()
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def connect_auto_apply(self):
|
||||||
|
"""Connect all widgets to auto-apply changes."""
|
||||||
|
self.source_column_spin.valueChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||||
|
self.target_column_spin.valueChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||||
|
self.speaker_column_spin.valueChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||||
|
self.skip_header_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||||
|
self.use_target_if_not_empty_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||||
|
self.write_next_column_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||||
|
self.parse_name_tags_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||||
|
self.parse_m_markers_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||||
|
self.remove_furigana_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||||
|
self.skip_comment_rows_cb.stateChanged.connect(lambda: self.apply_to_module(show_messages=False))
|
||||||
|
|
||||||
|
def get_config(self):
|
||||||
|
"""Get current configuration as dictionary (converts UI 1-based to internal 0-based)."""
|
||||||
|
# Convert from 1-based UI to 0-based internal
|
||||||
|
# Source/Target: UI shows 1-100, internal uses 0-99
|
||||||
|
# Speaker: UI shows 0 (None) or 1-100, internal uses -1 (None) or 0-99
|
||||||
|
speaker_ui = self.speaker_column_spin.value()
|
||||||
|
speaker_internal = speaker_ui - 1 if speaker_ui > 0 else -1
|
||||||
|
|
||||||
|
return {
|
||||||
|
"SOURCE_COLUMN": self.source_column_spin.value() - 1,
|
||||||
|
"TARGET_COLUMN": self.target_column_spin.value() - 1,
|
||||||
|
"SPEAKER_COLUMN": speaker_internal,
|
||||||
|
"SKIP_HEADER_ROW": self.skip_header_cb.isChecked(),
|
||||||
|
"USE_TARGET_IF_NOT_EMPTY": self.use_target_if_not_empty_cb.isChecked(),
|
||||||
|
"WRITE_TO_NEXT_COLUMN": self.write_next_column_cb.isChecked(),
|
||||||
|
"PARSE_NAME_TAGS": self.parse_name_tags_cb.isChecked(),
|
||||||
|
"PARSE_M_MARKERS": self.parse_m_markers_cb.isChecked(),
|
||||||
|
"REMOVE_FURIGANA": self.remove_furigana_cb.isChecked(),
|
||||||
|
"SKIP_COMMENT_ROWS": self.skip_comment_rows_cb.isChecked(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_config(self, config):
|
||||||
|
"""Set configuration from dictionary (converts internal 0-based to UI 1-based)."""
|
||||||
|
# Convert from 0-based internal to 1-based UI
|
||||||
|
# Source/Target: internal uses 0-99, UI shows 1-100
|
||||||
|
# Speaker: internal uses -1 (None) or 0-99, UI shows 0 (None) or 1-100
|
||||||
|
speaker_internal = config.get("SPEAKER_COLUMN", -1)
|
||||||
|
speaker_ui = speaker_internal + 1 if speaker_internal >= 0 else 0
|
||||||
|
|
||||||
|
self.source_column_spin.setValue(config.get("SOURCE_COLUMN", 0) + 1)
|
||||||
|
self.target_column_spin.setValue(config.get("TARGET_COLUMN", 1) + 1)
|
||||||
|
self.speaker_column_spin.setValue(speaker_ui)
|
||||||
|
self.skip_header_cb.setChecked(config.get("SKIP_HEADER_ROW", True))
|
||||||
|
self.use_target_if_not_empty_cb.setChecked(config.get("USE_TARGET_IF_NOT_EMPTY", False))
|
||||||
|
self.write_next_column_cb.setChecked(config.get("WRITE_TO_NEXT_COLUMN", False))
|
||||||
|
self.parse_name_tags_cb.setChecked(config.get("PARSE_NAME_TAGS", False))
|
||||||
|
self.parse_m_markers_cb.setChecked(config.get("PARSE_M_MARKERS", False))
|
||||||
|
self.remove_furigana_cb.setChecked(config.get("REMOVE_FURIGANA", False))
|
||||||
|
self.skip_comment_rows_cb.setChecked(config.get("SKIP_COMMENT_ROWS", False))
|
||||||
|
|
||||||
|
def read_csv_config(self, module_path: Path) -> dict:
|
||||||
|
"""Read current configuration from csv.py module file."""
|
||||||
|
if not module_path.exists():
|
||||||
|
return {}
|
||||||
|
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(module_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Read integer values
|
||||||
|
int_patterns = {
|
||||||
|
"SOURCE_COLUMN": r'^SOURCE_COLUMN\s*=\s*(-?\d+)',
|
||||||
|
"TARGET_COLUMN": r'^TARGET_COLUMN\s*=\s*(-?\d+)',
|
||||||
|
"SPEAKER_COLUMN": r'^SPEAKER_COLUMN\s*=\s*(-?\d+)',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read boolean values
|
||||||
|
bool_patterns = {
|
||||||
|
"SKIP_HEADER_ROW": r'^SKIP_HEADER_ROW\s*=\s*(True|False)',
|
||||||
|
"USE_TARGET_IF_NOT_EMPTY": r'^USE_TARGET_IF_NOT_EMPTY\s*=\s*(True|False)',
|
||||||
|
"WRITE_TO_NEXT_COLUMN": r'^WRITE_TO_NEXT_COLUMN\s*=\s*(True|False)',
|
||||||
|
"PARSE_NAME_TAGS": r'^PARSE_NAME_TAGS\s*=\s*(True|False)',
|
||||||
|
"PARSE_M_MARKERS": r'^PARSE_M_MARKERS\s*=\s*(True|False)',
|
||||||
|
"REMOVE_FURIGANA": r'^REMOVE_FURIGANA\s*=\s*(True|False)',
|
||||||
|
"SKIP_COMMENT_ROWS": r'^SKIP_COMMENT_ROWS\s*=\s*(True|False)',
|
||||||
|
}
|
||||||
|
|
||||||
|
for line in content.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
for key, pattern in int_patterns.items():
|
||||||
|
match = re.match(pattern, line)
|
||||||
|
if match:
|
||||||
|
config[key] = int(match.group(1))
|
||||||
|
|
||||||
|
for key, pattern in bool_patterns.items():
|
||||||
|
match = re.match(pattern, line)
|
||||||
|
if match:
|
||||||
|
config[key] = match.group(1) == 'True'
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error reading CSV config: {e}")
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
def apply_to_module(self, show_messages=True):
|
||||||
|
"""Apply current configuration to the csv.py module file."""
|
||||||
|
if not self.config_integration:
|
||||||
|
if show_messages:
|
||||||
|
QMessageBox.warning(self, "Error", "Config integration not available")
|
||||||
|
return
|
||||||
|
|
||||||
|
module_path = Path("modules") / "csv.py"
|
||||||
|
|
||||||
|
if not module_path.exists():
|
||||||
|
if show_messages:
|
||||||
|
QMessageBox.warning(self, "Error", f"Module file not found: {module_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
config = self.get_config()
|
||||||
|
|
||||||
|
with open(module_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Update configuration values
|
||||||
|
updated_content = self._update_csv_config(content, config)
|
||||||
|
|
||||||
|
with open(module_path, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(updated_content)
|
||||||
|
|
||||||
|
self.config_changed.emit()
|
||||||
|
|
||||||
|
if show_messages:
|
||||||
|
QMessageBox.information(self, "Success", "CSV configuration applied successfully!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if show_messages:
|
||||||
|
QMessageBox.critical(self, "Error", f"Failed to apply configuration:\n{str(e)}")
|
||||||
|
|
||||||
|
def _update_csv_config(self, content: str, config: dict) -> str:
|
||||||
|
"""Update configuration values in the module content."""
|
||||||
|
import re
|
||||||
|
|
||||||
|
lines = content.split('\n')
|
||||||
|
updated_lines = []
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
updated_line = line
|
||||||
|
|
||||||
|
for key, value in config.items():
|
||||||
|
# Match lines like: CONFIG_NAME = value # comment
|
||||||
|
pattern = rf'^({re.escape(key)})\s*=\s*.*?(#.*)?$'
|
||||||
|
match = re.match(pattern, line.strip())
|
||||||
|
|
||||||
|
if match:
|
||||||
|
comment = match.group(2) if match.group(2) else ""
|
||||||
|
if comment:
|
||||||
|
comment = " " + comment
|
||||||
|
|
||||||
|
# Format value appropriately
|
||||||
|
if isinstance(value, bool):
|
||||||
|
value_str = str(value)
|
||||||
|
else:
|
||||||
|
value_str = str(value)
|
||||||
|
|
||||||
|
updated_line = f"{key} = {value_str}{comment}"
|
||||||
|
break
|
||||||
|
|
||||||
|
updated_lines.append(updated_line)
|
||||||
|
|
||||||
|
return '\n'.join(updated_lines)
|
||||||
|
|
@ -986,21 +986,6 @@ class TranslationTab(QWidget):
|
||||||
self.mode_combo.currentTextChanged.connect(self._on_mode_changed)
|
self.mode_combo.currentTextChanged.connect(self._on_mode_changed)
|
||||||
trans_form.addRow(mode_label, self.mode_combo)
|
trans_form.addRow(mode_label, self.mode_combo)
|
||||||
|
|
||||||
# CSV Format dropdown (only visible when CSV engine is selected)
|
|
||||||
self.csv_format_label = QLabel("CSV Format:")
|
|
||||||
self.csv_format_label.setFixedWidth(100)
|
|
||||||
self.csv_format_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
|
||||||
self.csv_format_combo = QComboBox()
|
|
||||||
self.csv_format_combo.addItem("Translator++")
|
|
||||||
self.csv_format_combo.addItem("Single")
|
|
||||||
self.csv_format_combo.addItem("Multiple")
|
|
||||||
self.csv_format_combo.addItem("Speaker&Text")
|
|
||||||
self.csv_format_combo.setFixedWidth(300)
|
|
||||||
trans_form.addRow(self.csv_format_label, self.csv_format_combo)
|
|
||||||
# Hide by default (shown when CSV is selected)
|
|
||||||
self.csv_format_label.setVisible(False)
|
|
||||||
self.csv_format_combo.setVisible(False)
|
|
||||||
|
|
||||||
layout.addLayout(trans_form)
|
layout.addLayout(trans_form)
|
||||||
layout.addWidget(create_horizontal_line())
|
layout.addWidget(create_horizontal_line())
|
||||||
|
|
||||||
|
|
@ -1141,11 +1126,6 @@ class TranslationTab(QWidget):
|
||||||
else:
|
else:
|
||||||
self.mode_combo.setCurrentIndex(0)
|
self.mode_combo.setCurrentIndex(0)
|
||||||
|
|
||||||
# Show/hide CSV format dropdown based on engine selection
|
|
||||||
is_csv = "csv" in lowered
|
|
||||||
self.csv_format_label.setVisible(is_csv)
|
|
||||||
self.csv_format_combo.setVisible(is_csv)
|
|
||||||
|
|
||||||
# Refresh file list to show only files matching the selected module's extensions
|
# Refresh file list to show only files matching the selected module's extensions
|
||||||
self.refresh_file_lists()
|
self.refresh_file_lists()
|
||||||
|
|
||||||
|
|
@ -1627,13 +1607,6 @@ class TranslationTab(QWidget):
|
||||||
)
|
)
|
||||||
|
|
||||||
if reply == QMessageBox.Yes:
|
if reply == QMessageBox.Yes:
|
||||||
# Write CSV format to csv.tmp if CSV module is selected
|
|
||||||
if "csv" in selected_module[0].lower():
|
|
||||||
csv_format_index = self.csv_format_combo.currentIndex() + 1 # 1-based (1-4)
|
|
||||||
csv_tmp_path = self.project_root / "csv.tmp"
|
|
||||||
with open(csv_tmp_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write(str(csv_format_index))
|
|
||||||
|
|
||||||
# Switch to progress view
|
# Switch to progress view
|
||||||
self.file_stack.setCurrentIndex(1)
|
self.file_stack.setCurrentIndex(1)
|
||||||
|
|
||||||
|
|
|
||||||
386
modules/csv.py
386
modules/csv.py
|
|
@ -39,6 +39,18 @@ MISMATCH = [] # Lists files that thdata a mismatch error (Length of GPT list re
|
||||||
FILENAME = None
|
FILENAME = None
|
||||||
BRACKETNAMES = False
|
BRACKETNAMES = False
|
||||||
|
|
||||||
|
# CSV Configuration Settings (configurable via GUI)
|
||||||
|
SOURCE_COLUMN = 2 # Which column has the source text to translate
|
||||||
|
TARGET_COLUMN = 3 # Which column to write translations to
|
||||||
|
SPEAKER_COLUMN = -1 # Which column has speaker names (-1 = none)
|
||||||
|
SKIP_HEADER_ROW = True # Skip the first row (header)
|
||||||
|
USE_TARGET_IF_NOT_EMPTY = False # Use target column text if not empty (T++ style)
|
||||||
|
WRITE_TO_NEXT_COLUMN = False # Write to column after target instead of overwriting
|
||||||
|
PARSE_NAME_TAGS = False # Parse :name[] tags in text
|
||||||
|
PARSE_M_MARKERS = False # Parse \M markers in text
|
||||||
|
REMOVE_FURIGANA = False # Remove furigana annotations <=>
|
||||||
|
SKIP_COMMENT_ROWS = False # Skip rows starting with 'comment'
|
||||||
|
|
||||||
# Regex - Need to change this if you want to translate from/to other languages. Default is Japanese Regex
|
# Regex - Need to change this if you want to translate from/to other languages. Default is Japanese Regex
|
||||||
LANGREGEX = r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9\uFF61-\uFF9F]+"
|
LANGREGEX = r"[一-龠ぁ-ゔァ-ヴーa-zA-Z0-9\uFF61-\uFF9F]+"
|
||||||
|
|
||||||
|
|
@ -152,30 +164,6 @@ def parseCSV(readFile, writeFile, filename):
|
||||||
totalLines = 0
|
totalLines = 0
|
||||||
global LOCK
|
global LOCK
|
||||||
|
|
||||||
# Read from tmp files
|
|
||||||
if os.path.isfile("csv.tmp"):
|
|
||||||
with open("csv.tmp") as tmpFile:
|
|
||||||
format = tmpFile.readline()
|
|
||||||
else:
|
|
||||||
format = ""
|
|
||||||
|
|
||||||
# Choices
|
|
||||||
while format not in ["1", "2", "3", "4"]:
|
|
||||||
format = input("\n\nSelect the CSV Format:\n\n1. Translator++\n2. Single\n3. Multiple\n4. Speaker&Text\n")
|
|
||||||
match format:
|
|
||||||
case "1":
|
|
||||||
format = "1"
|
|
||||||
case "2":
|
|
||||||
format = "2"
|
|
||||||
case "3":
|
|
||||||
format = "3"
|
|
||||||
case "4":
|
|
||||||
format = "4"
|
|
||||||
|
|
||||||
# Write to file for later use
|
|
||||||
with open("csv.tmp", "w", encoding="utf-8") as tmpFile:
|
|
||||||
tmpFile.write(f"{format}")
|
|
||||||
|
|
||||||
# Get total for progress bar
|
# Get total for progress bar
|
||||||
totalLines = len(readFile.readlines())
|
totalLines = len(readFile.readlines())
|
||||||
readFile.seek(0)
|
readFile.seek(0)
|
||||||
|
|
@ -199,7 +187,7 @@ def parseCSV(readFile, writeFile, filename):
|
||||||
data.append(row)
|
data.append(row)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = translateCSV(data, pbar, writeFile, writer, filename, None, format)
|
response = translateCSV(data, pbar, writeFile, writer, filename, None)
|
||||||
totalTokens[0] = response[0]
|
totalTokens[0] = response[0]
|
||||||
totalTokens[1] = response[1]
|
totalTokens[1] = response[1]
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
@ -224,7 +212,22 @@ def flush_progress_csv(writeFile, writer, rows):
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
def translateCSV(data, pbar, writeFile, writer, filename, translatedList, format):
|
def translateCSV(data, pbar, writeFile, writer, filename, translatedList):
|
||||||
|
"""
|
||||||
|
Unified CSV translation function using configurable settings.
|
||||||
|
|
||||||
|
Uses global settings:
|
||||||
|
- SOURCE_COLUMN: column index for source text
|
||||||
|
- TARGET_COLUMN: column index to write translations
|
||||||
|
- SPEAKER_COLUMN: column index for speaker names (-1 = none)
|
||||||
|
- SKIP_HEADER_ROW: whether to skip first row
|
||||||
|
- USE_TARGET_IF_NOT_EMPTY: use existing target text if present (T++ style)
|
||||||
|
- WRITE_TO_NEXT_COLUMN: write to column after target
|
||||||
|
- PARSE_NAME_TAGS: parse :name[] tags
|
||||||
|
- PARSE_M_MARKERS: parse \\M markers
|
||||||
|
- REMOVE_FURIGANA: remove furigana
|
||||||
|
- SKIP_COMMENT_ROWS: skip rows with 'comment' in first column
|
||||||
|
"""
|
||||||
global LOCK, ESTIMATE, PBAR
|
global LOCK, ESTIMATE, PBAR
|
||||||
PBAR = pbar
|
PBAR = pbar
|
||||||
translatedText = ""
|
translatedText = ""
|
||||||
|
|
@ -235,219 +238,123 @@ def translateCSV(data, pbar, writeFile, writer, filename, translatedList, format
|
||||||
try:
|
try:
|
||||||
# Translate
|
# Translate
|
||||||
while i < len(data):
|
while i < len(data):
|
||||||
match format:
|
# Skip header row if configured
|
||||||
# T++ Format: Source Text on column 1. TL Target on Column 2
|
if SKIP_HEADER_ROW and i == 0:
|
||||||
case "1":
|
i += 1
|
||||||
# Get String
|
continue
|
||||||
if i != 0:
|
|
||||||
if data[i][1] == "":
|
# Skip comment rows if configured
|
||||||
jaString = data[i][0]
|
if SKIP_COMMENT_ROWS and len(data[i]) > 0 and 'comment' in str(data[i][0]).lower():
|
||||||
else:
|
i += 1
|
||||||
jaString = data[i][1]
|
continue
|
||||||
|
|
||||||
|
# Check if row has enough columns
|
||||||
|
if len(data[i]) <= SOURCE_COLUMN:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get source text
|
||||||
|
jaString = ""
|
||||||
|
speaker = ""
|
||||||
|
|
||||||
|
# If USE_TARGET_IF_NOT_EMPTY is enabled (T++ style), check target column first
|
||||||
|
if USE_TARGET_IF_NOT_EMPTY and len(data[i]) > TARGET_COLUMN and data[i][TARGET_COLUMN]:
|
||||||
|
jaString = data[i][TARGET_COLUMN]
|
||||||
|
else:
|
||||||
|
jaString = data[i][SOURCE_COLUMN] if data[i][SOURCE_COLUMN] else ""
|
||||||
|
|
||||||
|
# Skip empty strings
|
||||||
|
if not jaString:
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Handle speaker column if configured
|
||||||
|
if SPEAKER_COLUMN >= 0 and len(data[i]) > SPEAKER_COLUMN and data[i][SPEAKER_COLUMN]:
|
||||||
|
speakerResponse = getSpeaker(data[i][SPEAKER_COLUMN])
|
||||||
|
totalTokens[0] += speakerResponse[1][0]
|
||||||
|
totalTokens[1] += speakerResponse[1][1]
|
||||||
|
speaker = speakerResponse[0]
|
||||||
|
data[i][SPEAKER_COLUMN] = speaker
|
||||||
|
|
||||||
|
# Parse :name[] tags if configured
|
||||||
|
if PARSE_NAME_TAGS and ':name' in jaString:
|
||||||
|
match = re.search(r":name\[([^\]]+?)\]\n([\w\W]*)", jaString)
|
||||||
|
if match:
|
||||||
|
# Translate speaker name
|
||||||
|
response = getSpeaker(match.group(1))
|
||||||
|
speaker = response[0]
|
||||||
|
totalTokens[0] += response[1][0]
|
||||||
|
totalTokens[1] += response[1][1]
|
||||||
|
data[i][SOURCE_COLUMN] = data[i][SOURCE_COLUMN].replace(match.group(1), speaker)
|
||||||
|
|
||||||
|
# Extract text portion
|
||||||
|
jaString = match.group(2)
|
||||||
|
# Remove voice markers
|
||||||
|
voMatch = re.search(r"\\[vfF]+\[.+]", jaString)
|
||||||
|
if voMatch:
|
||||||
|
jaString = jaString.replace(voMatch.group(0), "")
|
||||||
|
|
||||||
|
# Parse \M markers if configured
|
||||||
|
if PARSE_M_MARKERS and '\\M' in jaString:
|
||||||
|
match = re.search(r"\\M.+\n([\w\W]*)", jaString)
|
||||||
|
if match:
|
||||||
|
jaString = match.group(1)
|
||||||
|
voMatch = re.search(r"\\[vfF]+\[.+]", jaString)
|
||||||
|
if voMatch:
|
||||||
|
jaString = jaString.replace(voMatch.group(0), "")
|
||||||
|
|
||||||
|
# Remove furigana if configured
|
||||||
|
if REMOVE_FURIGANA:
|
||||||
|
jaString = re.sub(r"<(.*)=.*>", r"\1", jaString)
|
||||||
|
|
||||||
|
# Store original for replacement
|
||||||
|
ojaString = jaString
|
||||||
|
|
||||||
|
# Remove textwrap
|
||||||
|
jaString = jaString.replace("\\n", " ")
|
||||||
|
jaString = jaString.replace("\n", " ")
|
||||||
|
|
||||||
|
# Pass 1: Collect strings
|
||||||
|
if not translatedList:
|
||||||
|
if speaker:
|
||||||
|
stringList.append(f"[{speaker}]: {jaString}")
|
||||||
|
else:
|
||||||
|
stringList.append(jaString)
|
||||||
|
|
||||||
|
# Pass 2: Apply translations
|
||||||
|
else:
|
||||||
|
# Grab and pop translation
|
||||||
|
translatedText = translatedList[0]
|
||||||
|
translatedList.pop(0)
|
||||||
|
|
||||||
|
# Remove speaker prefix from translation if present
|
||||||
|
if speaker:
|
||||||
|
translatedText = re.sub(r"^\[?(.+?)\]?\s?[|:]\s?", "", translatedText)
|
||||||
|
|
||||||
|
# Add wordwrap
|
||||||
|
translatedText = dazedwrap.wrapText(translatedText, WIDTH)
|
||||||
|
translatedText = translatedText.replace("\n", "\\n")
|
||||||
|
|
||||||
|
# Determine target column
|
||||||
|
actual_target = TARGET_COLUMN + 1 if WRITE_TO_NEXT_COLUMN else TARGET_COLUMN
|
||||||
|
|
||||||
|
# Ensure row has enough columns
|
||||||
|
while len(data[i]) <= actual_target:
|
||||||
|
data[i].append("")
|
||||||
|
|
||||||
|
# Set data
|
||||||
|
if PARSE_NAME_TAGS or PARSE_M_MARKERS:
|
||||||
|
# Replace original text portion
|
||||||
|
data[i][actual_target] = data[i][actual_target].replace(ojaString, translatedText) if data[i][actual_target] else translatedText
|
||||||
|
else:
|
||||||
|
data[i][actual_target] = translatedText
|
||||||
|
|
||||||
|
flush_progress_csv(writeFile, writer, data)
|
||||||
|
|
||||||
|
# Iterate
|
||||||
|
i += 1
|
||||||
|
|
||||||
# Remove Textwrap
|
# EOF - Process collected strings
|
||||||
jaString = jaString.replace("\\n", " ")
|
|
||||||
|
|
||||||
# Pass 1
|
|
||||||
if not translatedList:
|
|
||||||
stringList.append(jaString)
|
|
||||||
|
|
||||||
# Pass 2
|
|
||||||
else:
|
|
||||||
# Grab and Pop
|
|
||||||
translatedText = translatedList[0]
|
|
||||||
translatedList.pop(0)
|
|
||||||
|
|
||||||
# Add Wordwrap
|
|
||||||
translatedText = dazedwrap.wrapText(translatedText, WIDTH)
|
|
||||||
translatedText = translatedText.replace("\n", "\\n")
|
|
||||||
|
|
||||||
# Set Data
|
|
||||||
data[i][1] = translatedText
|
|
||||||
flush_progress_csv(writeFile, writer, data)
|
|
||||||
|
|
||||||
# Iterate
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# Target Format
|
|
||||||
case "2":
|
|
||||||
# Set Values
|
|
||||||
sourceColumn = 0
|
|
||||||
targetColumn = 1
|
|
||||||
|
|
||||||
# Check if Translated
|
|
||||||
jaString = data[i][sourceColumn]
|
|
||||||
|
|
||||||
# Remove Textwrap
|
|
||||||
jaString = jaString.replace("\\n", " ")
|
|
||||||
|
|
||||||
# Pass 1
|
|
||||||
if not translatedList:
|
|
||||||
stringList.append(jaString)
|
|
||||||
|
|
||||||
# Pass 2
|
|
||||||
else:
|
|
||||||
# Grab and Pop
|
|
||||||
translatedText = translatedList[0]
|
|
||||||
translatedList.pop(0)
|
|
||||||
|
|
||||||
# Add Wordwrap
|
|
||||||
translatedText = dazedwrap.wrapText(translatedText, WIDTH)
|
|
||||||
translatedText = translatedText.replace("\n", "\\n")
|
|
||||||
|
|
||||||
# Set Data
|
|
||||||
data[i][targetColumn] = translatedText
|
|
||||||
flush_progress_csv(writeFile, writer, data)
|
|
||||||
|
|
||||||
# Iterate
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# In Place Format
|
|
||||||
case "3":
|
|
||||||
# Set columns to translate. Leave empty to translate all.
|
|
||||||
targetColumns = [1]
|
|
||||||
|
|
||||||
# False - Place translation in source column
|
|
||||||
# True - Place translation in next column
|
|
||||||
targetNextRow = False
|
|
||||||
|
|
||||||
# Skip 1st Row
|
|
||||||
skipFirstRow = True
|
|
||||||
|
|
||||||
for j in range(len(data[i])):
|
|
||||||
if skipFirstRow and i == 0:
|
|
||||||
continue
|
|
||||||
if j in targetColumns and data[i][j]:
|
|
||||||
# Check if Translated
|
|
||||||
jaString = data[i][j]
|
|
||||||
speaker = ""
|
|
||||||
vo = ""
|
|
||||||
|
|
||||||
if ':name' in jaString:
|
|
||||||
match = re.search(r":name\[([^\]]+?)\]\n([\w\W]*)", jaString)
|
|
||||||
if match:
|
|
||||||
# TL Speaker
|
|
||||||
response = getSpeaker(match.group(1))
|
|
||||||
speaker = response[0]
|
|
||||||
totalTokens[0] += response[1][0]
|
|
||||||
totalTokens[1] += response[1][1]
|
|
||||||
data[i][j] = data[i][j].replace(match.group(1), speaker)
|
|
||||||
|
|
||||||
# TL Text
|
|
||||||
jaString = match.group(2)
|
|
||||||
voMatch = re.search(r"\\[vfF]+\[.+]", jaString)
|
|
||||||
if voMatch:
|
|
||||||
vo = voMatch.group(0)
|
|
||||||
jaString = jaString.replace(vo, "")
|
|
||||||
jaString = jaString.replace(vo, "")
|
|
||||||
elif '\\M' in jaString:
|
|
||||||
match = re.search(r"\\M.+\n([\w\W]*)", jaString)
|
|
||||||
if match:
|
|
||||||
jaString = match.group(1)
|
|
||||||
voMatch = re.search(r"\\[vfF]+\[.+]", jaString)
|
|
||||||
if voMatch:
|
|
||||||
vo = voMatch.group(0)
|
|
||||||
jaString = jaString.replace(vo, "")
|
|
||||||
elif 'comment' in data[i][0]:
|
|
||||||
# Skip comments
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Remove Textwrap
|
|
||||||
ojaString = jaString
|
|
||||||
jaString = jaString.replace("\n", " ")
|
|
||||||
|
|
||||||
# Pass 1
|
|
||||||
if not translatedList:
|
|
||||||
if speaker:
|
|
||||||
stringList.append(f"[{speaker}]: {jaString}")
|
|
||||||
else:
|
|
||||||
stringList.append(jaString)
|
|
||||||
|
|
||||||
# Pass 2
|
|
||||||
else:
|
|
||||||
# Grab and Pop
|
|
||||||
translatedText = translatedList[0]
|
|
||||||
translatedList.pop(0)
|
|
||||||
|
|
||||||
# Remove speaker
|
|
||||||
if speaker:
|
|
||||||
translatedText = re.sub(r"^\[?(.+?)\]?\s?[|:]\s?", "", translatedText)
|
|
||||||
|
|
||||||
# Add Wordwrap
|
|
||||||
translatedText = dazedwrap.wrapText(translatedText, WIDTH)
|
|
||||||
|
|
||||||
# Set Data
|
|
||||||
if targetNextRow:
|
|
||||||
data[i][j + 1] = data[i][j + 1].replace(ojaString, f"{translatedText}")
|
|
||||||
else:
|
|
||||||
data[i][j] = data[i][j].replace(ojaString, f"{translatedText}")
|
|
||||||
flush_progress_csv(writeFile, writer, data)
|
|
||||||
|
|
||||||
# Iterate
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# Speaker & Text Format
|
|
||||||
case "4":
|
|
||||||
# Set columns to translate. Leave empty to translate all.
|
|
||||||
speakerColumn = 2
|
|
||||||
textColumn = 9
|
|
||||||
speaker = ""
|
|
||||||
|
|
||||||
if len(data[i]) > textColumn and data[i][textColumn]:
|
|
||||||
# Speaker
|
|
||||||
if data[i][speakerColumn]:
|
|
||||||
speakerResponse = getSpeaker(data[i][speakerColumn])
|
|
||||||
totalTokens[0] += speakerResponse[1][0]
|
|
||||||
totalTokens[1] += speakerResponse[1][1]
|
|
||||||
speaker = speakerResponse[0]
|
|
||||||
data[i][speakerColumn] = speaker
|
|
||||||
|
|
||||||
# Get Text
|
|
||||||
jaString = data[i][textColumn]
|
|
||||||
|
|
||||||
# Remove Textwrap
|
|
||||||
jaString = jaString.replace("\\n", " ")
|
|
||||||
|
|
||||||
# Remove Furigana
|
|
||||||
jaString = re.sub(r"<(.*)=.*>", r"\1", jaString)
|
|
||||||
|
|
||||||
# Pass 1
|
|
||||||
if not translatedList:
|
|
||||||
# Append Speaker
|
|
||||||
if speaker:
|
|
||||||
jaString = f"[{speaker}]: {jaString}"
|
|
||||||
|
|
||||||
# Append to List
|
|
||||||
stringList.append(jaString)
|
|
||||||
|
|
||||||
# Pass 2
|
|
||||||
else:
|
|
||||||
# Grab and Pop
|
|
||||||
translatedText = translatedList[0]
|
|
||||||
translatedList.pop(0)
|
|
||||||
|
|
||||||
# Add Wordwrap
|
|
||||||
translatedText = dazedwrap.wrapText(translatedText, WIDTH)
|
|
||||||
translatedText = translatedText.replace("\n", "\\n")
|
|
||||||
|
|
||||||
# Check for more than 3 newlines (Shoujo Ramune)
|
|
||||||
newline_count = translatedText.count("\\n")
|
|
||||||
if newline_count >= 3:
|
|
||||||
parts = translatedText.split("\\n", 3)
|
|
||||||
data[i][textColumn] = parts[0] + "\\n" + parts[1] + "\\n" + parts[2]
|
|
||||||
new_row = data[i].copy()
|
|
||||||
new_row[textColumn] = parts[3]
|
|
||||||
new_row[0] = str(int(new_row[0]) + 1) # Add 1 to the line number
|
|
||||||
data.insert(i + 1, new_row)
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
data[i][textColumn] = translatedText
|
|
||||||
flush_progress_csv(writeFile, writer, data)
|
|
||||||
|
|
||||||
# Iterate
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
# EOF
|
|
||||||
if len(stringList) > 0:
|
if len(stringList) > 0:
|
||||||
# Set Progress
|
# Set Progress
|
||||||
pbar.total = len(stringList)
|
pbar.total = len(stringList)
|
||||||
|
|
@ -461,8 +368,7 @@ def translateCSV(data, pbar, writeFile, writer, filename, translatedList, format
|
||||||
|
|
||||||
# Set Strings
|
# Set Strings
|
||||||
if len(stringList) == len(translatedList):
|
if len(stringList) == len(translatedList):
|
||||||
translateCSV(data, pbar, writer, filename, translatedList, format)
|
translateCSV(data, pbar, writeFile, writer, filename, translatedList)
|
||||||
|
|
||||||
# Mismatch
|
# Mismatch
|
||||||
else:
|
else:
|
||||||
with LOCK:
|
with LOCK:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue