chore(localization): added translate support to theme name, description and screenshots
All checks were successful
Code check / Check code (push) Successful in 1m6s

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
2025-12-26 13:02:45 +05:00
parent a9e9f4e4e3
commit 613b28a751
14 changed files with 221 additions and 18 deletions

View File

@@ -31,7 +31,7 @@ mkdir -p ~/.local/share/PortProtonQT/themes/my_custom_theme
## 🎨 Style File (`styles.py`)
Create a `styles.py` in the theme root. It should define variables or functions that return QSS (Qt Style Sheets). For better organization, you can split your theme into multiple submodules by creating a `styles` subdirectory with separate Python files for different components, and import them in `styles.py`.
Create a `styles.py` in the theme root. It should define variables or functions that return QSS (Qt Style Sheets). For better organization, you can split your theme into multiple submodules by creating a subdirectory (e.g., `styles`, `components`, etc.) with separate Python files for different components, and import them in `styles.py`.
**Example of modular structure:**
```
@@ -40,7 +40,7 @@ my_custom_theme/
├── metainfo.ini
├── fonts/
├── images/
└── styles/
└── styles/ # This can be named anything (e.g., components, modules, etc.)
├── __init__.py # This empty file makes the directory a Python package
├── constants.py
├── base.py
@@ -54,7 +54,7 @@ my_custom_theme/
**Main styles.py file:**
```python
# Import from the theme's submodules using absolute paths relative to the package
# Replace 'my_custom_theme' with your actual theme folder name
# Replace 'my_custom_theme' with your actual theme folder name and 'styles' with your subdirectory name
from portprotonqt.themes.my_custom_theme.styles.constants import *
from portprotonqt.themes.my_custom_theme.styles.base import *
from portprotonqt.themes.my_custom_theme.styles.game_card import *
@@ -239,18 +239,52 @@ GAME_CARD_ANIMATION = {
```ini
[Metainfo]
name = My Custom Theme
name_en = My Custom Theme
name_ru = Моя пользовательская тема
author = Your Name
author_link = https://example.com
description = Description of your theme.
description_en = Description of your theme.
description_ru = Описание вашей темы.
```
### Translation Support
You must provide translations for your theme's name and description by adding language-specific fields:
- `name_en`, `name_ru`, etc. for theme names
- `description_en`, `description_ru`, etc. for theme descriptions
The application will automatically select the appropriate translation based on the user's system language, falling back to English if translations are not available for the user's language.
---
## 🖼 Screenshots
Folder: `images/screenshots/` — place UI screenshots there.
### Screenshot Translation Support
You can provide translations for screenshot captions by adding entries to the `[Screenshots]` section in your `metainfo.ini` file:
```ini
[Screenshots]
auto_installs_en = Auto-installs
auto_installs_ru = Автоустановки
library_en = Library
library_ru = Библиотека
game_card_en = Game Card
game_card_ru = Карточка
context_menu_en = Context Menu
context_menu_ru = Контекстное меню
portproton_settings_en = PortProton Settings
portproton_settings_ru = Настройки PortProton
wine_settings_en = Wine Settings
wine_settings_ru = Настройки Wine
themes_en = Themes
themes_ru = Темы
```
Screenshot files should be named in English (without spaces), and the application will display the appropriate translated caption based on the user's system language, falling back to English if translations are not available.
---
## 🔡 Fonts and Icons (optional)

View File

@@ -31,7 +31,7 @@ mkdir -p ~/.local/share/PortProtonQT/themes/my_custom_theme
## 🎨 Файл стилей (`styles.py`)
Создайте `styles.py` в корне темы. В нём определите переменные и/или функции, возвращающие QSS-оформление (Qt Style Sheets). Для лучшей организации кода, вы можете разделить тему на несколько подмодулей, создав поддиректорию `styles` с отдельными Python-файлами для разных компонентов, и импортировать их в `styles.py`.
Создайте `styles.py` в корне темы. В нём определите переменные и/или функции, возвращающие QSS-оформление (Qt Style Sheets). Для лучшей организации кода, вы можете разделить тему на несколько подмодулей, создав поддиректорию (например, `styles`, `components` и т.д.) с отдельными Python-файлами для разных компонентов, и импортировать их в `styles.py`.
**Пример модульной структуры:**
```
@@ -40,7 +40,7 @@ my_custom_theme/
├── metainfo.ini
├── fonts/
├── images/
└── styles/
└── styles/ # Это может быть названо как угодно (например, components, modules и т.д.)
├── __init__.py # Этот пустой файл делает директорию Python-пакетом
├── constants.py
├── base.py
@@ -54,7 +54,7 @@ my_custom_theme/
**Основной файл styles.py:**
```python
# Импорт из подмодулей темы с использованием абсолютных путей относительно пакета
# Замените 'my_custom_theme' на фактическое имя папки вашей темы
# Замените 'my_custom_theme' на фактическое имя папки вашей темы и 'styles' на имя вашей поддиректории
from portprotonqt.themes.my_custom_theme.styles.constants import *
from portprotonqt.themes.my_custom_theme.styles.base import *
from portprotonqt.themes.my_custom_theme.styles.game_card import *
@@ -239,18 +239,52 @@ GAME_CARD_ANIMATION = {
```ini
[Metainfo]
name = My Custom Theme
name_en = My Custom Theme
name_ru = Моя пользовательская тема
author = Ваше имя
author_link = https://example.com
description = Описание вашей темы.
description_en = Description of your theme.
description_ru = Описание вашей темы.
```
### Поддержка переводов
Вы должны предоставить переводы для названия и описания вашей темы, добавив поля с указанием языка:
- `name_en`, `name_ru` и т.д. для названий тем
- `description_en`, `description_ru` и т.д. для описаний тем
Приложение автоматически выберет соответствующий перевод на основе языка системы пользователя, с откатом к английскому языку, если переводы недоступны для языка пользователя.
---
## 🖼 Скриншоты
Папка: `images/screenshots/` — любые изображения оформления темы.
### Поддержка перевода скриншотов
Вы можете предоставить переводы для подписей к скриншотам, добавив записи в секцию `[Screenshots]` в файле `metainfo.ini`:
```ini
[Screenshots]
auto_installs_en = Auto-installs
auto_installs_ru = Автоустановки
library_en = Library
library_ru = Библиотека
game_card_en = Game Card
game_card_ru = Карточка
context_menu_en = Context Menu
context_menu_ru = Контекстное меню
portproton_settings_en = PortProton Settings
portproton_settings_ru = Настройки PortProton
wine_settings_en = Wine Settings
wine_settings_ru = Настройки Wine
themes_en = Themes
themes_ru = Темы
```
Файлы скриншотов должны быть названы на английском языке (без пробелов), и приложение будет отображать соответствующую переведенную подпись в зависимости от языка системы пользователя, с откатом к английскому языку, если переводы недоступны.
---
## 🔡 Шрифты и иконки (опционально)

View File

@@ -3,6 +3,7 @@ import configparser
import shutil
import subprocess
from portprotonqt.logger import get_logger
from portprotonqt.localization import get_theme_translations
logger = get_logger(__name__)
@@ -228,13 +229,17 @@ def load_theme_metainfo(theme_name):
theme_folder = os.path.join(themes_dir, theme_name)
metainfo_file = os.path.join(theme_folder, "metainfo.ini")
if os.path.exists(metainfo_file):
# Load translated theme name and description
theme_translations = get_theme_translations(metainfo_file)
cp = configparser.ConfigParser()
cp.read(metainfo_file, encoding="utf-8")
if "Metainfo" in cp:
meta["author"] = cp.get("Metainfo", "author", fallback="Unknown")
meta["author_link"] = cp.get("Metainfo", "author_link", fallback="")
meta["description"] = cp.get("Metainfo", "description", fallback="")
meta["name"] = cp.get("Metainfo", "name", fallback=theme_name)
# Use translated name and description
meta["name"] = theme_translations.get("name", theme_name)
meta["description"] = theme_translations.get("description", "")
break
return meta

View File

@@ -1,4 +1,5 @@
import gettext
import configparser
from pathlib import Path
import locale
import os
@@ -102,3 +103,97 @@ def read_metadata_translations(metadata_file, language_code):
translations['description'] = line[len('description='):].strip()
return translations
def get_screenshot_caption(base_filename, metainfo_file, language_code=None):
"""
Возвращает перевод названия скриншота на основе языка пользователя.
Args:
base_filename: Имя файла без расширения
metainfo_file: Путь к файлу metainfo.ini
language_code: Код языка (если None, будет определен автоматически)
Returns:
Переведенное название скриншота
"""
if language_code is None:
system_locale = get_system_locale()
language_code = system_locale.split('_')[0] if '_' in system_locale else system_locale
# Загружаем переводы из metainfo.ini
screenshot_translations = {}
if metainfo_file and os.path.exists(metainfo_file):
cp = configparser.ConfigParser()
cp.read(metainfo_file, encoding="utf-8")
if "Screenshots" in cp:
for key in cp.options("Screenshots"):
screenshot_translations[key] = cp.get("Screenshots", key)
# Ищем перевод в формате: base_filename_languagecode
caption = base_filename # По умолчанию используем базовое имя файла
if screenshot_translations:
# Попробуем перевод для конкретного языка (например, "library_ru")
lang_specific_key = f"{base_filename}_{language_code}"
# Попробуем английский перевод (например, "library_en")
english_key = f"{base_filename}_en"
if lang_specific_key in screenshot_translations:
caption = screenshot_translations[lang_specific_key]
elif english_key in screenshot_translations:
caption = screenshot_translations[english_key]
elif base_filename in screenshot_translations:
caption = screenshot_translations[base_filename] # fallback to untranslated key
return caption
def get_theme_translations(metainfo_file, language_code=None):
"""
Возвращает переводы названия и описания темы на основе языка пользователя.
Args:
metainfo_file: Путь к файлу metainfo.ini
language_code: Код языка (если None, будет определен автоматически)
Returns:
Словарь с полями 'name' и 'description' с переведенными значениями
"""
if language_code is None:
system_locale = get_system_locale()
language_code = system_locale.split('_')[0] if '_' in system_locale else system_locale
# Загружаем переводы из metainfo.ini
translations = {'name': '', 'description': ''}
if metainfo_file and os.path.exists(metainfo_file):
cp = configparser.ConfigParser()
cp.read(metainfo_file, encoding="utf-8")
if "Metainfo" in cp:
# Попробуем перевод названия для конкретного языка (например, "name_ru")
lang_specific_name_key = f"name_{language_code}"
# Попробуем английский перевод названия (например, "name_en")
english_name_key = "name_en"
# Ищем перевод названия
if cp.has_option("Metainfo", lang_specific_name_key):
translations['name'] = cp.get("Metainfo", lang_specific_name_key)
elif cp.has_option("Metainfo", english_name_key):
translations['name'] = cp.get("Metainfo", english_name_key)
elif cp.has_option("Metainfo", "name"):
translations['name'] = cp.get("Metainfo", "name")
# Попробуем перевод описания для конкретного языка (например, "description_ru")
lang_specific_desc_key = f"description_{language_code}"
# Попробуем английский перевод описания (например, "description_en")
english_desc_key = "description_en"
# Ищем перевод описания
if cp.has_option("Metainfo", lang_specific_desc_key):
translations['description'] = cp.get("Metainfo", lang_specific_desc_key)
elif cp.has_option("Metainfo", english_desc_key):
translations['description'] = cp.get("Metainfo", english_desc_key)
elif cp.has_option("Metainfo", "description"):
translations['description'] = cp.get("Metainfo", "description")
return translations

View File

@@ -2494,8 +2494,8 @@ class MainWindow(QMainWindow):
screenshots = load_theme_screenshots(theme_name)
if screenshots:
self.screenshotsCarousel.update_images([
(pixmap, os.path.splitext(filename)[0])
for pixmap, filename in screenshots
(pixmap, caption)
for pixmap, caption in screenshots
])
self.screenshotsCarousel.show()
else:

View File

@@ -4,6 +4,7 @@ from portprotonqt.logger import get_logger
from portprotonqt.theme_security import check_theme_safety, is_safe_image_file
from PySide6.QtGui import QIcon, QFontDatabase, QPixmap
from portprotonqt.config_utils import save_theme_to_config, load_theme_metainfo
from portprotonqt.localization import get_screenshot_caption
# Icon caching for performance optimization
_icon_cache = {}
@@ -35,10 +36,20 @@ def list_themes():
def load_theme_screenshots(theme_name):
"""
Загружает все скриншоты из папки "screenshots", расположенной в папке темы.
Возвращает список кортежей (pixmap, filename).
Возвращает список кортежей (pixmap, caption), где caption - это перевод названия скриншота.
Если папка отсутствует или пуста, возвращается пустой список.
"""
screenshots = []
# Find the metainfo file for the theme
metainfo_file = None
for themes_dir in THEMES_DIRS:
theme_folder = os.path.join(themes_dir, theme_name)
temp_metainfo_file = os.path.join(theme_folder, "metainfo.ini")
if os.path.exists(temp_metainfo_file):
metainfo_file = temp_metainfo_file
break
for themes_dir in THEMES_DIRS:
theme_folder = os.path.join(themes_dir, theme_name)
screenshots_folder = os.path.join(theme_folder, "images", "screenshots")
@@ -48,7 +59,13 @@ def load_theme_screenshots(theme_name):
if os.path.isfile(screenshot_path) and is_safe_image_file(screenshot_path):
pixmap = QPixmap(screenshot_path)
if not pixmap.isNull():
screenshots.append((pixmap, file))
# Get the base filename without extension
base_filename = os.path.splitext(file)[0]
# Get translated caption using localization function
caption = get_screenshot_caption(base_filename, metainfo_file)
screenshots.append((pixmap, caption))
return screenshots
def load_theme_fonts(theme_name):

View File

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 225 KiB

View File

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

View File

@@ -1,5 +1,23 @@
[Metainfo]
author = Dervart
author_link =
description = Стандартная тема PortProtonQt (тёмный вариант)
name = Clean Dark
name_en = Clean Dark
name_ru = Чистая темная
description_en = Standard PortProtonQt theme (dark variant)
description_ru = Стандартная тема PortProtonQt (тёмный вариант)
[Screenshots]
auto_installs_en = Auto-installs
auto_installs_ru = Автоустановки
library_en = Library
library_ru = Библиотека
game_card_en = Game Card
game_card_ru = Карточка
context_menu_en = Context Menu
context_menu_ru = Контекстное меню
portproton_settings_en = PortProton Settings
portproton_settings_ru = Настройки PortProton
wine_settings_en = Wine Settings
wine_settings_ru = Настройки Wine
themes_en = Themes
themes_ru = Темы