Compare commits
	
		
			11 Commits
		
	
	
		
			0efc3a8701
			...
			e57770f796
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						e57770f796
	
				 | 
					
					
						|||
| 
						
						
							
						
						49cd77ee38
	
				 | 
					
					
						|||
| 
						
						
							
						
						d26b9774a0
	
				 | 
					
					
						|||
| 
						
						
							
						
						9a27d67dc0
	
				 | 
					
					
						|||
| 
						
						
							
						
						b0fff5af0c
	
				 | 
					
					
						|||
| 
						
						
							
						
						e54fac8aa4
	
				 | 
					
					
						|||
| 
						
						
							
						
						f111674260
	
				 | 
					
					
						|||
| 
						
						
							
						
						a5df7f0477
	
				 | 
					
					
						|||
| 
						
						
							
						
						f2954497d9
	
				 | 
					
					
						|||
| 
						
						
							
						
						80bbab692d
	
				 | 
					
					
						|||
| 
						
						
							
						
						731e919884
	
				 | 
					
					
						
@@ -1,6 +1,6 @@
 | 
				
			|||||||
# See https://pre-commit.com for more information
 | 
					# See https://pre-commit.com for more information
 | 
				
			||||||
# See https://pre-commit.com/hooks.html for more hooks
 | 
					# See https://pre-commit.com/hooks.html for more hooks
 | 
				
			||||||
exclude: '(data/|documentation/|portprotonqt/locales/|dev-scripts/|\.venv/|venv/|.*\.svg$)'
 | 
					exclude: '(data/|documentation/|portprotonqt/locales/|portprotonqt/custom_data/|dev-scripts/|\.venv/|venv/|.*\.svg$)'
 | 
				
			||||||
repos:
 | 
					repos:
 | 
				
			||||||
  - repo: https://github.com/pre-commit/pre-commit-hooks
 | 
					  - repo: https://github.com/pre-commit/pre-commit-hooks
 | 
				
			||||||
    rev: v5.0.0
 | 
					    rev: v5.0.0
 | 
				
			||||||
@@ -27,8 +27,9 @@ repos:
 | 
				
			|||||||
        name: pyright
 | 
					        name: pyright
 | 
				
			||||||
        entry: pyright
 | 
					        entry: pyright
 | 
				
			||||||
        language: system
 | 
					        language: system
 | 
				
			||||||
        'types_or': [python, pyi]
 | 
					        types_or: [python, pyi]
 | 
				
			||||||
        require_serial: true
 | 
					        require_serial: true
 | 
				
			||||||
 | 
					        exclude: '^portprotonqt/themes/[^/]+/styles\.py$'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: local
 | 
					  - repo: local
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
@@ -37,5 +38,5 @@ repos:
 | 
				
			|||||||
        entry: ./dev-scripts/check_qss_properties.py
 | 
					        entry: ./dev-scripts/check_qss_properties.py
 | 
				
			||||||
        language: system
 | 
					        language: system
 | 
				
			||||||
        types: [file]
 | 
					        types: [file]
 | 
				
			||||||
        files: \.py$
 | 
					        files: ^portprotonqt/themes/[^/]+/styles\.py$
 | 
				
			||||||
        pass_filenames: false
 | 
					        pass_filenames: false
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,15 +6,16 @@
 | 
				
			|||||||
## [Unreleased]
 | 
					## [Unreleased]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Added
 | 
					### Added
 | 
				
			||||||
 | 
					- Переводы в переопределениях (за подробностями в документацию)
 | 
				
			||||||
 | 
					- Обложки и описания для всех автоинсталлов
 | 
				
			||||||
 | 
					- Возможность указать ссылку для скачивания обложки в диалоге добавления игры
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Changed
 | 
					### Changed
 | 
				
			||||||
- Оптимизированны обложки автоинсталлов
 | 
					- Оптимизированны обложки автоинсталлов
 | 
				
			||||||
 | 
					- Папка custom_data исключена из сборки модуля для уменьшение его размера
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Fixed
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
### Contributors
 | 
					### Contributors
 | 
				
			||||||
- @Vector_null
 | 
					- @Vector_null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,9 +20,9 @@ Current translation status:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
| Locale | Progress | Translated |
 | 
					| Locale | Progress | Translated |
 | 
				
			||||||
| :----- | -------: | ---------: |
 | 
					| :----- | -------: | ---------: |
 | 
				
			||||||
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 192 |
 | 
					| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 194 |
 | 
				
			||||||
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 192 |
 | 
					| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 194 |
 | 
				
			||||||
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 192 of 192 |
 | 
					| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 194 of 194 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,9 +20,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
| Локаль | Прогресс | Переведено |
 | 
					| Локаль | Прогресс | Переведено |
 | 
				
			||||||
| :----- | -------: | ---------: |
 | 
					| :----- | -------: | ---------: |
 | 
				
			||||||
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 192 |
 | 
					| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 194 |
 | 
				
			||||||
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 192 |
 | 
					| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 194 |
 | 
				
			||||||
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 192 из 192 |
 | 
					| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 194 из 194 |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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: 720 KiB After Width: | Height: | Size: 720 KiB  | 
| 
		 Before Width: | Height: | Size: 655 KiB After Width: | Height: | Size: 655 KiB  | 
| 
		 Before Width: | Height: | Size: 315 KiB After Width: | Height: | Size: 315 KiB  | 
| 
		 Before Width: | Height: | Size: 978 KiB After Width: | Height: | Size: 978 KiB  | 
| 
		 Before Width: | Height: | Size: 650 KiB After Width: | Height: | Size: 650 KiB  | 
| 
		 Before Width: | Height: | Size: 391 KiB After Width: | Height: | Size: 391 KiB  | 
| 
		 Before Width: | Height: | Size: 710 KiB After Width: | Height: | Size: 710 KiB  | 
| 
		 Before Width: | Height: | Size: 670 KiB After Width: | Height: | Size: 670 KiB  | 
| 
		 Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB  | 
| 
		 Before Width: | Height: | Size: 814 KiB After Width: | Height: | Size: 814 KiB  | 
| 
		 Before Width: | Height: | Size: 566 KiB After Width: | Height: | Size: 566 KiB  | 
| 
		 Before Width: | Height: | Size: 895 KiB After Width: | Height: | Size: 895 KiB  | 
| 
		 Before Width: | Height: | Size: 627 KiB After Width: | Height: | Size: 627 KiB  | 
| 
		 Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB  | 
| 
		 Before Width: | Height: | Size: 722 KiB After Width: | Height: | Size: 722 KiB  | 
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import os
 | 
					import os
 | 
				
			||||||
import tempfile
 | 
					import tempfile
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
from typing import cast, TYPE_CHECKING
 | 
					from typing import cast, TYPE_CHECKING
 | 
				
			||||||
from PySide6.QtGui import QPixmap, QIcon
 | 
					from PySide6.QtGui import QPixmap, QIcon
 | 
				
			||||||
from PySide6.QtWidgets import (
 | 
					from PySide6.QtWidgets import (
 | 
				
			||||||
@@ -14,6 +15,7 @@ from portprotonqt.logger import get_logger
 | 
				
			|||||||
import portprotonqt.themes.standart.styles as default_styles
 | 
					import portprotonqt.themes.standart.styles as default_styles
 | 
				
			||||||
from portprotonqt.theme_manager import ThemeManager
 | 
					from portprotonqt.theme_manager import ThemeManager
 | 
				
			||||||
from portprotonqt.custom_widgets import AutoSizeButton
 | 
					from portprotonqt.custom_widgets import AutoSizeButton
 | 
				
			||||||
 | 
					from portprotonqt.downloader import Downloader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if TYPE_CHECKING:
 | 
					if TYPE_CHECKING:
 | 
				
			||||||
    from portprotonqt.main_window import MainWindow
 | 
					    from portprotonqt.main_window import MainWindow
 | 
				
			||||||
@@ -449,6 +451,7 @@ class AddGameDialog(QDialog):
 | 
				
			|||||||
        self.original_name = game_name
 | 
					        self.original_name = game_name
 | 
				
			||||||
        self.last_exe_path = exe_path  # Store last selected exe path
 | 
					        self.last_exe_path = exe_path  # Store last selected exe path
 | 
				
			||||||
        self.last_cover_path = cover_path  # Store last selected cover path
 | 
					        self.last_cover_path = cover_path  # Store last selected cover path
 | 
				
			||||||
 | 
					        self.downloader = Downloader(max_workers=4)  # Initialize Downloader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.setWindowTitle(_("Edit Game") if edit_mode else _("Add Game"))
 | 
					        self.setWindowTitle(_("Edit Game") if edit_mode else _("Add Game"))
 | 
				
			||||||
        self.setModal(True)
 | 
					        self.setModal(True)
 | 
				
			||||||
@@ -472,8 +475,7 @@ class AddGameDialog(QDialog):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        # Exe path
 | 
					        # Exe path
 | 
				
			||||||
        exe_label = QLabel(_("Path to Executable:"))
 | 
					        exe_label = QLabel(_("Path to Executable:"))
 | 
				
			||||||
        exe_label.setStyleSheet(
 | 
					        exe_label.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
 | 
				
			||||||
            self.theme.PARAMS_TITLE_STYLE)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.exeEdit = CustomLineEdit(self, theme=self.theme)
 | 
					        self.exeEdit = CustomLineEdit(self, theme=self.theme)
 | 
				
			||||||
        self.exeEdit.setStyleSheet(self.theme.ADDGAME_INPUT_STYLE)
 | 
					        self.exeEdit.setStyleSheet(self.theme.ADDGAME_INPUT_STYLE)
 | 
				
			||||||
@@ -550,7 +552,7 @@ class AddGameDialog(QDialog):
 | 
				
			|||||||
            exeBrowseButton.setFixedWidth(self.exeEdit.width())
 | 
					            exeBrowseButton.setFixedWidth(self.exeEdit.width())
 | 
				
			||||||
            coverBrowseButton.setFixedWidth(self.coverEdit.width())
 | 
					            coverBrowseButton.setFixedWidth(self.coverEdit.width())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Вызываем после отображения окна, когда размеры установлены, чтобы реально дождаться, когда всё сформируется
 | 
					        # Вызываем после отображения окна, когда размеры установлены
 | 
				
			||||||
        QTimer.singleShot(0, update_button_widths)
 | 
					        QTimer.singleShot(0, update_button_widths)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Обновляем превью, если в режиме редактирования
 | 
					        # Обновляем превью, если в режиме редактирования
 | 
				
			||||||
@@ -615,15 +617,46 @@ class AddGameDialog(QDialog):
 | 
				
			|||||||
        """Обработчик выбора файла обложки в FileExplorer"""
 | 
					        """Обработчик выбора файла обложки в FileExplorer"""
 | 
				
			||||||
        if file_path and os.path.splitext(file_path)[1].lower() in ('.png', '.jpg', '.jpeg', '.bmp'):
 | 
					        if file_path and os.path.splitext(file_path)[1].lower() in ('.png', '.jpg', '.jpeg', '.bmp'):
 | 
				
			||||||
            self.coverEdit.setText(file_path)
 | 
					            self.coverEdit.setText(file_path)
 | 
				
			||||||
            self.last_cover_path = file_path  # Update last selected cover path
 | 
					            self.last_cover_path = file_path
 | 
				
			||||||
 | 
					            self.updatePreview()
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            logger.warning(f"Selected file is not a valid image: {file_path}")
 | 
					            logger.warning(f"Selected file is not a valid image: {file_path}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def handleDownloadedCover(self, file_path):
 | 
				
			||||||
 | 
					        """Handle the downloaded cover image and update the preview."""
 | 
				
			||||||
 | 
					        if file_path and os.path.isfile(file_path):
 | 
				
			||||||
 | 
					            self.last_cover_path = file_path
 | 
				
			||||||
 | 
					            pixmap = QPixmap(file_path)
 | 
				
			||||||
 | 
					            if not pixmap.isNull():
 | 
				
			||||||
 | 
					                self.coverPreview.setPixmap(pixmap.scaled(250, 250, Qt.AspectRatioMode.KeepAspectRatio))
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.coverPreview.setText(_("Invalid image"))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            self.coverPreview.setText(_("Failed to download cover"))
 | 
				
			||||||
 | 
					            logger.warning(f"Failed to download cover to {file_path}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def updatePreview(self):
 | 
					    def updatePreview(self):
 | 
				
			||||||
        """Update the cover preview image."""
 | 
					        """Update the cover preview image."""
 | 
				
			||||||
        cover_path = self.coverEdit.text().strip()
 | 
					        cover_path = self.coverEdit.text().strip()
 | 
				
			||||||
        exe_path = self.exeEdit.text().strip()
 | 
					        exe_path = self.exeEdit.text().strip()
 | 
				
			||||||
        if cover_path and os.path.isfile(cover_path):
 | 
					
 | 
				
			||||||
 | 
					        # Check if cover_path is a URL
 | 
				
			||||||
 | 
					        url_pattern = r'^https?://[^\s/$.?#].[^\s]*$'
 | 
				
			||||||
 | 
					        if re.match(url_pattern, cover_path):
 | 
				
			||||||
 | 
					            # Create a temporary file for the downloaded image
 | 
				
			||||||
 | 
					            fd, local_path = tempfile.mkstemp(suffix=".png")
 | 
				
			||||||
 | 
					            os.close(fd)
 | 
				
			||||||
 | 
					            os.unlink(local_path)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Start asynchronous download
 | 
				
			||||||
 | 
					            self.downloader.download_async(
 | 
				
			||||||
 | 
					                url=cover_path,
 | 
				
			||||||
 | 
					                local_path=local_path,
 | 
				
			||||||
 | 
					                timeout=10,
 | 
				
			||||||
 | 
					                callback=self.handleDownloadedCover
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            self.coverPreview.setText(_("Downloading cover..."))
 | 
				
			||||||
 | 
					        elif cover_path and os.path.isfile(cover_path):
 | 
				
			||||||
            pixmap = QPixmap(cover_path)
 | 
					            pixmap = QPixmap(cover_path)
 | 
				
			||||||
            if not pixmap.isNull():
 | 
					            if not pixmap.isNull():
 | 
				
			||||||
                self.coverPreview.setPixmap(pixmap.scaled(250, 250, Qt.AspectRatioMode.KeepAspectRatio))
 | 
					                self.coverPreview.setPixmap(pixmap.scaled(250, 250, Qt.AspectRatioMode.KeepAspectRatio))
 | 
				
			||||||
@@ -666,8 +699,8 @@ class AddGameDialog(QDialog):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        os.makedirs(os.path.dirname(icon_path), exist_ok=True)
 | 
					        os.makedirs(os.path.dirname(icon_path), exist_ok=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Generate thumbnail (128x128) from exe
 | 
					        # Generate thumbnail (128x128) from exe if no cover is provided
 | 
				
			||||||
        if not generate_thumbnail(exe_path, icon_path, size=128):
 | 
					        if not self.last_cover_path and not generate_thumbnail(exe_path, icon_path, size=128):
 | 
				
			||||||
            logger.error(f"Failed to generate thumbnail from exe: {exe_path}")
 | 
					            logger.error(f"Failed to generate thumbnail from exe: {exe_path}")
 | 
				
			||||||
            icon_path = ""  # Set empty icon if generation fails
 | 
					            icon_path = ""  # Set empty icon if generation fails
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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: 2025-07-03 19:29+0700\n"
 | 
					"POT-Creation-Date: 2025-07-06 17:56+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"
 | 
				
			||||||
@@ -296,6 +296,12 @@ msgstr ""
 | 
				
			|||||||
msgid "Invalid image"
 | 
					msgid "Invalid image"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Failed to download cover"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Downloading cover..."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "No cover selected"
 | 
					msgid "No cover selected"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -338,6 +344,9 @@ msgstr ""
 | 
				
			|||||||
msgid "Pending"
 | 
					msgid "Pending"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Unknown Game"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Library"
 | 
					msgid "Library"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -362,9 +371,6 @@ msgstr ""
 | 
				
			|||||||
msgid "Loading PortProton games..."
 | 
					msgid "Loading PortProton games..."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Unknown Game"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
msgid "Game Library"
 | 
					msgid "Game Library"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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: 2025-07-03 19:29+0700\n"
 | 
					"POT-Creation-Date: 2025-07-06 17:56+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"
 | 
				
			||||||
@@ -296,6 +296,12 @@ msgstr ""
 | 
				
			|||||||
msgid "Invalid image"
 | 
					msgid "Invalid image"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Failed to download cover"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Downloading cover..."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "No cover selected"
 | 
					msgid "No cover selected"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -338,6 +344,9 @@ msgstr ""
 | 
				
			|||||||
msgid "Pending"
 | 
					msgid "Pending"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Unknown Game"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Library"
 | 
					msgid "Library"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -362,9 +371,6 @@ msgstr ""
 | 
				
			|||||||
msgid "Loading PortProton games..."
 | 
					msgid "Loading PortProton games..."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Unknown Game"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
msgid "Game Library"
 | 
					msgid "Game Library"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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: 2025-07-03 19:29+0700\n"
 | 
					"POT-Creation-Date: 2025-07-06 17:56+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"
 | 
				
			||||||
@@ -294,6 +294,12 @@ msgstr ""
 | 
				
			|||||||
msgid "Invalid image"
 | 
					msgid "Invalid image"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Failed to download cover"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Downloading cover..."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "No cover selected"
 | 
					msgid "No cover selected"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -336,6 +342,9 @@ msgstr ""
 | 
				
			|||||||
msgid "Pending"
 | 
					msgid "Pending"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Unknown Game"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Library"
 | 
					msgid "Library"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -360,9 +369,6 @@ msgstr ""
 | 
				
			|||||||
msgid "Loading PortProton games..."
 | 
					msgid "Loading PortProton games..."
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Unknown Game"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
msgid "Game Library"
 | 
					msgid "Game Library"
 | 
				
			||||||
msgstr ""
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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: 2025-07-03 19:29+0700\n"
 | 
					"POT-Creation-Date: 2025-07-06 17:56+0500\n"
 | 
				
			||||||
"PO-Revision-Date: 2025-07-03 19:28+0700\n"
 | 
					"PO-Revision-Date: 2025-07-06 17:56+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"
 | 
				
			||||||
@@ -303,6 +303,12 @@ msgstr "Применить"
 | 
				
			|||||||
msgid "Invalid image"
 | 
					msgid "Invalid image"
 | 
				
			||||||
msgstr "Недопустимое изображение"
 | 
					msgstr "Недопустимое изображение"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Failed to download cover"
 | 
				
			||||||
 | 
					msgstr "Не удалось скачать обложку"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Downloading cover..."
 | 
				
			||||||
 | 
					msgstr "Скачивание обложки..."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "No cover selected"
 | 
					msgid "No cover selected"
 | 
				
			||||||
msgstr "Обложка не выбрана"
 | 
					msgstr "Обложка не выбрана"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -345,6 +351,9 @@ msgstr "Бронза"
 | 
				
			|||||||
msgid "Pending"
 | 
					msgid "Pending"
 | 
				
			||||||
msgstr "В ожидании"
 | 
					msgstr "В ожидании"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					msgid "Unknown Game"
 | 
				
			||||||
 | 
					msgstr "Неизвестная игра"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Library"
 | 
					msgid "Library"
 | 
				
			||||||
msgstr "Библиотека"
 | 
					msgstr "Библиотека"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -369,9 +378,6 @@ msgstr "Загрузка игр из Steam..."
 | 
				
			|||||||
msgid "Loading PortProton games..."
 | 
					msgid "Loading PortProton games..."
 | 
				
			||||||
msgstr "Загрузка игр из PortProton..."
 | 
					msgstr "Загрузка игр из PortProton..."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
msgid "Unknown Game"
 | 
					 | 
				
			||||||
msgstr "Неизвестная игра"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
msgid "Game Library"
 | 
					msgid "Game Library"
 | 
				
			||||||
msgstr "Игровая библиотека"
 | 
					msgstr "Игровая библиотека"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,7 +44,7 @@ dependencies = [
 | 
				
			|||||||
portprotonqt = "portprotonqt.app:main"
 | 
					portprotonqt = "portprotonqt.app:main"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.setuptools.package-data]
 | 
					[tool.setuptools.package-data]
 | 
				
			||||||
"portprotonqt" = ["themes/**/*", "locales/**/*", "custom_data/**/*"]
 | 
					"portprotonqt" = ["themes/**/*", "locales/**/*"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[tool.setuptools.packages.find]
 | 
					[tool.setuptools.packages.find]
 | 
				
			||||||
exclude = ["build-aux", "dev-scripts", "documentation", "data"]
 | 
					exclude = ["build-aux", "dev-scripts", "documentation", "data"]
 | 
				
			||||||
 
 | 
				
			|||||||