#!/usr/bin/env python3 from PyQt5.QtWidgets import QMainWindow, QApplication, QAction, QMenuBar, QMessageBox, QFileDialog, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QTableWidget, QTableWidgetItem, QHeaderView, QPushButton from PyQt5.QtCore import Qt import sys import os class LangToolWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Dusk Language Editor") self.showMaximized() 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) # Header editor header_layout = QHBoxLayout() header_layout.addWidget(QLabel("Language:")) self.language_edit = QLineEdit() header_layout.addWidget(self.language_edit) header_layout.addWidget(QLabel("Plural-Forms:")) self.plural_edit = QLineEdit() header_layout.addWidget(self.plural_edit) header_layout.addWidget(QLabel("Content-Type:")) self.content_type_edit = QLineEdit("text/plain; charset=UTF-8") header_layout.addWidget(self.content_type_edit) layout.addLayout(header_layout) # PO entries editor 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) layout.addWidget(self.po_table) # Row add/remove buttons 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) layout.addLayout(row_btn_layout) 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): language = "" plural = "" entries = [] with open(file_path, "r", encoding="utf-8") as f: lines = f.readlines() i = 0 while i < len(lines): line = lines[i].strip() if line.startswith('"Language:'): language = line.split(':',1)[1].strip('"\n ') elif line.startswith('"Plural-Forms:'): plural = line.split(':',1)[1].strip('"\n ') elif line.startswith('msgid'): msgid = line[6:].strip('"') i += 1 msgstr = lines[i].strip()[7:].strip('"') if i < len(lines) and lines[i].strip().startswith('msgstr') else "" entries.append((msgid, msgstr)) i += 1 self.language_edit.setText(language) self.plural_edit.setText(plural) self.po_table.setRowCount(len(entries)) for row, (msgid, msgstr) in enumerate(entries): self.po_table.setItem(row, 0, QTableWidgetItem(msgid)) self.po_table.setItem(row, 1, QTableWidgetItem(msgstr)) def save_file(self, file_path): language = self.language_edit.text() plural = self.plural_edit.text() content_type = self.content_type_edit.text() entries = [] for row in range(self.po_table.rowCount()): msgid = self.po_table.item(row, 0) msgstr = self.po_table.item(row, 1) msgid_text = msgid.text() if msgid else "" msgstr_text = msgstr.text() if msgstr else "" if msgid_text or msgstr_text: entries.append((msgid_text, msgstr_text)) with open(file_path, "w", encoding="utf-8") as f: f.write('msgid ""\nmsgstr ""\n') f.write(f'"Language: {language}"\n') f.write(f'"Content-Type: {content_type}"\n') f.write(f'"Plural-Forms: {plural}"\n') f.write('\n') for msgid, msgstr in entries: f.write(f'msgid "{msgid}"\nmsgstr "{msgstr}"\n\n') 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() if __name__ == "__main__": app = QApplication(sys.argv) window = LangToolWindow() window.show() sys.exit(app.exec_())