I18N Code Work done.

This commit is contained in:
Christopher T. Johnson
2024-02-14 10:02:27 -05:00
parent 633d5e48b1
commit 793d758377
24 changed files with 8712 additions and 206 deletions

View File

@@ -3,6 +3,7 @@ import os
import xml.dom.minidom
from typing import Dict, List, cast
from PyQt6.QtCore import QCoreApplication
from PyQt6.QtSql import QSqlQuery
from main import query_error
@@ -20,13 +21,16 @@ class Book:
return
def load(self, book_id: int) -> None:
translate = QCoreApplication.translate
query = QSqlQuery()
query.prepare("SELECT * FROM books where book_id = :book_id")
query.bindValue(":book_id", book_id)
if not query.exec():
query_error(query)
if not query.next():
raise Exception(f"Missing book? book_id={book_id}")
raise Exception(
translate("Book", "Missing book? book_id=") + f"{book_id}"
)
self.metadata = {
"title": query.value("title"),
"creator": query.value("author"),

View File

@@ -17,7 +17,7 @@ from PyQt6.QtSql import QSqlQuery, QSqlQueryModel
from PyQt6.QtWidgets import QDialog, QDialogButtonBox, QStyledItemDelegate
from main import query_error
from ui.PeopleDialog import Ui_Dialog
from ui.PeopleDialog import Ui_PersonDialog
class blockHandler(HTMLParser):
@@ -104,7 +104,7 @@ class MLStripper(HTMLParser):
return self.text.getvalue()
class PersonDialog(QDialog, Ui_Dialog):
class PersonDialog(QDialog, Ui_PersonDialog):
SectionIdRole = Qt.ItemDataRole.UserRole
SectionSequenceRole = Qt.ItemDataRole.UserRole + 1
BookIdRole = Qt.ItemDataRole.UserRole + 2
@@ -125,12 +125,12 @@ class PersonDialog(QDialog, Ui_Dialog):
if not query.exec():
query_error(query)
model.setQuery(query)
self.bookCombo.setPlaceholderText("Select A Book")
self.bookCombo.setPlaceholderText(self.tr("Select A Book"))
self.bookCombo.setModel(model)
self.bookCombo.setModelColumn(1)
self.bookCombo.setCurrentIndex(-1)
model: QStandardItemModel = QStandardItemModel() # type: ignore[no-redef]
self.sectionCombo.setPlaceholderText("Select A Section")
self.sectionCombo.setPlaceholderText(self.tr("Select A Section"))
self.sectionCombo.setModel(model)
self.sectionCombo.setEnabled(False)
self.sectionCombo.setCurrentIndex(-1)
@@ -168,7 +168,7 @@ class PersonDialog(QDialog, Ui_Dialog):
if not query.exec():
query_error(query)
if not query.next():
raise Exception(f"No person record for {person_id}")
raise Exception(self.tr("No person record for ") + f"{person_id}")
self.person_id = person_id
self.nameEdit.setText(query.value("name"))
self.orgEdit.setText(query.value("organization"))
@@ -183,7 +183,8 @@ class PersonDialog(QDialog, Ui_Dialog):
)
if len(matches) != 1:
raise Exception(
f"Match failed looking for book_id: {query.value('book_id')}"
self.tr("Match failed looking for book_id: ")
+ f"{query.value('book_id')}"
)
row = int(matches[0].row())
self.bookCombo.setCurrentIndex(row)
@@ -215,10 +216,14 @@ class PersonDialog(QDialog, Ui_Dialog):
)
html += "</head><body>\n"
html += f"<h1>{title}</h1>\n"
html += self.makeNotes()
html += self.makeDefinitions()
html += self.makeText()
html += "</body>\n</html>\n"
#
# XXX - Use the sound module, don't do this by hand
#
if self.sender() == self.printBtn:
dev = None
for output in QMediaDevices.audioOutputs():
@@ -312,9 +317,8 @@ class PersonDialog(QDialog, Ui_Dialog):
book_id = model.data(model.createIndex(row, 0))
query.bindValue(":book_id", book_id)
section_id = self.sectionCombo.currentData(PersonDialog.SectionIdRole)
print(f"section_id: {section_id}")
if not section_id:
raise Exception(f"Section id is null")
raise Exception(self.tr("Section id is null"))
if not query.exec():
query_error(query)
if self.person_id <= 0:
@@ -426,7 +430,9 @@ class PersonDialog(QDialog, Ui_Dialog):
if not section_query.exec():
query_error(section_query)
if not section_query.next():
raise Exception(f"Missing section {section_id}")
raise Exception(
self.tr("Missing section ") + f"{section_id}"
)
section = blockHandler()
section.feed(section_query.value("content"))
html += section.get_block(query.value("block")) + "\n"
@@ -437,3 +443,24 @@ class PersonDialog(QDialog, Ui_Dialog):
html = '<div class="stats">'
html += "</div>\n"
return html
def makeNotes(self) -> str:
html = '<div class="notes">'
query = QSqlQuery()
query.prepare(
"SELECT * FROM sessions " "WHERE session_id = :session_id"
)
row = self.sessionCombo.currentIndex()
model = self.sessionCombo.model()
index = model.index(row, 0)
session_id = index.data()
query.bindValue(":session_id", session_id)
if not query.exec():
query_error(query)
first = True
if not query.next():
return ""
html += "<h3>" + self.tr("Notes") + "</h3>\n"
html += query.value("notes")
html += "</div>"
return html

View File

@@ -1,19 +1,28 @@
import json
import os
from typing import Any, Dict, List, Optional, Type, cast
import re
from typing import Any, Dict, List, Optional, Self, Type, cast
from PyQt6.QtCore import Qt, pyqtSlot
from PyQt6.QtCore import (
QCoreApplication,
QLocale,
QResource,
Qt,
QTranslator,
pyqtSlot,
)
from PyQt6.QtMultimedia import QMediaDevices
from PyQt6.QtWidgets import QAbstractItemView, QDialog, QListWidgetItem, QWidget
from ui.Preferences import Ui_Dialog
from ui.Preferences import Ui_Preferences
class Preferences(QDialog, Ui_Dialog):
class Preferences(QDialog, Ui_Preferences):
translator: Optional[QTranslator]
_instance = None
preferences: Dict[str, str | List[str]]
def __new__(cls: Type[Preferences]) -> Preferences:
def __new__(cls: Type[Self]) -> Self:
if cls._instance:
return cls._instance
cls._instance = super(Preferences, cls).__new__(cls)
@@ -59,6 +68,15 @@ class Preferences(QDialog, Ui_Dialog):
description = output.description()
self.alertList.addItem(description)
self.playerList.addItem(description)
resource = QResource("/translate")
translator = QTranslator()
for child in resource.children():
fn = f":/translate/{child}"
if translator.load(fn):
locale = QLocale(translator.language())
lang_code = locale.language()
language = QLocale.languageToString(lang_code)
self.languageCombo.addItem(language, translator.language())
self.setCurrent()
return
@@ -72,6 +90,7 @@ class Preferences(QDialog, Ui_Dialog):
"phoneticFont": "Gentium",
"alertOutputs": ["default"],
"playerOutputs": ["Feed for virtual microphone"],
"language": "pt",
}
for output in self.preferences["alertOutputs"]:
if output == "default":
@@ -87,6 +106,17 @@ class Preferences(QDialog, Ui_Dialog):
output, Qt.MatchFlag.MatchExactly
):
item.setSelected(True)
index = self.languageCombo.findData(self.preferences["language"])
if index >= 0:
self.languageCombo.setCurrentIndex(index)
self.translator = QTranslator()
if self.translator.load(
f":/translate/esl_reader_{self.preferences['language']}"
):
assert self.translator is not None
QCoreApplication.installTranslator(self.translator)
else:
self.translator = None
index = self.readerCombo.findText(self.preferences["readerFont"])
if index >= 0:
self.readerCombo.setCurrentIndex(index)
@@ -107,6 +137,7 @@ class Preferences(QDialog, Ui_Dialog):
self.preferences[
"phoneticFont"
] = self.phoneticsCombo.currentFont().family()
self.preferences["language"] = self.languageCombo.currentData()
self.preferences["alertOutputs"] = [
x.data(Qt.ItemDataRole.DisplayRole)
for x in self.alertList.selectedItems()
@@ -117,5 +148,22 @@ class Preferences(QDialog, Ui_Dialog):
]
with open("preferences.json", "w") as f:
json.dump(self.preferences, f, indent=2)
if self.translator:
if self.translator.language() != self.preferences["language"]:
full_code: str = cast(str, self.preferences["language"])
locale = QLocale(full_code)
language = locale.language()
code = QLocale.languageToCode(
language, QLocale.LanguageCodeType.ISO639Part1
)
print(language, code)
fn = f":/translate/esl_reader_{code}.qm"
translator = QTranslator()
if translator.load(fn):
QCoreApplication.removeTranslator(self.translator)
self.translator = translator
QCoreApplication.installTranslator(self.translator)
else:
print(self.tr("Unable to load translation file ") + f"{fn}")
super().accept()
return

View File

@@ -36,10 +36,10 @@ from lib.preferences import Preferences
from lib.session import SessionDialog
from lib.sounds import SoundOff
from main import query_error
from ui.ReadDialog import Ui_Dialog
from ui.ReadDialog import Ui_ReadDialog
class ReadDialog(QDialog, Ui_Dialog):
class ReadDialog(QDialog, Ui_ReadDialog):
playSound = pyqtSignal(str)
playAlert = pyqtSignal()
block: int
@@ -112,9 +112,9 @@ class ReadDialog(QDialog, Ui_Dialog):
@pyqtSlot()
def timerAction(self) -> None:
if self.session.isActive(): # We are stopping
self.sessionBtn.setText("Start")
self.sessionBtn.setText(self.tr("Start"))
else:
self.sessionBtn.setText("Stop")
self.sessionBtn.setText(self.tr("Stop"))
self.session.timerAction()
self.newParagraph.emit(self.section_id, self.block)
return
@@ -153,7 +153,6 @@ class ReadDialog(QDialog, Ui_Dialog):
@pyqtSlot()
def playAction(self) -> None:
print("playAction")
idx = self.stackedWidget.currentIndex()
if idx == 0: # Reading
# find word
@@ -188,11 +187,11 @@ class ReadDialog(QDialog, Ui_Dialog):
self.phonetics = None
if not self.phonetics:
return
print("Searching for audio file")
print(self.tr("Searching for audio file"))
for entry in self.phonetics:
if len(entry["audio"]) > 0:
# self.parent().playAlert.emit()
print(f"playing {entry['audio']}")
print(self.tr("playing ") + f"{entry['audio']}")
self.playSound.emit(entry["audio"])
return
@@ -279,16 +278,23 @@ class ReadDialog(QDialog, Ui_Dialog):
return
def defToHtml(self, word: str, definition: Dict[str, Any]) -> str:
SPEAKER = "\U0001F508"
html = f'<h1 class="def-word">{word}</h1>' + "\n"
try:
words: List[str] = []
for phonetic in definition["phonetics"]:
# XXX - Some phonetics have text and audio but audio is empty,
# some have just text and some have just audio
if phonetic["text"] in words:
continue
words.append(phonetic["text"])
html += f'<p class="phonetic">{phonetic["text"]}</p>' + "\n"
html += f'<p class="phonetic">{phonetic["text"]}'
if "audio" in phonetic:
html += f'<a href="{phonetic["audio"]}">{SPEAKER}</a>'
html += "</p>\n"
except Exception:
pass
print(html + "\n")
html += '<ul class="outer">' + "\n"
for meaning in definition["meanings"]:
html += f"<li>{meaning['partOfSpeech']}"
@@ -391,17 +397,17 @@ class ReadDialog(QDialog, Ui_Dialog):
# Event handlers
#
def keyReleaseEvent(self, event: Optional[QKeyEvent]) -> None:
self.nextBtn.setText("Next Para")
self.prevBtn.setText("Prev Para")
self.defineBtn.setText("Show Def")
self.nextBtn.setText(self.tr("Next Paragraph"))
self.prevBtn.setText(self.tr("Previous Paragraph"))
self.defineBtn.setText(self.tr("Definition"))
self.paragraphs = True
super().keyReleaseEvent(event)
return
def keyPressEvent(self, event: Optional[QKeyEvent]) -> None:
self.nextBtn.setText("Next Sect")
self.prevBtn.setText("Prev Sect")
self.defineBtn.setText("Add Def")
self.nextBtn.setText(self.tr("Next Section"))
self.prevBtn.setText(self.tr("Previous Secttion"))
self.defineBtn.setText(self.tr("Definition"))
self.paragraphs = False
super().keyPressEvent(event)
return
@@ -601,11 +607,11 @@ class ReadDialog(QDialog, Ui_Dialog):
def returnAction(self) -> None:
self.returnBtn.setVisible(False)
if self.paragraphs:
self.nextBtn.setText("Next Para")
self.prevBtn.setText("Prev Para")
self.nextBtn.setText(self.tr("Next Paragraph"))
self.prevBtn.setText(self.tr("Previous Paragraph"))
else:
self.nextBtn.setText("Next Sect")
self.prevBtn.setText("Prev Sect")
self.nextBtn.setText(self.tr("Next Section"))
self.prevBtn.setText(self.tr("Previous Section"))
self.stackedWidget.setCurrentIndex(0)
self.update()
return
@@ -613,8 +619,8 @@ class ReadDialog(QDialog, Ui_Dialog):
def showDefinition(self) -> None:
idx = self.stackedWidget.currentIndex()
self.returnBtn.setVisible(True)
self.nextBtn.setText("Next Def")
self.prevBtn.setText("Prev Def")
self.nextBtn.setText(self.tr("Next Definition"))
self.prevBtn.setText(self.tr("Previous Definition"))
if not self.display_definition(idx):
return
self.stackedWidget.setCurrentIndex(1)

View File

@@ -15,10 +15,10 @@ from PyQt6.QtSql import QSqlQuery
from PyQt6.QtWidgets import QCheckBox, QDialog, QListView, QMessageBox
from main import query_error
from ui.SessionDialog import Ui_Dialog
from ui.SessionDialog import Ui_SessionDialog
class SessionDialog(QDialog, Ui_Dialog):
class SessionDialog(QDialog, Ui_SessionDialog):
WordIdRole = Qt.ItemDataRole.UserRole
SectionIdRole = Qt.ItemDataRole.UserRole + 1
BlockRole = Qt.ItemDataRole.UserRole + 2
@@ -90,8 +90,8 @@ class SessionDialog(QDialog, Ui_Dialog):
@pyqtSlot()
def rejected(self): # type: ignore[no-untyped-def]
msg = QMessageBox()
msg.setText("There is unsaved data.")
msg.setInformativeText("Do you want to save the session?")
msg.setText(self.tr("There is unsaved data."))
msg.setInformativeText(self.tr("Do you want to save the session?"))
msg.setStandardButtons(
QMessageBox.StandardButton.Save
| QMessageBox.StandardButton.Discard
@@ -212,7 +212,7 @@ class SessionDialog(QDialog, Ui_Dialog):
if not query.exec():
query_error(query)
else:
print(f"Not active: {word_id}")
print(self.tr("Not active: ") + f"{word_id}")
return
@pyqtSlot(int, int)

View File

@@ -1,4 +1,4 @@
from typing import Optional, Type, cast
from typing import Optional, Self, Type, cast
from PyQt6.QtCore import QObject, Qt, QUrl, pyqtSlot
from PyQt6.QtMultimedia import (
@@ -15,7 +15,7 @@ from PyQt6.QtMultimedia import (
class SoundOff(QObject):
_instance = None
def __new__(cls: Type[SoundOff]) -> SoundOff:
def __new__(cls: Type[Self]) -> Self:
if cls._instance:
return cls._instance
cls._instance = super(SoundOff, cls).__new__(cls)