Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add translation support. #31

Merged
merged 3 commits into from
Aug 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ nexus.egg-info/
.coverage
**/__pycache__/
src/nexus/ui/*.py
src/nexus/translations/
12 changes: 12 additions & 0 deletions dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import os
import sys
import argparse
import glob
from pathlib import Path

# Error and exit on Python version < 3.11
if sys.version_info < (3, 11):
Expand Down Expand Up @@ -51,6 +53,16 @@
os.system(f"{venv_path}pyside6-uic ui/banword.ui -o src/nexus/ui/BanwordDialog.py")
os.system(f"{venv_path}pyside6-uic ui/confirm.ui -o src/nexus/ui/ConfirmDialog.py")

print("Generating TS template...")
os.system(f"{venv_path}pyside6-lupdate " +
' '.join(glob.glob('ui/*.ui') + ["src/nexus/GUI.py"]) +
" -ts translations/i18n_en.ts")

print("Generating QM files...")
os.makedirs('src/nexus/translations', exist_ok=True)
for i in glob.glob('translations/*.ts'):
os.system(f"{venv_path}pyside6-lrelease {i} -qm src/nexus/translations/{Path(i).stem}.qm")

if not (args.no_build or args.ui_only):
# Pyinstaller command
build_cmd = "pyinstaller -Fn nexus src/nexus/__main__.py"
Expand Down
44 changes: 33 additions & 11 deletions src/nexus/GUI.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import argparse
from threading import Thread
from pathlib import Path

from PySide6.QtCore import Qt
from PySide6.QtCore import Qt, QTranslator, QLocale
from PySide6.QtWidgets import QApplication, QPushButton, QStatusBar, QTableWidget, QTableWidgetItem, QMainWindow, \
QDialog, QFileDialog

Expand Down Expand Up @@ -46,12 +47,29 @@ def __init__(self, *args, **kwargs):
self.setupUi(self)


class Translator(QTranslator):
"""Custom translator"""

def translate(self, context: str, source: str, disambiguation=None, n=-1):
final = super().translate(context, source, disambiguation, n)
if final:
return final
return source


class GUI(object):
"""Nexus GUI"""

def __init__(self, args: argparse.Namespace):
"""Initialize GUI"""
self.app = QApplication([])

self.translator = Translator(self.app)

if self.translator.load(QLocale.system(), 'i18n', '_', str(Path(__file__).resolve().parent)+'/translations'):
self.app.installTranslator(self.translator)
self.tr = self.translator.translate

self.window = MainWindow()

# Components
Expand Down Expand Up @@ -84,12 +102,12 @@ def stop_logging(self):

def start_stop(self):
"""Controller for start/stop logging button"""
if self.start_stop_button.text() == "Start logging":
if self.start_stop_button.text() == self.tr("GUI", "Start logging"):
# Update button to starting
# TODO: fix signal blocking (not currently working)
self.start_stop_button.blockSignals(True)
self.start_stop_button.setEnabled(False)
self.start_stop_button.setText("Starting...")
self.start_stop_button.setText(self.tr("GUI", "Starting..."))
self.start_stop_button.setStyleSheet("background-color: yellow")
self.window.repaint()

Expand All @@ -100,11 +118,11 @@ def start_stop(self):
# Update button to stop
while not (self.freqlog and self.freqlog.is_logging):
pass
self.start_stop_button.setText("Stop logging")
self.start_stop_button.setText(self.tr("GUI", "Stop logging"))
self.start_stop_button.setStyleSheet("background-color: red")
self.start_stop_button.setEnabled(True)
self.start_stop_button.blockSignals(False)
self.statusbar.showMessage("Logging started")
self.statusbar.showMessage(self.tr("GUI", "Logging started"))
self.window.repaint()
else:
# Update button to stopping
Expand All @@ -119,11 +137,11 @@ def start_stop(self):

# Update button to start
self.logging_thread.join()
self.start_stop_button.setText("Start logging")
self.start_stop_button.setText(self.tr("GUI", "Start logging"))
self.start_stop_button.setStyleSheet("background-color: green")
self.start_stop_button.setEnabled(True)
self.start_stop_button.blockSignals(False)
self.statusbar.showMessage("Logging stopped")
self.statusbar.showMessage(self.tr("GUI", "Logging stopped"))
self.window.repaint()

def refresh(self):
Expand Down Expand Up @@ -156,7 +174,7 @@ def refresh(self):
self.chentry_table.setRowCount(len(words))
self.chentry_table.resizeColumnsToContents()
self.chentry_table.setSortingEnabled(True)
self.statusbar.showMessage(f"Loaded {len(words)} freqlogged words")
self.statusbar.showMessage(self.tr("GUI", "Loaded {} freqlogged words").format(len(words)))

def show_banlist(self):
"""Controller for banlist button"""
Expand Down Expand Up @@ -217,8 +235,11 @@ def remove_banword():
] = (CaseSensitivity.SENSITIVE if bl_dialog.banlistTable.item(row.row(), 2).text() == "Sensitive"
else CaseSensitivity.INSENSITIVE)
conf_dialog = ConfirmDialog()
conf_dialog.confirmText.setText(
f"Unban {len(selected_words)} word{'s' if len(selected_words) > 1 else ''}?")
if len(selected_words) > 1:
confirmText = self.tr("GUI", "Unban {} words")
else:
confirmText = self.tr("GUI", "Unban one word")
conf_dialog.confirmText.setText(confirmText.format(len(selected_words)))
conf_dialog.buttonBox.accepted.connect(lambda: self.temp_freqlog.unban_words(selected_words))
conf_dialog.exec()
refresh_banlist()
Expand All @@ -234,7 +255,8 @@ def export(self):
if not filename.endswith(".csv"):
filename += ".csv"
num_exported = self.temp_freqlog.export_words_to_csv(filename)
self.statusbar.showMessage(f"Exported {num_exported} words to {filename}")
self.statusbar.showMessage(
self.tr("GUI", "Exported {} words to {}".format(num_exported, filename)))

def exec(self):
"""Start the GUI"""
Expand Down
210 changes: 210 additions & 0 deletions translations/i18n_en.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US">
<context>
<name>BanlistDialog</name>
<message>
<location filename="../ui/banlist.ui" line="14"/>
<source>Banlist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/banlist.ui" line="39"/>
<source>Word</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/banlist.ui" line="44"/>
<source>Date added</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/banlist.ui" line="49"/>
<source>Case</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/banlist.ui" line="62"/>
<source>Add</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/banlist.ui" line="69"/>
<source>Remove</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>BanwordDialog</name>
<message>
<location filename="../ui/banword.ui" line="17"/>
<source>Ban word</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/banword.ui" line="25"/>
<source>Word to ban:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/banword.ui" line="39"/>
<source>Case:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/banword.ui" line="46"/>
<source>Insensitive</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/banword.ui" line="56"/>
<source>Sensitive</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/banword.ui" line="63"/>
<source>First char</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>ConfirmDialog</name>
<message>
<location filename="../ui/confirm.ui" line="14"/>
<source>Ban word</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/confirm.ui" line="20"/>
<source>Are you sure?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>GUI</name>
<message>
<location filename="../src/nexus/GUI.py" line="108"/>
<location filename="../src/nexus/GUI.py" line="140"/>
<source>GUI</source>
<comment>Start logging</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/nexus/GUI.py" line="110"/>
<source>GUI</source>
<comment>Starting...</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/nexus/GUI.py" line="121"/>
<source>GUI</source>
<comment>Stop logging</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/nexus/GUI.py" line="125"/>
<source>GUI</source>
<comment>Logging started</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/nexus/GUI.py" line="144"/>
<source>GUI</source>
<comment>Logging stopped</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/nexus/GUI.py" line="177"/>
<source>GUI</source>
<comment>Loaded {} freqlogged words</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/nexus/GUI.py" line="240"/>
<source>GUI</source>
<comment>Unban {} words</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/nexus/GUI.py" line="242"/>
<source>GUI</source>
<comment>Unban one word</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../src/nexus/GUI.py" line="259"/>
<source>GUI</source>
<comment>Exported {} words to {}</comment>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../ui/main.ui" line="14"/>
<source>Nexus</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/main.ui" line="32"/>
<source>Start logging</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/main.ui" line="39"/>
<source>Refresh</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/main.ui" line="46"/>
<source>Banlist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/main.ui" line="53"/>
<source>Export</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/main.ui" line="67"/>
<source>Chorded Entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/main.ui" line="110"/>
<source>Chord</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/main.ui" line="115"/>
<source>Frequency</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/main.ui" line="120"/>
<location filename="../ui/main.ui" line="184"/>
<source>Last used</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/main.ui" line="131"/>
<source>Character Entry</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/main.ui" line="174"/>
<source>Word</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/main.ui" line="179"/>
<source>Freq.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/main.ui" line="189"/>
<source>Avg speed</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>
Loading
Loading