13 Commits

Author SHA1 Message Date
Renovate Bot
9534c882ff chore(deps): update https://gitea.com/actions/setup-python digest to a309ff8 2026-01-25 00:01:17 +00:00
8f9e6ea958 chore(build): switch from pyproject to meson build system
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2026-01-19 06:19:04 +00:00
Renovate Bot
19aecd75c4 chore(deps): update archlinux:base-devel docker digest to 84c36fa 2026-01-18 05:56:49 +00:00
Renovate Bot
905ebe1d61 chore(deps): update https://gitea.com/actions/setup-node digest to 6044e13 2026-01-18 00:01:59 +00:00
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
39 changed files with 1989 additions and 184 deletions

View File

@@ -2,8 +2,14 @@ name: Nightly Build - AppImage, Arch, Fedora
on: on:
workflow_dispatch: workflow_dispatch:
inputs:
branch:
description: 'Branch to build'
required: false
default: 'main'
env: env:
BUILD_BRANCH: ${{ inputs.branch || 'main' }}
PKGDEST: "/tmp/portprotonqt" PKGDEST: "/tmp/portprotonqt"
PACKAGE: "portprotonqt" PACKAGE: "portprotonqt"
@@ -12,7 +18,7 @@ jobs:
name: Build AppImage name: Build AppImage
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
container: container:
image: archlinux:base-devel@sha256:ebcaeca69c4d416f848aedcd27fe224384fd506f86046526a5d49ec6d9e29db1 image: archlinux:base-devel@sha256:84c36fa73fc692775e6b99de0d6a10967005b459ef170fc4faea426673b5e7b6
options: --privileged --device /dev/fuse options: --privileged --device /dev/fuse
steps: steps:
- name: Prepare container - name: Prepare container
@@ -22,12 +28,14 @@ jobs:
pacman -Syu --noconfirm --disable-download-timeout --needed git wget gnupg nodejs npm xorg-server-xvfb zsync pacman -Syu --noconfirm --disable-download-timeout --needed git wget gnupg nodejs npm xorg-server-xvfb zsync
- uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
ref: ${{ env.BUILD_BRANCH }}
- name: Install appimage dependencies - name: Install appimage dependencies
run: | run: |
cd build-aux/AppImage cd build-aux/AppImage
chmod +x get-dependencies.sh portprotonqt-appimage.sh chmod +x get-dependencies.sh portprotonqt-appimage.sh
./get-dependencies.sh --git ./get-dependencies.sh --local --branch "${{ env.BUILD_BRANCH }}"
- name: Build AppImage - name: Build AppImage
run: | run: |
@@ -55,9 +63,8 @@ jobs:
steps: steps:
- name: Install build dependencies - name: Install build dependencies
run: | run: |
dnf install -y git rpmdevtools python3-devel python3-wheel python3-pip \ dnf install -y git rpmdevtools meson ninja-build python3-devel \
python3-build pyproject-rpm-macros systemd-rpm-macros python3-setuptools \ systemd-rpm-macros redhat-rpm-config nodejs npm
redhat-rpm-config nodejs npm
- name: Setup rpmbuild environment - name: Setup rpmbuild environment
run: | run: |
@@ -68,10 +75,13 @@ jobs:
- name: Checkout repo - name: Checkout repo
uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
ref: ${{ env.BUILD_BRANCH }}
- name: Copy fedora.spec - name: Copy fedora.spec
run: | run: |
cp build-aux/fedora-git.spec /home/rpmbuild/SPECS/${{ env.PACKAGE }}.spec sed "s|git clone https://git.linux-gaming.ru/Boria138/PortProtonQt.git|git clone -b ${{ env.BUILD_BRANCH }} https://git.linux-gaming.ru/Boria138/PortProtonQt.git|" \
build-aux/fedora-git.spec > /home/rpmbuild/SPECS/${{ env.PACKAGE }}.spec
chown -R rpmbuild:users /home/rpmbuild chown -R rpmbuild:users /home/rpmbuild
- name: Build RPM - name: Build RPM
@@ -88,7 +98,7 @@ jobs:
name: Build Arch Package name: Build Arch Package
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
container: container:
image: archlinux:base-devel@sha256:ebcaeca69c4d416f848aedcd27fe224384fd506f86046526a5d49ec6d9e29db1 image: archlinux:base-devel@sha256:84c36fa73fc692775e6b99de0d6a10967005b459ef170fc4faea426673b5e7b6
steps: steps:
- name: Prepare container - name: Prepare container
@@ -115,17 +125,20 @@ jobs:
chown user -R /tmp chown user -R /tmp
chown user -R .. chown user -R ..
- name: Checkout
uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
with:
ref: ${{ env.BUILD_BRANCH }}
- name: Build - name: Build
run: | run: |
cd /__w/portproton-repo cd /__w/portproton-repo
git clone https://git.linux-gaming.ru/Boria138/PortProtonQt.git cp -r "$GITHUB_WORKSPACE" PortProtonQt
cd /__w/portproton-repo/PortProtonQt/build-aux cd /__w/portproton-repo/PortProtonQt/build-aux
sed -i "s|source=(\"git+https://git.linux-gaming.ru/Boria138/PortProtonQt.git\")|source=(\"git+https://git.linux-gaming.ru/Boria138/PortProtonQt.git#branch=${{ env.BUILD_BRANCH }}\")|" PKGBUILD-git
chown user -R .. chown user -R ..
su user -c "yes '' | makepkg --noconfirm -s -p PKGBUILD-git" su user -c "yes '' | makepkg --noconfirm -s -p PKGBUILD-git"
- name: Checkout
uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Upload Arch package - name: Upload Arch package
uses: https://gitea.com/actions/gitea-upload-artifact@v4 uses: https://gitea.com/actions/gitea-upload-artifact@v4
with: with:

View File

@@ -8,7 +8,7 @@ on:
env: env:
# Common version, will be used for tagging the release # Common version, will be used for tagging the release
VERSION: 0.1.9 VERSION: 0.1.10
PKGDEST: "/tmp/portprotonqt" PKGDEST: "/tmp/portprotonqt"
PACKAGE: "portprotonqt" PACKAGE: "portprotonqt"
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
@@ -77,17 +77,17 @@ jobs:
chown user -R /tmp chown user -R /tmp
chown user -R .. chown user -R ..
- name: Checkout
uses: https://gitea.com/actions/checkout@v4
- name: Build - name: Build
run: | run: |
cd /__w/portproton-repo cd /__w/portproton-repo
git clone https://git.linux-gaming.ru/Boria138/PortProtonQt.git cp -r "$GITHUB_WORKSPACE" PortProtonQt
cd /__w/portproton-repo/PortProtonQt/build-aux cd /__w/portproton-repo/PortProtonQt/build-aux
chown user -R .. chown user -R ..
su user -c "yes '' | makepkg --noconfirm -s" su user -c "yes '' | makepkg --noconfirm -s"
- name: Checkout
uses: https://gitea.com/actions/checkout@v4
- name: Upload Arch package - name: Upload Arch package
uses: https://gitea.com/actions/gitea-upload-artifact@v4 uses: https://gitea.com/actions/gitea-upload-artifact@v4
with: with:
@@ -109,9 +109,8 @@ jobs:
steps: steps:
- name: Install build dependencies - name: Install build dependencies
run: | run: |
dnf install -y git rpmdevtools python3-devel python3-wheel python3-pip \ dnf install -y git rpmdevtools meson ninja-build python3-devel \
python3-build pyproject-rpm-macros systemd-rpm-macros python3-setuptools \ systemd-rpm-macros redhat-rpm-config nodejs npm
redhat-rpm-config nodejs npm
- name: Setup rpmbuild environment - name: Setup rpmbuild environment
run: | run: |

View File

@@ -1,4 +1,4 @@
name: Check Translations (disabled until yaspeller is fixed) name: Check Translations
run-name: Check spelling in translation files run-name: Check spelling in translation files
on: on:
push: push:
@@ -18,7 +18,7 @@ jobs:
uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Python - name: Set up Python
uses: https://gitea.com/actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6 uses: https://gitea.com/actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with: with:
python-version-file: "pyproject.toml" python-version-file: "pyproject.toml"

View File

@@ -9,6 +9,8 @@ on:
env: env:
PKGDEST: "/tmp/portprotonqt" PKGDEST: "/tmp/portprotonqt"
PACKAGE: "portprotonqt" PACKAGE: "portprotonqt"
PR_REPO_URL: ${{ github.event.pull_request.head.repo.clone_url }}
PR_BRANCH: ${{ github.head_ref }}
jobs: jobs:
changes: changes:
@@ -63,7 +65,7 @@ jobs:
needs: changes needs: changes
if: needs.changes.outputs.appimage == 'true' || github.event_name == 'workflow_dispatch' if: needs.changes.outputs.appimage == 'true' || github.event_name == 'workflow_dispatch'
container: container:
image: archlinux:base-devel@sha256:ebcaeca69c4d416f848aedcd27fe224384fd506f86046526a5d49ec6d9e29db1 image: archlinux:base-devel@sha256:84c36fa73fc692775e6b99de0d6a10967005b459ef170fc4faea426673b5e7b6
options: --privileged --device /dev/fuse options: --privileged --device /dev/fuse
steps: steps:
- name: Prepare container - name: Prepare container
@@ -78,7 +80,7 @@ jobs:
run: | run: |
cd build-aux/AppImage cd build-aux/AppImage
chmod +x get-dependencies.sh portprotonqt-appimage.sh chmod +x get-dependencies.sh portprotonqt-appimage.sh
./get-dependencies.sh ./get-dependencies.sh --local --branch "${{ env.PR_BRANCH }}" --repo "${{ env.PR_REPO_URL }}"
- name: Build AppImage - name: Build AppImage
run: | run: |
@@ -108,9 +110,8 @@ jobs:
steps: steps:
- name: Install build dependencies - name: Install build dependencies
run: | run: |
dnf install -y git rpmdevtools python3-devel python3-wheel python3-pip \ dnf install -y git rpmdevtools meson ninja-build python3-devel \
python3-build pyproject-rpm-macros python3-setuptools \ systemd-rpm-macros redhat-rpm-config nodejs npm
redhat-rpm-config nodejs npm
- name: Setup rpmbuild environment - name: Setup rpmbuild environment
run: | run: |
@@ -124,7 +125,8 @@ jobs:
- name: Copy fedora-git.spec - name: Copy fedora-git.spec
run: | run: |
cp build-aux/fedora-git.spec /home/rpmbuild/SPECS/${{ env.PACKAGE }}.spec sed "s|git clone https://git.linux-gaming.ru/Boria138/PortProtonQt.git|git clone -b ${{ env.PR_BRANCH }} ${{ env.PR_REPO_URL }}|" \
build-aux/fedora-git.spec > /home/rpmbuild/SPECS/${{ env.PACKAGE }}.spec
chown -R rpmbuild:users /home/rpmbuild chown -R rpmbuild:users /home/rpmbuild
- name: Build RPM - name: Build RPM
@@ -143,7 +145,7 @@ jobs:
needs: changes needs: changes
if: needs.changes.outputs.arch == 'true' || github.event_name == 'workflow_dispatch' if: needs.changes.outputs.arch == 'true' || github.event_name == 'workflow_dispatch'
container: container:
image: archlinux:base-devel@sha256:ebcaeca69c4d416f848aedcd27fe224384fd506f86046526a5d49ec6d9e29db1 image: archlinux:base-devel@sha256:84c36fa73fc692775e6b99de0d6a10967005b459ef170fc4faea426673b5e7b6
steps: steps:
- name: Prepare container - name: Prepare container
@@ -170,17 +172,18 @@ jobs:
chown user -R /tmp chown user -R /tmp
chown user -R .. chown user -R ..
- name: Checkout
uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Build - name: Build
run: | run: |
cd /__w/portproton-repo cd /__w/portproton-repo
git clone https://git.linux-gaming.ru/Boria138/PortProtonQt.git git clone -b ${{ env.PR_BRANCH }} ${{ env.PR_REPO_URL }} PortProtonQt
cd /__w/portproton-repo/PortProtonQt/build-aux cd /__w/portproton-repo/PortProtonQt/build-aux
sed -i "s|source=(\"git+https://git.linux-gaming.ru/Boria138/PortProtonQt.git\")|source=(\"git+${{ env.PR_REPO_URL }}#branch=${{ env.PR_BRANCH }}\")|" PKGBUILD-git
chown user -R .. chown user -R ..
su user -c "yes '' | makepkg --noconfirm -s -p PKGBUILD-git" su user -c "yes '' | makepkg --noconfirm -s -p PKGBUILD-git"
- name: Checkout
uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Upload Arch package - name: Upload Arch package
uses: https://gitea.com/actions/gitea-upload-artifact@v4 uses: https://gitea.com/actions/gitea-upload-artifact@v4
with: with:

View File

@@ -23,7 +23,7 @@ jobs:
- uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Node.js - name: Set up Node.js
uses: https://gitea.com/actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 uses: https://gitea.com/actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with: with:
node-version: 20 node-version: 20

View File

@@ -14,7 +14,7 @@ jobs:
uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Python - name: Set up Python
uses: https://gitea.com/actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6 uses: https://gitea.com/actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with: with:
python-version-file: "pyproject.toml" python-version-file: "pyproject.toml"

View File

@@ -13,7 +13,7 @@ jobs:
- uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 - uses: https://gitea.com/actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
- name: Set up Node.js - name: Set up Node.js
uses: https://gitea.com/actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 uses: https://gitea.com/actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
with: with:
node-version: 20 node-version: 20

View File

@@ -3,6 +3,35 @@
Все заметные изменения в этом проекте фиксируются в этом файле. Все заметные изменения в этом проекте фиксируются в этом файле.
Формат основан на [Keep a Changelog](https://keepachangelog.com/) и придерживается принципов [Semantic Versioning](https://semver.org/). Формат основан на [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 ## [0.1.9] - 2025-12-08
### Added ### Added

View File

@@ -2,22 +2,52 @@
set -eu set -eu
# Determine if git mode is enabled based on the first argument # Initialize variables
if [ "${1:-}" = "--git" ] || [ "${1:-}" = "-g" ]; then LOCAL_MODE=false
GIT_MODE=true BRANCH="main"
else REPO_URL="https://git.linux-gaming.ru/Boria138/PortProtonQt.git"
GIT_MODE=false
fi # Parse arguments
while [ $# -gt 0 ]; do
case "$1" in
--local|-l)
LOCAL_MODE=true
;;
--branch)
if [ -n "${2:-}" ] && [ "${2#-}" = "$2" ]; then
BRANCH="$2"
shift
else
echo "Error: --branch requires an argument"
exit 1
fi
;;
--repo)
if [ -n "${2:-}" ] && [ "${2#-}" = "$2" ]; then
REPO_URL="$2"
shift
else
echo "Error: --repo requires an argument"
exit 1
fi
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
shift
done
ARCH="$(uname -m)" ARCH="$(uname -m)"
PACKAGE_BUILDER="https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/refs/heads/main/useful-tools/make-aur-package.sh" PACKAGE_BUILDER="https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/refs/heads/main/useful-tools/make-aur-package.sh"
EXTRA_PACKAGES="https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/refs/heads/main/useful-tools/get-debloated-pkgs.sh" EXTRA_PACKAGES="https://raw.githubusercontent.com/pkgforge-dev/Anylinux-AppImages/refs/heads/main/useful-tools/get-debloated-pkgs.sh"
if [ "$GIT_MODE" = true ]; then if [ "$LOCAL_MODE" = true ]; then
echo "Using git version of PortProtonQt..." echo "Using local PKGBUILD-git from repository..."
PPQT_PKGBUILD="https://git.linux-gaming.ru/Boria138/PortProtonQt/raw/branch/main/build-aux/PKGBUILD-git" PPQT_PKGBUILD=""
else else
echo "Using stable version of PortProtonQt..." echo "Using stable version of PortProtonQt from main branch..."
PPQT_PKGBUILD="https://git.linux-gaming.ru/Boria138/PortProtonQt/raw/branch/main/build-aux/PKGBUILD" PPQT_PKGBUILD="https://git.linux-gaming.ru/Boria138/PortProtonQt/raw/branch/main/build-aux/PKGBUILD"
fi fi
@@ -37,7 +67,12 @@ chmod +x ./make-aur-package.sh
echo "Building PortProtonQt from PKGBUILD..." echo "Building PortProtonQt from PKGBUILD..."
echo "---------------------------------------------------------------" echo "---------------------------------------------------------------"
wget --retry-connrefused --tries=30 "$PPQT_PKGBUILD" -O ./PKGBUILD if [ "$LOCAL_MODE" = true ]; then
cp ../PKGBUILD-git ./PKGBUILD
else
wget --retry-connrefused --tries=30 "$PPQT_PKGBUILD" -O ./PKGBUILD
fi
sed -i "s|source=(\"git+https://git.linux-gaming.ru/Boria138/PortProtonQt.git\")|source=(\"git+${REPO_URL}#branch=$BRANCH\")|" PKGBUILD
makepkg -si --noconfirm makepkg -si --noconfirm
echo "Installing debloated packages..." echo "Installing debloated packages..."
@@ -46,7 +81,7 @@ wget --retry-connrefused --tries=30 "$EXTRA_PACKAGES" -O ./get-debloated-pkgs.sh
chmod +x ./get-debloated-pkgs.sh chmod +x ./get-debloated-pkgs.sh
./get-debloated-pkgs.sh --add-common --prefer-nano ./get-debloated-pkgs.sh --add-common --prefer-nano
if [ "$GIT_MODE" = true ]; then if [ "$LOCAL_MODE" = true ]; then
# For git version, we use portprotonqt-git # For git version, we use portprotonqt-git
pacman -Q portprotonqt-git | awk '{print $2}' | cut -d- -f1 > ~/version pacman -Q portprotonqt-git | awk '{print $2}' | cut -d- -f1 > ~/version
else else

View File

@@ -1,24 +1,21 @@
pkgname=portprotonqt pkgname=portprotonqt
pkgver=0.1.9 pkgver=0.1.10
pkgrel=1 pkgrel=1
pkgdesc="Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store" pkgdesc="Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store"
arch=('any') arch=('any')
url="https://git.linux-gaming.ru/Boria138/PortProtonQt" url="https://git.linux-gaming.ru/Boria138/PortProtonQt"
license=('GPL-3.0') license=('GPL-3.0')
depends=('python-requests' 'python-babel' 'python-evdev' 'python-pyudev' 'python-orjson' 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'}) makedepends=('meson' 'ninja')
source=("git+https://git.linux-gaming.ru/Boria138/PortProtonQt#tag=v$pkgver") source=("git+https://git.linux-gaming.ru/Boria138/PortProtonQt#tag=v$pkgver")
sha256sums=('SKIP') sha256sums=('SKIP')
build() { build() {
cd "$srcdir/PortProtonQt" arch-meson PortProtonQt build
python -m build --wheel --no-isolation meson compile -C build
} }
package() { package() {
cd "$srcdir/PortProtonQt" meson install -C build --destdir "$pkgdir"
python -m installer --destdir="$pkgdir" dist/*.whl
cp -r build-aux/share "$pkgdir/usr/"
cp -r build-aux/lib "$pkgdir/usr/"
} }

View File

@@ -6,8 +6,8 @@ arch=('any')
url="https://git.linux-gaming.ru/Boria138/PortProtonQt" url="https://git.linux-gaming.ru/Boria138/PortProtonQt"
license=('GPL-3.0') license=('GPL-3.0')
depends=('python-requests' 'python-babel' 'python-evdev' 'python-pyudev' 'python-orjson' 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'}) makedepends=('meson' 'ninja')
source=("git+https://git.linux-gaming.ru/Boria138/PortProtonQt.git") source=("git+https://git.linux-gaming.ru/Boria138/PortProtonQt.git")
sha256sums=('SKIP') sha256sums=('SKIP')
@@ -17,13 +17,10 @@ pkgver() {
} }
build() { build() {
cd "$srcdir/PortProtonQt" arch-meson PortProtonQt build
python -m build --wheel --no-isolation meson compile -C build
} }
package() { package() {
cd "$srcdir/PortProtonQt" meson install -C build --destdir "$pkgdir"
python -m installer --destdir="$pkgdir" dist/*.whl
cp -r build-aux/share "$pkgdir/usr/"
cp -r build-aux/lib "$pkgdir/usr/"
} }

View File

@@ -1,12 +1,12 @@
%global pypi_name portprotonqt %global pypi_name portprotonqt
%global pypi_version 0.1.1 %global pypi_version 0.1.10
%global oname PortProtonQt %global oname PortProtonQt
%global build_timestamp %(date +"%Y%m%d") %global build_timestamp %(date +"%Y%m%d")
%global _python_no_extras_requires 1 %global _python_no_extras_requires 1
%global rel_build 1.git.%{build_timestamp}%{?dist} %global rel_build 1.git.%{build_timestamp}%{?dist}
Name: python-%{pypi_name}-git Name: %{pypi_name}-git
Version: %{pypi_version} Version: %{pypi_version}
Release: %{rel_build} Release: %{rel_build}
Summary: Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store (development build) Summary: Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store (development build)
@@ -15,21 +15,15 @@ License: GPL-3.0
URL: https://git.linux-gaming.ru/Boria138/PortProtonQt URL: https://git.linux-gaming.ru/Boria138/PortProtonQt
BuildArch: noarch BuildArch: noarch
BuildRequires: meson >= 0.61.2
BuildRequires: ninja-build
BuildRequires: python3-devel BuildRequires: python3-devel
BuildRequires: python3-wheel
BuildRequires: python3-pip
BuildRequires: python3-build
BuildRequires: pyproject-rpm-macros
BuildRequires: python3dist(setuptools)
BuildRequires: git BuildRequires: git
BuildRequires: systemd-rpm-macros BuildRequires: systemd-rpm-macros
%description Obsoletes: python3-%{pypi_name}-git < %{version}-%{release}
%{summary} Provides: python3-%{pypi_name}-git = %{version}-%{release}
%package -n python3-%{pypi_name}-git
Summary: %{summary}
%{?python_provide:%python_provide python3-%{pypi_name}}
Requires: python3-babel Requires: python3-babel
Requires: python3-evdev Requires: python3-evdev
Requires: python3-icoextract Requires: python3-icoextract
@@ -48,13 +42,14 @@ Requires: python3-rapidfuzz
Requires: python3-libarchive-c Requires: python3-libarchive-c
Requires: perl-Image-ExifTool Requires: perl-Image-ExifTool
Requires: xdg-utils Requires: xdg-utils
Requires: qt6-qtsvg
Requires: cabextract Requires: cabextract
Requires: gzip Requires: gzip
Requires: unzip Requires: unzip
Requires: curl Requires: curl
Requires: unrar Requires: unrar
%description -n python3-%{pypi_name}-git %description
This application provides a sleek, intuitive graphical interface for managing and launching games from PortProton, Steam, and Epic Games Store. It consolidates your game libraries into a single, user-friendly hub for seamless navigation and organization. Its lightweight structure and cross-platform support deliver a cohesive gaming experience, eliminating the need for multiple launchers. Unique PortProton integration enhances Linux gaming, enabling effortless play of Windows-based titles with minimal setup. This application provides a sleek, intuitive graphical interface for managing and launching games from PortProton, Steam, and Epic Games Store. It consolidates your game libraries into a single, user-friendly hub for seamless navigation and organization. Its lightweight structure and cross-platform support deliver a cohesive gaming experience, eliminating the need for multiple launchers. Unique PortProton integration enhances Linux gaming, enabling effortless play of Windows-based titles with minimal setup.
%{?python_disable_dependency_generator} %{?python_disable_dependency_generator}
@@ -64,17 +59,17 @@ git clone https://git.linux-gaming.ru/Boria138/PortProtonQt.git
%build %build
cd %{oname} cd %{oname}
%pyproject_wheel %meson -Dpython_libdir=%{python3_sitelib} -Dudevdir=%{_udevrulesdir}
%meson_build
%install %install
cd %{oname} cd %{oname}
%pyproject_install %meson_install
%pyproject_save_files %{pypi_name} %find_lang %{pypi_name}
cp -r build-aux/share %{buildroot}/usr/
cp -r build-aux/lib %{buildroot}/usr/
%files -n python3-%{pypi_name}-git -f %{pyproject_files} %files -f %{oname}/%{pypi_name}.lang
%{_bindir}/%{pypi_name} %{_bindir}/%{pypi_name}
%{python3_sitelib}/%{pypi_name}/
%{_datadir}/icons/hicolor/scalable/apps/ru.linux_gaming.PortProtonQt.svg %{_datadir}/icons/hicolor/scalable/apps/ru.linux_gaming.PortProtonQt.svg
%{_metainfodir}/ru.linux_gaming.PortProtonQt.metainfo.xml %{_metainfodir}/ru.linux_gaming.PortProtonQt.metainfo.xml
%{_udevrulesdir}/60-portprotonqt.rules %{_udevrulesdir}/60-portprotonqt.rules

View File

@@ -1,9 +1,9 @@
%global pypi_name portprotonqt %global pypi_name portprotonqt
%global pypi_version 0.1.9 %global pypi_version 0.1.10
%global oname PortProtonQt %global oname PortProtonQt
%global _python_no_extras_requires 1 %global _python_no_extras_requires 1
Name: python-%{pypi_name} Name: %{pypi_name}
Version: %{pypi_version} Version: %{pypi_version}
Release: 1%{?dist} Release: 1%{?dist}
Summary: Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store Summary: Modern GUI for managing and launching games from PortProton, Steam, and Epic Games Store
@@ -12,21 +12,15 @@ License: GPL-3.0
URL: https://git.linux-gaming.ru/Boria138/PortProtonQt URL: https://git.linux-gaming.ru/Boria138/PortProtonQt
BuildArch: noarch BuildArch: noarch
BuildRequires: meson >= 0.61.2
BuildRequires: ninja-build
BuildRequires: python3-devel BuildRequires: python3-devel
BuildRequires: python3-wheel
BuildRequires: python3-pip
BuildRequires: python3-build
BuildRequires: pyproject-rpm-macros
BuildRequires: python3dist(setuptools)
BuildRequires: git BuildRequires: git
BuildRequires: systemd-rpm-macros BuildRequires: systemd-rpm-macros
%description Obsoletes: python3-%{pypi_name} < %{version}-%{release}
%{summary} Provides: python3-%{pypi_name} = %{version}-%{release}
%package -n python3-%{pypi_name}
Summary: %{summary}
%{?python_provide:%python_provide python3-%{pypi_name}}
Requires: python3-babel Requires: python3-babel
Requires: python3-evdev Requires: python3-evdev
Requires: python3-icoextract Requires: python3-icoextract
@@ -45,13 +39,14 @@ Requires: python3-rapidfuzz
Requires: python3-libarchive-c Requires: python3-libarchive-c
Requires: perl-Image-ExifTool Requires: perl-Image-ExifTool
Requires: xdg-utils Requires: xdg-utils
Requires: qt6-qtsvg
Requires: cabextract Requires: cabextract
Requires: gzip Requires: gzip
Requires: unzip Requires: unzip
Requires: curl Requires: curl
Requires: unrar Requires: unrar
%description -n python3-%{pypi_name} %description
This application provides a sleek, intuitive graphical interface for managing and launching games from PortProton, Steam, and Epic Games Store. It consolidates your game libraries into a single, user-friendly hub for seamless navigation and organization. Its lightweight structure and cross-platform support deliver a cohesive gaming experience, eliminating the need for multiple launchers. Unique PortProton integration enhances Linux gaming, enabling effortless play of Windows-based titles with minimal setup. This application provides a sleek, intuitive graphical interface for managing and launching games from PortProton, Steam, and Epic Games Store. It consolidates your game libraries into a single, user-friendly hub for seamless navigation and organization. Its lightweight structure and cross-platform support deliver a cohesive gaming experience, eliminating the need for multiple launchers. Unique PortProton integration enhances Linux gaming, enabling effortless play of Windows-based titles with minimal setup.
%{?python_disable_dependency_generator} %{?python_disable_dependency_generator}
@@ -63,17 +58,17 @@ git checkout v%{pypi_version}
%build %build
cd %{oname} cd %{oname}
%pyproject_wheel %meson -Dpython_libdir=%{python3_sitelib} -Dudevdir=%{_udevrulesdir}
%meson_build
%install %install
cd %{oname} cd %{oname}
%pyproject_install %meson_install
%pyproject_save_files %{pypi_name} %find_lang %{pypi_name}
cp -r build-aux/share %{buildroot}/usr/
cp -r build-aux/lib %{buildroot}/usr/
%files -n python3-%{pypi_name} -f %{pyproject_files} %files -f %{oname}/%{pypi_name}.lang
%{_bindir}/%{pypi_name} %{_bindir}/%{pypi_name}
%{python3_sitelib}/%{pypi_name}/
%{_datadir}/icons/hicolor/scalable/apps/ru.linux_gaming.PortProtonQt.svg %{_datadir}/icons/hicolor/scalable/apps/ru.linux_gaming.PortProtonQt.svg
%{_metainfodir}/ru.linux_gaming.PortProtonQt.metainfo.xml %{_metainfodir}/ru.linux_gaming.PortProtonQt.metainfo.xml
%{_udevrulesdir}/60-portprotonqt.rules %{_udevrulesdir}/60-portprotonqt.rules

9
build-aux/portprotonqt Normal file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env python3
import sys
from portprotonqt.app import main
if __name__ == "__main__":
sys.exit(main())

View File

@@ -12,7 +12,9 @@ BASE_DIR = Path(__file__).parent.parent
APPIMAGE_RECIPE = BASE_DIR / "build-aux" / "AppImageBuilder.yml" APPIMAGE_RECIPE = BASE_DIR / "build-aux" / "AppImageBuilder.yml"
ARCH_PKGBUILD = BASE_DIR / "build-aux" / "PKGBUILD" ARCH_PKGBUILD = BASE_DIR / "build-aux" / "PKGBUILD"
FEDORA_SPEC = BASE_DIR / "build-aux" / "fedora.spec" FEDORA_SPEC = BASE_DIR / "build-aux" / "fedora.spec"
FEDORA_GIT_SPEC = BASE_DIR / "build-aux" / "fedora-git.spec"
PYPROJECT = BASE_DIR / "pyproject.toml" PYPROJECT = BASE_DIR / "pyproject.toml"
MESON_BUILD = BASE_DIR / "meson.build"
APP_PY = BASE_DIR / "portprotonqt" / "app.py" APP_PY = BASE_DIR / "portprotonqt" / "app.py"
GITEA_WORKFLOW = BASE_DIR / ".gitea" / "workflows" / "build.yml" GITEA_WORKFLOW = BASE_DIR / ".gitea" / "workflows" / "build.yml"
CHANGELOG = BASE_DIR / "CHANGELOG.md" CHANGELOG = BASE_DIR / "CHANGELOG.md"
@@ -56,6 +58,7 @@ def bump_fedora(path: Path, old: str, new: str) -> bool:
path.write_text(new_text, encoding='utf-8') path.write_text(new_text, encoding='utf-8')
return bool(count) return bool(count)
def bump_pyproject(path: Path, old: str, new: str) -> bool: def bump_pyproject(path: Path, old: str, new: str) -> bool:
""" """
Update version in pyproject.toml under [project] Update version in pyproject.toml under [project]
@@ -69,6 +72,19 @@ def bump_pyproject(path: Path, old: str, new: str) -> bool:
path.write_text(new_text, encoding='utf-8') path.write_text(new_text, encoding='utf-8')
return bool(count) return bool(count)
def bump_meson(path: Path, old: str, new: str) -> bool:
"""
Update version in meson.build
"""
if not path.exists():
return False
text = path.read_text(encoding='utf-8')
pattern = re.compile(r"(version:\s*)'" + re.escape(old) + r"'")
new_text, count = pattern.subn(lambda m: m.group(1) + f"'{new}'", text)
if count:
path.write_text(new_text, encoding='utf-8')
return bool(count)
def bump_app_py(path: Path, old: str, new: str) -> bool: def bump_app_py(path: Path, old: str, new: str) -> bool:
""" """
Update __app_version__ in app.py Update __app_version__ in app.py
@@ -120,7 +136,9 @@ def main():
(APPIMAGE_RECIPE, bump_appimage), (APPIMAGE_RECIPE, bump_appimage),
(ARCH_PKGBUILD, bump_arch), (ARCH_PKGBUILD, bump_arch),
(FEDORA_SPEC, bump_fedora), (FEDORA_SPEC, bump_fedora),
(FEDORA_GIT_SPEC, bump_fedora),
(PYPROJECT, bump_pyproject), (PYPROJECT, bump_pyproject),
(MESON_BUILD, bump_meson),
(APP_PY, bump_app_py), (APP_PY, bump_app_py),
(GITEA_WORKFLOW, bump_workflow), (GITEA_WORKFLOW, bump_workflow),
(CHANGELOG, bump_changelog) (CHANGELOG, bump_changelog)

View File

@@ -17,8 +17,9 @@ README_EN = GUIDE_DIR / "README.md"
README_RU = GUIDE_DIR / "README.ru.md" README_RU = GUIDE_DIR / "README.ru.md"
LOCALES_PATH = Path(__file__).parent.parent / "portprotonqt" / "locales" LOCALES_PATH = Path(__file__).parent.parent / "portprotonqt" / "locales"
THEMES_PATH = Path(__file__).parent.parent / "portprotonqt" / "themes" THEMES_PATH = Path(__file__).parent.parent / "portprotonqt" / "themes"
MESON_BUILD = Path(__file__).parent.parent / "portprotonqt" / "meson.build"
README_FILES = [README_EN, README_RU] README_FILES = [README_EN, README_RU]
POT_FILE = LOCALES_PATH / "messages.pot" POT_FILE = LOCALES_PATH / "portprotonqt.pot"
# ---------- Версия проекта ---------- # ---------- Версия проекта ----------
def _get_version() -> str: def _get_version() -> str:
@@ -27,16 +28,16 @@ def _get_version() -> str:
# ---------- Обновление README ---------- # ---------- Обновление README ----------
def _update_coverage(lines: list[str]) -> None: def _update_coverage(lines: list[str]) -> None:
# Парсим статистику из вывода pybabel --statistics # Парсим статистику из вывода pybabel --statistics
locales_stats = [line for line in lines if line.endswith(".po")] locales_stats = [line for line in lines if "portprotonqt.po" in line]
# Извлекаем (count, pct, locale) и сортируем # Извлекаем (count, pct, locale) и сортируем
rows = sorted( rows = sorted(
(m := re.search( row for stat in locales_stats
if (m := re.search(
r"""(\d+\ of\ \d+).* # message counts r"""(\d+\ of\ \d+).* # message counts
\((\d+\%)\).* # message percentage \((\d+\%)\).* # message percentage
locales\/(.*)\/LC_MESSAGES # locale name""", locales\/([^/]+)\/LC_MESSAGES # locale name""",
stat, re.VERBOSE stat, re.VERBOSE
)) and m.groups() )) and (row := m.groups())
for stat in locales_stats
) )
for md_file in README_FILES: for md_file in README_FILES:
@@ -59,14 +60,14 @@ def _update_coverage(lines: list[str]) -> None:
"| Локаль | Прогресс | Переведено |\n" "| Локаль | Прогресс | Переведено |\n"
"| :----- | -------: | ---------: |\n" "| :----- | -------: | ---------: |\n"
) )
fmt = lambda count, pct, loc: f"| [{loc}](./{loc}/LC_MESSAGES/messages.po) | {pct} | {count.replace(' of ', ' из ')} |" fmt = lambda count, pct, loc: f"| [{loc}](./{loc}/LC_MESSAGES/portprotonqt.po) | {pct} | {count.replace(' of ', ' из ')} |"
else: else:
table_header = ( table_header = (
"<!-- Auto-generated coverage table -->\n\n" "<!-- Auto-generated coverage table -->\n\n"
"| Locale | Progress | Translated |\n" "| Locale | Progress | Translated |\n"
"| :----- | -------: | ---------: |\n" "| :----- | -------: | ---------: |\n"
) )
fmt = lambda count, pct, loc: f"| [{loc}](./{loc}/LC_MESSAGES/messages.po) | {pct} | {count} |" fmt = lambda count, pct, loc: f"| [{loc}](./{loc}/LC_MESSAGES/portprotonqt.po) | {pct} | {count} |"
# Собираем строки и добавляем '---' в конце # Собираем строки и добавляем '---' в конце
coverage_table = ( coverage_table = (
@@ -100,7 +101,7 @@ def _update_coverage(lines: list[str]) -> None:
def compile_locales() -> None: def compile_locales() -> None:
CommandLineInterface().run([ CommandLineInterface().run([
"pybabel", "compile", "--use-fuzzy", "--directory", "pybabel", "compile", "--use-fuzzy", "--directory",
f"{LOCALES_PATH.resolve()}", "--statistics" f"{LOCALES_PATH.resolve()}", "--domain=portprotonqt", "--statistics"
]) ])
def extract_strings() -> None: def extract_strings() -> None:
@@ -121,10 +122,39 @@ def update_locales() -> None:
"pybabel", "update", "pybabel", "update",
f"--input-file={POT_FILE.resolve()}", f"--input-file={POT_FILE.resolve()}",
f"--output-dir={LOCALES_PATH.resolve()}", f"--output-dir={LOCALES_PATH.resolve()}",
"--domain=portprotonqt",
"--ignore-obsolete", "--ignore-obsolete",
"--update-header-comment", "--update-header-comment",
]) ])
def _update_meson_locales(new_locales: list[str]) -> None:
"""Обновляет список языков в meson.build."""
if not MESON_BUILD.exists():
return
text = MESON_BUILD.read_text(encoding="utf-8")
# Ищем foreach lang : ['de', 'es', 'pt', 'ru']
pattern = r"(foreach\s+lang\s*:\s*\[)([^\]]+)(\])"
match = re.search(pattern, text)
if not match:
return
# Парсим текущий список языков
current_langs_str = match.group(2)
current_langs = re.findall(r"'([^']+)'", current_langs_str)
# Добавляем новые языки и сортируем
all_langs = sorted(set(current_langs) | set(new_locales))
# Формируем новый список
new_langs_str = ", ".join(f"'{lang}'" for lang in all_langs)
new_text = text[:match.start()] + match.group(1) + new_langs_str + match.group(3) + text[match.end():]
if new_text != text:
MESON_BUILD.write_text(new_text, encoding="utf-8")
print(f"Updated meson.build with locales: {all_langs}")
def create_new(locales: list[str]) -> None: def create_new(locales: list[str]) -> None:
if not POT_FILE.exists(): if not POT_FILE.exists():
extract_strings() extract_strings()
@@ -133,8 +163,11 @@ def create_new(locales: list[str]) -> None:
"pybabel", "init", "pybabel", "init",
f"--input-file={POT_FILE.resolve()}", f"--input-file={POT_FILE.resolve()}",
f"--output-dir={LOCALES_PATH.resolve()}", f"--output-dir={LOCALES_PATH.resolve()}",
"--domain=portprotonqt",
f"--locale={locale}" f"--locale={locale}"
]) ])
# Обновляем meson.build с новыми локалями
_update_meson_locales(locales)
# ---------- Игнорируемые префиксы для spellcheck ---------- # ---------- Игнорируемые префиксы для spellcheck ----------
IGNORED_PREFIXES = () IGNORED_PREFIXES = ()
@@ -148,6 +181,41 @@ def load_ignored_prefixes(ignore_file=".spellignore"):
IGNORED_PREFIXES = load_ignored_prefixes() + ("PortProton", "flatpak") IGNORED_PREFIXES = load_ignored_prefixes() + ("PortProton", "flatpak")
# ---------- Проверка fuzzy строк ----------
def find_fuzzy_entries(filepath: Path) -> list[tuple[int, str, str]]:
"""Находит fuzzy записи в .po файле. Возвращает список (номер_строки, msgid, флаги)."""
fuzzy_entries = []
lines = filepath.read_text(encoding='utf-8').splitlines()
i = 0
while i < len(lines):
line = lines[i].strip()
# Ищем комментарий с флагами, содержащий fuzzy
if line.startswith('#,') and 'fuzzy' in line:
flags = line[2:].strip()
line_num = i + 1
# Ищем следующий msgid
msgid = ""
i += 1
while i < len(lines):
next_line = lines[i].strip()
if next_line.startswith('msgid '):
match = re.match(r'^msgid\s+"(.*)"', next_line)
if match:
msgid = match.group(1)
i += 1
# Собираем многострочный msgid
while i < len(lines) and lines[i].strip().startswith('"'):
msgid += lines[i].strip()[1:-1]
i += 1
break
i += 1
# Пропускаем пустой msgid (заголовок PO файла)
if msgid:
fuzzy_entries.append((line_num, msgid, flags))
else:
i += 1
return fuzzy_entries
# ---------- Проверка орфографии с параллелизмом ---------- # ---------- Проверка орфографии с параллелизмом ----------
speller = YandexSpeller() speller = YandexSpeller()
MSGID_RE = re.compile(r'^msgid\s+"(.*)"') MSGID_RE = re.compile(r'^msgid\s+"(.*)"')
@@ -208,17 +276,36 @@ def main(args) -> int:
if args.create_new: if args.create_new:
create_new(args.create_new) create_new(args.create_new)
if args.spellcheck: if args.spellcheck:
files = list(LOCALES_PATH.glob("**/*.po")) + [POT_FILE] files = list(LOCALES_PATH.glob("**/portprotonqt.po")) + [POT_FILE]
seen = set(); has_err = False seen = set(); has_err = False
issues_summary = defaultdict(list) issues_summary = defaultdict(list)
fuzzy_summary = defaultdict(list)
for f in files: for f in files:
if not f.exists() or f in seen: continue if not f.exists() or f in seen: continue
seen.add(f) seen.add(f)
# Проверка fuzzy строк (только для .po файлов)
if f.suffix == '.po':
fuzzy_entries = find_fuzzy_entries(f)
if fuzzy_entries:
fuzzy_summary[f] = fuzzy_entries
has_err = True
if check_file(f, issues_summary): if check_file(f, issues_summary):
has_err = True has_err = True
else: else:
print(f"{f} — no errors found.") if f not in fuzzy_summary:
if has_err: print(f"{f} — no errors found.")
# Вывод fuzzy строк
if fuzzy_summary:
print("\n⚠️ Fuzzy Entries (require review before release):")
for file, entries in fuzzy_summary.items():
print(f"\n{file}")
print("-----")
for idx, (line_num, msgid, flags) in enumerate(entries, 1):
print(f"{idx}. Line {line_num}: [{flags}]")
print(f" msgid: \"{msgid[:80]}{'...' if len(msgid) > 80 else ''}\"")
print("-----")
# Вывод орфографических ошибок
if issues_summary:
print("\n📋 Summary of Spelling Errors:") print("\n📋 Summary of Spelling Errors:")
for file, errs in issues_summary.items(): for file, errs in issues_summary.items():
print(f"\n{file}") print(f"\n{file}")

View File

@@ -13,7 +13,7 @@
## 📖 Overview ## 📖 Overview
Localization in `PortProtonQT` is powered by `Babel` using `.po/.mo` files stored under `LC_MESSAGES/messages.po` for each language. Localization in `PortProtonQT` is powered by `Babel` using `.po/.mo` files stored under `LC_MESSAGES/portprotonqt.po` for each language.
Current translation status: Current translation status:
@@ -21,9 +21,10 @@ Current translation status:
| Locale | Progress | Translated | | Locale | Progress | Translated |
| :----- | -------: | ---------: | | :----- | -------: | ---------: |
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 374 | | [de](./de/LC_MESSAGES/portprotonqt.po) | 0% | 0 of 376 |
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 374 | | [es](./es/LC_MESSAGES/portprotonqt.po) | 0% | 0 of 376 |
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 374 of 374 | | [pt](./pt/LC_MESSAGES/portprotonqt.po) | 0% | 0 of 376 |
| [ru](./ru/LC_MESSAGES/portprotonqt.po) | 100% | 376 of 376 |
--- ---
@@ -39,7 +40,7 @@ source .venv/bin/activate
python dev-scripts/l10n.py --create-new <locale_code> python dev-scripts/l10n.py --create-new <locale_code>
``` ```
2. Edit the file `portprotonqt/locales/<locale>/LC_MESSAGES/messages.po` in Poedit or any text editor. 2. Edit the file `portprotonqt/locales/<locale>/LC_MESSAGES/portprotonqt.po` in Poedit or any text editor.
--- ---

View File

@@ -13,7 +13,7 @@
## 📖 Обзор ## 📖 Обзор
Локализация в `PortProtonQT` осуществляется через систему `.po/.mo` файлов и управляется утилитой `Babel`. Все переводы находятся в подкаталогах вида `LC_MESSAGES/messages.po` для каждой поддерживаемой локали. Локализация в `PortProtonQT` осуществляется через систему `.po/.mo` файлов и управляется утилитой `Babel`. Все переводы находятся в подкаталогах вида `LC_MESSAGES/portprotonqt.po` для каждой поддерживаемой локали.
Текущий статус перевода: Текущий статус перевода:
@@ -21,9 +21,10 @@
| Локаль | Прогресс | Переведено | | Локаль | Прогресс | Переведено |
| :----- | -------: | ---------: | | :----- | -------: | ---------: |
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 374 | | [de](./de/LC_MESSAGES/portprotonqt.po) | 0% | 0 из 376 |
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 374 | | [es](./es/LC_MESSAGES/portprotonqt.po) | 0% | 0 из 376 |
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 374 из 374 | | [pt](./pt/LC_MESSAGES/portprotonqt.po) | 0% | 0 из 376 |
| [ru](./ru/LC_MESSAGES/portprotonqt.po) | 100% | 376 из 376 |
--- ---
@@ -39,7 +40,7 @@ source .venv/bin/activate
python dev-scripts/l10n.py --create-new <код_локали> python dev-scripts/l10n.py --create-new <код_локали>
``` ```
2. Отредактируйте файл `portprotonqt/locales/<локаль>/LC_MESSAGES/messages.po` в Poedit или любом текстовом редакторе. 2. Отредактируйте файл `portprotonqt/locales/<локаль>/LC_MESSAGES/portprotonqt.po` в Poedit или любом текстовом редакторе.
--- ---

83
meson.build Normal file
View File

@@ -0,0 +1,83 @@
project('portprotonqt',
version: '0.1.10',
meson_version: '>= 0.61.2',
license: 'GPL-3.0',
)
# Project directories
prefix = get_option('prefix')
datadir = prefix / get_option('datadir')
bindir = prefix / get_option('bindir')
# Python module for site-packages path
python = import('python')
python3 = python.find_installation('python3')
pythondir = join_paths(prefix, python3.get_path('purelib'))
python_libdir = get_option('python_libdir')
if python_libdir == ''
python_installdir = pythondir
else
python_installdir = python_libdir
endif
pkgdatadir = python_installdir / meson.project_name()
conf = configuration_data()
conf.set('PYTHON', python.find_installation('python3').full_path())
# Install Python package
subdir('portprotonqt')
# Install entry point script with proper name to avoid conflict in build directory
configured_portprotonqt = configure_file(
input: 'build-aux/portprotonqt',
output: 'portprotonqt-script',
configuration: conf,
)
# Install the configured script with the correct name
install_data(configured_portprotonqt,
install_dir: bindir,
install_mode: 'rwxr-xr-x',
rename: 'portprotonqt')
# Install desktop file
install_data(
'build-aux/share/applications/ru.linux_gaming.PortProtonQt.desktop',
install_dir: datadir / 'applications',
)
# Install icon
install_data(
'build-aux/share/icons/hicolor/scalable/apps/ru.linux_gaming.PortProtonQt.svg',
install_dir: datadir / 'icons/hicolor/scalable/apps',
)
# Install metainfo
install_data(
'build-aux/share/metainfo/ru.linux_gaming.PortProtonQt.metainfo.xml',
install_dir: datadir / 'metainfo',
)
# Install bash completion
install_data(
'build-aux/share/bash-completion/completions/portprotonqt',
install_dir: datadir / 'bash-completion/completions',
)
# Install udev rules
udevdir = get_option('udevdir')
if udevdir == ''
udev = dependency('udev', required: false)
if udev.found()
udevdir = udev.get_variable(pkgconfig: 'udevdir') / 'rules.d'
else
udevdir = prefix / get_option('libdir') / 'udev/rules.d'
endif
endif
install_data(
'build-aux/lib/udev/rules.d/60-portprotonqt.rules',
install_dir: udevdir,
)

11
meson_options.txt Normal file
View File

@@ -0,0 +1,11 @@
option('udevdir',
type: 'string',
value: '',
description: 'Directory for udev rules (auto-detected if empty)',
)
option('python_libdir',
type: 'string',
value: '',
description: 'Python site-packages directory (auto-detected if empty)',
)

View File

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

View File

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

View File

@@ -971,15 +971,69 @@ class ProtonManager(QDialog):
if os.path.exists(filepath): if os.path.exists(filepath):
total_size += os.path.getsize(filepath) total_size += os.path.getsize(filepath)
# Convert to human readable format # Convert to human readable format (binary units)
for unit in ['B', 'KB', 'MB', 'GB']: if total_size == 0:
if total_size < 1024.0: return "0 B"
return f"{total_size:.1f} {unit}" elif total_size < 1024:
total_size /= 1024.0 return f"{total_size}.0 B"
return f"{total_size:.1f} TB" 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: except Exception:
return _("Unknown") 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): def on_cell_clicked(self, row):
"""Обработка клика по ячейке - переключение флажка при клике по любой ячейке в строке""" """Обработка клика по ячейке - переключение флажка при клике по любой ячейке в строке"""
tab = self.tab_widget.currentWidget() tab = self.tab_widget.currentWidget()
@@ -1033,6 +1087,7 @@ class ProtonManager(QDialog):
table = current_tab.findChild(QTableWidget) table = current_tab.findChild(QTableWidget)
if table: if table:
selected_count = 0 selected_count = 0
total_size = 0
for row in range(table.rowCount()): for row in range(table.rowCount()):
checkbox_widget = table.cellWidget(row, 0) checkbox_widget = table.cellWidget(row, 0)
@@ -1041,6 +1096,14 @@ class ProtonManager(QDialog):
if checkbox and checkbox.isChecked(): if checkbox and checkbox.isChecked():
selected_count += 1 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: if selected_count > 0:
selection_text = _('Selected {} assets:\n').format(selected_count) selection_text = _('Selected {} assets:\n').format(selected_count)
@@ -1061,6 +1124,10 @@ class ProtonManager(QDialog):
selection_text += f"{item_number}. {version_name}\n" selection_text += f"{item_number}. {version_name}\n"
item_number += 1 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.setText(_('Delete Selected'))
self.download_btn.setEnabled(True) self.download_btn.setEnabled(True)
else: else:
@@ -1078,9 +1145,49 @@ class ProtonManager(QDialog):
if self.selected_assets: if self.selected_assets:
selection_text = _('Selected {} assets:\n').format(len(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): for i, asset_data in enumerate(self.selected_assets.values(), 1):
selection_text += f"{i}. {asset_data['asset_name']}\n" 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.selection_text.setPlainText(selection_text)
self.download_btn.setText(_('Download Selected')) self.download_btn.setText(_('Download Selected'))
self.download_btn.setEnabled(True) 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) pixmap = QPixmap(local_path)
# Check if the pixmap loaded successfully # Check if the pixmap loaded successfully
if pixmap.isNull(): if pixmap.isNull():
logger.warning(f"Failed to load image from {local_path}") logger.warning(f"Failed to load image from {local_path}, removing corrupted file")
finish_with(pixmap) os.remove(local_path)
return else:
finish_with(pixmap)
return
def on_downloaded(result: str | None): def on_downloaded(result: str | None):
pixmap = QPixmap() pixmap = QPixmap()
@@ -111,9 +113,11 @@ def load_pixmap_async(cover: str, width: int, height: int, callback: Callable[[Q
pixmap = QPixmap(local_path) pixmap = QPixmap(local_path)
# Check if the pixmap loaded successfully # Check if the pixmap loaded successfully
if pixmap.isNull(): if pixmap.isNull():
logger.warning(f"Failed to load image from {local_path}") logger.warning(f"Failed to load image from {local_path}, removing corrupted file")
finish_with(pixmap) os.remove(local_path)
return else:
finish_with(pixmap)
return
def on_downloaded(result: str | None): def on_downloaded(result: str | None):
pixmap = QPixmap() pixmap = QPixmap()
@@ -148,9 +152,11 @@ def load_pixmap_async(cover: str, width: int, height: int, callback: Callable[[Q
pixmap = QPixmap(local_path) pixmap = QPixmap(local_path)
# Check if the pixmap loaded successfully # Check if the pixmap loaded successfully
if pixmap.isNull(): if pixmap.isNull():
logger.warning(f"Failed to load image from {local_path}") logger.warning(f"Failed to load image from {local_path}, removing corrupted file")
finish_with(pixmap) os.remove(local_path)
return else:
finish_with(pixmap)
return
def on_downloaded(result: str | None): def on_downloaded(result: str | None):
pixmap = QPixmap() 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 # Check if the pixmap loaded successfully
if pixmap.isNull(): if pixmap.isNull():
logger.warning(f"Failed to load image from {cover}") logger.warning(f"Failed to load image from {cover}")
finish_with(pixmap) # Remove corrupted file only if it's in the cache directory
return 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) placeholder_path = theme_manager.get_theme_image("placeholder", current_theme_name)
pixmap = QPixmap() pixmap = QPixmap()

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-01-12 19:50+0500\n" "POT-Creation-Date: 2026-01-12 20:59+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de_DE\n" "Language: de_DE\n"
@@ -544,9 +544,21 @@ msgstr ""
msgid "Selected {} assets:\n" msgid "Selected {} assets:\n"
msgstr "" msgstr ""
#, python-brace-format
msgid ""
"\n"
"Total size to delete: {}\n"
msgstr ""
msgid "Delete Selected" msgid "Delete Selected"
msgstr "" msgstr ""
#, python-brace-format
msgid ""
"\n"
"Total size to download: {}\n"
msgstr ""
msgid "Downloading in Progress" msgid "Downloading in Progress"
msgstr "" msgstr ""

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-01-12 19:50+0500\n" "POT-Creation-Date: 2026-01-12 20:59+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: es_ES\n" "Language: es_ES\n"
@@ -544,9 +544,21 @@ msgstr ""
msgid "Selected {} assets:\n" msgid "Selected {} assets:\n"
msgstr "" msgstr ""
#, python-brace-format
msgid ""
"\n"
"Total size to delete: {}\n"
msgstr ""
msgid "Delete Selected" msgid "Delete Selected"
msgstr "" msgstr ""
#, python-brace-format
msgid ""
"\n"
"Total size to download: {}\n"
msgstr ""
msgid "Downloading in Progress" msgid "Downloading in Progress"
msgstr "" msgstr ""

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PortProtonQt 0.1.1\n" "Project-Id-Version: PortProtonQt 0.1.1\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-01-12 19:50+0500\n" "POT-Creation-Date: 2026-01-18 11:43+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -542,9 +542,21 @@ msgstr ""
msgid "Selected {} assets:\n" msgid "Selected {} assets:\n"
msgstr "" msgstr ""
#, python-brace-format
msgid ""
"\n"
"Total size to delete: {}\n"
msgstr ""
msgid "Delete Selected" msgid "Delete Selected"
msgstr "" msgstr ""
#, python-brace-format
msgid ""
"\n"
"Total size to download: {}\n"
msgstr ""
msgid "Downloading in Progress" msgid "Downloading in Progress"
msgstr "" msgstr ""

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -9,8 +9,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2026-01-12 19:50+0500\n" "POT-Creation-Date: 2026-01-12 20:59+0500\n"
"PO-Revision-Date: 2026-01-03 20:32+0500\n" "PO-Revision-Date: 2026-01-12 20:59+0500\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language: ru_RU\n" "Language: ru_RU\n"
"Language-Team: ru_RU <LL@li.org>\n" "Language-Team: ru_RU <LL@li.org>\n"
@@ -506,7 +506,6 @@ msgstr "Бронза"
msgid "Pending" msgid "Pending"
msgstr "В ожидании" msgstr "В ожидании"
#, fuzzy
msgid "Manage Wine versions" msgid "Manage Wine versions"
msgstr "Управление версиями WINE" msgstr "Управление версиями WINE"
@@ -522,19 +521,16 @@ msgstr "Скачивание: "
msgid "Download Selected" msgid "Download Selected"
msgstr "Скачать выбранное" msgstr "Скачать выбранное"
#, fuzzy
msgid "Installed" msgid "Installed"
msgstr "Удаление WINE" msgstr "Установленные"
#, fuzzy, python-brace-format #, python-brace-format
msgid "Error loading wine data: {error}" msgid "Error loading wine data: {error}"
msgstr "Ошибка загрузки WINE: {error}" msgstr "Ошибка загрузки WINE: {error}"
#, fuzzy
msgid "Version Name" msgid "Version Name"
msgstr "Версия WINE" msgstr "Версия WINE"
#, fuzzy
msgid "Size" msgid "Size"
msgstr "Размер" msgstr "Размер"
@@ -555,9 +551,25 @@ msgstr "Выберите, чтобы удалить WINE"
msgid "Selected {} assets:\n" msgid "Selected {} assets:\n"
msgstr "Выбранно {} WINE:\n" msgstr "Выбранно {} WINE:\n"
#, python-brace-format
msgid ""
"\n"
"Total size to delete: {}\n"
msgstr ""
"\n"
"Общий размер на удаление: {}\n"
msgid "Delete Selected" msgid "Delete Selected"
msgstr "Удалить выбранное" msgstr "Удалить выбранное"
#, python-brace-format
msgid ""
"\n"
"Total size to download: {}\n"
msgstr ""
"\n"
"Общий размер на загрузку: {}\n"
msgid "Downloading in Progress" msgid "Downloading in Progress"
msgstr "Скачивание" msgstr "Скачивание"
@@ -573,27 +585,26 @@ msgstr "Пожалуйста выберите хотя бы один WINE для
msgid "Please wait for current downloading to complete." msgid "Please wait for current downloading to complete."
msgstr "Пожалуйста подождите завершения скачивания." msgstr "Пожалуйста подождите завершения скачивания."
#, fuzzy
msgid "Please select at least one version to delete." msgid "Please select at least one version to delete."
msgstr "Пожалуйста выберите хотя бы один WINE для удаления." msgstr "Пожалуйста выберите хотя бы один WINE для удаления."
#, fuzzy, python-brace-format #, python-brace-format
msgid "" msgid ""
"Are you sure you want to delete {} selected version(s)?\n" "Are you sure you want to delete {} selected version(s)?\n"
"\n" "\n"
"This action cannot be undone." "This action cannot be undone."
msgstr "" msgstr ""
"Вы уверены, что хотите удалить выбранные WINE?\n" "Вы уверены, что хотите удалить {} выбранные WINE?\n"
"\n" "\n"
"Это действие нельзя отменить." "Это действие нельзя отменить."
#, fuzzy, python-brace-format #, python-brace-format
msgid "Failed to remove version at {}: {}" msgid "Failed to remove version at {}: {}"
msgstr "Не удалось удалить WINE '{}': {}" msgstr "Не удалось удалить WINE '{}': {}"
#, python-brace-format #, python-brace-format
msgid "Successfully removed {} version(s)." msgid "Successfully removed {} version(s)."
msgstr "Успешно удалено {} WINE" msgstr "Успешно удалено {} WINE."
msgid "Downloading Complete" msgid "Downloading Complete"
msgstr "Скачивание завершено" msgstr "Скачивание завершено"
@@ -738,7 +749,6 @@ msgstr "Удалить Префикс"
msgid "Clear Prefix" msgid "Clear Prefix"
msgstr "Очистить Префикс" msgstr "Очистить Префикс"
#, fuzzy
msgid "Manage WINE versions" msgid "Manage WINE versions"
msgstr "Управление версиями WINE" msgstr "Управление версиями WINE"

View File

@@ -33,11 +33,21 @@ LOCALE_MAP = {
'el': 'greek', 'el': 'greek',
} }
translate = gettext.translation( # Try system locale directory first, fallback to local for development
domain="messages", _system_localedir = Path("/usr/share/locale")
localedir = Path(__file__).parent / "locales", _local_localedir = Path(__file__).parent / "locales"
fallback=True,
) try:
translate = gettext.translation(
domain="portprotonqt",
localedir=_system_localedir,
)
except FileNotFoundError:
translate = gettext.translation(
domain="portprotonqt",
localedir=_local_localedir,
fallback=True,
)
_ = translate.gettext _ = translate.gettext
def get_system_locale(): def get_system_locale():

54
portprotonqt/meson.build Normal file
View File

@@ -0,0 +1,54 @@
# Install Python source files
install_data(
'__init__.py',
'animations.py',
'app.py',
'cli.py',
'config_utils.py',
'context_menu_manager.py',
'custom_widgets.py',
'detail_pages.py',
'dialogs.py',
'downloader.py',
'egs_api.py',
'game_card.py',
'game_library_manager.py',
'get_wine_module.py',
'howlongtobeat_api.py',
'image_utils.py',
'input_manager.py',
'keyboard_layouts.py',
'localization.py',
'logger.py',
'main_window.py',
'portproton_api.py',
'preloader.py',
'search_utils.py',
'settings_manager.py',
'steam_api.py',
'system_overlay.py',
'theme_manager.py',
'theme_security.py',
'time_utils.py',
'tray_manager.py',
'version_utils.py',
'virtual_keyboard.py',
install_dir: pkgdatadir,
)
# Install themes
install_subdir(
'themes',
install_dir: pkgdatadir,
exclude_directories: ['__pycache__'],
exclude_files: ['*.pyc'],
)
# Install locales - only .mo files (compiled translations)
# exclude_files doesn't work recursively, so we install each language manually
foreach lang : ['de', 'es', 'pt', 'ru']
install_data(
'locales' / lang / 'LC_MESSAGES' / 'portprotonqt.mo',
install_dir: get_option('localedir') / lang / 'LC_MESSAGES',
)
endforeach

View File

@@ -94,8 +94,13 @@ def parse_playtime_file(file_path):
if len(parts) < 3: if len(parts) < 3:
continue continue
exe_path = parts[0] exe_path = parts[0]
seconds = int(parts[2]) # Find playtime: first numeric value after exe_path
playtime_data[exe_path] = seconds # 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 return playtime_data
def format_playtime(seconds): def format_playtime(seconds):

View File

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

2
uv.lock generated
View File

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