Add localization support and language selection
Introduces English and Spanish translation files, integrates localization throughout dialogs, and adds a language selection tab to the custom options dialog. UI strings are now loaded via the localization manager, enabling dynamic language switching and improved internationalization.
This commit is contained in:
@@ -19,6 +19,8 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from src.utils.ytsage_localization import _
|
||||
|
||||
from src.core.ytsage_ffmpeg import get_ffmpeg_path
|
||||
from src.core.ytsage_utils import _version_cache, check_ffmpeg, get_ffmpeg_version, get_ytdlp_version, refresh_version_cache
|
||||
from src.core.ytsage_yt_dlp import check_ytdlp_installed, get_yt_dlp_path
|
||||
@@ -60,7 +62,7 @@ class AboutDialog(QDialog):
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self._parent = parent # Store parent to access version etc.
|
||||
self.setWindowTitle("About YTSage")
|
||||
self.setWindowTitle(_("about.title"))
|
||||
self.setMinimumSize(460, 420) # Slightly increased to accommodate paths
|
||||
self.resize(460, 440) # Slightly increased initial size
|
||||
self.setMaximumSize(500, 480) # Reasonable maximum size
|
||||
@@ -157,13 +159,13 @@ class AboutDialog(QDialog):
|
||||
layout.addWidget(title_label)
|
||||
|
||||
version_label = QLabel(
|
||||
f"<span style='color: #cccccc; font-size: 13px; font-weight: normal;'>Version {getattr(self._parent, 'version', '4.9.0b')}</span>"
|
||||
f"<span style='color: #cccccc; font-size: 13px; font-weight: normal;'>{_('about.version', version=getattr(self._parent, 'version', '4.9.0b'))}</span>"
|
||||
)
|
||||
version_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
layout.addWidget(version_label)
|
||||
|
||||
# Description - more compact
|
||||
description_label = QLabel("Modern YouTube downloader with a clean PySide6 interface.")
|
||||
description_label = QLabel(_("about.description"))
|
||||
description_label.setWordWrap(True)
|
||||
description_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
description_label.setStyleSheet("color: #ffffff; font-size: 11px; margin: 6px 0;")
|
||||
@@ -174,13 +176,13 @@ class AboutDialog(QDialog):
|
||||
info_layout.setSpacing(15)
|
||||
|
||||
author_label = QLabel(
|
||||
"By: <a href='https://github.com/oop7/' style='color: #c90000; text-decoration: none; font-size: 10px;'>oop7</a>"
|
||||
f"{_('about.author', author='<a href=\'https://github.com/oop7/\' style=\'color: #c90000; text-decoration: none; font-size: 10px;\'>oop7</a>')}"
|
||||
)
|
||||
author_label.setOpenExternalLinks(True)
|
||||
info_layout.addWidget(author_label)
|
||||
|
||||
repo_label = QLabel(
|
||||
"GitHub: <a href='https://github.com/oop7/YTSage/' style='color: #c90000; text-decoration: none; font-size: 10px;'>YTSage</a>"
|
||||
f"{_('about.github', repo='<a href=\'https://github.com/oop7/YTSage/\' style=\'color: #c90000; text-decoration: none; font-size: 10px;\'>YTSage</a>')}"
|
||||
)
|
||||
repo_label.setOpenExternalLinks(True)
|
||||
info_layout.addWidget(repo_label)
|
||||
@@ -219,7 +221,7 @@ class AboutDialog(QDialog):
|
||||
header_layout.setContentsMargins(0, 0, 0, 5)
|
||||
|
||||
# System Information title
|
||||
title_label = QLabel("System Information")
|
||||
title_label = QLabel(_("about.system_info"))
|
||||
title_label.setStyleSheet(
|
||||
"""
|
||||
QLabel {
|
||||
@@ -237,7 +239,7 @@ class AboutDialog(QDialog):
|
||||
header_layout.addStretch()
|
||||
|
||||
# Create refresh button
|
||||
self.refresh_btn = QPushButton("🔄")
|
||||
self.refresh_btn = QPushButton(_("about.refresh"))
|
||||
self.refresh_btn.setFixedSize(16, 16)
|
||||
self.refresh_btn.setStyleSheet(
|
||||
"""
|
||||
@@ -283,7 +285,7 @@ class AboutDialog(QDialog):
|
||||
|
||||
def _show_loading_message(self) -> None:
|
||||
"""Show a compact loading message while system information is being gathered."""
|
||||
loading_label = QLabel("🔄 Loading system information...")
|
||||
loading_label = QLabel(_("about.loading"))
|
||||
loading_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
loading_label.setStyleSheet(
|
||||
"""
|
||||
@@ -381,7 +383,7 @@ class AboutDialog(QDialog):
|
||||
# yt-dlp Status - compact version with path
|
||||
ytdlp_found = check_ytdlp_installed()
|
||||
ytdlp_status_text = (
|
||||
"<span style='color: #4CAF50;'>✓ Detected</span>" if ytdlp_found else "<span style='color: #F44336;'>✗ Missing</span>"
|
||||
f"<span style='color: #4CAF50;'>{_('about.detected')}</span>" if ytdlp_found else f"<span style='color: #F44336;'>{_('about.missing')}</span>"
|
||||
)
|
||||
ytdlp_version = get_ytdlp_version()
|
||||
|
||||
@@ -409,11 +411,11 @@ class AboutDialog(QDialog):
|
||||
# FFmpeg Status - compact version with path
|
||||
ffmpeg_found = check_ffmpeg()
|
||||
ffmpeg_status_text = (
|
||||
"<span style='color: #4CAF50;'>✓ Detected</span>"
|
||||
f"<span style='color: #4CAF50;'>{_('about.detected')}</span>"
|
||||
if ffmpeg_found
|
||||
else "<span style='color: #F44336;'>✗ Missing</span>"
|
||||
else f"<span style='color: #F44336;'>{_('about.missing')}</span>"
|
||||
)
|
||||
ffmpeg_version = get_ffmpeg_version() if ffmpeg_found else "Not Available"
|
||||
ffmpeg_version = get_ffmpeg_version() if ffmpeg_found else _('about.not_available')
|
||||
|
||||
# Get FFmpeg path
|
||||
ffmpeg_path_text = None
|
||||
@@ -440,7 +442,7 @@ class AboutDialog(QDialog):
|
||||
|
||||
def refresh_version_info(self) -> None:
|
||||
"""Refresh version information manually."""
|
||||
self.refresh_btn.setText("🔄 Refreshing...")
|
||||
self.refresh_btn.setText(_('about.refreshing'))
|
||||
self.refresh_btn.setEnabled(False)
|
||||
|
||||
# Perform refresh in a separate thread to avoid blocking UI
|
||||
@@ -457,7 +459,7 @@ class AboutDialog(QDialog):
|
||||
|
||||
def on_refresh_finished(self, success) -> None:
|
||||
"""Handle refresh completion."""
|
||||
self.refresh_btn.setText("🔄 Refresh")
|
||||
self.refresh_btn.setText(_('about.refresh'))
|
||||
self.refresh_btn.setEnabled(True)
|
||||
|
||||
if success:
|
||||
@@ -466,8 +468,8 @@ class AboutDialog(QDialog):
|
||||
# Show error message with proper styling
|
||||
msg_box = QMessageBox(self)
|
||||
msg_box.setIcon(QMessageBox.Icon.Warning)
|
||||
msg_box.setWindowTitle("Refresh Failed")
|
||||
msg_box.setText("Failed to refresh version information.")
|
||||
msg_box.setWindowTitle(_('about.refresh_failed'))
|
||||
msg_box.setText(_('about.refresh_failed_message'))
|
||||
msg_box.setWindowIcon(self.windowIcon())
|
||||
msg_box.setStyleSheet(
|
||||
"""
|
||||
|
||||
@@ -31,6 +31,8 @@ from PySide6.QtWidgets import (
|
||||
from src.core.ytsage_yt_dlp import get_yt_dlp_path
|
||||
from src.utils.ytsage_constants import YTDLP_DOCS_URL
|
||||
from src.utils.ytsage_config_manager import ConfigManager
|
||||
from src.utils.ytsage_localization import LocalizationManager, _
|
||||
from src.utils.ytsage_logger import logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from src.gui.ytsage_gui_main import YTSageApp # only for type hints (no runtime import)
|
||||
@@ -68,7 +70,7 @@ class CommandWorker(QObject):
|
||||
base_cmd.append(self.url)
|
||||
|
||||
# Emit the full command
|
||||
self.output_received.emit(f"🔧 Full command: {' '.join(str(cmd) for cmd in base_cmd)}")
|
||||
self.output_received.emit(_('custom_command.full_command', command=' '.join(str(cmd) for cmd in base_cmd)))
|
||||
self.output_received.emit("=" * 50)
|
||||
|
||||
# Run the command
|
||||
@@ -90,22 +92,22 @@ class CommandWorker(QObject):
|
||||
self.output_received.emit("=" * 50)
|
||||
|
||||
if ret != 0:
|
||||
self.output_received.emit(f"❌ Command failed with exit code {ret}")
|
||||
self.output_received.emit(_('custom_command.command_failed', code=ret))
|
||||
self.command_finished.emit(False, ret)
|
||||
else:
|
||||
self.output_received.emit("✅ Command completed successfully!")
|
||||
self.output_received.emit(_('custom_command.command_success'))
|
||||
self.command_finished.emit(True, ret)
|
||||
|
||||
except Exception as e:
|
||||
self.output_received.emit("=" * 50)
|
||||
self.error_occurred.emit(f"❌ Error executing command: {str(e)}")
|
||||
self.error_occurred.emit(_('custom_command.command_error', error=str(e)))
|
||||
|
||||
|
||||
class CustomOptionsDialog(QDialog):
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self._parent: YTSageApp = cast("YTSageApp", self.parent()) # cast will help with auto complete and type hint checking.
|
||||
self.setWindowTitle("Custom Options")
|
||||
self.setWindowTitle(_("dialogs.custom_options"))
|
||||
self.setMinimumSize(550, 400) # Made even shorter
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
@@ -118,38 +120,35 @@ class CustomOptionsDialog(QDialog):
|
||||
cookies_layout = QVBoxLayout(cookies_tab)
|
||||
|
||||
# Help text
|
||||
help_text = QLabel(
|
||||
"Choose how to provide cookies for logging in.\n"
|
||||
"This allows downloading of private videos and premium quality audio."
|
||||
)
|
||||
help_text = QLabel(_('cookies.help_text'))
|
||||
help_text.setWordWrap(True)
|
||||
help_text.setStyleSheet("color: #999999; padding: 10px;")
|
||||
cookies_layout.addWidget(help_text)
|
||||
|
||||
# Cookie source selection
|
||||
cookie_source_group = QGroupBox("Cookie Source")
|
||||
cookie_source_group = QGroupBox(_('cookies.cookie_source'))
|
||||
cookie_source_layout = QVBoxLayout(cookie_source_group)
|
||||
|
||||
# Radio buttons for cookie source
|
||||
self.cookie_file_radio = QRadioButton("Use cookie file (Netscape format)")
|
||||
self.cookie_file_radio = QRadioButton(_('cookies.use_cookie_file'))
|
||||
self.cookie_file_radio.setChecked(True)
|
||||
self.cookie_file_radio.toggled.connect(self.on_cookie_source_changed)
|
||||
cookie_source_layout.addWidget(self.cookie_file_radio)
|
||||
|
||||
self.cookie_browser_radio = QRadioButton("Extract cookies from browser")
|
||||
self.cookie_browser_radio = QRadioButton(_('cookies.extract_from_browser'))
|
||||
self.cookie_browser_radio.toggled.connect(self.on_cookie_source_changed)
|
||||
cookie_source_layout.addWidget(self.cookie_browser_radio)
|
||||
|
||||
cookies_layout.addWidget(cookie_source_group)
|
||||
|
||||
# Cookie file section
|
||||
self.cookie_file_group = QGroupBox("Cookie File")
|
||||
self.cookie_file_group = QGroupBox(_('cookies.cookie_file'))
|
||||
file_layout = QVBoxLayout(self.cookie_file_group)
|
||||
|
||||
# File path input and browse button
|
||||
path_layout = QHBoxLayout()
|
||||
self.cookie_path_input = QLineEdit()
|
||||
self.cookie_path_input.setPlaceholderText("Path to cookies file (Netscape format)")
|
||||
self.cookie_path_input.setPlaceholderText(_('cookies.cookie_file_placeholder'))
|
||||
if hasattr(self._parent, "cookie_file_path") and self._parent.cookie_file_path:
|
||||
# Convert Path to string properly and validate
|
||||
cookie_path_str = str(self._parent.cookie_file_path)
|
||||
@@ -158,7 +157,7 @@ class CustomOptionsDialog(QDialog):
|
||||
self.cookie_path_input.setText(cookie_path_str)
|
||||
path_layout.addWidget(self.cookie_path_input)
|
||||
|
||||
self.browse_button = QPushButton("Browse")
|
||||
self.browse_button = QPushButton(_('buttons.browse'))
|
||||
self.browse_button.clicked.connect(self.browse_cookie_file)
|
||||
path_layout.addWidget(self.browse_button)
|
||||
file_layout.addLayout(path_layout)
|
||||
@@ -166,16 +165,16 @@ class CustomOptionsDialog(QDialog):
|
||||
cookies_layout.addWidget(self.cookie_file_group)
|
||||
|
||||
# Browser selection section
|
||||
self.cookie_browser_group = QGroupBox("Browser Selection")
|
||||
self.cookie_browser_group = QGroupBox(_('cookies.browser_selection'))
|
||||
browser_layout = QVBoxLayout(self.cookie_browser_group)
|
||||
|
||||
browser_help = QLabel("Select the browser to extract cookies from. Make sure the browser is closed before extraction.")
|
||||
browser_help = QLabel(_('cookies.browser_help'))
|
||||
browser_help.setWordWrap(True)
|
||||
browser_help.setStyleSheet("color: #999999; font-size: 11px;")
|
||||
browser_layout.addWidget(browser_help)
|
||||
|
||||
browser_select_layout = QHBoxLayout()
|
||||
browser_select_layout.addWidget(QLabel("Browser:"))
|
||||
browser_select_layout.addWidget(QLabel(_('cookies.browser_label')))
|
||||
|
||||
self.browser_combo = QComboBox()
|
||||
self.browser_combo.addItems(["chrome", "firefox", "safari", "edge", "opera", "brave", "chromium", "vivaldi"])
|
||||
@@ -184,9 +183,9 @@ class CustomOptionsDialog(QDialog):
|
||||
|
||||
# Optional profile field
|
||||
profile_layout = QHBoxLayout()
|
||||
profile_layout.addWidget(QLabel("Profile (optional):"))
|
||||
profile_layout.addWidget(QLabel(_('cookies.profile_label')))
|
||||
self.profile_input = QLineEdit()
|
||||
self.profile_input.setPlaceholderText("Profile name or path (leave empty for default)")
|
||||
self.profile_input.setPlaceholderText(_('cookies.profile_placeholder'))
|
||||
profile_layout.addWidget(self.profile_input)
|
||||
browser_layout.addLayout(profile_layout)
|
||||
|
||||
@@ -207,12 +206,7 @@ class CustomOptionsDialog(QDialog):
|
||||
command_layout = QVBoxLayout(command_tab)
|
||||
|
||||
# Improved help text
|
||||
cmd_help_text = QLabel(
|
||||
"Enter your custom yt-dlp command below. The current URL will be automatically appended.<br><br>"
|
||||
"For complete list of options and usage examples, "
|
||||
f'<a href="{YTDLP_DOCS_URL}">click here to view the official yt-dlp documentation</a>.<br><br>'
|
||||
"Note: Download path and output filename template will be automatically handled."
|
||||
)
|
||||
cmd_help_text = QLabel(_('custom_command.help_text', docs_url=YTDLP_DOCS_URL))
|
||||
cmd_help_text.setWordWrap(True)
|
||||
cmd_help_text.setOpenExternalLinks(True) # Enable clicking links
|
||||
cmd_help_text.setTextFormat(Qt.TextFormat.RichText) # Enable HTML rendering
|
||||
@@ -238,13 +232,13 @@ class CustomOptionsDialog(QDialog):
|
||||
command_layout.addWidget(cmd_help_text)
|
||||
|
||||
# Command input label
|
||||
input_label = QLabel("yt-dlp Arguments:")
|
||||
input_label = QLabel(_('custom_command.input_label'))
|
||||
input_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #ffffff; margin-top: 10px;")
|
||||
command_layout.addWidget(input_label)
|
||||
|
||||
# Command input
|
||||
self.command_input = QPlainTextEdit()
|
||||
self.command_input.setPlaceholderText("Enter yt-dlp arguments here...\n\n" "e.g. --extract-audio --audio-format mp3")
|
||||
self.command_input.setPlaceholderText(_('custom_command.input_placeholder'))
|
||||
self.command_input.setMinimumHeight(80) # Reduced further from 100
|
||||
self.command_input.setStyleSheet(
|
||||
"""
|
||||
@@ -269,7 +263,7 @@ class CustomOptionsDialog(QDialog):
|
||||
button_layout = QHBoxLayout()
|
||||
button_layout.setSpacing(10)
|
||||
|
||||
clear_btn = QPushButton("Clear")
|
||||
clear_btn = QPushButton(_('buttons.clear'))
|
||||
clear_btn.clicked.connect(lambda: self.command_input.clear())
|
||||
clear_btn.setStyleSheet(
|
||||
"""
|
||||
@@ -292,7 +286,7 @@ class CustomOptionsDialog(QDialog):
|
||||
button_layout.addStretch() # Push run button to the right
|
||||
|
||||
# Run command button
|
||||
self.run_btn = QPushButton("Run Command")
|
||||
self.run_btn = QPushButton(_('buttons.run_command'))
|
||||
self.run_btn.clicked.connect(self.run_custom_command)
|
||||
self.run_btn.setDefault(True)
|
||||
button_layout.addWidget(self.run_btn)
|
||||
@@ -300,14 +294,14 @@ class CustomOptionsDialog(QDialog):
|
||||
command_layout.addLayout(button_layout)
|
||||
|
||||
# Output label
|
||||
output_label = QLabel("Command Output:")
|
||||
output_label = QLabel(_('custom_command.output_label'))
|
||||
output_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #ffffff; margin-top: 15px;")
|
||||
command_layout.addWidget(output_label)
|
||||
|
||||
# Log output
|
||||
self.log_output = QTextEdit()
|
||||
self.log_output.setReadOnly(True)
|
||||
self.log_output.setPlaceholderText("Command output will appear here...")
|
||||
self.log_output.setPlaceholderText(_('custom_command.output_placeholder'))
|
||||
self.log_output.setMinimumHeight(100) # Reduced further from 120
|
||||
self.log_output.setStyleSheet(
|
||||
"""
|
||||
@@ -332,56 +326,50 @@ class CustomOptionsDialog(QDialog):
|
||||
proxy_layout = QVBoxLayout(proxy_tab)
|
||||
|
||||
# Help text
|
||||
proxy_help_text = QLabel(
|
||||
"Configure proxy settings for network connections and geo-verification.\n"
|
||||
"Proxy can help bypass regional restrictions and improve download performance."
|
||||
)
|
||||
proxy_help_text = QLabel(_('proxy.help_text'))
|
||||
proxy_help_text.setWordWrap(True)
|
||||
proxy_help_text.setStyleSheet("color: #999999; padding: 10px;")
|
||||
proxy_layout.addWidget(proxy_help_text)
|
||||
|
||||
# Main Proxy section
|
||||
main_proxy_group = QGroupBox("Main Proxy")
|
||||
main_proxy_group = QGroupBox(_('proxy.main_proxy'))
|
||||
main_proxy_layout = QVBoxLayout(main_proxy_group)
|
||||
|
||||
main_proxy_help = QLabel("Use the specified HTTP/HTTPS/SOCKS proxy for all connections.")
|
||||
main_proxy_help = QLabel(_('proxy.main_proxy_help'))
|
||||
main_proxy_help.setWordWrap(True)
|
||||
main_proxy_help.setStyleSheet("color: #999999; font-size: 11px;")
|
||||
main_proxy_layout.addWidget(main_proxy_help)
|
||||
|
||||
# Main proxy input
|
||||
main_proxy_input_layout = QHBoxLayout()
|
||||
main_proxy_input_layout.addWidget(QLabel("Proxy URL:"))
|
||||
main_proxy_input_layout.addWidget(QLabel(_('proxy.proxy_url_label')))
|
||||
self.proxy_url_input = QLineEdit()
|
||||
self.proxy_url_input.setPlaceholderText("e.g., http://proxy.example.com:8080 or socks5://user:pass@127.0.0.1:1080")
|
||||
self.proxy_url_input.setPlaceholderText(_('proxy.proxy_url_placeholder'))
|
||||
self.proxy_url_input.textChanged.connect(self.validate_proxy_inputs)
|
||||
main_proxy_input_layout.addWidget(self.proxy_url_input)
|
||||
main_proxy_layout.addLayout(main_proxy_input_layout)
|
||||
|
||||
# Example text
|
||||
example_label = QLabel("Examples: http://proxy.com:8080, https://proxy.com:8080, socks5://127.0.0.1:1080")
|
||||
example_label = QLabel(_('proxy.proxy_examples'))
|
||||
example_label.setStyleSheet("color: #888888; font-size: 10px; font-style: italic;")
|
||||
main_proxy_layout.addWidget(example_label)
|
||||
|
||||
proxy_layout.addWidget(main_proxy_group)
|
||||
|
||||
# Geo-verification Proxy section
|
||||
geo_proxy_group = QGroupBox("Geo-verification Proxy")
|
||||
geo_proxy_group = QGroupBox(_('proxy.geo_proxy'))
|
||||
geo_proxy_layout = QVBoxLayout(geo_proxy_group)
|
||||
|
||||
geo_proxy_help = QLabel(
|
||||
"Use this proxy to verify IP address for geo-restricted sites. "
|
||||
"The main proxy (if set) is used for actual downloading."
|
||||
)
|
||||
geo_proxy_help = QLabel(_('proxy.geo_proxy_help'))
|
||||
geo_proxy_help.setWordWrap(True)
|
||||
geo_proxy_help.setStyleSheet("color: #999999; font-size: 11px;")
|
||||
geo_proxy_layout.addWidget(geo_proxy_help)
|
||||
|
||||
# Geo proxy input
|
||||
geo_proxy_input_layout = QHBoxLayout()
|
||||
geo_proxy_input_layout.addWidget(QLabel("Geo Proxy URL:"))
|
||||
geo_proxy_input_layout.addWidget(QLabel(_('proxy.geo_proxy_url_label')))
|
||||
self.geo_proxy_url_input = QLineEdit()
|
||||
self.geo_proxy_url_input.setPlaceholderText("e.g., http://us-proxy.example.com:8080")
|
||||
self.geo_proxy_url_input.setPlaceholderText(_('proxy.geo_proxy_url_placeholder'))
|
||||
self.geo_proxy_url_input.textChanged.connect(self.validate_proxy_inputs)
|
||||
geo_proxy_input_layout.addWidget(self.geo_proxy_url_input)
|
||||
geo_proxy_layout.addLayout(geo_proxy_input_layout)
|
||||
@@ -395,7 +383,7 @@ class CustomOptionsDialog(QDialog):
|
||||
|
||||
# Clear buttons
|
||||
clear_layout = QHBoxLayout()
|
||||
clear_main_proxy_btn = QPushButton("Clear Main Proxy")
|
||||
clear_main_proxy_btn = QPushButton(_('proxy.clear_main_proxy'))
|
||||
clear_main_proxy_btn.clicked.connect(lambda: self.proxy_url_input.clear())
|
||||
clear_main_proxy_btn.setStyleSheet(
|
||||
"""
|
||||
@@ -414,7 +402,7 @@ class CustomOptionsDialog(QDialog):
|
||||
)
|
||||
clear_layout.addWidget(clear_main_proxy_btn)
|
||||
|
||||
clear_geo_proxy_btn = QPushButton("Clear Geo Proxy")
|
||||
clear_geo_proxy_btn = QPushButton(_('proxy.clear_geo_proxy'))
|
||||
clear_geo_proxy_btn.clicked.connect(lambda: self.geo_proxy_url_input.clear())
|
||||
clear_geo_proxy_btn.setStyleSheet(
|
||||
"""
|
||||
@@ -438,13 +426,75 @@ class CustomOptionsDialog(QDialog):
|
||||
|
||||
proxy_layout.addStretch()
|
||||
|
||||
# === Language Tab ===
|
||||
language_tab = QWidget()
|
||||
language_layout = QVBoxLayout(language_tab)
|
||||
|
||||
# Help text
|
||||
language_help_text = QLabel(_("language.select_language"))
|
||||
language_help_text.setWordWrap(True)
|
||||
language_help_text.setStyleSheet("color: #ffffff; font-size: 14px; font-weight: bold; padding: 10px;")
|
||||
language_layout.addWidget(language_help_text)
|
||||
|
||||
# Current language info
|
||||
current_lang = ConfigManager.get("language") or "en"
|
||||
available_languages = LocalizationManager.get_available_languages()
|
||||
current_lang_display = available_languages.get(current_lang, current_lang.upper())
|
||||
|
||||
current_lang_label = QLabel(_("language.current_language", language=current_lang_display))
|
||||
current_lang_label.setWordWrap(True)
|
||||
current_lang_label.setStyleSheet("color: #999999; padding: 10px;")
|
||||
language_layout.addWidget(current_lang_label)
|
||||
|
||||
# Language selection group
|
||||
language_group = QGroupBox(_("language.select_language"))
|
||||
language_group_layout = QVBoxLayout(language_group)
|
||||
|
||||
# Language selection combo box
|
||||
language_select_layout = QHBoxLayout()
|
||||
language_select_layout.addWidget(QLabel(_("language.select_language") + ":"))
|
||||
|
||||
self.language_combo = QComboBox()
|
||||
|
||||
# Populate language combo with available languages
|
||||
for lang_code, display_name in available_languages.items():
|
||||
self.language_combo.addItem(display_name, lang_code)
|
||||
|
||||
# Set current selection
|
||||
current_index = self.language_combo.findData(current_lang)
|
||||
if current_index >= 0:
|
||||
self.language_combo.setCurrentIndex(current_index)
|
||||
|
||||
# Connect language change event
|
||||
self.language_combo.currentIndexChanged.connect(self.on_language_changed)
|
||||
|
||||
language_select_layout.addWidget(self.language_combo)
|
||||
language_group_layout.addLayout(language_select_layout)
|
||||
|
||||
language_layout.addWidget(language_group)
|
||||
|
||||
# Restart notice
|
||||
self.restart_notice = QLabel(_("language.restart_required"))
|
||||
self.restart_notice.setWordWrap(True)
|
||||
self.restart_notice.setStyleSheet(
|
||||
"color: #ffaa00; font-style: italic; padding: 10px; "
|
||||
"background-color: #2a2d36; border-radius: 6px; margin: 10px;"
|
||||
)
|
||||
self.restart_notice.setVisible(False) # Initially hidden
|
||||
language_layout.addWidget(self.restart_notice)
|
||||
|
||||
language_layout.addStretch()
|
||||
|
||||
# Add tabs to the tab widget
|
||||
self.tab_widget.addTab(cookies_tab, "Login with Cookies")
|
||||
self.tab_widget.addTab(command_tab, "Custom Command")
|
||||
self.tab_widget.addTab(proxy_tab, "Proxy")
|
||||
self.tab_widget.addTab(cookies_tab, _("tabs.cookies"))
|
||||
self.tab_widget.addTab(command_tab, _("tabs.custom_command"))
|
||||
self.tab_widget.addTab(proxy_tab, _("tabs.proxy"))
|
||||
self.tab_widget.addTab(language_tab, _("tabs.language"))
|
||||
|
||||
# Dialog buttons
|
||||
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||
button_box = QDialogButtonBox()
|
||||
ok_button = button_box.addButton(_("buttons.ok"), QDialogButtonBox.ButtonRole.AcceptRole)
|
||||
cancel_button = button_box.addButton(_("buttons.cancel"), QDialogButtonBox.ButtonRole.RejectRole)
|
||||
button_box.accepted.connect(self.accept)
|
||||
button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(button_box)
|
||||
@@ -524,7 +574,6 @@ class CustomOptionsDialog(QDialog):
|
||||
border: none;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIHZpZXdCb3g9IjAgMCAxMiAxMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTMgNEw2IDdMOSA0IiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+Cjwvc3ZnPgo=);
|
||||
}
|
||||
QComboBox QAbstractItemView {
|
||||
background-color: #1d1e22;
|
||||
@@ -610,18 +659,18 @@ class CustomOptionsDialog(QDialog):
|
||||
else:
|
||||
self.cookie_file_group.setVisible(False)
|
||||
self.cookie_browser_group.setVisible(True)
|
||||
self.cookie_status.setText("Browser cookies will be extracted when applied")
|
||||
self.cookie_status.setText(_("cookies.browser_extract_message"))
|
||||
self.cookie_status.setStyleSheet("color: #ffaa00; font-style: italic;")
|
||||
|
||||
def browse_cookie_file(self) -> None:
|
||||
# Open file dialog to select cookie file
|
||||
selected_files, _ = QFileDialog.getOpenFileName(self, "Select Cookie File", "", "Cookies files (*.txt *.lwp)")
|
||||
selected_files, _ = QFileDialog.getOpenFileName(self, _("cookies.select_file_title"), "", _("cookies.file_filter"))
|
||||
|
||||
if selected_files:
|
||||
# Ensure we have a valid full path
|
||||
cookie_path = Path(selected_files).resolve()
|
||||
self.cookie_path_input.setText(str(cookie_path))
|
||||
self.cookie_status.setText("Cookie file selected - Click OK to apply")
|
||||
self.cookie_status.setText(_("cookies.file_selected_message"))
|
||||
self.cookie_status.setStyleSheet("color: #00cc00; font-style: italic;")
|
||||
|
||||
def get_cookie_file_path(self) -> Path | None:
|
||||
@@ -760,7 +809,7 @@ class CustomOptionsDialog(QDialog):
|
||||
self.log_output.append(f"📁 Download path: {path}")
|
||||
self.log_output.append("=" * 50)
|
||||
self.run_btn.setEnabled(False)
|
||||
self.run_btn.setText("Running...")
|
||||
self.run_btn.setText(_("command.running"))
|
||||
|
||||
# Create worker and thread
|
||||
self.worker = CommandWorker(command, url, path)
|
||||
@@ -781,49 +830,63 @@ class CustomOptionsDialog(QDialog):
|
||||
def on_command_finished(self, success: bool, exit_code: int):
|
||||
"""Slot for when command finishes"""
|
||||
self.run_btn.setEnabled(True)
|
||||
self.run_btn.setText("Run Command")
|
||||
self.run_btn.setText(_("command.run_command"))
|
||||
|
||||
def on_error_occurred(self, error_msg: str):
|
||||
"""Slot for handling errors"""
|
||||
self.log_output.append(error_msg)
|
||||
self.run_btn.setEnabled(True)
|
||||
self.run_btn.setText("Run Command")
|
||||
self.run_btn.setText(_("buttons.run_command"))
|
||||
|
||||
def on_language_changed(self) -> None:
|
||||
"""Handle language selection change"""
|
||||
selected_lang_code = self.language_combo.currentData()
|
||||
if selected_lang_code:
|
||||
current_lang = ConfigManager.get("language") or "en"
|
||||
if selected_lang_code != current_lang:
|
||||
# Save the new language preference
|
||||
ConfigManager.set("language", selected_lang_code)
|
||||
|
||||
# Update LocalizationManager
|
||||
LocalizationManager.set_language(selected_lang_code)
|
||||
|
||||
# Show restart notice
|
||||
self.restart_notice.setVisible(True)
|
||||
|
||||
logger.info(f"Language changed to: {selected_lang_code}")
|
||||
|
||||
|
||||
class TimeRangeDialog(QDialog):
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Download Video Section")
|
||||
self.setWindowTitle(_('time_range.title'))
|
||||
self.setMinimumWidth(400)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Help text explaining the feature
|
||||
help_text = QLabel(
|
||||
"Download only specific parts of a video by specifying time ranges.\n"
|
||||
"Use HH:MM:SS format or seconds. Leave start or end empty to download from beginning or to end."
|
||||
)
|
||||
help_text = QLabel(_('time_range.help_text'))
|
||||
help_text.setWordWrap(True)
|
||||
help_text.setStyleSheet("color: #999999; padding: 10px;")
|
||||
layout.addWidget(help_text)
|
||||
|
||||
# Time range section
|
||||
time_group = QGroupBox("Time Range")
|
||||
time_group = QGroupBox(_('time_range.time_range_group'))
|
||||
time_layout = QVBoxLayout()
|
||||
|
||||
# Start time row
|
||||
start_layout = QHBoxLayout()
|
||||
start_layout.addWidget(QLabel("Start Time:"))
|
||||
start_layout.addWidget(QLabel(_('time_range.start_time')))
|
||||
self.start_time_input = QLineEdit()
|
||||
self.start_time_input.setPlaceholderText("00:00:00 (or leave empty for start)")
|
||||
self.start_time_input.setPlaceholderText(_('time_range.start_time_placeholder'))
|
||||
start_layout.addWidget(self.start_time_input)
|
||||
time_layout.addLayout(start_layout)
|
||||
|
||||
# End time row
|
||||
end_layout = QHBoxLayout()
|
||||
end_layout.addWidget(QLabel("End Time:"))
|
||||
end_layout.addWidget(QLabel(_('time_range.end_time')))
|
||||
self.end_time_input = QLineEdit()
|
||||
self.end_time_input.setPlaceholderText("00:10:00 (or leave empty for end)")
|
||||
self.end_time_input.setPlaceholderText(_('time_range.end_time_placeholder'))
|
||||
end_layout.addWidget(self.end_time_input)
|
||||
time_layout.addLayout(end_layout)
|
||||
|
||||
@@ -831,7 +894,7 @@ class TimeRangeDialog(QDialog):
|
||||
layout.addWidget(time_group)
|
||||
|
||||
# Force keyframes option
|
||||
self.force_keyframes = QCheckBox("Force keyframes at cuts (better accuracy, slower)")
|
||||
self.force_keyframes = QCheckBox(_('time_range.force_keyframes'))
|
||||
self.force_keyframes.setChecked(True)
|
||||
self.force_keyframes.setStyleSheet(
|
||||
"""
|
||||
@@ -859,7 +922,9 @@ class TimeRangeDialog(QDialog):
|
||||
layout.addWidget(self.force_keyframes)
|
||||
|
||||
# Buttons
|
||||
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||
button_box = QDialogButtonBox()
|
||||
ok_button = button_box.addButton(_("buttons.ok"), QDialogButtonBox.ButtonRole.AcceptRole)
|
||||
cancel_button = button_box.addButton(_("buttons.cancel"), QDialogButtonBox.ButtonRole.RejectRole)
|
||||
button_box.accepted.connect(self.accept)
|
||||
button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(button_box)
|
||||
|
||||
@@ -17,11 +17,13 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from src.utils.ytsage_localization import _
|
||||
|
||||
|
||||
class SubtitleSelectionDialog(QDialog):
|
||||
def __init__(self, available_manual, available_auto, previously_selected, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Select Subtitles")
|
||||
self.setWindowTitle(_("dialogs.select_subtitles"))
|
||||
self.setMinimumWidth(400)
|
||||
self.setMinimumHeight(300)
|
||||
|
||||
@@ -35,7 +37,7 @@ class SubtitleSelectionDialog(QDialog):
|
||||
|
||||
# Filter input
|
||||
self.filter_input = QLineEdit()
|
||||
self.filter_input.setPlaceholderText("Filter languages (e.g., en, es)...")
|
||||
self.filter_input.setPlaceholderText(_("dialogs.filter_languages_placeholder"))
|
||||
self.filter_input.textChanged.connect(self.filter_list)
|
||||
self.filter_input.setStyleSheet(
|
||||
"""
|
||||
@@ -72,7 +74,9 @@ class SubtitleSelectionDialog(QDialog):
|
||||
self.populate_list()
|
||||
|
||||
# OK and Cancel buttons
|
||||
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||
button_box = QDialogButtonBox()
|
||||
ok_button = button_box.addButton(_("buttons.ok"), QDialogButtonBox.ButtonRole.AcceptRole)
|
||||
cancel_button = button_box.addButton(_("buttons.cancel"), QDialogButtonBox.ButtonRole.RejectRole)
|
||||
button_box.accepted.connect(self.accept)
|
||||
button_box.rejected.connect(self.reject)
|
||||
|
||||
@@ -128,7 +132,8 @@ class SubtitleSelectionDialog(QDialog):
|
||||
combined_subs[lang_code] = f"{lang_code} - Auto-generated"
|
||||
|
||||
if not combined_subs:
|
||||
no_subs_label = QLabel("No subtitles available" + (f" matching '{filter_text}'" if filter_text else ""))
|
||||
matching_text = _("dialogs.matching") if filter_text else ""
|
||||
no_subs_label = QLabel(_("dialogs.no_subtitles_available") + (f" {matching_text} '{filter_text}'" if filter_text else ""))
|
||||
no_subs_label.setStyleSheet("color: #aaaaaa; padding: 10px;")
|
||||
self.list_layout.addWidget(no_subs_label)
|
||||
return
|
||||
@@ -205,8 +210,8 @@ class PlaylistSelectionDialog(QDialog):
|
||||
|
||||
# Top buttons (Select/Deselect All)
|
||||
button_layout = QHBoxLayout()
|
||||
select_all_btn = QPushButton("Select All")
|
||||
deselect_all_btn = QPushButton("Deselect All")
|
||||
select_all_btn = QPushButton(_("buttons.select_all"))
|
||||
deselect_all_btn = QPushButton(_("buttons.deselect_all"))
|
||||
select_all_btn.clicked.connect(self._select_all)
|
||||
deselect_all_btn.clicked.connect(self._deselect_all)
|
||||
# Style the buttons to match the subtitle dialog
|
||||
@@ -250,7 +255,9 @@ class PlaylistSelectionDialog(QDialog):
|
||||
self._populate_list(previously_selected_string)
|
||||
|
||||
# Dialog buttons (OK/Cancel)
|
||||
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||
button_box = QDialogButtonBox()
|
||||
ok_button = button_box.addButton(_("buttons.ok"), QDialogButtonBox.ButtonRole.AcceptRole)
|
||||
cancel_button = button_box.addButton(_("buttons.cancel"), QDialogButtonBox.ButtonRole.RejectRole)
|
||||
button_box.accepted.connect(self.accept)
|
||||
button_box.rejected.connect(self.reject)
|
||||
|
||||
@@ -428,50 +435,50 @@ class SponsorBlockCategoryDialog(QDialog):
|
||||
# Default SponsorBlock categories with descriptions
|
||||
SPONSORBLOCK_CATEGORIES = {
|
||||
"sponsor": {
|
||||
"name": "Sponsor",
|
||||
"description": "Paid promotion, paid referrals and direct advertisements",
|
||||
"name_key": "sponsorblock.sponsor",
|
||||
"description_key": "sponsorblock.sponsor_desc",
|
||||
"default": True,
|
||||
},
|
||||
"selfpromo": {
|
||||
"name": "Unpaid/Self Promotion",
|
||||
"description": "Unpaid promotion of creators' own content",
|
||||
"name_key": "sponsorblock.selfpromo",
|
||||
"description_key": "sponsorblock.selfpromo_desc",
|
||||
"default": True,
|
||||
},
|
||||
"interaction": {
|
||||
"name": "Interaction Reminder",
|
||||
"description": "Asking viewers to like, subscribe, or follow social media",
|
||||
"name_key": "sponsorblock.interaction",
|
||||
"description_key": "sponsorblock.interaction_desc",
|
||||
"default": True,
|
||||
},
|
||||
"intro": {
|
||||
"name": "Intro",
|
||||
"description": "Video introduction that can be skipped",
|
||||
"name_key": "sponsorblock.intro",
|
||||
"description_key": "sponsorblock.intro_desc",
|
||||
"default": False,
|
||||
},
|
||||
"outro": {
|
||||
"name": "Outro/End Cards",
|
||||
"description": "Credits or when the video ends",
|
||||
"name_key": "sponsorblock.outro",
|
||||
"description_key": "sponsorblock.outro_desc",
|
||||
"default": False,
|
||||
},
|
||||
"preview": {
|
||||
"name": "Preview/Recap",
|
||||
"description": "Quick recap of previous videos or preview of what's coming up",
|
||||
"name_key": "sponsorblock.preview",
|
||||
"description_key": "sponsorblock.preview_desc",
|
||||
"default": False,
|
||||
},
|
||||
"music_offtopic": {
|
||||
"name": "Non-Music Section",
|
||||
"description": "Only for music videos. Marks non-music sections",
|
||||
"name_key": "sponsorblock.music_offtopic",
|
||||
"description_key": "sponsorblock.music_offtopic_desc",
|
||||
"default": False,
|
||||
},
|
||||
"filler": {
|
||||
"name": "Filler Tangent",
|
||||
"description": "Tangential scenes added only for filler or humor",
|
||||
"name_key": "sponsorblock.filler",
|
||||
"description_key": "sponsorblock.filler_desc",
|
||||
"default": False,
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, previously_selected=None, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("SponsorBlock Categories")
|
||||
self.setWindowTitle(_("dialogs.sponsorblock_categories"))
|
||||
self.setMinimumWidth(500)
|
||||
self.setMinimumHeight(400)
|
||||
|
||||
@@ -489,15 +496,12 @@ class SponsorBlockCategoryDialog(QDialog):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Title and description
|
||||
title_label = QLabel("SponsorBlock Categories")
|
||||
title_label = QLabel(_("dialogs.sponsorblock_categories"))
|
||||
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #ffffff; margin: 10px;")
|
||||
layout.addWidget(title_label)
|
||||
|
||||
desc_label = QLabel(
|
||||
"Select which types of video segments to automatically remove during download.\n"
|
||||
"SponsorBlock uses community-submitted data to identify these segments."
|
||||
)
|
||||
desc_label = QLabel(_("dialogs.sponsorblock_description"))
|
||||
desc_label.setWordWrap(True)
|
||||
desc_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
desc_label.setStyleSheet("color: #cccccc; margin: 10px; font-size: 11px;")
|
||||
@@ -521,8 +525,8 @@ class SponsorBlockCategoryDialog(QDialog):
|
||||
category_layout.setContentsMargins(0, 0, 0, 0)
|
||||
category_layout.setSpacing(2)
|
||||
|
||||
# Create checkbox with just the name
|
||||
checkbox = QCheckBox(category_info["name"])
|
||||
# Create checkbox with localized name
|
||||
checkbox = QCheckBox(_(category_info["name_key"]))
|
||||
checkbox.setProperty("category_id", category_id)
|
||||
|
||||
# Determine if this category should be checked
|
||||
@@ -559,8 +563,8 @@ class SponsorBlockCategoryDialog(QDialog):
|
||||
"""
|
||||
)
|
||||
|
||||
# Create description label
|
||||
desc_label = QLabel(category_info["description"])
|
||||
# Create description label with localized text
|
||||
desc_label = QLabel(_(category_info["description_key"]))
|
||||
desc_label.setStyleSheet("color: #aaaaaa; font-size: 11px; margin-left: 28px; margin-bottom: 8px;")
|
||||
desc_label.setWordWrap(True)
|
||||
|
||||
@@ -577,15 +581,15 @@ class SponsorBlockCategoryDialog(QDialog):
|
||||
# Quick selection buttons
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
select_defaults_btn = QPushButton("Select Defaults")
|
||||
select_defaults_btn = QPushButton(_("buttons.select_defaults"))
|
||||
select_defaults_btn.clicked.connect(self.select_defaults)
|
||||
select_defaults_btn.setStyleSheet(self._get_button_style())
|
||||
|
||||
select_all_btn = QPushButton("Select All")
|
||||
select_all_btn = QPushButton(_("buttons.select_all"))
|
||||
select_all_btn.clicked.connect(self.select_all)
|
||||
select_all_btn.setStyleSheet(self._get_button_style())
|
||||
|
||||
deselect_all_btn = QPushButton("Deselect All")
|
||||
deselect_all_btn = QPushButton(_("buttons.deselect_all"))
|
||||
deselect_all_btn.clicked.connect(self.deselect_all)
|
||||
deselect_all_btn.setStyleSheet(self._get_button_style())
|
||||
|
||||
@@ -597,7 +601,9 @@ class SponsorBlockCategoryDialog(QDialog):
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# Dialog buttons
|
||||
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||
button_box = QDialogButtonBox()
|
||||
ok_button = button_box.addButton(_("buttons.ok"), QDialogButtonBox.ButtonRole.AcceptRole)
|
||||
cancel_button = button_box.addButton(_("buttons.cancel"), QDialogButtonBox.ButtonRole.RejectRole)
|
||||
button_box.accepted.connect(self.accept)
|
||||
button_box.rejected.connect(self.reject)
|
||||
|
||||
|
||||
@@ -34,12 +34,13 @@ from src.core.ytsage_utils import (
|
||||
update_auto_update_settings,
|
||||
)
|
||||
from src.utils.ytsage_logger import logger
|
||||
from src.utils.ytsage_localization import _
|
||||
|
||||
|
||||
class DownloadSettingsDialog(QDialog):
|
||||
def __init__(self, current_path, current_limit, current_unit_index, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Download Settings")
|
||||
self.setWindowTitle(_("settings.title"))
|
||||
self.setMinimumWidth(450)
|
||||
self.setMinimumHeight(400)
|
||||
self.current_path = current_path
|
||||
@@ -163,7 +164,7 @@ class DownloadSettingsDialog(QDialog):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# --- Download Path Section ---
|
||||
path_group_box = QGroupBox("Download Path")
|
||||
path_group_box = QGroupBox(_("settings.download_path"))
|
||||
path_layout = QVBoxLayout()
|
||||
|
||||
self.path_display = QLabel(str(self.current_path))
|
||||
@@ -173,7 +174,8 @@ class DownloadSettingsDialog(QDialog):
|
||||
)
|
||||
path_layout.addWidget(self.path_display)
|
||||
|
||||
browse_button = QPushButton("Browse...")
|
||||
|
||||
browse_button = QPushButton(_("settings.browse"))
|
||||
browse_button.clicked.connect(self.browse_new_path)
|
||||
path_layout.addWidget(browse_button)
|
||||
|
||||
@@ -181,11 +183,11 @@ class DownloadSettingsDialog(QDialog):
|
||||
layout.addWidget(path_group_box)
|
||||
|
||||
# --- Speed Limit Section ---
|
||||
speed_group_box = QGroupBox("Speed Limit")
|
||||
speed_group_box = QGroupBox(_("settings.speed_limit"))
|
||||
speed_layout = QHBoxLayout()
|
||||
|
||||
self.speed_limit_input = QLineEdit(str(self.current_limit))
|
||||
self.speed_limit_input.setPlaceholderText("None")
|
||||
self.speed_limit_input.setPlaceholderText(_("settings.speed_limit_placeholder"))
|
||||
speed_layout.addWidget(self.speed_limit_input)
|
||||
|
||||
self.speed_limit_unit = QComboBox()
|
||||
@@ -197,25 +199,25 @@ class DownloadSettingsDialog(QDialog):
|
||||
layout.addWidget(speed_group_box)
|
||||
|
||||
# --- Auto-Update yt-dlp Section ---
|
||||
auto_update_group_box = QGroupBox("Auto-Update yt-dlp")
|
||||
auto_update_group_box = QGroupBox(_("settings.auto_update_ytdlp"))
|
||||
auto_update_layout = QVBoxLayout()
|
||||
|
||||
# Load current auto-update settings
|
||||
auto_settings = get_auto_update_settings()
|
||||
|
||||
# Enable/Disable auto-update checkbox
|
||||
self.auto_update_enabled = QCheckBox("Enable automatic yt-dlp updates")
|
||||
self.auto_update_enabled = QCheckBox(_("settings.enable_auto_updates"))
|
||||
self.auto_update_enabled.setChecked(auto_settings["enabled"])
|
||||
auto_update_layout.addWidget(self.auto_update_enabled)
|
||||
|
||||
# Frequency options
|
||||
frequency_label = QLabel("Update frequency:")
|
||||
frequency_label = QLabel(_("settings.update_frequency"))
|
||||
frequency_label.setStyleSheet("color: #ffffff; margin-top: 10px;")
|
||||
auto_update_layout.addWidget(frequency_label)
|
||||
|
||||
self.startup_radio = QRadioButton("Check on every startup (minimum 1 hour between checks)")
|
||||
self.daily_radio = QRadioButton("Check daily")
|
||||
self.weekly_radio = QRadioButton("Check weekly")
|
||||
self.startup_radio = QRadioButton(_("settings.check_startup"))
|
||||
self.daily_radio = QRadioButton(_("settings.check_daily"))
|
||||
self.weekly_radio = QRadioButton(_("settings.check_weekly"))
|
||||
|
||||
# Set current selection based on saved settings
|
||||
current_frequency = auto_settings["frequency"]
|
||||
@@ -232,7 +234,7 @@ class DownloadSettingsDialog(QDialog):
|
||||
|
||||
# Test update button
|
||||
test_update_layout = QHBoxLayout()
|
||||
test_update_button = QPushButton("Check for Updates Now")
|
||||
test_update_button = QPushButton(_("settings.check_updates_now"))
|
||||
test_update_button.clicked.connect(self.test_update_check)
|
||||
test_update_layout.addWidget(test_update_button)
|
||||
test_update_layout.addStretch()
|
||||
@@ -242,13 +244,15 @@ class DownloadSettingsDialog(QDialog):
|
||||
layout.addWidget(auto_update_group_box)
|
||||
|
||||
# Dialog buttons (OK/Cancel)
|
||||
button_box = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel)
|
||||
button_box = QDialogButtonBox()
|
||||
ok_button = button_box.addButton(_("buttons.ok"), QDialogButtonBox.ButtonRole.AcceptRole)
|
||||
cancel_button = button_box.addButton(_("buttons.cancel"), QDialogButtonBox.ButtonRole.RejectRole)
|
||||
button_box.accepted.connect(self.accept)
|
||||
button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(button_box)
|
||||
|
||||
def browse_new_path(self) -> None:
|
||||
new_path = QFileDialog.getExistingDirectory(self, "Select Download Directory", str(self.current_path))
|
||||
new_path = QFileDialog.getExistingDirectory(self, _("dialogs.select_folder"), str(self.current_path))
|
||||
if new_path:
|
||||
self.current_path = new_path
|
||||
self.path_display.setText(self.current_path)
|
||||
@@ -316,8 +320,8 @@ class DownloadSettingsDialog(QDialog):
|
||||
if "Error" in current_version:
|
||||
msg_box = self._create_styled_message_box(
|
||||
QMessageBox.Icon.Warning,
|
||||
"Update Check",
|
||||
"Could not determine current yt-dlp version.",
|
||||
_("settings.update_check_title"),
|
||||
_("settings.could_not_determine_version"),
|
||||
)
|
||||
msg_box.exec()
|
||||
return
|
||||
@@ -334,22 +338,22 @@ class DownloadSettingsDialog(QDialog):
|
||||
if version_parser.parse(latest_version) > version_parser.parse(current_version):
|
||||
msg_box = self._create_styled_message_box(
|
||||
QMessageBox.Icon.Information,
|
||||
"Update Check",
|
||||
f"Update available!\n\nCurrent: {current_version}\nLatest: {latest_version}\n\nUse the 'Update yt-dlp' button in the main window to update.",
|
||||
_("settings.update_check_title"),
|
||||
_("settings.update_available_dialog", current=current_version, latest=latest_version),
|
||||
)
|
||||
msg_box.exec()
|
||||
else:
|
||||
msg_box = self._create_styled_message_box(
|
||||
QMessageBox.Icon.Information,
|
||||
"Update Check",
|
||||
f"yt-dlp is up to date!\n\nCurrent version: {current_version}",
|
||||
_("settings.update_check_title"),
|
||||
_("settings.up_to_date_dialog", version=current_version),
|
||||
)
|
||||
msg_box.exec()
|
||||
except Exception as e:
|
||||
msg_box = self._create_styled_message_box(
|
||||
QMessageBox.Icon.Warning,
|
||||
"Update Check",
|
||||
f"Error checking for updates: {e}",
|
||||
_("settings.update_check_title"),
|
||||
_("settings.error_checking_updates", error=str(e)),
|
||||
)
|
||||
msg_box.exec()
|
||||
|
||||
@@ -375,13 +379,13 @@ class DownloadSettingsDialog(QDialog):
|
||||
if update_auto_update_settings(enabled, frequency):
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Settings Saved",
|
||||
"Auto-update settings have been saved successfully!",
|
||||
_("settings.settings_saved_title"),
|
||||
_("settings.settings_saved_message"),
|
||||
)
|
||||
else:
|
||||
QMessageBox.warning(self, "Error", "Failed to save auto-update settings.")
|
||||
QMessageBox.warning(self, _("settings.error_title"), _("settings.failed_save_settings"))
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error", f"Error saving auto-update settings: {e}")
|
||||
QMessageBox.critical(self, _("settings.error_title"), _("settings.error_saving_settings", error=str(e)))
|
||||
|
||||
# Call the parent accept method to close the dialog
|
||||
super().accept()
|
||||
@@ -390,7 +394,7 @@ class DownloadSettingsDialog(QDialog):
|
||||
class AutoUpdateSettingsDialog(QDialog):
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Auto-Update Settings")
|
||||
self.setWindowTitle(_("settings.auto_update_title"))
|
||||
self.setMinimumWidth(400)
|
||||
self.setMinimumHeight(300)
|
||||
|
||||
@@ -406,32 +410,32 @@ class AutoUpdateSettingsDialog(QDialog):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Title
|
||||
title_label = QLabel("<h2>🔄 Auto-Update Settings</h2>")
|
||||
title_label = QLabel(f"<h2>{_("settings.auto_update_header")}</h2>")
|
||||
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
layout.addWidget(title_label)
|
||||
|
||||
# Description
|
||||
desc_label = QLabel("Configure automatic updates for yt-dlp to ensure you always have the latest features and bug fixes.")
|
||||
desc_label = QLabel(_("settings.auto_update_description"))
|
||||
desc_label.setWordWrap(True)
|
||||
desc_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
desc_label.setStyleSheet("color: #cccccc; margin: 10px; font-size: 11px;")
|
||||
layout.addWidget(desc_label)
|
||||
|
||||
# Enable/Disable auto-update
|
||||
self.enable_checkbox = QCheckBox("Enable automatic yt-dlp updates")
|
||||
self.enable_checkbox = QCheckBox(_("settings.enable_auto_updates"))
|
||||
self.enable_checkbox.setChecked(True) # Default enabled
|
||||
self.enable_checkbox.toggled.connect(self.on_enable_toggled)
|
||||
layout.addWidget(self.enable_checkbox)
|
||||
|
||||
# Frequency options
|
||||
frequency_group = QGroupBox("Update Frequency")
|
||||
frequency_group = QGroupBox(_("settings.update_frequency_group"))
|
||||
frequency_layout = QVBoxLayout()
|
||||
|
||||
self.frequency_group = QButtonGroup(self)
|
||||
|
||||
self.startup_radio = QRadioButton("Check on every startup (minimum 1 hour between checks)")
|
||||
self.daily_radio = QRadioButton("Check daily")
|
||||
self.weekly_radio = QRadioButton("Check weekly")
|
||||
self.startup_radio = QRadioButton(_("settings.check_startup"))
|
||||
self.daily_radio = QRadioButton(_("settings.check_daily"))
|
||||
self.weekly_radio = QRadioButton(_("settings.check_weekly"))
|
||||
|
||||
self.daily_radio.setChecked(True) # Default to daily
|
||||
|
||||
@@ -447,12 +451,12 @@ class AutoUpdateSettingsDialog(QDialog):
|
||||
layout.addWidget(frequency_group)
|
||||
|
||||
# Current status
|
||||
status_group = QGroupBox("Current Status")
|
||||
status_group = QGroupBox(_("settings.current_status"))
|
||||
status_layout = QVBoxLayout()
|
||||
|
||||
self.current_version_label = QLabel("Current yt-dlp version: Checking...")
|
||||
self.last_check_label = QLabel("Last update check: Never")
|
||||
self.next_check_label = QLabel("Next check: Based on settings")
|
||||
self.current_version_label = QLabel(_("settings.current_version_label"))
|
||||
self.last_check_label = QLabel(_("settings.last_check_label"))
|
||||
self.next_check_label = QLabel(_("settings.next_check_label"))
|
||||
|
||||
status_layout.addWidget(self.current_version_label)
|
||||
status_layout.addWidget(self.last_check_label)
|
||||
@@ -462,17 +466,17 @@ class AutoUpdateSettingsDialog(QDialog):
|
||||
layout.addWidget(status_group)
|
||||
|
||||
# Manual check button
|
||||
self.manual_check_btn = QPushButton("🔍 Check for Updates Now")
|
||||
self.manual_check_btn = QPushButton(_("settings.manual_check_button"))
|
||||
self.manual_check_btn.clicked.connect(self.manual_check)
|
||||
layout.addWidget(self.manual_check_btn)
|
||||
|
||||
# Buttons
|
||||
button_layout = QHBoxLayout()
|
||||
|
||||
self.save_btn = QPushButton("Save Settings")
|
||||
self.save_btn = QPushButton(_("settings.save_settings"))
|
||||
self.save_btn.clicked.connect(self.save_settings)
|
||||
|
||||
self.cancel_btn = QPushButton("Cancel")
|
||||
self.cancel_btn = QPushButton(_("buttons.cancel"))
|
||||
self.cancel_btn.clicked.connect(self.reject)
|
||||
|
||||
button_layout.addWidget(self.save_btn)
|
||||
@@ -721,19 +725,19 @@ class AutoUpdateSettingsDialog(QDialog):
|
||||
if update_auto_update_settings(enabled, frequency):
|
||||
msg_box = self._create_styled_message_box(
|
||||
QMessageBox.Icon.Information,
|
||||
"Settings Saved",
|
||||
"✅ Auto-update settings have been saved successfully!",
|
||||
_("settings.settings_saved_title"),
|
||||
_("settings.settings_saved_successfully"),
|
||||
)
|
||||
msg_box.exec()
|
||||
self.accept()
|
||||
else:
|
||||
msg_box = self._create_styled_message_box(
|
||||
QMessageBox.Icon.Warning,
|
||||
"Error",
|
||||
"❌ Failed to save auto-update settings.\nPlease try again.",
|
||||
_("settings.error_title"),
|
||||
_("settings.failed_save_settings"),
|
||||
)
|
||||
msg_box.exec()
|
||||
except Exception as e:
|
||||
logger.exception(f"Error saving auto-update settings: {e}")
|
||||
msg_box = self._create_styled_message_box(QMessageBox.Icon.Critical, "Error", f"❌ Error saving settings: {e}")
|
||||
msg_box = self._create_styled_message_box(QMessageBox.Icon.Critical, _("settings.error_title"), _("settings", "error_saving", error=str(e)))
|
||||
msg_box.exec()
|
||||
|
||||
@@ -18,6 +18,7 @@ from PySide6.QtWidgets import QDialog, QHBoxLayout, QLabel, QProgressBar, QPushB
|
||||
from src.core.ytsage_utils import get_ytdlp_version, load_config, save_config
|
||||
from src.core.ytsage_yt_dlp import get_yt_dlp_path
|
||||
from src.utils.ytsage_constants import OS_NAME, SUBPROCESS_CREATIONFLAGS, YTDLP_APP_BIN_PATH, YTDLP_DOWNLOAD_URL
|
||||
from src.utils.ytsage_localization import _
|
||||
from src.utils.ytsage_logger import logger
|
||||
|
||||
try:
|
||||
@@ -118,15 +119,15 @@ class UpdateThread(QThread):
|
||||
error_message = ""
|
||||
success = False
|
||||
try:
|
||||
self.update_status.emit("🔍 Checking current installation...")
|
||||
self.update_status.emit(_('update.checking_current'))
|
||||
self.update_progress.emit(10)
|
||||
|
||||
# Get the yt-dlp path
|
||||
try:
|
||||
yt_dlp_path = get_yt_dlp_path()
|
||||
self.update_status.emit(f"📍 Found yt-dlp at: {yt_dlp_path}")
|
||||
self.update_status.emit(_('update.found_at', path=yt_dlp_path))
|
||||
except Exception as e:
|
||||
self.update_status.emit(f"❌ Error getting yt-dlp path: {e}")
|
||||
self.update_status.emit(_('update.error_getting_path', error=e))
|
||||
self.update_finished.emit(False, f"❌ Error getting yt-dlp path: {e}")
|
||||
return
|
||||
|
||||
@@ -152,17 +153,17 @@ class UpdateThread(QThread):
|
||||
is_app_managed = False
|
||||
|
||||
if is_app_managed:
|
||||
self.update_status.emit("📦 Updating app-managed yt-dlp binary...")
|
||||
self.update_status.emit(_('update.updating_binary'))
|
||||
success = self._update_binary(yt_dlp_path)
|
||||
else:
|
||||
self.update_status.emit("🐍 Updating system yt-dlp via pip...")
|
||||
self.update_status.emit(_('update.updating_pip'))
|
||||
success = self._update_via_pip()
|
||||
|
||||
if success:
|
||||
self.update_progress.emit(100)
|
||||
error_message = "✅ yt-dlp has been successfully updated!"
|
||||
else:
|
||||
error_message = "❌ Failed to update yt-dlp. Please try again or check your internet connection."
|
||||
error_message = _('update.update_failed')
|
||||
|
||||
except requests.RequestException as e:
|
||||
error_message = f"❌ Network error during update: {e}"
|
||||
@@ -196,61 +197,61 @@ class UpdateThread(QThread):
|
||||
logger.info("UpdateThread: yt-dlp update completed successfully.")
|
||||
if result.stdout:
|
||||
logger.debug(f"yt-dlp output: {result.stdout.strip()}")
|
||||
self.update_status.emit("✅ Binary successfully updated!")
|
||||
self.update_status.emit(_('update.binary_updated'))
|
||||
self.update_progress.emit(95)
|
||||
return True
|
||||
else:
|
||||
logger.error(f"UpdateThread: yt-dlp update failed. {result.stderr.strip()}")
|
||||
self.update_status.emit(f"❌ yt-dlp update failed: {result.stderr.strip()}")
|
||||
self.update_status.emit(_('update.update_failed_stderr', error=result.stderr.strip()))
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
logger.error("UpdateThread: yt-dlp update timed out.")
|
||||
self.update_status.emit("❌ yt-dlp update timed out.")
|
||||
self.update_status.emit(_('update.update_timeout'))
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"UpdateThread: Unexpected error during update: {e}")
|
||||
self.update_status.emit(f"❌ Unexpected error during update: {e}")
|
||||
self.update_status.emit(_('update.unexpected_error', error=e))
|
||||
return False
|
||||
|
||||
def _update_via_pip(self) -> bool:
|
||||
"""Update yt-dlp via pip."""
|
||||
try:
|
||||
self.update_status.emit("🔍 Checking current pip installation...")
|
||||
self.update_status.emit(_('update.checking_pip'))
|
||||
self.update_progress.emit(30)
|
||||
|
||||
# Get current version
|
||||
try:
|
||||
current_version = get_version("yt-dlp")
|
||||
self.update_status.emit(f"📋 Current version: {current_version}")
|
||||
self.update_status.emit(_('update.current_version', version=current_version))
|
||||
except PackageNotFoundError:
|
||||
self.update_status.emit("⚠️ yt-dlp not found via pip, attempting installation...")
|
||||
self.update_status.emit(_('update.not_found_pip'))
|
||||
current_version = "0.0.0"
|
||||
|
||||
self.update_progress.emit(40)
|
||||
|
||||
# Get the latest version from PyPI
|
||||
self.update_status.emit("🌐 Checking for latest version...")
|
||||
self.update_status.emit(_('update.checking_latest'))
|
||||
response = requests.get("https://pypi.org/pypi/yt-dlp/json", timeout=10)
|
||||
|
||||
if response.status_code != 200:
|
||||
self.update_status.emit("❌ Failed to check for updates")
|
||||
self.update_status.emit(_('update.failed_check_updates'))
|
||||
return False
|
||||
|
||||
data = response.json()
|
||||
latest_version = data["info"]["version"]
|
||||
self.update_status.emit(f"🆕 Latest version: {latest_version}")
|
||||
self.update_status.emit(_('update.latest_version', version=latest_version))
|
||||
self.update_progress.emit(50)
|
||||
|
||||
# Compare versions
|
||||
if version.parse(latest_version) > version.parse(current_version):
|
||||
self.update_status.emit(f"⬆️ Updating from {current_version} to {latest_version}...")
|
||||
self.update_status.emit(_('update.updating_from_to', current=current_version, latest=latest_version))
|
||||
self.update_progress.emit(60)
|
||||
|
||||
try:
|
||||
# Run pip update with timeout
|
||||
self.update_status.emit("📦 Running pip install --upgrade...")
|
||||
self.update_status.emit(_('update.running_pip_install'))
|
||||
update_result = subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "--upgrade", "yt-dlp"],
|
||||
capture_output=True,
|
||||
@@ -263,11 +264,11 @@ class UpdateThread(QThread):
|
||||
self.update_progress.emit(85)
|
||||
|
||||
if update_result.returncode == 0:
|
||||
self.update_status.emit("✅ Pip update completed successfully!")
|
||||
self.update_status.emit(_('update.pip_completed'))
|
||||
self.update_progress.emit(95)
|
||||
return True
|
||||
else:
|
||||
self.update_status.emit(f"❌ Pip update failed: {update_result.stderr}")
|
||||
self.update_status.emit(_('update.pip_failed', error=update_result.stderr))
|
||||
return False
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
@@ -289,7 +290,7 @@ class UpdateThread(QThread):
|
||||
class YTDLPUpdateDialog(QDialog):
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Update yt-dlp")
|
||||
self.setWindowTitle(_('update.title'))
|
||||
self.setMinimumWidth(450)
|
||||
self.setMinimumHeight(200)
|
||||
self._closing = False # Flag to track if dialog is closing
|
||||
@@ -297,7 +298,7 @@ class YTDLPUpdateDialog(QDialog):
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# Status label
|
||||
self.status_label = QLabel("Checking for updates...")
|
||||
self.status_label = QLabel(_('update.checking'))
|
||||
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.status_label.setWordWrap(True)
|
||||
self.status_label.setMinimumHeight(60)
|
||||
@@ -310,11 +311,11 @@ class YTDLPUpdateDialog(QDialog):
|
||||
|
||||
# Buttons
|
||||
button_layout = QHBoxLayout()
|
||||
self.update_btn = QPushButton("Update")
|
||||
self.update_btn = QPushButton(_('buttons.update'))
|
||||
self.update_btn.clicked.connect(self.perform_update)
|
||||
self.update_btn.setEnabled(False)
|
||||
|
||||
self.close_btn = QPushButton("Close")
|
||||
self.close_btn = QPushButton(_('buttons.close'))
|
||||
self.close_btn.clicked.connect(self.close)
|
||||
|
||||
button_layout.addWidget(self.update_btn)
|
||||
@@ -370,7 +371,7 @@ class YTDLPUpdateDialog(QDialog):
|
||||
self.check_version()
|
||||
|
||||
def check_version(self) -> None:
|
||||
self.status_label.setText("Checking for updates...")
|
||||
self.status_label.setText(_('update.checking'))
|
||||
self.update_btn.setEnabled(False)
|
||||
self.version_check_thread = VersionCheckThread()
|
||||
self.version_check_thread.finished.connect(self.on_version_check_finished)
|
||||
@@ -387,7 +388,7 @@ class YTDLPUpdateDialog(QDialog):
|
||||
return
|
||||
|
||||
if not current_version or not latest_version:
|
||||
self.status_label.setText("Could not determine versions.")
|
||||
self.status_label.setText(_('update.could_not_determine'))
|
||||
self.update_btn.setEnabled(False)
|
||||
return
|
||||
|
||||
@@ -398,7 +399,7 @@ class YTDLPUpdateDialog(QDialog):
|
||||
|
||||
if current_ver < latest_ver:
|
||||
self.status_label.setText(
|
||||
f"Update available!\nCurrent version: {current_version}\nLatest version: {latest_version}"
|
||||
_('update.update_available', current=current_version, latest=latest_version)
|
||||
)
|
||||
self.update_btn.setEnabled(True)
|
||||
else:
|
||||
@@ -408,22 +409,22 @@ class YTDLPUpdateDialog(QDialog):
|
||||
# If version parsing fails, do a simple string comparison
|
||||
if current_version != latest_version:
|
||||
self.status_label.setText(
|
||||
f"Update available! (Comparison failed)\nCurrent: {current_version}\nLatest: {latest_version}"
|
||||
_('update.update_available_failed', current=current_version, latest=latest_version)
|
||||
)
|
||||
self.update_btn.setEnabled(True)
|
||||
else:
|
||||
self.status_label.setText(f"yt-dlp is up to date (version {current_version})")
|
||||
self.status_label.setText(_('update.up_to_date', version=current_version))
|
||||
self.update_btn.setEnabled(False)
|
||||
except Exception as e:
|
||||
self.status_label.setText(f"Error comparing versions: {e}")
|
||||
self.status_label.setText(_('update.error_comparing', error=e))
|
||||
self.update_btn.setEnabled(False)
|
||||
|
||||
def perform_update(self) -> None:
|
||||
# Immediate visual feedback
|
||||
self.update_btn.setEnabled(False)
|
||||
self.close_btn.setEnabled(False)
|
||||
self.update_btn.setText("Updating...")
|
||||
self.status_label.setText("🚀 Initializing update process...")
|
||||
self.update_btn.setText(_('update.updating'))
|
||||
self.status_label.setText(_('update.initializing'))
|
||||
|
||||
# Show progress bar immediately
|
||||
self.progress_bar.setRange(0, 100)
|
||||
@@ -461,7 +462,7 @@ class YTDLPUpdateDialog(QDialog):
|
||||
self.progress_bar.setValue(100)
|
||||
self.status_label.setText(message)
|
||||
self.close_btn.setEnabled(True)
|
||||
self.update_btn.setText("Update") # Reset button text
|
||||
self.update_btn.setText(_('buttons.update')) # Reset button text
|
||||
|
||||
if success:
|
||||
# Show success briefly then auto-check version
|
||||
|
||||
Reference in New Issue
Block a user