6 Commits
v0.1.3 ... main

Author SHA1 Message Date
a5df7f0477 chore(changelog): update
All checks were successful
Code and build check / Check code (push) Successful in 1m35s
Code and build check / Build with uv (push) Successful in 58s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-07-06 13:19:13 +05:00
f2954497d9 chore(readme): update todo
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-07-06 13:18:04 +05:00
80bbab692d chore(documentation): mention localization in custom data
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-07-06 13:17:07 +05:00
731e919884 feat: added translate support to custom data
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-07-06 13:10:37 +05:00
0efc3a8701 chore(changelog): update
All checks were successful
Code and build check / Check code (push) Successful in 1m50s
Code and build check / Build with uv (push) Successful in 1m8s
renovate / renovate (push) Successful in 28s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-07-05 15:43:46 +05:00
fa847d167b optimize covers to reduce file size thanks to @Vector_null
All checks were successful
Code and build check / Check code (pull_request) Successful in 1m41s
Code and build check / Build with uv (pull_request) Successful in 1m1s
2025-07-05 17:02:18 +07:00
84 changed files with 76 additions and 31 deletions

View File

@ -3,6 +3,24 @@
Все заметные изменения в этом проекте фиксируются в этом файле. Все заметные изменения в этом проекте фиксируются в этом файле.
Формат основан на [Keep a Changelog](https://keepachangelog.com/) и придерживается принципов [Semantic Versioning](https://semver.org/). Формат основан на [Keep a Changelog](https://keepachangelog.com/) и придерживается принципов [Semantic Versioning](https://semver.org/).
## [Unreleased]
### Added
- Переводы в переопределениях (за подробностями в документацию)
### Changed
- Оптимизированны обложки автоинсталлов
### Fixed
### Contributors
- @Vector_null
---
## [0.1.3] - 2025-07-05 ## [0.1.3] - 2025-07-05
### Added ### Added

View File

@ -34,7 +34,7 @@
- [ ] Достигнуть паритета функциональности с PortProton - [ ] Достигнуть паритета функциональности с PortProton
- [X] Добавить возможность изменения названия, описания и обложки через файлы `.local/share/PortProtonQT/custom_data/exe_name/{desc,name,cover}` - [X] Добавить возможность изменения названия, описания и обложки через файлы `.local/share/PortProtonQT/custom_data/exe_name/{desc,name,cover}`
- [X] Добавить встроенное переопределение названия, описания и обложки, например, по пути `portprotonqt/custom_data` [Документация](documentation/metadata_override/) - [X] Добавить встроенное переопределение названия, описания и обложки, например, по пути `portprotonqt/custom_data` [Документация](documentation/metadata_override/)
- [ ] Добавить переводы в переопределения - [X] Добавить переводы в переопределения
- [ ] Придумать как переопределять launcher.exe - [ ] Придумать как переопределять launcher.exe
- [X] Добавить в карточку игры сведения о поддержке геймпада - [X] Добавить в карточку игры сведения о поддержке геймпада
- [X] Добавить в карточки данные с ProtonDB - [X] Добавить в карточки данные с ProtonDB

View File

@ -50,7 +50,9 @@ Each `<exe_name>` folder can include:
- `metadata.txt` — contains name and description: - `metadata.txt` — contains name and description:
```txt ```txt
name=My Game Title name=My Game Title
name_ru=My Game Title (in russian language)
description=My Game Description description=My Game Description
description_ru=My Game Description (in russian language)
``` ```
- `cover.<extension>` — image file (`.png`, `.jpg`, `.jpeg`, `.bmp`) - `cover.<extension>` — image file (`.png`, `.jpg`, `.jpeg`, `.bmp`)

View File

@ -50,7 +50,9 @@
- `metadata.txt` — имя и описание в формате: - `metadata.txt` — имя и описание в формате:
```txt ```txt
name=Моё название игры name=Моё название игры
description=Описание моей игры name_en=Моё название игры (на английском)
description=Описание моей игры (на английском)
description_en=Описание моей игры
``` ```
- `cover.<расширение>` — обложка (`.png`, `.jpg`, `.jpeg`, `.bmp`) - `cover.<расширение>` — обложка (`.png`, `.jpg`, `.jpeg`, `.bmp`)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 KiB

After

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 KiB

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 217 KiB

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 702 KiB

After

Width:  |  Height:  |  Size: 528 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 827 KiB

After

Width:  |  Height:  |  Size: 660 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 429 KiB

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 870 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 KiB

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 946 KiB

After

Width:  |  Height:  |  Size: 764 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 511 KiB

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 936 KiB

After

Width:  |  Height:  |  Size: 736 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 KiB

After

Width:  |  Height:  |  Size: 523 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 KiB

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 963 KiB

After

Width:  |  Height:  |  Size: 824 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 757 KiB

After

Width:  |  Height:  |  Size: 634 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 954 KiB

After

Width:  |  Height:  |  Size: 854 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 860 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 552 KiB

After

Width:  |  Height:  |  Size: 439 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 KiB

After

Width:  |  Height:  |  Size: 650 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 833 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 901 KiB

After

Width:  |  Height:  |  Size: 784 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 864 KiB

After

Width:  |  Height:  |  Size: 720 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 895 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 769 KiB

After

Width:  |  Height:  |  Size: 627 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 806 KiB

After

Width:  |  Height:  |  Size: 653 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 KiB

After

Width:  |  Height:  |  Size: 606 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 684 KiB

After

Width:  |  Height:  |  Size: 684 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 963 KiB

After

Width:  |  Height:  |  Size: 814 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 961 KiB

After

Width:  |  Height:  |  Size: 824 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 701 KiB

After

Width:  |  Height:  |  Size: 511 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 KiB

After

Width:  |  Height:  |  Size: 526 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 592 KiB

After

Width:  |  Height:  |  Size: 447 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 724 KiB

After

Width:  |  Height:  |  Size: 604 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 623 KiB

After

Width:  |  Height:  |  Size: 557 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 KiB

After

Width:  |  Height:  |  Size: 850 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 590 KiB

After

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 KiB

After

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 KiB

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 875 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 KiB

After

Width:  |  Height:  |  Size: 610 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 888 KiB

After

Width:  |  Height:  |  Size: 763 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 646 KiB

After

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 978 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 777 KiB

After

Width:  |  Height:  |  Size: 650 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

After

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 810 KiB

After

Width:  |  Height:  |  Size: 710 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 840 KiB

After

Width:  |  Height:  |  Size: 670 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 656 KiB

After

Width:  |  Height:  |  Size: 566 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 814 KiB

After

Width:  |  Height:  |  Size: 655 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 885 KiB

After

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 315 KiB

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 KiB

After

Width:  |  Height:  |  Size: 751 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 694 KiB

After

Width:  |  Height:  |  Size: 550 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 KiB

After

Width:  |  Height:  |  Size: 563 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 653 KiB

After

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 191 KiB

View File

@ -1,6 +1,7 @@
import gettext import gettext
from pathlib import Path from pathlib import Path
import locale import locale
import os
from babel import Locale from babel import Locale
LOCALE_MAP = { LOCALE_MAP = {
@ -72,3 +73,32 @@ def get_egs_language():
# Если что-то пошло не так — используем английский по умолчанию # Если что-то пошло не так — используем английский по умолчанию
return 'en' return 'en'
def read_metadata_translations(metadata_file, language_code):
"""
Читает переводы из metadata.txt для указанного языка.
Возвращает словарь с полями name и description.
Для name: использует name_<language_code>, затем name_en, затем name, и наконец _('Unknown Game').
Для description: использует description_<language_code>, затем description_en, затем description.
"""
translations = {'name': _('Unknown Game'), 'description': ''}
if not os.path.exists(metadata_file):
return translations
with open(metadata_file, encoding='utf-8') as f:
for line in f:
line = line.strip()
if line.startswith(f'name_{language_code}='):
translations['name'] = line[len(f'name_{language_code}='):].strip()
elif line.startswith('name_en=') and translations['name'] == _('Unknown Game'):
translations['name'] = line[len('name_en='):].strip()
elif line.startswith('name=') and translations['name'] == _('Unknown Game'):
translations['name'] = line[len('name='):].strip()
elif line.startswith(f'description_{language_code}='):
translations['description'] = line[len(f'description_{language_code}='):].strip()
elif line.startswith('description_en=') and not translations['description']:
translations['description'] = line[len('description_en='):].strip()
elif line.startswith('description=') and not translations['description']:
translations['description'] = line[len('description='):].strip()
return translations

View File

@ -28,7 +28,7 @@ from portprotonqt.config_utils import (
save_fullscreen_config, read_window_geometry, save_window_geometry, reset_config, save_fullscreen_config, read_window_geometry, save_window_geometry, reset_config,
clear_cache, read_auto_fullscreen_gamepad, save_auto_fullscreen_gamepad, read_rumble_config, save_rumble_config clear_cache, read_auto_fullscreen_gamepad, save_auto_fullscreen_gamepad, read_rumble_config, save_rumble_config
) )
from portprotonqt.localization import _ from portprotonqt.localization import _, get_egs_language, read_metadata_translations
from portprotonqt.logger import get_logger from portprotonqt.logger import get_logger
from portprotonqt.downloader import Downloader from portprotonqt.downloader import Downloader
@ -465,11 +465,9 @@ class MainWindow(QMainWindow):
os.makedirs(user_custom_folder, exist_ok=True) os.makedirs(user_custom_folder, exist_ok=True)
builtin_cover = "" builtin_cover = ""
builtin_name = None
builtin_desc = None
user_cover = "" user_cover = ""
user_name = None user_game_folder=""
user_desc = None builtin_game_folder=""
if game_exe: if game_exe:
exe_name = os.path.splitext(os.path.basename(game_exe))[0] exe_name = os.path.splitext(os.path.basename(game_exe))[0]
@ -477,6 +475,7 @@ class MainWindow(QMainWindow):
user_game_folder = os.path.join(user_custom_folder, exe_name) user_game_folder = os.path.join(user_custom_folder, exe_name)
os.makedirs(user_game_folder, exist_ok=True) os.makedirs(user_game_folder, exist_ok=True)
# Чтение обложки
builtin_files = set(os.listdir(builtin_game_folder)) if os.path.exists(builtin_game_folder) else set() builtin_files = set(os.listdir(builtin_game_folder)) if os.path.exists(builtin_game_folder) else set()
for ext in [".jpg", ".png", ".jpeg", ".bmp"]: for ext in [".jpg", ".png", ".jpeg", ".bmp"]:
candidate = f"cover{ext}" candidate = f"cover{ext}"
@ -484,16 +483,6 @@ class MainWindow(QMainWindow):
builtin_cover = os.path.join(builtin_game_folder, candidate) builtin_cover = os.path.join(builtin_game_folder, candidate)
break break
builtin_metadata_file = os.path.join(builtin_game_folder, "metadata.txt")
if os.path.exists(builtin_metadata_file):
with open(builtin_metadata_file, encoding="utf-8") as f:
for line in f:
line = line.strip()
if line.startswith("name="):
builtin_name = line[len("name="):].strip()
elif line.startswith("description="):
builtin_desc = line[len("description="):].strip()
user_files = set(os.listdir(user_game_folder)) if os.path.exists(user_game_folder) else set() user_files = set(os.listdir(user_game_folder)) if os.path.exists(user_game_folder) else set()
for ext in [".jpg", ".png", ".jpeg", ".bmp"]: for ext in [".jpg", ".png", ".jpeg", ".bmp"]:
candidate = f"cover{ext}" candidate = f"cover{ext}"
@ -501,16 +490,7 @@ class MainWindow(QMainWindow):
user_cover = os.path.join(user_game_folder, candidate) user_cover = os.path.join(user_game_folder, candidate)
break break
user_metadata_file = os.path.join(user_game_folder, "metadata.txt") # Чтение статистики
if os.path.exists(user_metadata_file):
with open(user_metadata_file, encoding="utf-8") as f:
for line in f:
line = line.strip()
if line.startswith("name="):
user_name = line[len("name="):].strip()
elif line.startswith("description="):
user_desc = line[len("description="):].strip()
if self.portproton_location: if self.portproton_location:
statistics_file = os.path.join(self.portproton_location, "data", "tmp", "statistics") statistics_file = os.path.join(self.portproton_location, "data", "tmp", "statistics")
try: try:
@ -526,13 +506,26 @@ class MainWindow(QMainWindow):
print(f"Failed to parse playtime data: {e}") print(f"Failed to parse playtime data: {e}")
def on_steam_info(steam_info: dict): def on_steam_info(steam_info: dict):
final_name = user_name or builtin_name or desktop_name # Определяем текущий язык
final_desc = (user_desc if user_desc is not None else language_code = get_egs_language()
builtin_desc if builtin_desc is not None else
steam_info.get("description", "")) # Чтение переводов из metadata.txt
user_metadata_file = os.path.join(user_game_folder, "metadata.txt")
builtin_metadata_file = os.path.join(builtin_game_folder, "metadata.txt")
# Сначала пытаемся загрузить пользовательские переводы
translations = {'name': desktop_name, 'description': ''}
if os.path.exists(user_metadata_file):
translations = read_metadata_translations(user_metadata_file, language_code)
elif os.path.exists(builtin_metadata_file):
translations = read_metadata_translations(builtin_metadata_file, language_code)
final_name = translations['name']
final_desc = translations['description'] or steam_info.get("description", "")
final_cover = (user_cover if user_cover else final_cover = (user_cover if user_cover else
builtin_cover if builtin_cover else builtin_cover if builtin_cover else
steam_info.get("cover", "") or entry.get("Icon", "")) steam_info.get("cover", "") or entry.get("Icon", ""))
callback(( callback((
final_name, final_name,
final_desc, final_desc,