Add internationalization support with English and French translations
- Introduced a new i18n module for managing translations using JSON files. - Added English (en.json) and French (fr.json) translation files for UI elements. - Implemented lazy initialization of the translator to load translations on demand. - Added unit tests for translation lookup, fallback behavior, and available languages detection.
This commit is contained in:
parent
fd0482ed2d
commit
7daec731ca
11 changed files with 1184 additions and 280 deletions
|
|
@ -43,6 +43,7 @@ from webdrop_bridge.config import Config
|
|||
from webdrop_bridge.core.drag_interceptor import DragInterceptor
|
||||
from webdrop_bridge.core.validator import PathValidator
|
||||
from webdrop_bridge.ui.restricted_web_view import RestrictedWebEngineView
|
||||
from webdrop_bridge.utils.i18n import tr
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -245,7 +246,7 @@ class OpenDropZone(QWidget):
|
|||
self._icon_label.setPixmap(pixmap)
|
||||
self._icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self._icon_label.setStyleSheet(self._NORMAL_STYLE)
|
||||
self._icon_label.setToolTip("Drop a file here to open it with its default application")
|
||||
self._icon_label.setToolTip(tr("toolbar.tooltip.open_drop"))
|
||||
layout.addWidget(self._icon_label)
|
||||
|
||||
self.setMinimumSize(QSize(44, 44))
|
||||
|
|
@ -322,7 +323,7 @@ class OpenWithDropZone(OpenDropZone):
|
|||
parent: Parent widget.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self._icon_label.setToolTip("Drop a file here to choose which app should open it")
|
||||
self._icon_label.setToolTip(tr("toolbar.tooltip.open_with_drop"))
|
||||
|
||||
def dropEvent(self, event) -> None: # type: ignore[override]
|
||||
"""Emit dropped local files for app-chooser handling."""
|
||||
|
|
@ -985,8 +986,8 @@ class MainWindow(QMainWindow):
|
|||
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"Checkout Asset",
|
||||
f"Do you want to check out this asset?\n\n{filename}",
|
||||
tr("dialog.checkout.title"),
|
||||
tr("dialog.checkout.msg", filename=filename),
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
QMessageBox.StandardButton.Yes,
|
||||
)
|
||||
|
|
@ -1090,8 +1091,8 @@ class MainWindow(QMainWindow):
|
|||
# Show error dialog to user
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Drag-and-Drop Error",
|
||||
f"Could not complete the drag-and-drop operation.\n\nError: {error}",
|
||||
tr("dialog.drag_error.title"),
|
||||
tr("dialog.drag_error.msg", error=error),
|
||||
)
|
||||
|
||||
def _on_file_opened_via_drop(self, file_path: str) -> None:
|
||||
|
|
@ -1101,7 +1102,7 @@ class MainWindow(QMainWindow):
|
|||
file_path: Local file path that was opened.
|
||||
"""
|
||||
logger.info(f"Opened via drop zone: {file_path}")
|
||||
self.statusBar().showMessage(f"Opened: {Path(file_path).name}", 4000)
|
||||
self.statusBar().showMessage(tr("status.opened", name=Path(file_path).name), 4000)
|
||||
|
||||
def _on_file_open_failed_via_drop(self, file_path: str, error: str) -> None:
|
||||
"""Handle a failure to open a file dropped on the OpenDropZone.
|
||||
|
|
@ -1113,9 +1114,8 @@ class MainWindow(QMainWindow):
|
|||
logger.warning(f"Failed to open via drop zone '{file_path}': {error}")
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Open File Error",
|
||||
f"Could not open the file with its default application.\n\n"
|
||||
f"File: {file_path}\nError: {error}",
|
||||
tr("dialog.open_file_error.title"),
|
||||
tr("dialog.open_file_error.msg", file_path=file_path, error=error),
|
||||
)
|
||||
|
||||
def _on_file_open_with_requested(self, file_path: str) -> None:
|
||||
|
|
@ -1125,15 +1125,15 @@ class MainWindow(QMainWindow):
|
|||
file_path: Local file path to open using an app chooser.
|
||||
"""
|
||||
if self._open_with_app_chooser(file_path):
|
||||
self.statusBar().showMessage(f"Choose app for: {Path(file_path).name}", 4000)
|
||||
self.statusBar().showMessage(tr("status.choose_app", name=Path(file_path).name), 4000)
|
||||
logger.info(f"Opened app chooser for '{file_path}'")
|
||||
return
|
||||
|
||||
logger.warning(f"Could not open app chooser for '{file_path}'")
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Open With Error",
|
||||
"Could not open an application chooser for this file on your platform.",
|
||||
tr("dialog.open_with_error.title"),
|
||||
tr("dialog.open_with_error.msg"),
|
||||
)
|
||||
|
||||
def _open_with_app_chooser(self, file_path: str) -> bool:
|
||||
|
|
@ -1234,7 +1234,7 @@ class MainWindow(QMainWindow):
|
|||
logger.info(f"Download started: {filename}")
|
||||
|
||||
# Update status bar (temporarily)
|
||||
self.status_bar.showMessage(f"📥 Download: {filename}", 3000)
|
||||
self.status_bar.showMessage(tr("status.download_started", filename=filename), 3000)
|
||||
|
||||
# Connect to state changed for progress tracking
|
||||
download.stateChanged.connect(
|
||||
|
|
@ -1248,7 +1248,7 @@ class MainWindow(QMainWindow):
|
|||
|
||||
except Exception as e:
|
||||
logger.error(f"Error handling download: {e}", exc_info=True)
|
||||
self.status_bar.showMessage(f"Download error: {e}", 5000)
|
||||
self.status_bar.showMessage(tr("status.download_error", error=str(e)), 5000)
|
||||
|
||||
def _on_download_finished(self, download: QWebEngineDownloadRequest, file_path: Path) -> None:
|
||||
"""Handle download completion.
|
||||
|
|
@ -1266,13 +1266,17 @@ class MainWindow(QMainWindow):
|
|||
|
||||
if state == QWebEngineDownloadRequest.DownloadState.DownloadCompleted:
|
||||
logger.info(f"Download completed: {file_path.name}")
|
||||
self.status_bar.showMessage(f"Download completed: {file_path.name}", 5000)
|
||||
self.status_bar.showMessage(
|
||||
tr("status.download_completed", name=file_path.name), 5000
|
||||
)
|
||||
elif state == QWebEngineDownloadRequest.DownloadState.DownloadCancelled:
|
||||
logger.info(f"Download cancelled: {file_path.name}")
|
||||
self.status_bar.showMessage(f"⚠️ Download abgebrochen: {file_path.name}", 3000)
|
||||
self.status_bar.showMessage(
|
||||
tr("status.download_cancelled", name=file_path.name), 3000
|
||||
)
|
||||
elif state == QWebEngineDownloadRequest.DownloadState.DownloadInterrupted:
|
||||
logger.warning(f"Download interrupted: {file_path.name}")
|
||||
self.status_bar.showMessage(f"❌ Download fehlgeschlagen: {file_path.name}", 5000)
|
||||
self.status_bar.showMessage(tr("status.download_failed", name=file_path.name), 5000)
|
||||
except Exception as e:
|
||||
logger.error(f"Error in download finished handler: {e}", exc_info=True)
|
||||
|
||||
|
|
@ -1395,7 +1399,7 @@ class MainWindow(QMainWindow):
|
|||
else self.style().standardIcon(self.style().StandardPixmap.SP_DirHomeIcon)
|
||||
)
|
||||
home_action = toolbar.addAction(home_icon, "")
|
||||
home_action.setToolTip("Home")
|
||||
home_action.setToolTip(tr("toolbar.tooltip.home"))
|
||||
home_action.triggered.connect(self._navigate_home)
|
||||
|
||||
# Refresh button
|
||||
|
|
@ -1435,32 +1439,32 @@ class MainWindow(QMainWindow):
|
|||
|
||||
# About button (info icon) on the right
|
||||
about_action = toolbar.addAction("ℹ️")
|
||||
about_action.setToolTip("About WebDrop Bridge")
|
||||
about_action.setToolTip(tr("toolbar.tooltip.about"))
|
||||
about_action.triggered.connect(self._show_about_dialog)
|
||||
|
||||
# Settings button on the right
|
||||
settings_action = toolbar.addAction("⚙️")
|
||||
settings_action.setToolTip("Settings")
|
||||
settings_action.setToolTip(tr("toolbar.tooltip.settings"))
|
||||
settings_action.triggered.connect(self._show_settings_dialog)
|
||||
|
||||
# Check for Updates button on the right
|
||||
check_updates_action = toolbar.addAction("🔄")
|
||||
check_updates_action.setToolTip("Check for Updates")
|
||||
check_updates_action.setToolTip(tr("toolbar.tooltip.check_updates"))
|
||||
check_updates_action.triggered.connect(self._on_manual_check_for_updates)
|
||||
|
||||
# Clear cache button on the right
|
||||
clear_cache_action = toolbar.addAction("🗑️")
|
||||
clear_cache_action.setToolTip("Clear Cache and Cookies")
|
||||
clear_cache_action.setToolTip(tr("toolbar.tooltip.clear_cache"))
|
||||
clear_cache_action.triggered.connect(self._clear_cache_and_cookies)
|
||||
|
||||
# Log file button on the right
|
||||
log_action = toolbar.addAction("📋")
|
||||
log_action.setToolTip("Open Log File")
|
||||
log_action.setToolTip(tr("toolbar.tooltip.open_log"))
|
||||
log_action.triggered.connect(self._open_log_file)
|
||||
|
||||
# Developer Tools button on the right
|
||||
dev_tools_action = toolbar.addAction("🔧")
|
||||
dev_tools_action.setToolTip("Developer Tools (F12)")
|
||||
dev_tools_action.setToolTip(tr("toolbar.tooltip.dev_tools"))
|
||||
dev_tools_action.triggered.connect(self._open_developer_tools)
|
||||
|
||||
def _open_log_file(self) -> None:
|
||||
|
|
@ -1486,8 +1490,8 @@ class MainWindow(QMainWindow):
|
|||
else:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Log File Not Found",
|
||||
f"No log file found at:\n{log_file}",
|
||||
tr("dialog.log_not_found.title"),
|
||||
tr("dialog.log_not_found.msg", log_file=str(log_file)),
|
||||
)
|
||||
|
||||
def _open_developer_tools(self) -> None:
|
||||
|
|
@ -1504,7 +1508,7 @@ class MainWindow(QMainWindow):
|
|||
|
||||
# Create new window
|
||||
self._dev_tools_window = QMainWindow()
|
||||
self._dev_tools_window.setWindowTitle("🔧 Developer Tools")
|
||||
self._dev_tools_window.setWindowTitle(tr("dialog.dev_tools.window_title"))
|
||||
self._dev_tools_window.setGeometry(100, 100, 1200, 700)
|
||||
self._dev_tools_window.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
|
||||
|
||||
|
|
@ -1530,8 +1534,8 @@ class MainWindow(QMainWindow):
|
|||
logger.error(f"Failed to open Developer Tools window: {e}", exc_info=True)
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Developer Tools",
|
||||
f"Could not open Developer Tools:\n{e}",
|
||||
tr("dialog.dev_tools.error_title"),
|
||||
tr("dialog.dev_tools.error_msg", error=str(e)),
|
||||
)
|
||||
|
||||
def _create_status_bar(self) -> None:
|
||||
|
|
@ -1539,7 +1543,7 @@ class MainWindow(QMainWindow):
|
|||
self.status_bar = self.statusBar()
|
||||
|
||||
# Update status label
|
||||
self.update_status_label = QLabel("Ready")
|
||||
self.update_status_label = QLabel(tr("status.ready"))
|
||||
self.update_status_label.setStyleSheet("margin-right: 10px;")
|
||||
self.status_bar.addPermanentWidget(self.update_status_label)
|
||||
|
||||
|
|
@ -1550,10 +1554,26 @@ class MainWindow(QMainWindow):
|
|||
status: Status text to display
|
||||
emoji: Optional emoji prefix (rotating, checkmark, download, warning symbols)
|
||||
"""
|
||||
# Map known internal status strings to translated display text
|
||||
_STATIC_STATUS_MAP = {
|
||||
"Checking for updates": "update.status.checking",
|
||||
"Ready": "update.status.ready",
|
||||
"Update deferred": "update.status.deferred",
|
||||
"Ready to install": "update.status.ready_to_install",
|
||||
"Installation started": "update.status.installation_started",
|
||||
"Installation failed": "update.status.installation_failed",
|
||||
"Download failed": "update.status.download_failed",
|
||||
"Verification failed": "update.status.verification_failed",
|
||||
"Operation timed out": "update.status.timed_out",
|
||||
"Check timed out - no server response": "update.status.check_timed_out",
|
||||
"Download timed out - no server response": "update.status.download_timed_out",
|
||||
}
|
||||
tr_key = _STATIC_STATUS_MAP.get(status)
|
||||
display = tr(tr_key) if tr_key else status
|
||||
if emoji:
|
||||
self.update_status_label.setText(f"{emoji} {status}")
|
||||
self.update_status_label.setText(f"{emoji} {display}")
|
||||
else:
|
||||
self.update_status_label.setText(status)
|
||||
self.update_status_label.setText(display)
|
||||
|
||||
def _on_manual_check_for_updates(self) -> None:
|
||||
"""Handle manual check for updates from menu.
|
||||
|
|
@ -1589,17 +1609,16 @@ class MainWindow(QMainWindow):
|
|||
# Show confirmation message
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Cache Cleared",
|
||||
"Browser cache and cookies have been cleared successfully.\n\n"
|
||||
"You may need to reload the page or restart the application for changes to take effect.",
|
||||
tr("dialog.cache_cleared.title"),
|
||||
tr("dialog.cache_cleared.msg"),
|
||||
)
|
||||
logger.info("Cache and cookies cleared successfully")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to clear cache and cookies: {e}")
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Error",
|
||||
f"Failed to clear cache and cookies: {str(e)}",
|
||||
tr("dialog.cache_clear_failed.title"),
|
||||
tr("dialog.cache_clear_failed.msg", error=str(e)),
|
||||
)
|
||||
|
||||
def _show_about_dialog(self) -> None:
|
||||
|
|
@ -1608,16 +1627,15 @@ class MainWindow(QMainWindow):
|
|||
|
||||
about_text = (
|
||||
f"<b>{self.config.app_name}</b><br>"
|
||||
f"Version: {self.config.app_version}<br>"
|
||||
f"{tr('about.version', version=self.config.app_version)}<br>"
|
||||
f"<br>"
|
||||
f"Bridges web-based drag-and-drop workflows with native file operations "
|
||||
f"for professional desktop applications.<br>"
|
||||
f"{tr('about.description')}<br>"
|
||||
f"<br>"
|
||||
f"<b>Toolbar Drop Zones:</b><br>"
|
||||
f"Open icon: Opens dropped files with the system default app.<br>"
|
||||
f"Open-with icon: Shows an app chooser for dropped files.<br>"
|
||||
f"<b>{tr('about.drop_zones_title')}</b><br>"
|
||||
f"{tr('about.open_icon_desc')}<br>"
|
||||
f"{tr('about.open_with_icon_desc')}<br>"
|
||||
f"<br>"
|
||||
f"<b>Product of:</b><br>"
|
||||
f"<b>{tr('about.product_of')}</b><br>"
|
||||
f"<b>hörl Information Management GmbH</b><br>"
|
||||
f"Silberburgstraße 126<br>"
|
||||
f"70176 Stuttgart, Germany<br>"
|
||||
|
|
@ -1628,10 +1646,10 @@ class MainWindow(QMainWindow):
|
|||
f"<b>Web:</b> <a href='https://www.hoerl-im.de/'>https://www.hoerl-im.de/</a><br>"
|
||||
f"</small>"
|
||||
f"<br>"
|
||||
f"<small>© 2026 hörl Information Management GmbH. All rights reserved.</small>"
|
||||
f"<small>{tr('about.rights')}</small>"
|
||||
)
|
||||
|
||||
QMessageBox.about(self, f"About {self.config.app_name}", about_text)
|
||||
QMessageBox.about(self, tr("about.title", app_name=self.config.app_name), about_text)
|
||||
|
||||
def _show_settings_dialog(self) -> None:
|
||||
"""Show Settings dialog for configuration management.
|
||||
|
|
@ -1704,18 +1722,17 @@ class MainWindow(QMainWindow):
|
|||
from PySide6.QtWidgets import QMessageBox
|
||||
|
||||
msg = QMessageBox(self)
|
||||
msg.setWindowTitle("Domain Changed - Restart Recommended")
|
||||
msg.setWindowTitle(tr("dialog.domain_changed.title"))
|
||||
msg.setIcon(QMessageBox.Icon.Warning)
|
||||
msg.setText(
|
||||
"Web Application Domain Has Changed\n\n"
|
||||
"You've switched to a different domain. For maximum stability and "
|
||||
"to ensure proper authentication, the application should be restarted.\n\n"
|
||||
"The profile and cache have been cleared, but we recommend restarting."
|
||||
)
|
||||
msg.setText(tr("dialog.domain_changed.msg"))
|
||||
|
||||
# Add custom buttons
|
||||
restart_now_btn = msg.addButton("Restart Now", QMessageBox.ButtonRole.AcceptRole)
|
||||
restart_later_btn = msg.addButton("Restart Later", QMessageBox.ButtonRole.RejectRole)
|
||||
restart_now_btn = msg.addButton(
|
||||
tr("dialog.domain_changed.restart_now"), QMessageBox.ButtonRole.AcceptRole
|
||||
)
|
||||
restart_later_btn = msg.addButton(
|
||||
tr("dialog.domain_changed.restart_later"), QMessageBox.ButtonRole.RejectRole
|
||||
)
|
||||
|
||||
msg.exec()
|
||||
|
||||
|
|
@ -1769,9 +1786,8 @@ class MainWindow(QMainWindow):
|
|||
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Restart Failed",
|
||||
f"Could not automatically restart the application:\n\n{str(e)}\n\n"
|
||||
"Please restart manually.",
|
||||
tr("dialog.restart_failed.title"),
|
||||
tr("dialog.restart_failed.msg", error=str(e)),
|
||||
)
|
||||
|
||||
def _navigate_home(self) -> None:
|
||||
|
|
@ -1880,10 +1896,8 @@ class MainWindow(QMainWindow):
|
|||
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Update Check Timeout",
|
||||
"The server did not respond within 30 seconds.\n\n"
|
||||
"This may be due to a network issue or server unavailability.\n\n"
|
||||
"Please check your connection and try again.",
|
||||
tr("dialog.update_timeout.title"),
|
||||
tr("dialog.update_timeout.msg"),
|
||||
)
|
||||
|
||||
safety_timer = QTimer()
|
||||
|
|
@ -1960,7 +1974,7 @@ class MainWindow(QMainWindow):
|
|||
error_message: Error description
|
||||
"""
|
||||
logger.error(f"Update check failed: {error_message}")
|
||||
self.set_update_status(f"Check failed: {error_message}", emoji="❌")
|
||||
self.set_update_status(tr("update.status.check_failed", error=error_message), emoji="❌")
|
||||
self._is_manual_check = False
|
||||
|
||||
# Close checking dialog first, then show error
|
||||
|
|
@ -1971,8 +1985,8 @@ class MainWindow(QMainWindow):
|
|||
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Update Check Failed",
|
||||
f"Could not check for updates:\n\n{error_message}\n\nPlease try again later.",
|
||||
tr("dialog.update_failed.title"),
|
||||
tr("dialog.update_failed.msg", error=error_message),
|
||||
)
|
||||
|
||||
def _on_update_available(self, release) -> None:
|
||||
|
|
@ -1988,7 +2002,7 @@ class MainWindow(QMainWindow):
|
|||
self._is_manual_check = False
|
||||
|
||||
# Update status to show update available
|
||||
self.set_update_status(f"Update available: v{release.version}", emoji="✅")
|
||||
self.set_update_status(tr("update.status.available", version=release.version), emoji="✅")
|
||||
|
||||
# Show update available dialog
|
||||
from webdrop_bridge.ui.update_manager_ui import UpdateAvailableDialog
|
||||
|
|
@ -2016,7 +2030,7 @@ class MainWindow(QMainWindow):
|
|||
def _on_user_update_later(self) -> None:
|
||||
"""Handle user clicking 'Later' button."""
|
||||
logger.info("User deferred update")
|
||||
self.set_update_status("Update deferred", emoji="")
|
||||
self.set_update_status(tr("update.status.deferred"), emoji="")
|
||||
|
||||
def _start_update_download(self, release) -> None:
|
||||
"""Start downloading the update in background thread.
|
||||
|
|
@ -2025,7 +2039,7 @@ class MainWindow(QMainWindow):
|
|||
release: Release object to download
|
||||
"""
|
||||
logger.info(f"Starting download for v{release.version}")
|
||||
self.set_update_status(f"Downloading v{release.version}", emoji="⬇️")
|
||||
self.set_update_status(tr("update.status.downloading", version=release.version), emoji="⬇️")
|
||||
|
||||
# Show download progress dialog
|
||||
from webdrop_bridge.ui.update_manager_ui import DownloadingDialog
|
||||
|
|
@ -2139,7 +2153,7 @@ class MainWindow(QMainWindow):
|
|||
self.downloading_dialog = None
|
||||
|
||||
logger.info(f"Download complete: {installer_path}")
|
||||
self.set_update_status("Ready to install", emoji="✅")
|
||||
self.set_update_status(tr("update.status.ready_to_install"), emoji="✅")
|
||||
|
||||
# Show install confirmation dialog
|
||||
install_dialog = InstallDialog(parent=self)
|
||||
|
|
@ -2163,8 +2177,8 @@ class MainWindow(QMainWindow):
|
|||
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"Download Failed",
|
||||
f"Could not download the update:\n\n{error}\n\nPlease try again later.",
|
||||
tr("dialog.download_failed.title"),
|
||||
tr("dialog.download_failed.msg", error=error),
|
||||
)
|
||||
|
||||
def _on_download_progress(self, downloaded: int, total: int) -> None:
|
||||
|
|
@ -2192,10 +2206,10 @@ class MainWindow(QMainWindow):
|
|||
)
|
||||
|
||||
if manager.install_update(installer_path):
|
||||
self.set_update_status("Installation started", emoji="✅")
|
||||
self.set_update_status(tr("update.status.installation_started"), emoji="✅")
|
||||
logger.info("Update installer launched successfully")
|
||||
else:
|
||||
self.set_update_status("Installation failed", emoji="❌")
|
||||
self.set_update_status(tr("update.status.installation_failed"), emoji="❌")
|
||||
logger.error("Failed to launch update installer")
|
||||
|
||||
|
||||
|
|
@ -2226,7 +2240,7 @@ class UpdateCheckWorker(QObject):
|
|||
logger.debug("UpdateCheckWorker.run() starting")
|
||||
|
||||
# Notify checking status
|
||||
self.update_status.emit("Checking for updates", "🔄")
|
||||
self.update_status.emit("Checking for updates", "🔄") # Translated by set_update_status
|
||||
|
||||
# Create a fresh event loop for this thread
|
||||
logger.debug("Creating new event loop for worker thread")
|
||||
|
|
@ -2248,15 +2262,17 @@ class UpdateCheckWorker(QObject):
|
|||
else:
|
||||
# No update available - show ready status
|
||||
logger.info("No update available")
|
||||
self.update_status.emit("Ready", "")
|
||||
self.update_status.emit(
|
||||
"Ready", ""
|
||||
) # English sentinel; _on_update_status compares this
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning("Update check timed out - server not responding")
|
||||
self.check_failed.emit("Server not responding - check again later")
|
||||
self.check_failed.emit(tr("worker.server_not_responding"))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Update check failed: {e}", exc_info=True)
|
||||
self.check_failed.emit(f"Check failed: {str(e)[:50]}")
|
||||
self.check_failed.emit(tr("worker.check_failed", error=str(e)[:50]))
|
||||
finally:
|
||||
# Properly close the event loop
|
||||
if loop is not None:
|
||||
|
|
@ -2297,7 +2313,9 @@ class UpdateDownloadWorker(QObject):
|
|||
loop = None
|
||||
try:
|
||||
# Download the update
|
||||
self.update_status.emit(f"Downloading v{self.release.version}", "⬇️")
|
||||
self.update_status.emit(
|
||||
tr("update.status.downloading", version=self.release.version), "⬇️"
|
||||
)
|
||||
|
||||
# Create a fresh event loop for this thread
|
||||
loop = asyncio.new_event_loop()
|
||||
|
|
@ -2319,8 +2337,10 @@ class UpdateDownloadWorker(QObject):
|
|||
)
|
||||
|
||||
if not installer_path:
|
||||
self.update_status.emit("Download failed", "❌")
|
||||
self.download_failed.emit("No installer found in release")
|
||||
self.update_status.emit(
|
||||
"Download failed", "❌"
|
||||
) # Translated by set_update_status
|
||||
self.download_failed.emit(tr("worker.no_installer"))
|
||||
logger.error("Download failed - no installer found")
|
||||
return
|
||||
|
||||
|
|
@ -2337,7 +2357,7 @@ class UpdateDownloadWorker(QObject):
|
|||
|
||||
if not checksum_ok:
|
||||
self.update_status.emit("Verification failed", "❌")
|
||||
self.download_failed.emit("Checksum verification failed")
|
||||
self.download_failed.emit(tr("worker.checksum_failed"))
|
||||
logger.error("Checksum verification failed")
|
||||
return
|
||||
|
||||
|
|
@ -2346,17 +2366,17 @@ class UpdateDownloadWorker(QObject):
|
|||
|
||||
except asyncio.TimeoutError as e:
|
||||
logger.error(f"Download/verification timed out: {e}")
|
||||
self.update_status.emit("Operation timed out", "⏱️")
|
||||
self.download_failed.emit(
|
||||
"Download or verification timed out (no response from server)"
|
||||
)
|
||||
self.update_status.emit(
|
||||
"Operation timed out", "⏱️"
|
||||
) # Translated by set_update_status
|
||||
self.download_failed.emit(tr("worker.download_timed_out"))
|
||||
except Exception as e:
|
||||
logger.error(f"Error during download: {e}")
|
||||
self.download_failed.emit(f"Download error: {str(e)[:50]}")
|
||||
self.download_failed.emit(tr("worker.download_error", error=str(e)[:50]))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Download worker failed: {e}")
|
||||
self.download_failed.emit(f"Download error: {str(e)[:50]}")
|
||||
self.download_failed.emit(tr("worker.download_error", error=str(e)[:50]))
|
||||
finally:
|
||||
# Properly close the event loop
|
||||
if loop is not None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue