Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
a5df7f0477
|
|||
f2954497d9
|
|||
80bbab692d
|
|||
731e919884
|
|||
0efc3a8701
|
|||
fa847d167b
|
18
CHANGELOG.md
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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`)
|
||||||
|
|
||||||
|
@ -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`)
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 643 KiB After Width: | Height: | Size: 499 KiB |
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 147 KiB |
Before Width: | Height: | Size: 508 KiB After Width: | Height: | Size: 352 KiB |
Before Width: | Height: | Size: 217 KiB After Width: | Height: | Size: 121 KiB |
Before Width: | Height: | Size: 702 KiB After Width: | Height: | Size: 528 KiB |
Before Width: | Height: | Size: 827 KiB After Width: | Height: | Size: 660 KiB |
Before Width: | Height: | Size: 429 KiB After Width: | Height: | Size: 324 KiB |
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 870 KiB |
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 276 KiB |
Before Width: | Height: | Size: 946 KiB After Width: | Height: | Size: 764 KiB |
Before Width: | Height: | Size: 511 KiB After Width: | Height: | Size: 395 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 936 KiB After Width: | Height: | Size: 736 KiB |
Before Width: | Height: | Size: 674 KiB After Width: | Height: | Size: 523 KiB |
Before Width: | Height: | Size: 351 KiB After Width: | Height: | Size: 267 KiB |
Before Width: | Height: | Size: 963 KiB After Width: | Height: | Size: 824 KiB |
Before Width: | Height: | Size: 757 KiB After Width: | Height: | Size: 634 KiB |
Before Width: | Height: | Size: 954 KiB After Width: | Height: | Size: 854 KiB |
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 860 KiB |
Before Width: | Height: | Size: 552 KiB After Width: | Height: | Size: 439 KiB |
Before Width: | Height: | Size: 680 KiB After Width: | Height: | Size: 650 KiB |
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 283 KiB |
Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 228 KiB |
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 833 KiB |
Before Width: | Height: | Size: 901 KiB After Width: | Height: | Size: 784 KiB |
Before Width: | Height: | Size: 864 KiB After Width: | Height: | Size: 720 KiB |
Before Width: | Height: | Size: 346 KiB After Width: | Height: | Size: 315 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 895 KiB |
Before Width: | Height: | Size: 769 KiB After Width: | Height: | Size: 627 KiB |
Before Width: | Height: | Size: 806 KiB After Width: | Height: | Size: 653 KiB |
Before Width: | Height: | Size: 762 KiB After Width: | Height: | Size: 606 KiB |
Before Width: | Height: | Size: 684 KiB After Width: | Height: | Size: 684 KiB |
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 191 KiB |
Before Width: | Height: | Size: 963 KiB After Width: | Height: | Size: 814 KiB |
Before Width: | Height: | Size: 961 KiB After Width: | Height: | Size: 824 KiB |
Before Width: | Height: | Size: 701 KiB After Width: | Height: | Size: 511 KiB |
Before Width: | Height: | Size: 637 KiB After Width: | Height: | Size: 526 KiB |
Before Width: | Height: | Size: 592 KiB After Width: | Height: | Size: 447 KiB |
Before Width: | Height: | Size: 724 KiB After Width: | Height: | Size: 604 KiB |
Before Width: | Height: | Size: 623 KiB After Width: | Height: | Size: 557 KiB |
Before Width: | Height: | Size: 974 KiB After Width: | Height: | Size: 850 KiB |
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 590 KiB After Width: | Height: | Size: 428 KiB |
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 154 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 665 KiB After Width: | Height: | Size: 495 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 342 KiB |
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 875 KiB |
Before Width: | Height: | Size: 704 KiB After Width: | Height: | Size: 610 KiB |
Before Width: | Height: | Size: 888 KiB After Width: | Height: | Size: 763 KiB |
Before Width: | Height: | Size: 646 KiB After Width: | Height: | Size: 457 KiB |
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 978 KiB |
Before Width: | Height: | Size: 777 KiB After Width: | Height: | Size: 650 KiB |
Before Width: | Height: | Size: 469 KiB After Width: | Height: | Size: 391 KiB |
Before Width: | Height: | Size: 810 KiB After Width: | Height: | Size: 710 KiB |
Before Width: | Height: | Size: 840 KiB After Width: | Height: | Size: 670 KiB |
Before Width: | Height: | Size: 656 KiB After Width: | Height: | Size: 566 KiB |
Before Width: | Height: | Size: 814 KiB After Width: | Height: | Size: 655 KiB |
Before Width: | Height: | Size: 885 KiB After Width: | Height: | Size: 722 KiB |
Before Width: | Height: | Size: 315 KiB After Width: | Height: | Size: 285 KiB |
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 174 KiB |
Before Width: | Height: | Size: 257 KiB After Width: | Height: | Size: 201 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 768 KiB After Width: | Height: | Size: 751 KiB |
Before Width: | Height: | Size: 694 KiB After Width: | Height: | Size: 550 KiB |
Before Width: | Height: | Size: 691 KiB After Width: | Height: | Size: 563 KiB |
Before Width: | Height: | Size: 653 KiB After Width: | Height: | Size: 518 KiB |
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 159 KiB |
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 191 KiB |
@ -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
|
||||||
|
@ -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,
|
||||||
|