Files
dusk/tools/editortool/langtool.py
Dominic Masters be422d0a1e
All checks were successful
Build Dusk / build-psp (push) Successful in 1m5s
Build Dusk / build-linux (push) Successful in 1m2s
More langtool improvements
2025-11-16 00:04:28 -06:00

203 lines
7.0 KiB
Python

#!/usr/bin/env python3
from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, QMenuBar, QMessageBox, QFileDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QTableWidget, QTableWidgetItem, QHeaderView, QPushButton, QTabWidget, QFormLayout
from PyQt5.QtCore import Qt
import sys
import os
import polib
class LangToolWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Dusk Language Editor")
self.setGeometry(100, 100, 800, 600)
self.current_file = None
self.dirty = False
self.init_menu()
self.init_ui()
def init_ui(self):
central = QWidget()
layout = QVBoxLayout(central)
tabs = QTabWidget()
# Header Tab
header_tab = QWidget()
header_layout = QFormLayout(header_tab)
self.language_edit = QLineEdit()
self.language_edit.setMaximumWidth(220)
header_layout.addRow(QLabel("Language:"), self.language_edit)
self.plural_edit = QLineEdit()
self.plural_edit.setMaximumWidth(320)
header_layout.addRow(QLabel("Plural-Forms:"), self.plural_edit)
self.content_type_edit = QLineEdit("text/plain; charset=UTF-8")
self.content_type_edit.setMaximumWidth(320)
header_layout.addRow(QLabel("Content-Type:"), self.content_type_edit)
tabs.addTab(header_tab, "Header")
# Strings Tab
strings_tab = QWidget()
strings_layout = QVBoxLayout(strings_tab)
self.po_table = QTableWidget()
self.po_table.setColumnCount(2)
self.po_table.setHorizontalHeaderLabels(["msgid", "msgstr"])
self.po_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.po_table.verticalHeader().setMinimumWidth(22)
self.po_table.verticalHeader().setDefaultAlignment(Qt.AlignRight | Qt.AlignVCenter)
strings_layout.addWidget(self.po_table)
row_btn_layout = QHBoxLayout()
add_row_btn = QPushButton("Add Row")
remove_row_btn = QPushButton("Remove Row")
row_btn_layout.addWidget(add_row_btn)
row_btn_layout.addWidget(remove_row_btn)
strings_layout.addLayout(row_btn_layout)
tabs.addTab(strings_tab, "Strings")
layout.addWidget(tabs)
add_row_btn.clicked.connect(self.add_row)
remove_row_btn.clicked.connect(self.remove_row)
self.add_row_btn = add_row_btn
self.remove_row_btn = remove_row_btn
self.setCentralWidget(central)
# Connect edits to dirty flag
self.language_edit.textChanged.connect(self.set_dirty)
self.plural_edit.textChanged.connect(self.set_dirty)
self.content_type_edit.textChanged.connect(self.set_dirty)
self.po_table.itemChanged.connect(self.set_dirty)
def set_dirty(self):
self.dirty = True
self.update_save_action()
def init_menu(self):
menubar = self.menuBar()
file_menu = menubar.addMenu("File")
new_action = QAction("New", self)
open_action = QAction("Open", self)
save_action = QAction("Save", self)
save_as_action = QAction("Save As", self)
new_action.triggered.connect(lambda: self.handle_file_action("new"))
open_action.triggered.connect(lambda: self.handle_file_action("open"))
save_action.triggered.connect(self.handle_save)
save_as_action.triggered.connect(self.handle_save_as)
file_menu.addAction(new_action)
file_menu.addAction(open_action)
file_menu.addAction(save_action)
file_menu.addAction(save_as_action)
self.save_action = save_action # Store reference for enabling/disabling
self.update_save_action()
def update_save_action(self):
self.save_action.setEnabled(self.current_file is not None)
def handle_file_action(self, action):
if self.dirty:
reply = QMessageBox.question(self, "Save Changes?", "Do you want to save changes before proceeding?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
if reply == QMessageBox.Cancel:
return
elif reply == QMessageBox.Yes:
self.handle_save()
if action == "new":
self.new_file()
elif action == "open":
default_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../assets/locale"))
file_path, _ = QFileDialog.getOpenFileName(self, "Open Language File", default_dir, "PO Files (*.po)")
if file_path:
self.open_file(file_path)
def new_file(self):
self.current_file = None
self.dirty = False
self.language_edit.setText("")
self.plural_edit.setText("")
self.po_table.setRowCount(0)
self.update_save_action()
def open_file(self, file_path):
self.current_file = file_path
self.dirty = False
self.update_save_action()
self.load_po_file(file_path)
def load_po_file(self, file_path):
po = polib.pofile(file_path)
language = po.metadata.get('Language', '')
plural = po.metadata.get('Plural-Forms', '')
content_type = po.metadata.get('Content-Type', 'text/plain; charset=UTF-8')
self.language_edit.setText(language)
self.plural_edit.setText(plural)
self.content_type_edit.setText(content_type)
self.po_table.setRowCount(len(po))
for row, entry in enumerate(po):
self.po_table.setItem(row, 0, QTableWidgetItem(entry.msgid))
self.po_table.setItem(row, 1, QTableWidgetItem(entry.msgstr))
def save_file(self, file_path):
po = polib.POFile()
po.metadata = {
'Language': self.language_edit.text(),
'Content-Type': self.content_type_edit.text(),
'Plural-Forms': self.plural_edit.text(),
}
for row in range(self.po_table.rowCount()):
msgid_item = self.po_table.item(row, 0)
msgstr_item = self.po_table.item(row, 1)
msgid = msgid_item.text() if msgid_item else ''
msgstr = msgstr_item.text() if msgstr_item else ''
if msgid or msgstr:
entry = polib.POEntry(msgid=msgid, msgstr=msgstr)
po.append(entry)
po.save(file_path)
self.dirty = False
self.update_save_action()
def handle_save(self):
if self.current_file:
self.save_file(self.current_file)
else:
self.handle_save_as()
def handle_save_as(self):
default_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../assets/locale"))
file_path, _ = QFileDialog.getSaveFileName(self, "Save Language File As", default_dir, "PO Files (*.po)")
if file_path:
self.current_file = file_path
self.update_save_action()
self.save_file(file_path)
def add_row(self):
row = self.po_table.rowCount()
self.po_table.insertRow(row)
self.po_table.setItem(row, 0, QTableWidgetItem(""))
self.po_table.setItem(row, 1, QTableWidgetItem(""))
self.po_table.setCurrentCell(row, 0)
self.set_dirty()
def remove_row(self):
row = self.po_table.currentRow()
if row >= 0:
self.po_table.removeRow(row)
self.set_dirty()
def closeEvent(self, event):
if self.dirty:
reply = QMessageBox.question(self, "Save Changes?", "Do you want to save changes before exiting?", QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
if reply == QMessageBox.Cancel:
event.ignore()
return
elif reply == QMessageBox.Yes:
self.handle_save()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = LangToolWindow()
window.show()
sys.exit(app.exec_())