diff --git a/blackboard_sync/qt/LoginWebView.py b/blackboard_sync/qt/LoginWebView.py
index f903751b..81d27b04 100644
--- a/blackboard_sync/qt/LoginWebView.py
+++ b/blackboard_sync/qt/LoginWebView.py
@@ -16,11 +16,9 @@
# MA 02110-1301, USA.
import webbrowser
-from functools import partial
from threading import Timer
from requests.cookies import RequestsCookieJar
-from PyQt6.QtCore import QCoreApplication
from PyQt6.QtCore import pyqtSlot, pyqtSignal, QObject, QUrl
from PyQt6.QtWidgets import QWidget, QPushButton, QLabel
from PyQt6.QtNetwork import QNetworkCookie
@@ -30,7 +28,6 @@
from .assets import load_ui, get_theme_icon, AppIcon
-tr = partial(QCoreApplication.translate, 'LoginWebView')
WATCHDOG_DELAY = 30
@@ -125,7 +122,7 @@ def slot_help(self) -> None:
webbrowser.open(self.help_url)
def show_help(self) -> None:
- self.status.setText(tr(
+ self.status.setText(self.tr(
"Trouble logging in? Press the help button to let us know."
))
self.help_button.setVisible(True)
diff --git a/blackboard_sync/qt/LoginWebView.ui b/blackboard_sync/qt/LoginWebView.ui
index 2f59dc3c..cc7dbcaf 100644
--- a/blackboard_sync/qt/LoginWebView.ui
+++ b/blackboard_sync/qt/LoginWebView.ui
@@ -17,7 +17,7 @@
- Log in to Blackboard
+ Log in to Blackboard Learn
@@ -68,8 +68,15 @@
-
+
+ Go back
+
- ...
+
+
+
+
+ ..
@@ -77,12 +84,22 @@
24
+
+ Qt::NoArrow
+
-
+
+ Go to start
+
- ...
+
+
+
+
+ ..
@@ -107,8 +124,15 @@
-
+
+ Report issue
+
- ...
+
+
+
+
+ ..
diff --git a/blackboard_sync/qt/SettingsWindow.py b/blackboard_sync/qt/SettingsWindow.py
index 098b7c9c..dee1a2d3 100644
--- a/blackboard_sync/qt/SettingsWindow.py
+++ b/blackboard_sync/qt/SettingsWindow.py
@@ -103,6 +103,8 @@ def username(self) -> str:
@username.setter
def username(self, username: str) -> None:
if username:
- self.current_session_label.setText(f"Logged in as {username}")
+ self.current_session_label.setText(
+ self.tr("Logged in as ") + username)
else:
- self.current_session_label.setText("Not currently logged in")
+ self.current_session_label.setText(
+ self.tr("Not currently logged in"))
diff --git a/blackboard_sync/qt/SettingsWindow.ui b/blackboard_sync/qt/SettingsWindow.ui
index 4147786c..9f609b20 100644
--- a/blackboard_sync/qt/SettingsWindow.ui
+++ b/blackboard_sync/qt/SettingsWindow.ui
@@ -57,7 +57,7 @@
- Download Location
+ Download location
@@ -89,7 +89,7 @@
- Change Location
+ Change location
@@ -102,7 +102,7 @@
- Sync Every
+ Sync every
@@ -143,7 +143,7 @@
- User Session
+ User session
@@ -174,7 +174,7 @@
- Log Out
+ Log out
@@ -187,7 +187,7 @@
- Initial Setup
+ Initial setup
diff --git a/blackboard_sync/qt/SetupWizard.ui b/blackboard_sync/qt/SetupWizard.ui
index 03227570..daa1908b 100644
--- a/blackboard_sync/qt/SetupWizard.ui
+++ b/blackboard_sync/qt/SetupWizard.ui
@@ -26,7 +26,7 @@
- BlackboardSync Setup
+ Setup
@@ -54,7 +54,7 @@
- You are a few steps away from syncing your Blackboard content straight to your device!
+ You are a few steps away from syncing your Blackboard Learn content straight to your device!
Qt::PlainText
diff --git a/blackboard_sync/qt/SyncTrayIcon.py b/blackboard_sync/qt/SyncTrayIcon.py
index 8d638a74..1ba50208 100644
--- a/blackboard_sync/qt/SyncTrayIcon.py
+++ b/blackboard_sync/qt/SyncTrayIcon.py
@@ -15,21 +15,16 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
-from functools import partial
from datetime import datetime
from PyQt6.QtGui import QAction
from PyQt6.QtCore import pyqtSignal, QObject
from PyQt6.QtWidgets import QMenu, QSystemTrayIcon
-from PyQt6.QtCore import QCoreApplication
-
from .assets import logo, get_theme_icon, AppIcon
from .utils import time_ago
from .notification import Event, TrayMessages
-tr = partial(QCoreApplication.translate, 'SyncTrayMenu')
-
class SyncTrayMenu(QMenu):
@@ -47,27 +42,27 @@ def _init_ui(self) -> None:
close_icon = get_theme_icon(AppIcon.EXIT)
open_dir_icon = get_theme_icon(AppIcon.OPEN)
- self.refresh = QAction(tr("Sync now"))
+ self.refresh = QAction(self.tr("Sync now"))
self.refresh.setIcon(sync_icon)
self.addAction(self.refresh)
- self.open_dir = QAction(tr("Open downloads"))
+ self.open_dir = QAction(self.tr("Open downloads"))
self.open_dir.setIcon(open_dir_icon)
self.addAction(self.open_dir)
- self.preferences = QAction(tr("Preferences"))
+ self.preferences = QAction(self.tr("Preferences"))
self.addAction(self.preferences)
self.addSeparator()
- self._status = QAction(tr("You haven't logged in"))
+ self._status = QAction(self.tr("You haven't logged in"))
self._status.setEnabled(False)
self.addAction(self._status)
- self.reset_setup = QAction(tr("Redo Setup"))
+ self.reset_setup = QAction(self.tr("Setup"))
self.addAction(self.reset_setup)
- self.quit = QAction(tr("Quit"))
+ self.quit = QAction(self.tr("Quit"))
self.quit.setIcon(close_icon)
self.addAction(self.quit)
@@ -80,18 +75,18 @@ def set_logged_in(self, logged_in: bool) -> None:
if logged_in:
self.set_last_synced(self._last_synced)
else:
- self._status.setText(tr("Not Logged In"))
+ self._status.setText(self.tr("Not logged in"))
def set_last_synced(self, last_synced: datetime | None) -> None:
self._last_synced = last_synced
human_ago = time_ago(last_synced) if last_synced else "Never"
- self._status.setText(tr("Last Synced: ") + human_ago)
+ self._status.setText(self.tr("Last synced: ") + human_ago)
def set_currently_syncing(self, syncing: bool) -> None:
self.refresh.setEnabled(not syncing)
if syncing:
- self._status.setText(tr("Downloading now..."))
+ self._status.setText(self.tr("Downloading now..."))
class SyncTrayIcon(QSystemTrayIcon):
diff --git a/blackboard_sync/qt/dialogs.py b/blackboard_sync/qt/dialogs.py
index b6866112..c54eca20 100644
--- a/blackboard_sync/qt/dialogs.py
+++ b/blackboard_sync/qt/dialogs.py
@@ -17,17 +17,13 @@
import webbrowser
from pathlib import Path
-from functools import partial
-from PyQt6.QtCore import QCoreApplication, QObject
+from PyQt6.QtCore import QObject
from PyQt6.QtWidgets import QMessageBox, QFileDialog
from .assets import logo
-tr = partial(QCoreApplication.translate, 'Dialogs')
-
-
class DirDialog(QFileDialog):
"""Open the file dialog in directory mode."""
def init(self) -> None:
@@ -46,8 +42,8 @@ def __init__(self) -> None:
def redownload_dialog(self) -> bool:
q = QMessageBox()
- q.setText(tr("Should BlackboardSync redownload all files?"))
- q.setInformativeText(tr(
+ q.setText(self.tr("Do you wish to redownload all files?"))
+ q.setInformativeText(self.tr(
"Answer no if you intend to move all past downloads manually"
" (Recommended)."
))
@@ -61,10 +57,10 @@ def redownload_dialog(self) -> bool:
def uni_not_supported_dialog(self, url: str) -> None:
q = QMessageBox()
- q.setText(tr(
+ q.setText(self.tr(
"Unfortunately, your university is not yet supported"
))
- q.setInformativeText(tr(
+ q.setInformativeText(self.tr(
"You can help us provide support for it by visiting our website, "
"which you can access by pressing the help button."
))
@@ -79,10 +75,10 @@ def uni_not_supported_dialog(self, url: str) -> None:
def login_error_dialog(self, url: str) -> None:
q = QMessageBox()
- q.setText(tr(
+ q.setText(self.tr(
"There was an issue logging you in"
))
- q.setInformativeText(tr(
+ q.setInformativeText(self.tr(
"Please try again later, and if the error persists"
" contact our support by pressing the button below."
))
diff --git a/blackboard_sync/qt/notification.py b/blackboard_sync/qt/notification.py
index 65f3a0cd..041cf5b4 100644
--- a/blackboard_sync/qt/notification.py
+++ b/blackboard_sync/qt/notification.py
@@ -15,13 +15,11 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
-from functools import partial
from typing import NamedTuple
from enum import Enum, IntEnum, auto
from PyQt6.QtWidgets import QSystemTrayIcon
-from PyQt6.QtCore import QCoreApplication, QObject
-tr = partial(QCoreApplication.translate, 'TrayMessages')
+from PyQt6.QtCore import QObject
class Event(Enum):
@@ -55,18 +53,18 @@ def __init__(self) -> None:
self.messages = {
Event.UPDATE_AVAILABLE: SyncTrayMsg(
- tr('An update is available'),
- tr('You can update the app from your digital store'),
+ self.tr('An update is available'),
+ self.tr('You can update the app from your digital store'),
Severity.INFORMATION.value, Duration.SHORT
),
Event.DOWNLOAD_ERROR: SyncTrayMsg(
- tr('The download could not be completed'),
- tr('There was an error while downloading your content'),
+ self.tr('The download could not be completed'),
+ self.tr('There was an error while downloading your content'),
Severity.WARNING.value, Duration.LONG
),
Event.APP_RUNNING: SyncTrayMsg(
- tr('The app is running in the background'),
- tr('Click the tray icon to manage your downloads'),
+ self.tr('The app is running in the background'),
+ self.tr('Click the tray icon to manage your downloads'),
Severity.INFORMATION.value, Duration.LONG
)
}
diff --git a/blackboard_sync/qt/utils.py b/blackboard_sync/qt/utils.py
index 6fc2c8f0..b863bdae 100644
--- a/blackboard_sync/qt/utils.py
+++ b/blackboard_sync/qt/utils.py
@@ -23,7 +23,7 @@
from enum import IntEnum
from datetime import datetime, timezone
-from PyQt6.QtCore import QSettings
+from PyQt6.QtCore import QSettings, QObject
def open_in_file_browser(file: Path) -> None:
@@ -66,26 +66,69 @@ def add_to_startup(app_id: str) -> None:
settings.sync()
+class Time(IntEnum):
+ SECOND = 1
+ MINUTE = 60
+ HOUR = MINUTE * 60
+ DAY = HOUR * 24
+ WEEK = DAY * 7
+ MONTH = DAY * 30
+ YEAR = DAY * 365
+
+
+class TimeStrings(QObject):
+ def get_name(self, unit: Time, plural: bool) -> str:
+ # it's not as simple as appending an 's'
+ if plural:
+ return self.get_plural_name(unit)
+
+ match unit:
+ case Time.SECOND:
+ return self.tr("second")
+ case Time.MINUTE:
+ return self.tr("minute")
+ case Time.HOUR:
+ return self.tr("hour")
+ case Time.DAY:
+ return self.tr("day")
+ case Time.WEEK:
+ return self.tr("week")
+ case Time.MONTH:
+ return self.tr("month")
+ case Time.YEAR:
+ return self.tr("year")
+
+ def get_plural_name(self, unit: Time) -> str:
+ match unit:
+ case Time.SECOND:
+ return self.tr("seconds")
+ case Time.MINUTE:
+ return self.tr("minutes")
+ case Time.HOUR:
+ return self.tr("hours")
+ case Time.DAY:
+ return self.tr("days")
+ case Time.WEEK:
+ return self.tr("weeks")
+ case Time.MONTH:
+ return self.tr("months")
+ case Time.YEAR:
+ return self.tr("years")
+
+ @property
+ def template(self) -> str:
+ return self.tr("{n} {name} ago")
+
+
def time_ago(timestamp: datetime) -> str:
delta = datetime.now(tz=timezone.utc) - timestamp
s = int(delta.total_seconds())
- class Time(IntEnum):
- SECOND = 1
- MINUTE = 60
- HOUR = MINUTE * 60
- DAY = HOUR * 24
- WEEK = DAY * 7
- MONTH = DAY * 30
- YEAR = DAY * 365
-
- def __str__(self) -> str:
- return self.name.lower()
-
def get_human_time(seconds: int, unit: Time) -> str:
n = seconds // unit
- s = '' if n == 1 else 's'
- return f"{n} {unit}{s} ago"
+ t = TimeStrings()
+ name = t.get_name(unit, n != 1)
+ return t.template.format(n=n, name=name)
previous = Time.SECOND
diff --git a/tests/test_qt.py b/tests/test_qt.py
index d34aca44..173c9db8 100644
--- a/tests/test_qt.py
+++ b/tests/test_qt.py
@@ -67,7 +67,7 @@ def _make_setup_wizard(institutions: list[str]):
class TestSetupWizard:
- intro = 'You are a few steps away from syncing your Blackboard content straight to your device!'
+ intro = 'You are a few steps away from syncing your Blackboard Learn content straight to your device!'
uni_selection = 'First, tell us where you study'
location_selection = 'Where do you want files to be downloaded?'
page_number = 4
@@ -253,7 +253,7 @@ def test_settings_window_save_signal(self, qtbot, settings_window):
class TestSyncTrayIcon:
def test_tray_icon_initial_state(self, qtbot, tray_icon):
- assert tray_icon._menu._status.text() == 'Not Logged In'
+ assert tray_icon._menu._status.text() == 'Not logged in'
assert not tray_icon._menu.refresh.isVisible()
assert not tray_icon._menu.preferences.isVisible()
assert tray_icon._menu._status.isVisible()
@@ -261,7 +261,7 @@ def test_tray_icon_initial_state(self, qtbot, tray_icon):
def test_tray_icon_logged_in_state(self, qtbot, tray_icon):
tray_icon.set_logged_in(True)
- assert tray_icon._menu._status.text() != 'Not Logged In'
+ assert tray_icon._menu._status.text() != 'Not logged in'
assert tray_icon._menu.refresh.isVisible()
assert tray_icon._menu.refresh.isEnabled()
assert tray_icon._menu.preferences.isVisible()