10 Commits

Author SHA1 Message Date
ae0b3a0f1a chore(build): added qt6-svg
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2026-01-14 18:08:32 +05:00
678f28ed30 bump ver to 0.1.10
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2026-01-14 17:37:42 +05:00
e7d2860c0e chore(changelog): update
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2026-01-14 17:36:58 +05:00
55a7f77a33 fix(detail_pages): handle RuntimeError on detail page exit animation
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2026-01-13 14:29:37 +05:00
8e6c0aafd1 fix(image_utils): remove corrupted cached images on load failure
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2026-01-13 14:21:11 +05:00
dd65021976 fix(time_utils): make playtime parsing robust to malformed data
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2026-01-13 14:13:59 +05:00
5f3a451c50 chore(locale): update
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2026-01-12 21:03:51 +05:00
c33813dae5 feat(get_wine): added total size to update_selection_display
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2026-01-12 20:56:41 +05:00
88a436c29f fix(locales): clean fuzzy in poedit
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2026-01-12 20:40:08 +05:00
9ca4b38a14 Обновление русской локализации в окне "Управление версиями WINE" 2026-01-12 19:58:42 +05:00
23 changed files with 503 additions and 307 deletions

View File

@@ -8,7 +8,7 @@ on:
env:
# Common version, will be used for tagging the release
VERSION: 0.1.9
VERSION: 0.1.10
PKGDEST: "/tmp/portprotonqt"
PACKAGE: "portprotonqt"
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}

View File

@@ -3,6 +3,35 @@
Все заметные изменения в этом проекте фиксируются в этом файле.
Формат основан на [Keep a Changelog](https://keepachangelog.com/) и придерживается принципов [Semantic Versioning](https://semver.org/).
## [0.1.10] - 2026-01-14
### Added
- Детальная страница для автоустановок с описанием игры и возможности переуствновки
- Менеджер версий Wine для скачивания и удаления различных версий Wine и Proton
- Возможность перевода описание, названия тем на другие языки
- Возможность перевода подписи к скриншотам тем на другие языки
### Changed
- Проведена чистка мёртвого кода
- Улучшена проверка сторонних тем
- В документации по созданию тем добавлены примеры dropin тем
- Провеедена редактура перевода
- Переработана сортировка вайнов и префиксов во всех комбобоксах
- Список Wine и префиксов теперь обновляется на лету, а не при запуске приложения
- AppImage теперь работает на дистрибутивах использующий альтернативный libc, а так же на тех что не следуют FHS
### Fixed
- Изменение размера карточек автоустановок через геймпад
- Проведены исправления для утечек памяти
- Время игры теперь парсится даже если файл статистики повреждён
- При наличии битых обложек они теперь перекачиваются, а не провоцируют ошибки libpng
- Управление QmessageBox через стрелки клавиатуры
### Contributors
- @Vector_null
- @Dervart
- @Simple16
## [0.1.9] - 2025-12-08
### Added

View File

@@ -1,12 +1,12 @@
pkgname=portprotonqt
pkgver=0.1.9
pkgver=0.1.10
pkgrel=1
pkgdesc="Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store"
arch=('any')
url="https://git.linux-gaming.ru/Boria138/PortProtonQt"
license=('GPL-3.0')
depends=('python-requests' 'python-babel' 'python-evdev' 'python-pyudev' 'python-orjson'
'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'icoextract' 'python-pillow' 'perl-image-exiftool' 'xdg-utils' 'python-beautifulsoup4' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'unrar')
'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'python-rapidfuzz' 'icoextract' 'python-pillow' 'perl-image-exiftool' 'xdg-utils' 'python-beautifulsoup4' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'unrar' 'qt6-svg')
makedepends=('python-'{'build','installer','setuptools','wheel'})
source=("git+https://git.linux-gaming.ru/Boria138/PortProtonQt#tag=v$pkgver")
sha256sums=('SKIP')

View File

@@ -6,7 +6,7 @@ arch=('any')
url="https://git.linux-gaming.ru/Boria138/PortProtonQt"
license=('GPL-3.0')
depends=('python-requests' 'python-babel' 'python-evdev' 'python-pyudev' 'python-orjson'
'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'icoextract' 'python-pillow' 'python-rapidfuzz' 'perl-image-exiftool' 'xdg-utils' 'python-beautifulsoup4' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'unrar')
'python-psutil' 'python-tqdm' 'python-vdf' 'python-libarchive-c' 'pyside6' 'icoextract' 'python-pillow' 'python-rapidfuzz' 'perl-image-exiftool' 'xdg-utils' 'python-beautifulsoup4' 'python-websocket-client' 'cabextract' 'unzip' 'curl' 'unrar' 'qt6-svg')
makedepends=('python-'{'build','installer','setuptools','wheel'})
source=("git+https://git.linux-gaming.ru/Boria138/PortProtonQt.git")
sha256sums=('SKIP')

View File

@@ -48,6 +48,7 @@ Requires: python3-rapidfuzz
Requires: python3-libarchive-c
Requires: perl-Image-ExifTool
Requires: xdg-utils
Requires: qt6-qtsvg
Requires: cabextract
Requires: gzip
Requires: unzip

View File

@@ -1,5 +1,5 @@
%global pypi_name portprotonqt
%global pypi_version 0.1.9
%global pypi_version 0.1.10
%global oname PortProtonQt
%global _python_no_extras_requires 1
@@ -45,6 +45,7 @@ Requires: python3-rapidfuzz
Requires: python3-libarchive-c
Requires: perl-Image-ExifTool
Requires: xdg-utils
Requires: qt6-qtsvg
Requires: cabextract
Requires: gzip
Requires: unzip

View File

@@ -21,9 +21,9 @@ Current translation status:
| Locale | Progress | Translated |
| :----- | -------: | ---------: |
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 375 |
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 375 |
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 375 of 375 |
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 376 |
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 376 |
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 376 of 376 |
---

View File

@@ -21,9 +21,9 @@
| Локаль | Прогресс | Переведено |
| :----- | -------: | ---------: |
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 375 |
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 375 |
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 375 из 375 |
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 376 |
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 376 |
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 376 из 376 |
---

View File

@@ -556,8 +556,11 @@ class DetailPageAnimations:
if detail_page and not detail_page.isHidden():
detail_page.setGraphicsEffect(cast(Any, original_effect))
except RuntimeError:
logger.debug("Original effect already deleted")
cleanup_callback()
logger.debug("Detail page or effect already deleted")
try:
cleanup_callback()
except RuntimeError:
logger.debug("Error during cleanup callback")
# Check if animation is still valid before starting
if animation and not detail_page.isHidden():
@@ -594,10 +597,10 @@ class DetailPageAnimations:
animation.setEasingCurve(easing_curve)
def slide_cleanup():
# Check if page is still valid before cleanup
if not detail_page or detail_page.isHidden():
logger.debug("Detail page already cleaned up")
cleanup_callback()
try:
cleanup_callback()
except RuntimeError:
logger.debug("Error during slide cleanup callback")
# Check if animation is still valid before starting
if animation and not detail_page.isHidden():
@@ -647,20 +650,28 @@ class DetailPageAnimations:
return
def bounce_cleanup():
# Check if page is still valid before cleanup
if not detail_page or detail_page.isHidden():
logger.debug("Detail page already cleaned up")
cleanup_callback()
try:
cleanup_callback()
except RuntimeError:
logger.debug("Error during bounce cleanup callback")
group_anim.start(QAbstractAnimation.DeletionPolicy.DeleteWhenStopped)
self.animations[detail_page] = group_anim
group_anim.finished.connect(bounce_cleanup)
except RuntimeError:
# Widget was already deleted, which is expected after deleteLater()
logger.debug("Detail page already deleted during animation setup")
cleanup_callback()
try:
cleanup_callback()
except RuntimeError:
pass
except Exception as e:
logger.error(f"Error in animate_detail_page_exit: {e}", exc_info=True)
if detail_page in self.animations:
self.animations.pop(detail_page, None)
cleanup_callback()
try:
if detail_page in self.animations:
self.animations.pop(detail_page, None)
except RuntimeError:
pass
try:
cleanup_callback()
except RuntimeError:
pass

View File

@@ -17,7 +17,7 @@ from portprotonqt.cli import parse_args
__app_id__ = "ru.linux_gaming.PortProtonQt"
__app_name__ = "PortProtonQt"
__app_version__ = "0.1.9"
__app_version__ = "0.1.10"
def get_version():
try:

View File

@@ -863,10 +863,12 @@ class DetailPageManager:
logger.warning("Detail page not valid, bypassing animation and cleaning up directly")
self._exit_animation_in_progress = False
cleanup()
except RuntimeError:
logger.debug("Page deleted before animation could start")
self._exit_animation_in_progress = False
except Exception as e:
logger.error(f"Error starting exit animation: {e}", exc_info=True)
self._exit_animation_in_progress = False
cleanup() # Fallback to cleanup if animation fails
def open_portproton_forum_topic(self, name):
result = self.portproton_api.get_forum_topic_slug(name)

View File

@@ -971,15 +971,69 @@ class ProtonManager(QDialog):
if os.path.exists(filepath):
total_size += os.path.getsize(filepath)
# Convert to human readable format
for unit in ['B', 'KB', 'MB', 'GB']:
if total_size < 1024.0:
return f"{total_size:.1f} {unit}"
total_size /= 1024.0
return f"{total_size:.1f} TB"
# Convert to human readable format (binary units)
if total_size == 0:
return "0 B"
elif total_size < 1024:
return f"{total_size}.0 B"
elif total_size < 1024 * 1024:
return f"{int(total_size / 1024)}.{int((total_size / 1024 * 10) % 10)} KiB"
elif total_size < 1024 * 1024 * 1024:
return f"{int(total_size / (1024 * 1024))}.{int((total_size / (1024 * 1024) * 10) % 10)} MiB"
elif total_size < 1024 * 1024 * 1024 * 1024:
return f"{int(total_size / (1024 * 1024 * 1024))}.{int((total_size / (1024 * 1024 * 1024) * 10) % 10)} GiB"
else:
return f"{int(total_size / (1024 * 1024 * 1024 * 1024))}.{int((total_size / (1024 * 1024 * 1024 * 1024) * 10) % 10)} TiB"
except Exception:
return _("Unknown")
def convert_size_to_bytes(self, size_str):
"""Convert human-readable size string to bytes"""
if not size_str or size_str == _("Unknown"):
return 0
# Remove any extra text and extract the number and unit
size_str = size_str.strip()
# Handle different units
if size_str.endswith("TiB"):
num = float(size_str[:-3].strip())
return int(num * 1024 * 1024 * 1024 * 1024)
elif size_str.endswith("GiB"):
num = float(size_str[:-3].strip())
return int(num * 1024 * 1024 * 1024)
elif size_str.endswith("MiB"):
num = float(size_str[:-3].strip())
return int(num * 1024 * 1024)
elif size_str.endswith("KiB"):
num = float(size_str[:-3].strip())
return int(num * 1024)
elif size_str.endswith("B"):
num = float(size_str[:-1].strip())
return int(num)
else:
# If format is unknown, return 0
return 0
def format_bytes(self, bytes_value):
"""Format bytes to human-readable string"""
if bytes_value == 0:
return "0 B"
elif bytes_value < 1024:
return f"{bytes_value} B"
elif bytes_value < 1024 * 1024:
kb_value = bytes_value / 1024
return f"{kb_value:.1f} KiB"
elif bytes_value < 1024 * 1024 * 1024:
mb_value = bytes_value / (1024 * 1024)
return f"{mb_value:.1f} MiB"
elif bytes_value < 1024 * 1024 * 1024 * 1024:
gb_value = bytes_value / (1024 * 1024 * 1024)
return f"{gb_value:.1f} GiB"
else:
tb_value = bytes_value / (1024 * 1024 * 1024 * 1024)
return f"{tb_value:.1f} TiB"
def on_cell_clicked(self, row):
"""Обработка клика по ячейке - переключение флажка при клике по любой ячейке в строке"""
tab = self.tab_widget.currentWidget()
@@ -1033,6 +1087,7 @@ class ProtonManager(QDialog):
table = current_tab.findChild(QTableWidget)
if table:
selected_count = 0
total_size = 0
for row in range(table.rowCount()):
checkbox_widget = table.cellWidget(row, 0)
@@ -1041,6 +1096,14 @@ class ProtonManager(QDialog):
if checkbox and checkbox.isChecked():
selected_count += 1
# Get the size for the selected item
size_item = table.item(row, 2) # Size column
if size_item:
size_text = size_item.text()
size_bytes = self.convert_size_to_bytes(size_text)
if size_bytes:
total_size += size_bytes
if selected_count > 0:
selection_text = _('Selected {} assets:\n').format(selected_count)
@@ -1061,6 +1124,10 @@ class ProtonManager(QDialog):
selection_text += f"{item_number}. {version_name}\n"
item_number += 1
# Add total size to the selection text
total_size_text = self.format_bytes(total_size)
selection_text += _("\nTotal size to delete: {}\n").format(total_size_text)
self.download_btn.setText(_('Delete Selected'))
self.download_btn.setEnabled(True)
else:
@@ -1078,9 +1145,49 @@ class ProtonManager(QDialog):
if self.selected_assets:
selection_text = _('Selected {} assets:\n').format(len(self.selected_assets))
total_size = 0
for i, asset_data in enumerate(self.selected_assets.values(), 1):
selection_text += f"{i}. {asset_data['asset_name']}\n"
# Get size from JSON entry if available
# We need to search through all tabs to find the matching entry
for tab_index in range(self.tab_widget.count()):
tab = self.tab_widget.widget(tab_index)
table = tab.findChild(QTableWidget)
if table and self.tab_widget.tabText(tab_index) != _("Installed"):
# Search for the item in the table to get its size
for row in range(table.rowCount()):
table_item = table.item(row, 1) # Name column
if table_item:
# Extract just the name without extensions for comparison
table_item_name = table_item.text()
# Remove common extensions for comparison
for ext in ['.tar.xz', '.tar.gz', '.zip']:
if table_item_name.lower().endswith(ext):
table_item_name = table_item_name[:-len(ext)]
break
asset_name_for_comparison = asset_data['asset_name']
for ext in ['.tar.xz', '.tar.gz', '.zip']:
if asset_name_for_comparison.lower().endswith(ext):
asset_name_for_comparison = asset_name_for_comparison[:-len(ext)]
break
if table_item_name == asset_name_for_comparison:
user_data = table_item.data(Qt.ItemDataRole.UserRole)
if user_data and 'json_entry' in user_data:
json_entry = user_data['json_entry']
size_text = json_entry.get('size_human', 'Unknown')
size_bytes = self.convert_size_to_bytes(size_text)
if size_bytes:
total_size += size_bytes
break
# Add total size to the selection text
total_size_text = self.format_bytes(total_size)
selection_text += _("\nTotal size to download: {}\n").format(total_size_text)
self.selection_text.setPlainText(selection_text)
self.download_btn.setText(_('Download Selected'))
self.download_btn.setEnabled(True)

View File

@@ -71,9 +71,11 @@ def load_pixmap_async(cover: str, width: int, height: int, callback: Callable[[Q
pixmap = QPixmap(local_path)
# Check if the pixmap loaded successfully
if pixmap.isNull():
logger.warning(f"Failed to load image from {local_path}")
finish_with(pixmap)
return
logger.warning(f"Failed to load image from {local_path}, removing corrupted file")
os.remove(local_path)
else:
finish_with(pixmap)
return
def on_downloaded(result: str | None):
pixmap = QPixmap()
@@ -111,9 +113,11 @@ def load_pixmap_async(cover: str, width: int, height: int, callback: Callable[[Q
pixmap = QPixmap(local_path)
# Check if the pixmap loaded successfully
if pixmap.isNull():
logger.warning(f"Failed to load image from {local_path}")
finish_with(pixmap)
return
logger.warning(f"Failed to load image from {local_path}, removing corrupted file")
os.remove(local_path)
else:
finish_with(pixmap)
return
def on_downloaded(result: str | None):
pixmap = QPixmap()
@@ -148,9 +152,11 @@ def load_pixmap_async(cover: str, width: int, height: int, callback: Callable[[Q
pixmap = QPixmap(local_path)
# Check if the pixmap loaded successfully
if pixmap.isNull():
logger.warning(f"Failed to load image from {local_path}")
finish_with(pixmap)
return
logger.warning(f"Failed to load image from {local_path}, removing corrupted file")
os.remove(local_path)
else:
finish_with(pixmap)
return
def on_downloaded(result: str | None):
pixmap = QPixmap()
@@ -181,8 +187,13 @@ def load_pixmap_async(cover: str, width: int, height: int, callback: Callable[[Q
# Check if the pixmap loaded successfully
if pixmap.isNull():
logger.warning(f"Failed to load image from {cover}")
finish_with(pixmap)
return
# Remove corrupted file only if it's in the cache directory
if cover.startswith(image_folder):
logger.warning(f"Removing corrupted cached file {cover}")
os.remove(cover)
else:
finish_with(pixmap)
return
placeholder_path = theme_manager.get_theme_image("placeholder", current_theme_name)
pixmap = QPixmap()

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-01-04 00:12+0500\n"
"POT-Creation-Date: 2026-01-12 20:59+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de_DE\n"
@@ -256,55 +256,6 @@ msgstr ""
msgid "Select All"
msgstr ""
msgid "Delete Wine"
msgstr ""
msgid "Selected WINE:"
msgstr ""
msgid "No WINE selected"
msgstr ""
msgid "Delete Selected"
msgstr ""
msgid "Clear All"
msgstr ""
#, python-brace-format
msgid "Selected {} WINE:\n"
msgstr ""
msgid "No Selection"
msgstr ""
msgid "Please select at least one WINE to delete."
msgstr ""
#, python-brace-format
msgid ""
"Are you sure you want to delete the following WINE versions?\n"
"\n"
"{}"
msgstr ""
#, python-brace-format
msgid "Failed to delete WINE '{}': {}"
msgstr ""
msgid "Some Deletions Failed"
msgstr ""
#, python-brace-format
msgid ""
"Some WINE versions could not be deleted:\n"
"\n"
"{}"
msgstr ""
msgid "Selected WINE versions deleted successfully."
msgstr ""
msgid "Back"
msgstr ""
@@ -381,6 +332,12 @@ msgstr ""
msgid "Search"
msgstr ""
msgid "Apply"
msgstr ""
msgid "Clear All"
msgstr ""
#, python-brace-format
msgid "Launching {0}"
msgstr ""
@@ -422,9 +379,6 @@ msgstr ""
msgid "Cover Preview:"
msgstr ""
msgid "Apply"
msgstr ""
msgid "Invalid image"
msgstr ""
@@ -545,7 +499,7 @@ msgstr ""
msgid "Pending"
msgstr ""
msgid "Get other Wine"
msgid "Manage Wine versions"
msgstr ""
msgid "Selected assets:"
@@ -560,25 +514,84 @@ msgstr ""
msgid "Download Selected"
msgstr ""
msgid "Asset Name"
msgid "Installed"
msgstr ""
#, python-brace-format
msgid "Error loading wine data: {error}"
msgstr ""
msgid "Version Name"
msgstr ""
msgid "Size"
msgstr ""
msgid "Unknown"
msgstr ""
#, python-brace-format
msgid "{display_name} (installed)"
msgstr ""
msgid "No Wine/Proton versions installed"
msgstr ""
msgid "Select to remove this version"
msgstr ""
#, python-brace-format
msgid "Selected {} assets:\n"
msgstr ""
#, python-brace-format
msgid ""
"\n"
"Total size to delete: {}\n"
msgstr ""
msgid "Delete Selected"
msgstr ""
#, python-brace-format
msgid ""
"\n"
"Total size to download: {}\n"
msgstr ""
msgid "Downloading in Progress"
msgstr ""
msgid "Cannot clear selection while extraction is in progress."
msgstr ""
msgid "No Selection"
msgstr ""
msgid "Please select at least one archive to download."
msgstr ""
msgid "Please wait for current downloading to complete."
msgstr ""
msgid "Please select at least one version to delete."
msgstr ""
#, python-brace-format
msgid ""
"Are you sure you want to delete {} selected version(s)?\n"
"\n"
"This action cannot be undone."
msgstr ""
#, python-brace-format
msgid "Failed to remove version at {}: {}"
msgstr ""
#, python-brace-format
msgid "Successfully removed {} version(s)."
msgstr ""
msgid "Downloading Complete"
msgstr ""
@@ -716,16 +729,13 @@ msgstr ""
msgid "Load Prefix Backup"
msgstr ""
msgid "Delete Compatibility Tool"
msgstr ""
msgid "Delete Prefix"
msgstr ""
msgid "Clear Prefix"
msgstr ""
msgid "Download other WINE"
msgid "Manage WINE versions"
msgstr ""
msgid "Launching tool..."
@@ -905,9 +915,6 @@ msgstr ""
msgid "No link"
msgstr ""
msgid "Unknown"
msgstr ""
msgid "Name:"
msgstr ""

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-01-04 00:12+0500\n"
"POT-Creation-Date: 2026-01-12 20:59+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: es_ES\n"
@@ -256,55 +256,6 @@ msgstr ""
msgid "Select All"
msgstr ""
msgid "Delete Wine"
msgstr ""
msgid "Selected WINE:"
msgstr ""
msgid "No WINE selected"
msgstr ""
msgid "Delete Selected"
msgstr ""
msgid "Clear All"
msgstr ""
#, python-brace-format
msgid "Selected {} WINE:\n"
msgstr ""
msgid "No Selection"
msgstr ""
msgid "Please select at least one WINE to delete."
msgstr ""
#, python-brace-format
msgid ""
"Are you sure you want to delete the following WINE versions?\n"
"\n"
"{}"
msgstr ""
#, python-brace-format
msgid "Failed to delete WINE '{}': {}"
msgstr ""
msgid "Some Deletions Failed"
msgstr ""
#, python-brace-format
msgid ""
"Some WINE versions could not be deleted:\n"
"\n"
"{}"
msgstr ""
msgid "Selected WINE versions deleted successfully."
msgstr ""
msgid "Back"
msgstr ""
@@ -381,6 +332,12 @@ msgstr ""
msgid "Search"
msgstr ""
msgid "Apply"
msgstr ""
msgid "Clear All"
msgstr ""
#, python-brace-format
msgid "Launching {0}"
msgstr ""
@@ -422,9 +379,6 @@ msgstr ""
msgid "Cover Preview:"
msgstr ""
msgid "Apply"
msgstr ""
msgid "Invalid image"
msgstr ""
@@ -545,7 +499,7 @@ msgstr ""
msgid "Pending"
msgstr ""
msgid "Get other Wine"
msgid "Manage Wine versions"
msgstr ""
msgid "Selected assets:"
@@ -560,25 +514,84 @@ msgstr ""
msgid "Download Selected"
msgstr ""
msgid "Asset Name"
msgid "Installed"
msgstr ""
#, python-brace-format
msgid "Error loading wine data: {error}"
msgstr ""
msgid "Version Name"
msgstr ""
msgid "Size"
msgstr ""
msgid "Unknown"
msgstr ""
#, python-brace-format
msgid "{display_name} (installed)"
msgstr ""
msgid "No Wine/Proton versions installed"
msgstr ""
msgid "Select to remove this version"
msgstr ""
#, python-brace-format
msgid "Selected {} assets:\n"
msgstr ""
#, python-brace-format
msgid ""
"\n"
"Total size to delete: {}\n"
msgstr ""
msgid "Delete Selected"
msgstr ""
#, python-brace-format
msgid ""
"\n"
"Total size to download: {}\n"
msgstr ""
msgid "Downloading in Progress"
msgstr ""
msgid "Cannot clear selection while extraction is in progress."
msgstr ""
msgid "No Selection"
msgstr ""
msgid "Please select at least one archive to download."
msgstr ""
msgid "Please wait for current downloading to complete."
msgstr ""
msgid "Please select at least one version to delete."
msgstr ""
#, python-brace-format
msgid ""
"Are you sure you want to delete {} selected version(s)?\n"
"\n"
"This action cannot be undone."
msgstr ""
#, python-brace-format
msgid "Failed to remove version at {}: {}"
msgstr ""
#, python-brace-format
msgid "Successfully removed {} version(s)."
msgstr ""
msgid "Downloading Complete"
msgstr ""
@@ -716,16 +729,13 @@ msgstr ""
msgid "Load Prefix Backup"
msgstr ""
msgid "Delete Compatibility Tool"
msgstr ""
msgid "Delete Prefix"
msgstr ""
msgid "Clear Prefix"
msgstr ""
msgid "Download other WINE"
msgid "Manage WINE versions"
msgstr ""
msgid "Launching tool..."
@@ -905,9 +915,6 @@ msgstr ""
msgid "No link"
msgstr ""
msgid "Unknown"
msgstr ""
msgid "Name:"
msgstr ""

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PortProtonQt 0.1.1\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-01-04 00:12+0500\n"
"POT-Creation-Date: 2026-01-12 20:59+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -254,55 +254,6 @@ msgstr ""
msgid "Select All"
msgstr ""
msgid "Delete Wine"
msgstr ""
msgid "Selected WINE:"
msgstr ""
msgid "No WINE selected"
msgstr ""
msgid "Delete Selected"
msgstr ""
msgid "Clear All"
msgstr ""
#, python-brace-format
msgid "Selected {} WINE:\n"
msgstr ""
msgid "No Selection"
msgstr ""
msgid "Please select at least one WINE to delete."
msgstr ""
#, python-brace-format
msgid ""
"Are you sure you want to delete the following WINE versions?\n"
"\n"
"{}"
msgstr ""
#, python-brace-format
msgid "Failed to delete WINE '{}': {}"
msgstr ""
msgid "Some Deletions Failed"
msgstr ""
#, python-brace-format
msgid ""
"Some WINE versions could not be deleted:\n"
"\n"
"{}"
msgstr ""
msgid "Selected WINE versions deleted successfully."
msgstr ""
msgid "Back"
msgstr ""
@@ -379,6 +330,12 @@ msgstr ""
msgid "Search"
msgstr ""
msgid "Apply"
msgstr ""
msgid "Clear All"
msgstr ""
#, python-brace-format
msgid "Launching {0}"
msgstr ""
@@ -420,9 +377,6 @@ msgstr ""
msgid "Cover Preview:"
msgstr ""
msgid "Apply"
msgstr ""
msgid "Invalid image"
msgstr ""
@@ -543,7 +497,7 @@ msgstr ""
msgid "Pending"
msgstr ""
msgid "Get other Wine"
msgid "Manage Wine versions"
msgstr ""
msgid "Selected assets:"
@@ -558,25 +512,84 @@ msgstr ""
msgid "Download Selected"
msgstr ""
msgid "Asset Name"
msgid "Installed"
msgstr ""
#, python-brace-format
msgid "Error loading wine data: {error}"
msgstr ""
msgid "Version Name"
msgstr ""
msgid "Size"
msgstr ""
msgid "Unknown"
msgstr ""
#, python-brace-format
msgid "{display_name} (installed)"
msgstr ""
msgid "No Wine/Proton versions installed"
msgstr ""
msgid "Select to remove this version"
msgstr ""
#, python-brace-format
msgid "Selected {} assets:\n"
msgstr ""
#, python-brace-format
msgid ""
"\n"
"Total size to delete: {}\n"
msgstr ""
msgid "Delete Selected"
msgstr ""
#, python-brace-format
msgid ""
"\n"
"Total size to download: {}\n"
msgstr ""
msgid "Downloading in Progress"
msgstr ""
msgid "Cannot clear selection while extraction is in progress."
msgstr ""
msgid "No Selection"
msgstr ""
msgid "Please select at least one archive to download."
msgstr ""
msgid "Please wait for current downloading to complete."
msgstr ""
msgid "Please select at least one version to delete."
msgstr ""
#, python-brace-format
msgid ""
"Are you sure you want to delete {} selected version(s)?\n"
"\n"
"This action cannot be undone."
msgstr ""
#, python-brace-format
msgid "Failed to remove version at {}: {}"
msgstr ""
#, python-brace-format
msgid "Successfully removed {} version(s)."
msgstr ""
msgid "Downloading Complete"
msgstr ""
@@ -714,16 +727,13 @@ msgstr ""
msgid "Load Prefix Backup"
msgstr ""
msgid "Delete Compatibility Tool"
msgstr ""
msgid "Delete Prefix"
msgstr ""
msgid "Clear Prefix"
msgstr ""
msgid "Download other WINE"
msgid "Manage WINE versions"
msgstr ""
msgid "Launching tool..."
@@ -903,9 +913,6 @@ msgstr ""
msgid "No link"
msgstr ""
msgid "Unknown"
msgstr ""
msgid "Name:"
msgstr ""

View File

@@ -9,8 +9,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-01-04 00:12+0500\n"
"PO-Revision-Date: 2026-01-03 20:32+0500\n"
"POT-Creation-Date: 2026-01-12 20:59+0500\n"
"PO-Revision-Date: 2026-01-12 20:59+0500\n"
"Last-Translator: \n"
"Language: ru_RU\n"
"Language-Team: ru_RU <LL@li.org>\n"
@@ -263,61 +263,6 @@ msgstr "Удалить"
msgid "Select All"
msgstr "Выбрать всё"
msgid "Delete Wine"
msgstr "Удалить WINE"
msgid "Selected WINE:"
msgstr "Выбранные WINE:"
msgid "No WINE selected"
msgstr "WINE не выбраны"
msgid "Delete Selected"
msgstr "Удалить выбранное"
msgid "Clear All"
msgstr "Очистить выбранное"
#, python-brace-format
msgid "Selected {} WINE:\n"
msgstr "Выбранно {} WINE:\n"
msgid "No Selection"
msgstr "Не выбрано"
msgid "Please select at least one WINE to delete."
msgstr "Пожалуйста выберите хотя бы один WINE для удаления."
#, python-brace-format
msgid ""
"Are you sure you want to delete the following WINE versions?\n"
"\n"
"{}"
msgstr ""
"Вы уверены, что хотите удалить следующие версии WINE?\n"
"\n"
"{}"
#, python-brace-format
msgid "Failed to delete WINE '{}': {}"
msgstr "Не удалось удалить WINE '{}': {}"
msgid "Some Deletions Failed"
msgstr "Некоторые удаления не удалось выполнить"
#, python-brace-format
msgid ""
"Some WINE versions could not be deleted:\n"
"\n"
"{}"
msgstr ""
"Некоторые версии WINE не удалось удалить:\n"
"\n"
"{}"
msgid "Selected WINE versions deleted successfully."
msgstr "Выбранные версии WINE успешно удалены."
msgid "Back"
msgstr "Назад"
@@ -394,6 +339,12 @@ msgstr "Сохранить"
msgid "Search"
msgstr "Поиск"
msgid "Apply"
msgstr "Применить"
msgid "Clear All"
msgstr "Очистить выбранное"
#, python-brace-format
msgid "Launching {0}"
msgstr "Запуск {0}"
@@ -435,9 +386,6 @@ msgstr "Введите локальный путь или URL обложки"
msgid "Cover Preview:"
msgstr "Предпросмотр обложки:"
msgid "Apply"
msgstr "Применить"
msgid "Invalid image"
msgstr "Недопустимое изображение"
@@ -558,8 +506,8 @@ msgstr "Бронза"
msgid "Pending"
msgstr "В ожидании"
msgid "Get other Wine"
msgstr "Загрузка WINE"
msgid "Manage Wine versions"
msgstr "Управление версиями WINE"
msgid "Selected assets:"
msgstr "Выбранные WINE:"
@@ -573,25 +521,91 @@ msgstr "Скачивание: "
msgid "Download Selected"
msgstr "Скачать выбранное"
msgid "Asset Name"
msgstr "Наименование WINE"
msgid "Installed"
msgstr "Установленные"
#, python-brace-format
msgid "Error loading wine data: {error}"
msgstr "Ошибка загрузки WINE: {error}"
msgid "Version Name"
msgstr "Версия WINE"
msgid "Size"
msgstr "Размер"
msgid "Unknown"
msgstr "Неизвестен"
#, python-brace-format
msgid "{display_name} (installed)"
msgstr "{display_name} (установлен)"
msgid "No Wine/Proton versions installed"
msgstr "Отсутствуют установленные WINE/Proton"
msgid "Select to remove this version"
msgstr "Выберите, чтобы удалить WINE"
#, python-brace-format
msgid "Selected {} assets:\n"
msgstr "Выбранно {} WINE:\n"
#, python-brace-format
msgid ""
"\n"
"Total size to delete: {}\n"
msgstr ""
"\n"
"Общий размер на удаление: {}\n"
msgid "Delete Selected"
msgstr "Удалить выбранное"
#, python-brace-format
msgid ""
"\n"
"Total size to download: {}\n"
msgstr ""
"\n"
"Общий размер на загрузку: {}\n"
msgid "Downloading in Progress"
msgstr "Скачивание"
msgid "Cannot clear selection while extraction is in progress."
msgstr "Невозможно очистить выделение во время распаковки."
msgid "No Selection"
msgstr "Не выбрано"
msgid "Please select at least one archive to download."
msgstr "Пожалуйста выберите хотя бы один WINE для скачивания."
msgid "Please wait for current downloading to complete."
msgstr "Пожалуйста подождите завершения скачивания."
msgid "Please select at least one version to delete."
msgstr "Пожалуйста выберите хотя бы один WINE для удаления."
#, python-brace-format
msgid ""
"Are you sure you want to delete {} selected version(s)?\n"
"\n"
"This action cannot be undone."
msgstr ""
"Вы уверены, что хотите удалить {} выбранные WINE?\n"
"\n"
"Это действие нельзя отменить."
#, python-brace-format
msgid "Failed to remove version at {}: {}"
msgstr "Не удалось удалить WINE '{}': {}"
#, python-brace-format
msgid "Successfully removed {} version(s)."
msgstr "Успешно удалено {} WINE."
msgid "Downloading Complete"
msgstr "Скачивание завершено"
@@ -729,17 +743,14 @@ msgstr "Создать резервную копию префикса"
msgid "Load Prefix Backup"
msgstr "Загрузить резервную копию префикса"
msgid "Delete Compatibility Tool"
msgstr "Удалить WINE"
msgid "Delete Prefix"
msgstr "Удалить Префикс"
msgid "Clear Prefix"
msgstr "Очистить Префикс"
msgid "Download other WINE"
msgstr "Скачать другие WINE"
msgid "Manage WINE versions"
msgstr "Управление версиями WINE"
msgid "Launching tool..."
msgstr "Запуск инструмента..."
@@ -920,9 +931,6 @@ msgstr "Применить тему"
msgid "No link"
msgstr "Нет ссылки"
msgid "Unknown"
msgstr "Неизвестен"
msgid "Name:"
msgstr "Название:"

View File

@@ -94,8 +94,13 @@ def parse_playtime_file(file_path):
if len(parts) < 3:
continue
exe_path = parts[0]
seconds = int(parts[2])
playtime_data[exe_path] = seconds
# Find playtime: first numeric value after exe_path
# Format: <exe_path> <hash> <playtime_seconds> <platform> ...
# Hash is 64 hex chars, playtime is digits only
for i in range(1, len(parts)):
if parts[i].isdigit():
playtime_data[exe_path] = int(parts[i])
break
return playtime_data
def format_playtime(seconds):

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "portprotonqt"
version = "0.1.9"
version = "0.1.10"
description = "A project to rewrite PortProton (PortWINE) using PySide"
readme = "README.md"
license = { text = "GPL-3.0" }

2
uv.lock generated
View File

@@ -412,7 +412,7 @@ wheels = [
[[package]]
name = "portprotonqt"
version = "0.1.9"
version = "0.1.10"
source = { editable = "." }
dependencies = [
{ name = "babel" },