mirror of
https://github.com/RetroGameSets/RGSX.git
synced 2026-03-19 08:16:49 +01:00
Compare commits
39 Commits
v2.3.0.4.1
...
v2.3.1.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03d64d4401 | ||
|
|
798ef13dd3 | ||
|
|
40d0826a6b | ||
|
|
82dbf4e49d | ||
|
|
26f8499c83 | ||
|
|
0f671ccdf2 | ||
|
|
0c55d6d6d6 | ||
|
|
cdc81f795d | ||
|
|
961e46a77d | ||
|
|
7a061ef0bc | ||
|
|
57b75dc199 | ||
|
|
af42c31476 | ||
|
|
eeea4763f5 | ||
|
|
201d56fff4 | ||
|
|
3caa780e5a | ||
|
|
716fa23e4e | ||
|
|
d0eaf387b2 | ||
|
|
28a0013bee | ||
|
|
acb3eb33c3 | ||
|
|
486c9d0244 | ||
|
|
aed7da8b51 | ||
|
|
93fc4a023d | ||
|
|
b4398b1d82 | ||
|
|
751800026c | ||
|
|
8427ba60eb | ||
|
|
7fbf936af6 | ||
|
|
5fa606b3de | ||
|
|
179d10facd | ||
|
|
06c06d0223 | ||
|
|
18e5f6d637 | ||
|
|
d2a52c5a2e | ||
|
|
9df7a35e85 | ||
|
|
26f1f58d7a | ||
|
|
a9cdba3aa6 | ||
|
|
1a118247d9 | ||
|
|
5497727e2e | ||
|
|
56a6d6f17b | ||
|
|
3e769e4b18 | ||
|
|
3023a45402 |
81
.github/workflows/release.yml
vendored
81
.github/workflows/release.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
echo "Building RGSX package from ports/RGSX/ directory…"
|
||||
cd ports/RGSX
|
||||
|
||||
zip -r "../../dist/RGSX_${VERSION}.zip" . \
|
||||
zip -r "../../dist/RGSX_update_latest.zip" . \
|
||||
-x "logs/*" \
|
||||
"logs/**" \
|
||||
"images/*" \
|
||||
@@ -43,11 +43,36 @@ jobs:
|
||||
"*.log"
|
||||
|
||||
cd ../..
|
||||
cp -f "dist/RGSX_update_latest.zip" "dist/RGSX_latest.zip"
|
||||
|
||||
echo "Creating stable-named copy for latest download link…"
|
||||
cp -f "dist/RGSX_${VERSION}.zip" "dist/RGSX_latest.zip"
|
||||
echo "✓ RGSX package created successfully"
|
||||
|
||||
echo "✓ Package created successfully"
|
||||
echo ""
|
||||
echo "Building RGSX Full package (includes ports/ and windows/ directories)…"
|
||||
zip -r "dist/RGSX_full_latest.zip" ports windows \
|
||||
-x "ports/RGSX/logs/*" \
|
||||
"ports/RGSX/logs/**" \
|
||||
"ports/RGSX/images/*" \
|
||||
"ports/RGSX/images/**" \
|
||||
"ports/RGSX/games/*" \
|
||||
"ports/RGSX/games/**" \
|
||||
"ports/RGSX/scripts/*" \
|
||||
"ports/RGSX/scripts/**" \
|
||||
"ports/RGSX/__pycache__/*" \
|
||||
"ports/RGSX/__pycache__/**" \
|
||||
"ports/RGSX/*.pyc" \
|
||||
"ports/RGSX/sources.json" \
|
||||
"ports/RGSX/*.log" \
|
||||
"windows/logs/*" \
|
||||
"windows/*.xml" \
|
||||
"ports/*.xml" \
|
||||
"*.xml" \
|
||||
"*.pyc" \
|
||||
"*.xml" \
|
||||
"*.log"
|
||||
|
||||
|
||||
echo "✓ All packages created successfully"
|
||||
ls -lh dist/
|
||||
|
||||
- name: Create GitHub Release
|
||||
@@ -59,14 +84,50 @@ jobs:
|
||||
draft: false
|
||||
prerelease: false
|
||||
body: |
|
||||
## 📦 RGSX Release ${{ github.ref_name }}
|
||||
# 📦 RGSX Release ${{ github.ref_name }}
|
||||
|
||||
## 📥 Automatic Installation (Only for batocera Knulli)
|
||||
|
||||
### ON PC :
|
||||
1. Open File Manager (F1) then "Applications" and launch xterm
|
||||
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
|
||||
3. Launch RGSX from "Ports" menu
|
||||
|
||||
### ON RASPBERRY/ARM SBC / HANDHELD :
|
||||
1. Connect your device with SSH on a computer/smartphone connected to same network (ssh root@IPADDRESS , pass:linux)
|
||||
2. Use the command line `curl -L bit.ly/rgsx-install | sh`
|
||||
3. Launch RGSX from "Ports" menu
|
||||
|
||||
## 📥 Manual Installation
|
||||
|
||||
### Batocera/Knulli
|
||||
1. Download latest release : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_full_latest.zip
|
||||
2. Extract only PORTS folder in `/userdata/roms/`
|
||||
3. Launch RGSX from the Ports menu
|
||||
|
||||
### Retrobat/Full Installation on Windows
|
||||
1. Download latest release : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_full_latest.zip
|
||||
2. Extract all folders in your Retrobat\roms folder
|
||||
3. Launch RGSX from system "Windows"
|
||||
|
||||
|
||||
## 📥 Manual Update (you shouldn't need to do this as RGSX updates automatically on each start)
|
||||
|
||||
#### Batocera/Knulli
|
||||
1. Download latest update : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_update_latest.zip
|
||||
2. Extract only PORTS folder in `/userdata/roms/`
|
||||
3. Launch RGSX from the Ports menu
|
||||
|
||||
#### Retrobat
|
||||
1. Download latest update : https://github.com/RetroGameSets/RGSX/releases/latest/download/RGSX_update_latest.zip
|
||||
2. Extract all folders in your Retrobat\roms folder
|
||||
3. Launch RGSX from system "Windows"
|
||||
|
||||
|
||||
### 📥 Installation
|
||||
1. Extract to ROMS/PORTS/RGSX
|
||||
### 📖 Documentation
|
||||
[README.md](https://github.com/${{ github.repository }}/blob/main/README.md).
|
||||
[README.md](https://github.com/${{ github.repository }}/blob/main/README.md)
|
||||
files: |
|
||||
dist/RGSX_${{ github.ref_name }}.zip
|
||||
dist/RGSX_latest.zip
|
||||
dist/RGSX_update_latest.zip
|
||||
dist/RGSX_full_latest.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -11,10 +11,13 @@ ports/gamelist.xml
|
||||
prboom/
|
||||
*.log
|
||||
*.rar
|
||||
*.zip
|
||||
.vscode/
|
||||
ports/RGSX.bat
|
||||
.venv/
|
||||
audit_i18n.py
|
||||
prune_i18n.py
|
||||
Info.txt
|
||||
|
||||
# Docker test data
|
||||
data/
|
||||
|
||||
|
||||
40
docker/Dockerfile
Normal file
40
docker/Dockerfile
Normal file
@@ -0,0 +1,40 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Install system dependencies for ROM extraction
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
p7zip-full \
|
||||
unrar-free \
|
||||
curl \
|
||||
rsync \
|
||||
gosu \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create required directories
|
||||
RUN mkdir -p /userdata/saves/ports/rgsx \
|
||||
&& mkdir -p /userdata/roms/ports \
|
||||
&& mkdir -p /app
|
||||
|
||||
# Copy RGSX application files to /app (will be copied to volume at runtime)
|
||||
COPY ports/RGSX/ /app/RGSX/
|
||||
|
||||
# Copy entrypoint script
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
# Install Python dependencies
|
||||
# pygame is imported in some modules even in headless mode, so we include it
|
||||
RUN pip install --no-cache-dir requests pygame
|
||||
|
||||
# Set environment to headless mode
|
||||
ENV RGSX_HEADLESS=1
|
||||
|
||||
# Expose web interface port
|
||||
EXPOSE 5000
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
|
||||
CMD curl -f http://localhost:5000/ || exit 1
|
||||
|
||||
# Entrypoint copies app to volume, then runs command
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
CMD ["python", "rgsx_web.py", "--host", "0.0.0.0", "--port", "5000"]
|
||||
179
docker/README-DOCKER.md
Normal file
179
docker/README-DOCKER.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# 🐳 RGSX Docker - Headless Web Server
|
||||
|
||||
Run RGSX as a web-only service without the Pygame UI. Perfect for homelab/server setups.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Build the image
|
||||
docker build -t rgsx .
|
||||
|
||||
# Run with docker
|
||||
docker run -d \
|
||||
--name rgsx \
|
||||
-p 5000:5000 \
|
||||
-e PUID=99 \
|
||||
-e PGID=100 \
|
||||
-e RGSX_HEADLESS=1 \
|
||||
-v ./data/saves:/userdata/saves/ports/rgsx \
|
||||
-v ./data/roms:/userdata/roms/ports \
|
||||
-v ./data/logs:/userdata/roms/ports/RGSX/logs \
|
||||
rgsx
|
||||
|
||||
# Access the web interface
|
||||
open http://localhost:5000
|
||||
```
|
||||
|
||||
## What This Does
|
||||
|
||||
- Runs RGSX web server in headless mode (no Pygame UI)
|
||||
- Web interface accessible from any browser
|
||||
- ROMs and settings persist in `./data/` volumes
|
||||
- Container restarts automatically
|
||||
|
||||
## Configuration
|
||||
|
||||
### User Permissions (Important!)
|
||||
|
||||
**For SMB mounts (Unraid, Windows shares):**
|
||||
|
||||
Don't set PUID/PGID. The container runs as root, and the SMB server maps files to your authenticated user.
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
-e RGSX_HEADLESS=1 \
|
||||
...
|
||||
```
|
||||
|
||||
**For NFS/local storage:**
|
||||
|
||||
Set PUID and PGID to match your host user. Files will be owned by that user.
|
||||
|
||||
```bash
|
||||
docker run \
|
||||
-e PUID=1000 \
|
||||
-e PGID=1000 \
|
||||
-e RGSX_HEADLESS=1 \
|
||||
...
|
||||
```
|
||||
|
||||
**Find your user ID:**
|
||||
```bash
|
||||
id -u # Your UID
|
||||
id -g # Your GID
|
||||
```
|
||||
|
||||
### Change Port
|
||||
|
||||
```bash
|
||||
docker run -p 8080:5000 ... # Access on port 8080
|
||||
```
|
||||
|
||||
### Custom ROM Location
|
||||
|
||||
Map to your existing ROM collection:
|
||||
```bash
|
||||
docker run -v /your/existing/roms:/userdata/roms/ports ...
|
||||
```
|
||||
|
||||
### API Keys
|
||||
|
||||
Add your download service API keys to `./data/saves/`:
|
||||
|
||||
```bash
|
||||
# Add your API key (just the key, no extra text)
|
||||
echo "YOUR_KEY_HERE" > ./data/saves/1FichierAPI.txt
|
||||
|
||||
# Optional: AllDebrid/RealDebrid fallbacks
|
||||
echo "YOUR_KEY" > ./data/saves/AllDebridAPI.txt
|
||||
echo "YOUR_KEY" > ./data/saves/RealDebridAPI.txt
|
||||
|
||||
# Restart to apply
|
||||
docker restart rgsx
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Start
|
||||
docker start rgsx
|
||||
|
||||
# View logs
|
||||
docker logs -f rgsx
|
||||
|
||||
# Stop
|
||||
docker stop rgsx
|
||||
|
||||
# Update (after git pull)
|
||||
docker build --no-cache -t rgsx .
|
||||
docker stop rgsx && docker rm rgsx
|
||||
# Then re-run the docker run command
|
||||
```
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
RGSX/
|
||||
├── data/ # Created on first run
|
||||
│ ├── saves/ # Settings, history, API keys
|
||||
│ ├── roms/ # Downloaded ROMs
|
||||
│ └── logs/ # Application logs
|
||||
├── Dockerfile
|
||||
└── docker-compose.yml
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
RGSX already has a headless mode (`RGSX_HEADLESS=1`) and the web server (`rgsx_web.py`) works standalone - this was designed for the Batocera web service. The Docker setup just runs it in a container with proper volume mappings.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Permission denied errors / Can't delete files:**
|
||||
|
||||
The container creates files with the UID/GID specified by PUID/PGID environment variables:
|
||||
|
||||
```bash
|
||||
# Set correct PUID/PGID for your environment
|
||||
docker run -e PUID=1000 -e PGID=1000 ...
|
||||
```
|
||||
|
||||
**Changed PUID/PGID and container won't start:**
|
||||
|
||||
When you change PUID/PGID, old files with different ownership will cause rsync to fail. You MUST fix ownership on the storage server:
|
||||
|
||||
```bash
|
||||
# On your NAS/Unraid (via SSH), either:
|
||||
|
||||
# Option 1: Delete old files (easiest)
|
||||
rm -rf /mnt/user/roms/rgsx/roms/ports/RGSX/*
|
||||
|
||||
# Option 2: Change ownership to new PUID/PGID
|
||||
chown -R 1000:1000 /mnt/user/roms/rgsx/roms/ports/RGSX/
|
||||
```
|
||||
|
||||
Then restart the container.
|
||||
|
||||
**Port already in use:**
|
||||
```bash
|
||||
docker run -p 8080:5000 ... # Use port 8080 instead
|
||||
```
|
||||
|
||||
**Container won't start:**
|
||||
```bash
|
||||
docker logs rgsx
|
||||
```
|
||||
|
||||
## vs Traditional Install
|
||||
|
||||
| Feature | Docker | Batocera/RetroBat |
|
||||
|---------|--------|-------------------|
|
||||
| Interface | Web only | Pygame UI + Web |
|
||||
| Install | `docker run` | Manual setup |
|
||||
| Updates | `docker build` | git pull |
|
||||
| Access | Any device on network | Device only |
|
||||
| Use Case | Server/homelab | Gaming device |
|
||||
|
||||
## Support
|
||||
|
||||
- RGSX Issues: https://github.com/RetroGameSets/RGSX/issues
|
||||
- Discord: https://discord.gg/Vph9jwg3VV
|
||||
72
docker/docker-entrypoint.sh
Normal file
72
docker/docker-entrypoint.sh
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# If PUID/PGID are set, create user and run as that user
|
||||
# If not set, run as root (works for SMB mounts)
|
||||
if [ -n "$PUID" ] && [ -n "$PGID" ]; then
|
||||
echo "=== Creating user with PUID=$PUID, PGID=$PGID ==="
|
||||
|
||||
# Create group if it doesn't exist
|
||||
if ! getent group $PGID >/dev/null 2>&1; then
|
||||
groupadd -g $PGID rgsx
|
||||
fi
|
||||
|
||||
# Create user if it doesn't exist
|
||||
if ! getent passwd $PUID >/dev/null 2>&1; then
|
||||
useradd -u $PUID -g $PGID -m -s /bin/bash rgsx
|
||||
fi
|
||||
|
||||
# Fix ownership of app files
|
||||
chown -R $PUID:$PGID /app /userdata 2>/dev/null || true
|
||||
|
||||
echo "=== Running as user $(id -un $PUID) (UID=$PUID, GID=$PGID) ==="
|
||||
RUN_USER="gosu rgsx"
|
||||
else
|
||||
echo "=== Running as root (no PUID/PGID set) - for SMB mounts ==="
|
||||
RUN_USER=""
|
||||
fi
|
||||
|
||||
# Always sync RGSX app code to the mounted volume (for updates)
|
||||
echo "Syncing RGSX app code to /userdata/roms/ports/RGSX..."
|
||||
$RUN_USER mkdir -p /userdata/roms/ports/RGSX
|
||||
|
||||
# Try rsync
|
||||
if ! $RUN_USER rsync -av --delete /app/RGSX/ /userdata/roms/ports/RGSX/ 2>&1; then
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "WARNING: rsync partially failed!"
|
||||
echo "=========================================="
|
||||
echo "Some files may not have synced. Container will continue for debugging."
|
||||
echo ""
|
||||
if [ -n "$PUID" ] && [ -n "$PGID" ]; then
|
||||
echo "If using SMB, try removing PUID/PGID to run as root"
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "RGSX app code sync attempted."
|
||||
|
||||
# Create Batocera folder structure only if folders don't exist
|
||||
$RUN_USER mkdir -p /userdata/saves/ports/rgsx/images
|
||||
$RUN_USER mkdir -p /userdata/saves/ports/rgsx/games
|
||||
$RUN_USER mkdir -p /userdata/roms/ports/RGSX/logs
|
||||
|
||||
# Create default settings with show_unsupported_platforms enabled if config doesn't exist
|
||||
SETTINGS_FILE="/userdata/saves/ports/rgsx/rgsx_settings.json"
|
||||
if [ ! -f "$SETTINGS_FILE" ]; then
|
||||
echo "Creating default settings with all platforms visible..."
|
||||
$RUN_USER bash -c "cat > '$SETTINGS_FILE' << 'EOF'
|
||||
{
|
||||
\"show_unsupported_platforms\": true
|
||||
}
|
||||
EOF"
|
||||
echo "Default settings created!"
|
||||
fi
|
||||
|
||||
# Run the command
|
||||
cd /userdata/roms/ports/RGSX
|
||||
if [ -z "$RUN_USER" ]; then
|
||||
exec "$@"
|
||||
else
|
||||
exec $RUN_USER "$@"
|
||||
fi
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
# Supprimer SDL_VIDEODRIVER=fbcon pour laisser SDL choisir le pilote
|
||||
# export SDL_VIDEODRIVER=fbcon
|
||||
/usr/bin/python3 /userdata/roms/ports/RGSX
|
||||
#!/usr/bin/env python3
|
||||
SCRIPT_DIR=$(dirname "$(realpath "$0")")
|
||||
python3 "$SCRIPT_DIR/__main__.py"
|
||||
@@ -541,7 +541,6 @@ async def main():
|
||||
config.needs_redraw = True
|
||||
logger.info("Scraping terminé")
|
||||
|
||||
import threading
|
||||
thread = threading.Thread(target=scrape_async, daemon=True)
|
||||
thread.start()
|
||||
|
||||
@@ -712,7 +711,7 @@ async def main():
|
||||
config.history.append({
|
||||
"platform": platform_name,
|
||||
"game_name": game_name,
|
||||
"status": "downloading",
|
||||
"status": "Downloading",
|
||||
"progress": 0,
|
||||
"url": url,
|
||||
"timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
@@ -822,7 +821,7 @@ async def main():
|
||||
config.history.append({
|
||||
"platform": platform_name,
|
||||
"game_name": game_name,
|
||||
"status": "downloading",
|
||||
"status": "Downloading",
|
||||
"progress": 0,
|
||||
"message": _("download_in_progress") if _ else "Download in progress",
|
||||
"url": url,
|
||||
@@ -868,7 +867,7 @@ async def main():
|
||||
config.history.append({
|
||||
"platform": platform_name,
|
||||
"game_name": game_name,
|
||||
"status": "downloading",
|
||||
"status": "Downloading",
|
||||
"progress": 0,
|
||||
"message": _("download_in_progress") if _ else "Download in progress",
|
||||
"url": url,
|
||||
@@ -909,7 +908,7 @@ async def main():
|
||||
logger.debug(f"[HISTORY_SEARCH] Searching in {len(config.history)} history entries for url={url[:50]}...")
|
||||
for entry in config.history:
|
||||
#logger.debug(f"[HISTORY_ENTRY] Checking: url_match={entry['url'] == url}, status={entry['status']}, game={entry.get('game_name')}")
|
||||
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
|
||||
if entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
|
||||
#logger.debug(f"[HISTORY_MATCH] Found matching entry for {game_name}, updating status")
|
||||
entry["status"] = "Download_OK" if success else "Erreur"
|
||||
entry["progress"] = 100 if success else 0
|
||||
@@ -941,7 +940,7 @@ async def main():
|
||||
if "http" in message:
|
||||
message = message.split("https://")[0].strip()
|
||||
for entry in config.history:
|
||||
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
|
||||
if entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
|
||||
entry["status"] = "Erreur"
|
||||
entry["progress"] = 0
|
||||
entry["message"] = message
|
||||
@@ -972,7 +971,7 @@ async def main():
|
||||
success, message = data[1], data[2]
|
||||
logger.debug(f"[DOWNLOAD_TASK] Download task done - success={success}, message={message}, task_id={task_id}")
|
||||
for entry in config.history:
|
||||
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
|
||||
if entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
|
||||
entry["status"] = "Download_OK" if success else "Erreur"
|
||||
entry["progress"] = 100 if success else 0
|
||||
entry["message"] = message
|
||||
@@ -1002,7 +1001,7 @@ async def main():
|
||||
downloaded, total_size = data[1], data[2]
|
||||
progress = (downloaded / total_size * 100) if total_size > 0 else 0
|
||||
for entry in config.history:
|
||||
if entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
|
||||
if entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
|
||||
entry["progress"] = progress
|
||||
entry["status"] = "Téléchargement"
|
||||
config.needs_redraw = True
|
||||
|
||||
BIN
ports/RGSX/assets/progs/extract-xiso_linux
Normal file
BIN
ports/RGSX/assets/progs/extract-xiso_linux
Normal file
Binary file not shown.
BIN
ports/RGSX/assets/progs/extract-xiso_win.exe
Normal file
BIN
ports/RGSX/assets/progs/extract-xiso_win.exe
Normal file
Binary file not shown.
103
ports/RGSX/assets/progs/rgsx_web
Normal file
103
ports/RGSX/assets/progs/rgsx_web
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/bin/bash
|
||||
# BATOCERA SERVICE
|
||||
# name: RGSX Web Service for Batocera
|
||||
# description: Automatic launch Web interface service for RGSX
|
||||
# author: RetroGameSets / ninao.xyz
|
||||
# depends: python3
|
||||
# version: 1.3
|
||||
|
||||
SCRIPT="/userdata/roms/ports/RGSX/rgsx_web.py"
|
||||
PYTHON="/usr/bin/python3"
|
||||
PIDFILE="/var/run/rgsx_web.pid"
|
||||
LOGFILE="/userdata/roms/ports/RGSX/LOGS/rgsx_web_service.log"
|
||||
SERVICE_NAME="rgsx_web"
|
||||
|
||||
# Fonction utilitaire : vérifie si le service est activé dans batocera-settings
|
||||
is_enabled() {
|
||||
local enabled_services
|
||||
enabled_services="$(/usr/bin/batocera-settings-get system.services 2>/dev/null)"
|
||||
for s in $enabled_services; do
|
||||
if [ "$s" = "$SERVICE_NAME" ]; then
|
||||
echo "enabled"
|
||||
return
|
||||
fi
|
||||
done
|
||||
echo "disabled"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
if [ ! -f "$SCRIPT" ]; then
|
||||
echo "[${SERVICE_NAME}] Error: script not found at $SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
|
||||
echo "[${SERVICE_NAME}] Already running (PID $(cat "$PIDFILE"))"
|
||||
exit 0
|
||||
fi
|
||||
echo "[${SERVICE_NAME}] Starting..."
|
||||
mkdir -p "$(dirname "$LOGFILE")"
|
||||
$PYTHON "$SCRIPT" >> "$LOGFILE" 2>&1 &
|
||||
echo $! > "$PIDFILE"
|
||||
echo "[${SERVICE_NAME}] Started (PID $(cat "$PIDFILE"))"
|
||||
;;
|
||||
|
||||
stop)
|
||||
if [ -f "$PIDFILE" ]; then
|
||||
echo "[${SERVICE_NAME}] Stopping..."
|
||||
kill $(cat "$PIDFILE") 2>/dev/null && rm -f "$PIDFILE"
|
||||
echo "[${SERVICE_NAME}] Stopped"
|
||||
else
|
||||
pkill -f "$PYTHON $SCRIPT" && echo "[${SERVICE_NAME}] Stopped (no PID file)"
|
||||
fi
|
||||
;;
|
||||
|
||||
restart)
|
||||
echo "[${SERVICE_NAME}] Restarting..."
|
||||
"$0" stop
|
||||
sleep 1
|
||||
"$0" start
|
||||
;;
|
||||
|
||||
status)
|
||||
ENABLE_STATE=$(is_enabled)
|
||||
if [ -f "$PIDFILE" ] && kill -0 $(cat "$PIDFILE") 2>/dev/null; then
|
||||
echo "[${SERVICE_NAME}] Running (PID $(cat "$PIDFILE")) - ${ENABLE_STATE} on boot"
|
||||
exit 0
|
||||
elif pgrep -f "$PYTHON $SCRIPT" > /dev/null; then
|
||||
echo "[${SERVICE_NAME}] Running (detected without PID file) - ${ENABLE_STATE} on boot"
|
||||
exit 0
|
||||
else
|
||||
echo "[${SERVICE_NAME}] Not running - ${ENABLE_STATE} on boot"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
|
||||
enable)
|
||||
current=$(/usr/bin/batocera-settings-get system.services 2>/dev/null)
|
||||
if echo "$current" | grep -qw "$SERVICE_NAME"; then
|
||||
echo "[${SERVICE_NAME}] Already enabled on boot"
|
||||
else
|
||||
new_value="$current $SERVICE_NAME"
|
||||
/usr/bin/batocera-settings-set system.services "$new_value"
|
||||
echo "[${SERVICE_NAME}] Enabled on boot"
|
||||
fi
|
||||
;;
|
||||
|
||||
disable)
|
||||
current=$(/usr/bin/batocera-settings-get system.services 2>/dev/null)
|
||||
if echo "$current" | grep -qw "$SERVICE_NAME"; then
|
||||
new_value=$(echo "$current" | sed "s/\b$SERVICE_NAME\b//g" | xargs)
|
||||
/usr/bin/batocera-settings-set system.services "$new_value"
|
||||
echo "[${SERVICE_NAME}] Disabled on boot"
|
||||
else
|
||||
echo "[${SERVICE_NAME}] Already disabled"
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status|enable|disable}"
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -13,7 +13,7 @@ except Exception:
|
||||
pygame = None # type: ignore
|
||||
|
||||
# Version actuelle de l'application
|
||||
app_version = "2.3.0.4.1"
|
||||
app_version = "2.3.1.6"
|
||||
|
||||
|
||||
def get_application_root():
|
||||
@@ -97,8 +97,8 @@ GITHUB_RELEASES_URL = f"https://github.com/{GITHUB_REPO}/releases"
|
||||
|
||||
# URLs pour les mises à jour OTA (Over-The-Air)
|
||||
# Utilise le fichier RGSX_latest.zip qui pointe toujours vers la dernière version
|
||||
OTA_UPDATE_ZIP = f"{GITHUB_RELEASES_URL}/latest/download/RGSX_latest.zip"
|
||||
OTA_VERSION_ENDPOINT = "https://retrogamesets.fr/softs/version.json" # Endpoint pour vérifier la version disponible
|
||||
OTA_UPDATE_ZIP = f"{GITHUB_RELEASES_URL}/latest/download/RGSX_update_latest.zip"
|
||||
OTA_VERSION_ENDPOINT = "https://raw.githubusercontent.com/RetroGameSets/RGSX/refs/heads/main/version.json" # Endpoint pour vérifier la version disponible
|
||||
|
||||
# URLs legacy (conservées pour compatibilité)
|
||||
OTA_SERVER_URL = "https://retrogamesets.fr/softs/"
|
||||
@@ -106,8 +106,8 @@ OTA_data_ZIP = os.path.join(OTA_SERVER_URL, "games.zip")
|
||||
|
||||
#CHEMINS DES EXECUTABLES
|
||||
UNRAR_EXE = os.path.join(APP_FOLDER,"assets","progs","unrar.exe")
|
||||
XDVDFS_EXE = os.path.join(APP_FOLDER,"assets", "progs", "xdvdfs.exe")
|
||||
XDVDFS_LINUX = os.path.join(APP_FOLDER,"assets", "progs", "xdvdfs")
|
||||
XISO_EXE = os.path.join(APP_FOLDER,"assets", "progs", "extract-xiso_win.exe")
|
||||
XISO_LINUX = os.path.join(APP_FOLDER,"assets", "progs", "extact_xiso_linux")
|
||||
PS3DEC_EXE = os.path.join(APP_FOLDER,"assets", "progs", "ps3dec_win.exe")
|
||||
PS3DEC_LINUX = os.path.join(APP_FOLDER,"assets", "progs", "ps3dec_linux")
|
||||
SEVEN_Z_LINUX = os.path.join(APP_FOLDER,"assets", "progs", "7zz")
|
||||
|
||||
@@ -6,20 +6,29 @@ import re
|
||||
import os
|
||||
import datetime
|
||||
import threading
|
||||
import logging
|
||||
import config
|
||||
from config import REPEAT_DELAY, REPEAT_INTERVAL, REPEAT_ACTION_DEBOUNCE
|
||||
from config import CONTROLS_CONFIG_PATH
|
||||
from config import REPEAT_DELAY, REPEAT_INTERVAL, REPEAT_ACTION_DEBOUNCE, CONTROLS_CONFIG_PATH
|
||||
from display import draw_validation_transition, show_toast
|
||||
from network import download_rom, download_from_1fichier, is_1fichier_url, request_cancel
|
||||
from utils import (
|
||||
load_games, check_extension_before_download, is_extension_supported,
|
||||
load_extensions_json, play_random_music, sanitize_filename,
|
||||
save_music_config, load_api_keys, _get_dest_folder_name,
|
||||
extract_zip, extract_rar, find_file_with_or_without_extension
|
||||
extract_zip, extract_rar, find_file_with_or_without_extension, toggle_web_service_at_boot, check_web_service_status,
|
||||
restart_application, generate_support_zip, load_sources,
|
||||
ensure_download_provider_keys, missing_all_provider_keys, build_provider_paths_string
|
||||
)
|
||||
from history import load_history, clear_history, add_to_history, save_history
|
||||
import logging
|
||||
from language import _ # Import de la fonction de traduction
|
||||
from language import _, get_available_languages, set_language
|
||||
from rgsx_settings import (
|
||||
get_allow_unknown_extensions, set_display_grid, get_font_family, set_font_family,
|
||||
get_show_unsupported_platforms, set_show_unsupported_platforms,
|
||||
set_allow_unknown_extensions, get_hide_premium_systems, set_hide_premium_systems,
|
||||
get_sources_mode, set_sources_mode, set_symlink_option, get_symlink_option, load_rgsx_settings, save_rgsx_settings
|
||||
)
|
||||
from accessibility import save_accessibility_settings
|
||||
from scraper import get_game_metadata, download_image_to_surface
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -214,10 +223,10 @@ def _launch_next_queued_download():
|
||||
is_1fichier = queue_item['is_1fichier']
|
||||
task_id = queue_item['task_id']
|
||||
|
||||
# Mettre à jour le statut dans l'historique: queued -> downloading
|
||||
# Mettre à jour le statut dans l'historique: queued -> Downloading
|
||||
for entry in config.history:
|
||||
if entry.get('task_id') == task_id and entry.get('status') == 'queued':
|
||||
entry['status'] = 'downloading'
|
||||
if entry.get('task_id') == task_id and entry.get('status') == 'Queued':
|
||||
entry['status'] = 'Downloading'
|
||||
entry['message'] = _("download_in_progress")
|
||||
save_history(config.history)
|
||||
break
|
||||
@@ -640,12 +649,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
load_extensions_json()
|
||||
)
|
||||
zip_ok = bool(pending_download[3])
|
||||
allow_unknown = False
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
except Exception:
|
||||
allow_unknown = False
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
|
||||
# Si extension non supportée ET pas en archive connu, afficher avertissement
|
||||
if (not is_supported and not zip_ok) and not allow_unknown:
|
||||
@@ -665,15 +669,15 @@ def handle_controls(event, sources, joystick, screen):
|
||||
'is_zip_non_supported': pending_download[3],
|
||||
'is_1fichier': is_1fichier_url(url),
|
||||
'task_id': task_id,
|
||||
'status': 'queued'
|
||||
'status': 'Queued'
|
||||
}
|
||||
config.download_queue.append(queue_item)
|
||||
|
||||
# Ajouter une entrée à l'historique avec status "queued"
|
||||
# Ajouter une entrée à l'historique avec status "Queued"
|
||||
config.history.append({
|
||||
'platform': platform,
|
||||
'game_name': game_name,
|
||||
'status': 'queued',
|
||||
'status': 'Queued',
|
||||
'url': url,
|
||||
'progress': 0,
|
||||
'message': _("download_queued"),
|
||||
@@ -724,7 +728,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
if config.pending_download and len(config.pending_download) == 4:
|
||||
url, platform, game_name, is_zip_non_supported = config.pending_download
|
||||
if is_1fichier_url(url):
|
||||
from utils import ensure_download_provider_keys, missing_all_provider_keys, build_provider_paths_string
|
||||
ensure_download_provider_keys(False)
|
||||
|
||||
|
||||
@@ -827,7 +830,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
elif is_input_matched(event, "cancel") or is_input_matched(event, "history"):
|
||||
if config.history and config.current_history_item < len(config.history):
|
||||
entry = config.history[config.current_history_item]
|
||||
if entry.get("status") in ["downloading", "Téléchargement", "Extracting"] and is_input_matched(event, "cancel"):
|
||||
if entry.get("status") in ["Downloading", "Téléchargement", "Extracting"] and is_input_matched(event, "cancel"):
|
||||
config.menu_state = "confirm_cancel_download"
|
||||
config.confirm_cancel_selection = 0
|
||||
config.needs_redraw = True
|
||||
@@ -889,7 +892,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# 0 = Non, 1 = Oui
|
||||
if config.confirm_clear_selection == 1: # Oui
|
||||
clear_history()
|
||||
config.history = []
|
||||
config.history = load_history() # Recharger l'historique (conserve les téléchargements en cours)
|
||||
config.current_history_item = 0
|
||||
config.history_scroll_offset = 0
|
||||
config.menu_state = "history"
|
||||
@@ -1020,7 +1023,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# Lancer la recherche des métadonnées dans un thread séparé
|
||||
|
||||
def scrape_async():
|
||||
from scraper import get_game_metadata, download_image_to_surface
|
||||
logger.info(f"Scraping métadonnées pour {game_name} sur {platform}")
|
||||
metadata = get_game_metadata(game_name, platform)
|
||||
|
||||
@@ -1078,7 +1080,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
url = entry.get("url")
|
||||
if url:
|
||||
# Mettre à jour le statut
|
||||
entry["status"] = "downloading"
|
||||
entry["status"] = "Downloading"
|
||||
entry["progress"] = 0
|
||||
entry["message"] = "Téléchargement en cours"
|
||||
save_history(config.history)
|
||||
@@ -1209,7 +1211,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
entry = config.history[config.current_history_item]
|
||||
platform = entry.get("platform", "")
|
||||
|
||||
# threading est déjà importé en haut du fichier (ligne 8)
|
||||
|
||||
# Utiliser le chemin réel trouvé (avec ou sans extension)
|
||||
file_path = getattr(config, 'history_actual_path', None)
|
||||
@@ -1283,7 +1284,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# Mark all in-progress downloads as canceled in history
|
||||
try:
|
||||
for entry in getattr(config, 'history', []) or []:
|
||||
if entry.get("status") in ["downloading", "Téléchargement", "Extracting"]:
|
||||
if entry.get("status") in ["Downloading", "Téléchargement", "Extracting"]:
|
||||
entry["status"] = "Canceled"
|
||||
entry["progress"] = 0
|
||||
entry["message"] = _("download_canceled") if _ else "Download canceled"
|
||||
@@ -1355,10 +1356,8 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
elif config.selected_option == 5: # Restart
|
||||
from utils import restart_application
|
||||
restart_application(2000)
|
||||
elif config.selected_option == 6: # Support
|
||||
from utils import generate_support_zip
|
||||
success, message, zip_path = generate_support_zip()
|
||||
if success:
|
||||
config.support_zip_path = zip_path
|
||||
@@ -1435,7 +1434,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
idx = (idx + 1) % len(layouts) if is_input_matched(event, "right") else (idx - 1) % len(layouts)
|
||||
new_cols, new_rows = layouts[idx]
|
||||
try:
|
||||
from rgsx_settings import set_display_grid
|
||||
set_display_grid(new_cols, new_rows)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur set_display_grid: {e}")
|
||||
@@ -1443,7 +1441,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.GRID_ROWS = new_rows
|
||||
# Redémarrage automatique
|
||||
try:
|
||||
from utils import restart_application
|
||||
config.menu_state = "restart_popup"
|
||||
config.popup_message = _("popup_restarting") if _ else "Restarting..."
|
||||
config.popup_timer = 2000
|
||||
@@ -1453,7 +1450,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
# 1 font size
|
||||
elif sel == 1 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
from accessibility import save_accessibility_settings
|
||||
opts = getattr(config, 'font_scale_options', [0.75,1.0,1.25,1.5,1.75])
|
||||
idx = getattr(config, 'current_font_scale_index', 1)
|
||||
idx = max(0, idx-1) if is_input_matched(event, "left") else min(len(opts)-1, idx+1)
|
||||
@@ -1473,7 +1469,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# 2 font family cycle
|
||||
elif sel == 2 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
|
||||
try:
|
||||
from rgsx_settings import get_font_family, set_font_family
|
||||
families = getattr(config, 'FONT_FAMILIES', ["pixel"]) or ["pixel"]
|
||||
current = get_font_family()
|
||||
try:
|
||||
@@ -1508,10 +1503,8 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# 3 unsupported toggle
|
||||
elif sel == 3 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
|
||||
try:
|
||||
from rgsx_settings import get_show_unsupported_platforms, set_show_unsupported_platforms
|
||||
current = get_show_unsupported_platforms()
|
||||
new_val = set_show_unsupported_platforms(not current)
|
||||
from utils import load_sources
|
||||
load_sources()
|
||||
config.popup_message = _("menu_show_unsupported_enabled") if new_val else _("menu_show_unsupported_disabled")
|
||||
config.popup_timer = 3000
|
||||
@@ -1521,7 +1514,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# 4 allow unknown extensions
|
||||
elif sel == 4 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions, set_allow_unknown_extensions
|
||||
current = get_allow_unknown_extensions()
|
||||
new_val = set_allow_unknown_extensions(not current)
|
||||
config.popup_message = _("menu_allow_unknown_ext_enabled") if new_val else _("menu_allow_unknown_ext_disabled")
|
||||
@@ -1532,7 +1524,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# 5 hide premium systems
|
||||
elif sel == 5 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
try:
|
||||
from rgsx_settings import get_hide_premium_systems, set_hide_premium_systems
|
||||
cur = get_hide_premium_systems()
|
||||
new_val = set_hide_premium_systems(not cur)
|
||||
config.popup_message = ("Premium hidden" if new_val else "Premium visible") if _ is None else (_("popup_hide_premium_on") if new_val else _("popup_hide_premium_off"))
|
||||
@@ -1578,7 +1569,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
elif sel == 1 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
try:
|
||||
from rgsx_settings import get_sources_mode, set_sources_mode
|
||||
current_mode = get_sources_mode()
|
||||
new_mode = set_sources_mode('custom' if current_mode == 'rgsx' else 'rgsx')
|
||||
config.sources_mode = new_mode
|
||||
@@ -1609,7 +1599,13 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# Sous-menu Settings
|
||||
elif config.menu_state == "pause_settings_menu":
|
||||
sel = getattr(config, 'pause_settings_selection', 0)
|
||||
# Calculer le nombre total d'options selon le système
|
||||
total = 4 # music, symlink, api keys, back
|
||||
web_service_index = -1
|
||||
if config.OPERATING_SYSTEM == "Linux":
|
||||
total = 5 # music, symlink, web_service, api keys, back
|
||||
web_service_index = 2
|
||||
|
||||
if is_input_matched(event, "up"):
|
||||
config.pause_settings_selection = (sel - 1) % total
|
||||
config.needs_redraw = True
|
||||
@@ -1618,6 +1614,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
elif is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right"):
|
||||
sel = getattr(config, 'pause_settings_selection', 0)
|
||||
# Option 0: Music toggle
|
||||
if sel == 0 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
config.music_enabled = not config.music_enabled
|
||||
save_music_config()
|
||||
@@ -1630,18 +1627,39 @@ def handle_controls(event, sources, joystick, screen):
|
||||
pygame.mixer.music.stop()
|
||||
config.needs_redraw = True
|
||||
logger.info(f"Musique {'activée' if config.music_enabled else 'désactivée'} via settings")
|
||||
# Option 1: Symlink toggle
|
||||
elif sel == 1 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
from rgsx_settings import set_symlink_option, get_symlink_option
|
||||
current_status = get_symlink_option()
|
||||
success, message = set_symlink_option(not current_status)
|
||||
config.popup_message = message
|
||||
config.popup_timer = 3000 if success else 5000
|
||||
config.needs_redraw = True
|
||||
logger.info(f"Symlink option {'activée' if not current_status else 'désactivée'} via settings")
|
||||
elif sel == 2 and is_input_matched(event, "confirm"):
|
||||
# Option 2: Web Service toggle (seulement si Linux)
|
||||
elif sel == web_service_index and web_service_index >= 0 and (is_input_matched(event, "confirm") or is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
|
||||
current_status = check_web_service_status()
|
||||
# Afficher un message de chargement
|
||||
config.popup_message = _("settings_web_service_enabling") if not current_status else _("settings_web_service_disabling")
|
||||
config.popup_timer = 1000
|
||||
config.needs_redraw = True
|
||||
# Exécuter en thread pour ne pas bloquer l'UI
|
||||
def toggle_service():
|
||||
success, message = toggle_web_service_at_boot(not current_status)
|
||||
config.popup_message = message
|
||||
config.popup_timer = 5000 if success else 7000
|
||||
config.needs_redraw = True
|
||||
if success:
|
||||
logger.info(f"Service web {'activé' if not current_status else 'désactivé'} au démarrage")
|
||||
else:
|
||||
logger.error(f"Erreur toggle service web: {message}")
|
||||
threading.Thread(target=toggle_service, daemon=True).start()
|
||||
# Option API Keys (index varie selon Linux ou pas)
|
||||
elif sel == (web_service_index + 1 if web_service_index >= 0 else 2) and is_input_matched(event, "confirm"):
|
||||
config.menu_state = "pause_api_keys_status"
|
||||
config.needs_redraw = True
|
||||
elif sel == 3 and is_input_matched(event, "confirm"):
|
||||
# Option Back (dernière option)
|
||||
elif sel == (total - 1) and is_input_matched(event, "confirm"):
|
||||
config.menu_state = "pause_menu"
|
||||
config.last_state_change_time = pygame.time.get_ticks()
|
||||
config.needs_redraw = True
|
||||
@@ -1684,7 +1702,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
idx = (idx - 1) % len(layouts) if is_input_matched(event, "left") else (idx + 1) % len(layouts)
|
||||
new_cols, new_rows = layouts[idx]
|
||||
try:
|
||||
from rgsx_settings import set_display_grid
|
||||
set_display_grid(new_cols, new_rows)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur set_display_grid: {e}")
|
||||
@@ -1693,7 +1710,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
# Redémarrage automatique pour appliquer proprement la modification de layout
|
||||
try:
|
||||
from utils import restart_application
|
||||
# Montrer brièvement l'info puis redémarrer
|
||||
config.menu_state = "restart_popup"
|
||||
config.popup_message = _("popup_restarting")
|
||||
@@ -1703,7 +1719,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
logger.error(f"Erreur lors du redémarrage après changement de layout: {e}")
|
||||
# 1: font size adjust
|
||||
elif sel == 1 and (is_input_matched(event, "left") or is_input_matched(event, "right")):
|
||||
from accessibility import save_accessibility_settings
|
||||
opts = getattr(config, 'font_scale_options', [0.75, 1.0, 1.25, 1.5, 1.75])
|
||||
idx = getattr(config, 'current_font_scale_index', 1)
|
||||
idx = max(0, idx - 1) if is_input_matched(event, "left") else min(len(opts)-1, idx + 1)
|
||||
@@ -1723,10 +1738,8 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# 2: toggle unsupported
|
||||
elif sel == 2 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
|
||||
try:
|
||||
from rgsx_settings import get_show_unsupported_platforms, set_show_unsupported_platforms
|
||||
current = get_show_unsupported_platforms()
|
||||
new_val = set_show_unsupported_platforms(not current)
|
||||
from utils import load_sources
|
||||
load_sources()
|
||||
config.popup_message = _("menu_show_unsupported_enabled") if new_val else _("menu_show_unsupported_disabled")
|
||||
config.popup_timer = 3000
|
||||
@@ -1736,7 +1749,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# 3: toggle allow unknown extensions
|
||||
elif sel == 3 and (is_input_matched(event, "left") or is_input_matched(event, "right") or is_input_matched(event, "confirm")):
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions, set_allow_unknown_extensions
|
||||
current = get_allow_unknown_extensions()
|
||||
new_val = set_allow_unknown_extensions(not current)
|
||||
config.popup_message = _("menu_allow_unknown_ext_enabled") if new_val else _("menu_allow_unknown_ext_disabled")
|
||||
@@ -1794,7 +1806,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
logger.debug("Passage à restart_popup")
|
||||
# Redémarrage automatique
|
||||
from utils import restart_application
|
||||
restart_application(2000)
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la suppression du fichier sources.json ou dossiers: {e}")
|
||||
@@ -1809,7 +1820,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.popup_timer = 2000
|
||||
config.needs_redraw = True
|
||||
logger.debug("Passage à restart_popup")
|
||||
from utils import restart_application
|
||||
restart_application(2000)
|
||||
else: # Non
|
||||
config.menu_state = validate_menu_state(config.previous_menu_state)
|
||||
@@ -1833,8 +1843,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
# Sélecteur de langue
|
||||
elif config.menu_state == "language_select":
|
||||
# Gestion directe des événements pour le sélecteur de langue
|
||||
from language import get_available_languages, set_language, _
|
||||
|
||||
available_languages = get_available_languages()
|
||||
|
||||
if not available_languages:
|
||||
@@ -1913,8 +1921,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
config.needs_redraw = True
|
||||
else:
|
||||
btn_idx = config.selected_filter_index - total_items
|
||||
from rgsx_settings import load_rgsx_settings, save_rgsx_settings
|
||||
from utils import load_sources
|
||||
settings = load_rgsx_settings()
|
||||
if btn_idx == 0: # all visible
|
||||
config.filter_platforms_selection = [(n, False) for n, _ in config.filter_platforms_selection]
|
||||
@@ -1996,7 +2002,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
|
||||
# Vérifier d'abord l'extension avant d'ajouter à l'historique
|
||||
if is_1fichier_url(url):
|
||||
from utils import ensure_download_provider_keys, missing_all_provider_keys
|
||||
ensure_download_provider_keys(False)
|
||||
|
||||
# Avertissement si pas de clé (utilisation mode gratuit)
|
||||
@@ -2011,12 +2016,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
load_extensions_json()
|
||||
)
|
||||
zip_ok = bool(config.pending_download[3])
|
||||
allow_unknown = False
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
except Exception:
|
||||
allow_unknown = False
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
if (not is_supported and not zip_ok) and not allow_unknown:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "extension_warning"
|
||||
@@ -2047,12 +2047,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
extensions_data
|
||||
)
|
||||
zip_ok = bool(config.pending_download[3])
|
||||
allow_unknown = False
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
except Exception:
|
||||
allow_unknown = False
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
if (not is_supported and not zip_ok) and not allow_unknown:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "extension_warning"
|
||||
@@ -2101,7 +2096,6 @@ def handle_controls(event, sources, joystick, screen):
|
||||
|
||||
# Vérifier d'abord l'extension avant d'ajouter à l'historique
|
||||
if is_1fichier_url(url):
|
||||
from utils import ensure_download_provider_keys, missing_all_provider_keys
|
||||
ensure_download_provider_keys(False)
|
||||
|
||||
# Avertissement si pas de clé (utilisation mode gratuit)
|
||||
@@ -2116,12 +2110,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
load_extensions_json()
|
||||
)
|
||||
zip_ok = bool(config.pending_download[3])
|
||||
allow_unknown = False
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
except Exception:
|
||||
allow_unknown = False
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
if (not is_supported and not zip_ok) and not allow_unknown:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "extension_warning"
|
||||
@@ -2152,12 +2141,7 @@ def handle_controls(event, sources, joystick, screen):
|
||||
extensions_data
|
||||
)
|
||||
zip_ok = bool(config.pending_download[3])
|
||||
allow_unknown = False
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
except Exception:
|
||||
allow_unknown = False
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
if (not is_supported and not zip_ok) and not allow_unknown:
|
||||
config.previous_menu_state = config.menu_state
|
||||
config.menu_state = "extension_warning"
|
||||
|
||||
@@ -956,7 +956,7 @@ def draw_history_list(screen):
|
||||
# Cherche une entrée en cours de téléchargement pour afficher la vitesse
|
||||
speed_str = ""
|
||||
for entry in history:
|
||||
if entry.get("status") in ["Téléchargement", "downloading"]:
|
||||
if entry.get("status") in ["Téléchargement", "Downloading"]:
|
||||
speed = entry.get("speed", 0.0)
|
||||
if speed and speed > 0:
|
||||
speed_str = f" - {speed:.2f} Mo/s"
|
||||
@@ -1001,7 +1001,7 @@ def draw_history_list(screen):
|
||||
current_history_item_inverted = 0
|
||||
|
||||
speed = 0.0
|
||||
if history and history[current_history_item_inverted].get("status") in ["Téléchargement", "downloading"]:
|
||||
if history and history[current_history_item_inverted].get("status") in ["Téléchargement", "Downloading"]:
|
||||
speed = history[current_history_item_inverted].get("speed", 0.0)
|
||||
if speed > 0:
|
||||
speed_str = f"{speed:.2f} Mo/s"
|
||||
@@ -1094,7 +1094,7 @@ def draw_history_list(screen):
|
||||
provider_prefix = entry.get("provider_prefix") or (entry.get("provider") + ":" if entry.get("provider") else "")
|
||||
|
||||
# Compute status text (optimized version without redundant prefix for errors)
|
||||
if status in ["Téléchargement", "downloading"]:
|
||||
if status in ["Téléchargement", "Downloading"]:
|
||||
# Vérifier si un message personnalisé existe (ex: mode gratuit avec attente)
|
||||
custom_message = entry.get('message', '')
|
||||
# Détecter les messages du mode gratuit (commencent par '[' dans toutes les langues)
|
||||
@@ -1489,8 +1489,8 @@ def draw_controls(screen, menu_state, current_music_name=None, music_popup_start
|
||||
# Construire les lignes avec icônes
|
||||
icon_lines = []
|
||||
|
||||
# Sur la page d'accueil uniquement: afficher version et musique
|
||||
if menu_state == "platform":
|
||||
# Sur la page d'accueil et la page loading afficher version et musique
|
||||
if menu_state == "platform" or menu_state == "loading":
|
||||
control_parts = []
|
||||
|
||||
start_button = get_control_display('start', 'START')
|
||||
@@ -2040,6 +2040,7 @@ def draw_pause_games_menu(screen, selected_index):
|
||||
|
||||
def draw_pause_settings_menu(screen, selected_index):
|
||||
from rgsx_settings import get_symlink_option
|
||||
from utils import check_web_service_status
|
||||
# Music
|
||||
if config.music_enabled:
|
||||
music_name = config.current_music_name or ""
|
||||
@@ -2057,16 +2058,34 @@ def draw_pause_settings_menu(screen, selected_index):
|
||||
if ' : ' in symlink_option:
|
||||
base, val = symlink_option.split(' : ',1)
|
||||
symlink_option = f"{base} : < {val.strip()} >"
|
||||
|
||||
# Web Service at boot (only on Linux/Batocera)
|
||||
web_service_txt = ""
|
||||
if config.OPERATING_SYSTEM == "Linux":
|
||||
web_service_enabled = check_web_service_status()
|
||||
web_service_status = _("settings_web_service_enabled") if web_service_enabled else _("settings_web_service_disabled")
|
||||
web_service_txt = f"{_('settings_web_service')} : < {web_service_status} >"
|
||||
|
||||
api_keys_txt = _("menu_api_keys_status") if _ else "API Keys"
|
||||
back_txt = _("menu_back") if _ else "Back"
|
||||
options = [music_option, symlink_option, api_keys_txt, back_txt]
|
||||
|
||||
# Construction de la liste des options
|
||||
options = [music_option, symlink_option]
|
||||
if web_service_txt: # Ajouter seulement si Linux/Batocera
|
||||
options.append(web_service_txt)
|
||||
options.extend([api_keys_txt, back_txt])
|
||||
|
||||
_draw_submenu_generic(screen, _("menu_settings_category") if _ else "Settings", options, selected_index)
|
||||
instruction_keys = [
|
||||
"instruction_settings_music",
|
||||
"instruction_settings_symlink",
|
||||
]
|
||||
if web_service_txt:
|
||||
instruction_keys.append("instruction_settings_web_service")
|
||||
instruction_keys.extend([
|
||||
"instruction_settings_api_keys",
|
||||
"instruction_generic_back",
|
||||
]
|
||||
])
|
||||
key = instruction_keys[selected_index] if 0 <= selected_index < len(instruction_keys) else None
|
||||
if key:
|
||||
button_height = int(config.screen_height * 0.045)
|
||||
@@ -2182,6 +2201,7 @@ def draw_pause_api_keys_status(screen):
|
||||
hint_rect = hint_surf.get_rect(center=(config.screen_width//2, menu_y + menu_height - 30))
|
||||
screen.blit(hint_surf, hint_rect)
|
||||
|
||||
|
||||
def draw_filter_platforms_menu(screen):
|
||||
"""Affiche le menu de filtrage des plateformes (afficher/masquer)."""
|
||||
from rgsx_settings import load_rgsx_settings
|
||||
|
||||
@@ -50,12 +50,20 @@ def load_history():
|
||||
logger.warning(f"Format history.json invalide (pas une liste), retour liste vide")
|
||||
return []
|
||||
|
||||
# Filtrer les entrées valides au lieu de tout rejeter
|
||||
valid_entries = []
|
||||
invalid_count = 0
|
||||
for entry in history:
|
||||
if not all(key in entry for key in ['platform', 'game_name', 'status']):
|
||||
logger.warning(f"Entrée d'historique invalide : {entry}")
|
||||
return []
|
||||
#logger.debug(f"Historique chargé depuis {history_path}, {len(history)} entrées")
|
||||
return history
|
||||
if isinstance(entry, dict) and all(key in entry for key in ['platform', 'game_name', 'status']):
|
||||
valid_entries.append(entry)
|
||||
else:
|
||||
invalid_count += 1
|
||||
logger.warning(f"Entrée d'historique invalide ignorée : {entry}")
|
||||
|
||||
if invalid_count > 0:
|
||||
logger.info(f"Historique chargé : {len(valid_entries)} valides, {invalid_count} invalides ignorées")
|
||||
#logger.debug(f"Historique chargé depuis {history_path}, {len(valid_entries)} entrées")
|
||||
return valid_entries
|
||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||
logger.error(f"Erreur lors de la lecture de {history_path} : {e}")
|
||||
return []
|
||||
@@ -106,12 +114,26 @@ def add_to_history(platform, game_name, status, url=None, progress=0, message=No
|
||||
return entry
|
||||
|
||||
def clear_history():
|
||||
"""Vide l'historique."""
|
||||
"""Vide l'historique en conservant les téléchargements en cours."""
|
||||
history_path = getattr(config, 'HISTORY_PATH')
|
||||
try:
|
||||
# Charger l'historique actuel
|
||||
current_history = load_history()
|
||||
|
||||
# Conserver uniquement les entrées avec statut actif (téléchargement, extraction ou conversion en cours)
|
||||
# Supporter les deux variantes de statut (anglais et français)
|
||||
active_statuses = {"Downloading", "Téléchargement", "downloading", "Extracting", "Converting", "Queued"}
|
||||
preserved_entries = [
|
||||
entry for entry in current_history
|
||||
if entry.get("status") in active_statuses
|
||||
]
|
||||
|
||||
# Sauvegarder l'historique filtré
|
||||
with open(history_path, "w", encoding='utf-8') as f:
|
||||
json.dump([], f)
|
||||
logger.info(f"Historique vidé : {history_path}")
|
||||
json.dump(preserved_entries, f, indent=2, ensure_ascii=False)
|
||||
|
||||
removed_count = len(current_history) - len(preserved_entries)
|
||||
logger.info(f"Historique vidé : {history_path} ({removed_count} entrées supprimées, {len(preserved_entries)} conservées)")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors du vidage de {history_path} : {e}")
|
||||
|
||||
|
||||
@@ -134,13 +134,12 @@
|
||||
"network_auth_required": "Authentifizierung erforderlich (HTTP {0})",
|
||||
"network_access_denied": "Zugriff verweigert (HTTP {0})",
|
||||
"network_server_error": "Serverfehler (HTTP {0})",
|
||||
"network_download_ok": "Download erfolgreich: {0}",
|
||||
"download_already_present": " (bereits vorhanden)",
|
||||
"network_download_already_queued": "Dieser Download läuft bereits",
|
||||
"utils_extracted": "Extrahiert: {0}",
|
||||
"download_already_extracted": " (bereits extrahiert)",
|
||||
"download_in_progress": "Download läuft...",
|
||||
"download_queued": "In Download-Warteschlange",
|
||||
"download_started": "Download gestartet",
|
||||
"utils_extracted": "Extrahiert: {0}",
|
||||
"utils_corrupt_zip": "Beschädigtes ZIP-Archiv: {0}",
|
||||
"utils_permission_denied": "Berechtigung während der Extraktion verweigert: {0}",
|
||||
"utils_extraction_failed": "Extraktion fehlgeschlagen: {0}",
|
||||
@@ -175,172 +174,192 @@
|
||||
"api_key_empty_suffix": "leer",
|
||||
"menu_hide_premium_systems": "Premium-Systeme ausblenden",
|
||||
"popup_hide_premium_on": "Premium-Systeme ausgeblendet",
|
||||
"popup_hide_premium_off": "Premium-Systeme sichtbar"
|
||||
,"submenu_display_font_family": "Schrift"
|
||||
,"popup_font_family_changed": "Schrift geändert: {0}"
|
||||
,"instruction_pause_language": "Sprache der Oberfläche ändern"
|
||||
,"instruction_pause_controls": "Steuerungsübersicht ansehen oder neu zuordnen"
|
||||
,"instruction_pause_display": "Layout, Schriften und Systemsichtbarkeit konfigurieren"
|
||||
,"instruction_pause_games": "Verlauf öffnen, Quelle wechseln oder Liste aktualisieren"
|
||||
,"instruction_pause_settings": "Musik, Symlink-Option & API-Schlüsselstatus"
|
||||
,"instruction_pause_restart": "RGSX neu starten um Konfiguration neu zu laden"
|
||||
,"instruction_pause_support": "Eine Diagnose-ZIP-Datei für den Support erstellen"
|
||||
,"instruction_pause_quit": "RGSX Anwendung beenden"
|
||||
,"instruction_controls_help": "Komplette Referenz für Controller & Tastatur anzeigen"
|
||||
,"instruction_controls_remap": "Tasten / Buttons neu zuordnen"
|
||||
,"instruction_generic_back": "Zum vorherigen Menü zurückkehren"
|
||||
,"instruction_display_layout": "Rasterabmessungen (Spalten × Zeilen) durchschalten"
|
||||
,"instruction_display_font_size": "Schriftgröße für bessere Lesbarkeit anpassen"
|
||||
,"instruction_display_font_family": "Zwischen verfügbaren Schriftarten wechseln"
|
||||
,"instruction_display_show_unsupported": "Nicht in es_systems.cfg definierte Systeme anzeigen/ausblenden"
|
||||
,"instruction_display_unknown_ext": "Warnung für in es_systems.cfg fehlende Dateiendungen an-/abschalten"
|
||||
,"instruction_display_hide_premium": "Systeme ausblenden, die Premiumzugang erfordern über API: {providers}"
|
||||
,"instruction_display_filter_platforms": "Manuell wählen welche Systeme sichtbar sind"
|
||||
,"instruction_games_history": "Vergangene Downloads und Status anzeigen"
|
||||
,"instruction_games_source_mode": "Zwischen RGSX oder eigener Quellliste wechseln"
|
||||
,"instruction_games_update_cache": "Aktuelle Spieleliste erneut herunterladen & aktualisieren"
|
||||
,"instruction_settings_music": "Hintergrundmusik aktivieren oder deaktivieren"
|
||||
,"instruction_settings_symlink": "Verwendung von Symlinks für Installationen umschalten"
|
||||
,"instruction_settings_api_keys": "Gefundene Premium-API-Schlüssel ansehen"
|
||||
,"controls_desc_confirm": "Bestätigen (z.B. A/Kreuz)"
|
||||
,"controls_desc_cancel": "Abbrechen/Zurück (z.B. B/Kreis)"
|
||||
,"controls_desc_up": "UP ↑"
|
||||
,"controls_desc_down": "DOWN ↓"
|
||||
,"controls_desc_left": "LEFT ←"
|
||||
,"controls_desc_right": "RIGHT →"
|
||||
,"controls_desc_page_up": "Schnell nach oben (z.B. LT/L2)"
|
||||
,"controls_desc_page_down": "Schnell nach unten (z.B. RT/R2)"
|
||||
,"controls_desc_history": "Verlauf öffnen (z.B. Y/Dreieck)"
|
||||
,"controls_desc_clear_history": "Downloads: Mehrfachauswahl / Verlauf: Leeren (z.B. X/Quadrat)"
|
||||
,"controls_desc_filter": "Filtermodus: Öffnen/Bestätigen (z.B. Select)"
|
||||
,"controls_desc_delete": "Filtermodus: Zeichen löschen (z.B. LB/L1)"
|
||||
,"controls_desc_space": "Filtermodus: Leerzeichen hinzufügen (z.B. RB/R1)"
|
||||
,"controls_desc_start": "Pausenmenü öffnen (z.B. Start)"
|
||||
,"controls_mapping_title": "Steuerungszuordnung"
|
||||
,"controls_mapping_instruction": "Zum Bestätigen gedrückt halten:"
|
||||
,"controls_mapping_waiting": "Warte auf eine Taste oder einen Button..."
|
||||
,"controls_mapping_press": "Drücke eine Taste oder einen Button"
|
||||
,"status_already_present": "Bereits Vorhanden"
|
||||
,"footer_joystick": "Joystick: {0}"
|
||||
,"history_game_options_title": "Spiel Optionen"
|
||||
,"history_option_download_folder": "Datei lokalisieren"
|
||||
,"history_option_extract_archive": "Archiv extrahieren"
|
||||
,"history_option_scraper": "Metadaten scrapen"
|
||||
,"history_option_delete_game": "Spiel löschen"
|
||||
,"history_option_error_info": "Fehlerdetails"
|
||||
,"history_option_retry": "Download wiederholen"
|
||||
,"history_option_back": "Zurück"
|
||||
,"history_folder_path_label": "Zielpfad:"
|
||||
,"history_scraper_not_implemented": "Scraper noch nicht implementiert"
|
||||
,"history_confirm_delete": "Dieses Spiel von der Festplatte löschen?"
|
||||
,"history_file_not_found": "Datei nicht gefunden"
|
||||
,"history_extracting": "Extrahieren..."
|
||||
,"history_extracted": "Extrahiert"
|
||||
,"history_delete_success": "Spiel erfolgreich gelöscht"
|
||||
,"history_delete_error": "Fehler beim Löschen des Spiels: {0}"
|
||||
,"history_error_details_title": "Fehlerdetails"
|
||||
,"history_no_error_message": "Keine Fehlermeldung verfügbar"
|
||||
,"web_title": "RGSX Web-Oberfläche"
|
||||
,"web_tab_platforms": "Systemliste"
|
||||
,"web_tab_downloads": "Downloads"
|
||||
,"web_tab_history": "Verlauf"
|
||||
,"web_tab_settings": "Einstellungen"
|
||||
,"web_tab_update": "Liste aktualisieren"
|
||||
,"web_tooltip_platforms": "Systemliste"
|
||||
,"web_tooltip_downloads": "Downloads"
|
||||
,"web_tooltip_history": "Verlauf"
|
||||
,"web_tooltip_settings": "Einstellungen"
|
||||
,"web_tooltip_update": "Spieleliste aktualisieren"
|
||||
,"web_search_platform": "Systeme oder Spiele suchen..."
|
||||
,"web_search_game": "Spiel suchen..."
|
||||
,"web_search_results": "Ergebnisse für"
|
||||
,"web_no_results": "Keine Ergebnisse gefunden"
|
||||
,"web_platforms": "Systeme"
|
||||
,"web_games": "Spiele"
|
||||
,"web_error_search": "Suchfehler"
|
||||
,"web_back_platforms": "Zurück zu Plattformen"
|
||||
,"web_back": "Zurück"
|
||||
,"web_game_count": "{0} ({1} Spiele)"
|
||||
,"web_download": "Herunterladen"
|
||||
,"web_cancel": "Abbrechen"
|
||||
,"web_download_canceled": "Download abgebrochen"
|
||||
,"web_confirm_cancel": "Möchten Sie diesen Download wirklich abbrechen?"
|
||||
,"web_update_title": "Spieleliste wird aktualisiert..."
|
||||
,"web_update_message": "Cache wird gelöscht und Daten neu geladen..."
|
||||
,"web_update_wait": "Dies kann 10-30 Sekunden dauern"
|
||||
,"web_error": "Fehler"
|
||||
,"web_error_unknown": "Unbekannter Fehler"
|
||||
,"web_error_update": "Fehler beim Aktualisieren der Liste: {0}"
|
||||
,"web_error_download": "Fehler: {0}"
|
||||
,"web_history_clear": "Verlauf löschen"
|
||||
,"web_history_cleared": "Verlauf erfolgreich gelöscht!"
|
||||
,"web_error_clear_history": "Fehler beim Löschen des Verlaufs: {0}"
|
||||
,"web_settings_title": "Info & Einstellungen"
|
||||
,"web_settings_roms_folder": "Benutzerdefinierter ROMs-Ordner"
|
||||
,"web_settings_roms_placeholder": "Leer lassen für Standard"
|
||||
,"web_settings_browse": "Durchsuchen"
|
||||
,"web_settings_language": "Sprache"
|
||||
,"web_settings_font_scale": "Schriftgröße"
|
||||
,"web_settings_grid": "Rasterlayout"
|
||||
,"web_settings_font_family": "Schriftart"
|
||||
,"web_settings_music": "Musik"
|
||||
,"web_settings_symlink": "Symlink-Modus"
|
||||
,"web_settings_source_mode": "Spielequelle"
|
||||
,"web_settings_custom_url": "Benutzerdefinierte URL"
|
||||
,"web_settings_custom_url_placeholder": "https://beispiel.com/spiele.zip"
|
||||
,"web_settings_save": "Einstellungen speichern"
|
||||
,"web_settings_saved": "Einstellungen erfolgreich gespeichert!"
|
||||
,"web_settings_saved_restart": "Einstellungen erfolgreich gespeichert!\\n\\n⚠️ Einige Einstellungen erfordern einen Serverneustart:\\n- Benutzerdefinierter ROMs-Ordner\\n- Sprache\\n\\nBitte starten Sie den Webserver neu, um diese Änderungen anzuwenden."
|
||||
,"web_error_save_settings": "Fehler beim Speichern der Einstellungen: {0}"
|
||||
,"web_browse_title": "Verzeichnisse durchsuchen"
|
||||
,"web_browse_select_drive": "Laufwerk auswählen..."
|
||||
,"web_browse_drives": "Laufwerke"
|
||||
,"web_browse_parent": "Übergeordnet"
|
||||
,"web_browse_select": "Diesen Ordner auswählen"
|
||||
,"web_browse_cancel": "Abbrechen"
|
||||
,"web_browse_empty": "Keine Unterverzeichnisse gefunden"
|
||||
,"web_browse_alert_restart": "Wichtig: Sie müssen die Einstellungen SPEICHERN und dann den Webserver NEUSTARTEN, damit der benutzerdefinierte ROMs-Ordner wirksam wird.\\n\\n📝 Schritte:\\n1. Klicken Sie unten auf 'Einstellungen speichern'\\n2. Stoppen Sie den Webserver (Strg+C im Terminal)\\n3. Starten Sie den Webserver neu\\n\\nAusgewählter Pfad: {0}"
|
||||
,"web_error_browse": "Fehler beim Durchsuchen der Verzeichnisse: {0}"
|
||||
,"web_loading_platforms": "Lade Plattformen..."
|
||||
,"web_loading_games": "Lade Spiele..."
|
||||
,"web_no_platforms": "Keine Plattformen gefunden"
|
||||
,"web_no_downloads": "Keine Downloads im Gange"
|
||||
,"web_history_empty": "Keine abgeschlossenen Downloads"
|
||||
,"web_history_platform": "Plattform"
|
||||
,"web_history_size": "Größe"
|
||||
,"web_history_status_completed": "Abgeschlossen"
|
||||
,"web_history_status_error": "Fehler"
|
||||
,"web_settings_os": "Betriebssystem"
|
||||
,"web_settings_platforms_count": "Anzahl der Plattformen"
|
||||
,"web_settings_show_unsupported": "Nicht unterstützte Plattformen anzeigen (System fehlt in es_systems.cfg)"
|
||||
,"web_settings_allow_unknown": "Unbekannte Erweiterungen erlauben (keine Warnungen anzeigen)"
|
||||
,"web_restart_confirm_title": "Anwendung neu starten?"
|
||||
,"web_restart_confirm_message": "Die Einstellungen wurden gespeichert. Möchten Sie die Anwendung jetzt neu starten, um die Änderungen anzuwenden?"
|
||||
,"web_restart_yes": "Ja, neu starten"
|
||||
,"web_restart_no": "Nein, später"
|
||||
,"web_restart_success": "Neustart läuft..."
|
||||
,"web_restart_error": "Fehler beim Neustart: {0}"
|
||||
,"web_support": "Support"
|
||||
,"web_support_title": "📦 Support-Datei erstellt"
|
||||
,"web_support_message": "Support-Datei erfolgreich erstellt!\\n\\n📁 Inhalt:\\n• Steuerungskonfiguration\\n• Download-Verlauf\\n• RGSX-Einstellungen\\n• Anwendungsprotokolle\\n• Webserver-Protokolle\\n\\n💬 Um Hilfe zu erhalten:\\n1. Trete dem RGSX Discord bei\\n2. Beschreibe dein Problem\\n3. Teile diese ZIP-Datei\\n\\nDownload startet..."
|
||||
,"web_support_generating": "Support-Datei wird generiert..."
|
||||
,"web_support_download": "Support-Datei herunterladen"
|
||||
,"web_support_error": "Fehler beim Erstellen der Support-Datei: {0}"
|
||||
,"web_tab_queue": "Warteschlange"
|
||||
,"web_tooltip_queue": "Download-Warteschlange"
|
||||
,"web_queue_active_download": "⏳ Ein Download ist aktiv"
|
||||
,"web_queue_no_active": "✓ Kein aktiver Download"
|
||||
,"web_queue_title": "Download-Warteschlange"
|
||||
,"web_queue_empty": "Keine Elemente in der Warteschlange"
|
||||
,"web_queue_clear": "Warteschlange löschen"
|
||||
,"web_queue_cleared": "Warteschlange erfolgreich gelöscht!"
|
||||
,"web_confirm_remove_queue": "Dieses Element aus der Warteschlange entfernen?"
|
||||
,"web_confirm_clear_queue": "Gesamte Warteschlange löschen?"
|
||||
,"web_remove": "Entfernen"
|
||||
,"web_loading": "Lädt..."
|
||||
,"web_sort": "Sortieren nach"
|
||||
,"web_sort_name_asc": "A-Z (Name)"
|
||||
,"web_sort_name_desc": "Z-A (Name)"
|
||||
,"web_sort_size_asc": "Größe +- (Klein zuerst)"
|
||||
,"web_sort_size_desc": "Größe -+ (Groß zuerst)"
|
||||
"popup_hide_premium_off": "Premium-Systeme sichtbar",
|
||||
"submenu_display_font_family": "Schrift",
|
||||
"popup_font_family_changed": "Schrift geändert: {0}",
|
||||
"instruction_pause_language": "Sprache der Oberfläche ändern",
|
||||
"instruction_pause_controls": "Steuerungsübersicht ansehen oder neu zuordnen",
|
||||
"instruction_pause_display": "Layout, Schriften und Systemsichtbarkeit konfigurieren",
|
||||
"instruction_pause_games": "Verlauf öffnen, Quelle wechseln oder Liste aktualisieren",
|
||||
"instruction_pause_settings": "Musik, Symlink-Option & API-Schlüsselstatus",
|
||||
"instruction_pause_restart": "RGSX neu starten um Konfiguration neu zu laden",
|
||||
"instruction_pause_support": "Eine Diagnose-ZIP-Datei für den Support erstellen",
|
||||
"instruction_pause_quit": "RGSX Anwendung beenden",
|
||||
"instruction_controls_help": "Komplette Referenz für Controller & Tastatur anzeigen",
|
||||
"instruction_controls_remap": "Tasten / Buttons neu zuordnen",
|
||||
"instruction_generic_back": "Zum vorherigen Menü zurückkehren",
|
||||
"instruction_display_layout": "Rasterabmessungen (Spalten × Zeilen) durchschalten",
|
||||
"instruction_display_font_size": "Schriftgröße für bessere Lesbarkeit anpassen",
|
||||
"instruction_display_font_family": "Zwischen verfügbaren Schriftarten wechseln",
|
||||
"instruction_display_show_unsupported": "Nicht in es_systems.cfg definierte Systeme anzeigen/ausblenden",
|
||||
"instruction_display_unknown_ext": "Warnung für in es_systems.cfg fehlende Dateiendungen an-/abschalten",
|
||||
"instruction_display_hide_premium": "Systeme ausblenden, die Premiumzugang erfordern über API: {providers}",
|
||||
"instruction_display_filter_platforms": "Manuell wählen welche Systeme sichtbar sind",
|
||||
"instruction_games_history": "Vergangene Downloads und Status anzeigen",
|
||||
"instruction_games_source_mode": "Zwischen RGSX oder eigener Quellliste wechseln",
|
||||
"instruction_games_update_cache": "Aktuelle Spieleliste erneut herunterladen & aktualisieren",
|
||||
"instruction_settings_music": "Hintergrundmusik aktivieren oder deaktivieren",
|
||||
"instruction_settings_symlink": "Verwendung von Symlinks für Installationen umschalten",
|
||||
"instruction_settings_api_keys": "Gefundene Premium-API-Schlüssel ansehen",
|
||||
"instruction_settings_web_service": "Web-Dienst Autostart beim Booten aktivieren/deaktivieren",
|
||||
"settings_web_service": "Web-Dienst beim Booten",
|
||||
"settings_web_service_enabled": "Aktiviert",
|
||||
"settings_web_service_disabled": "Deaktiviert",
|
||||
"settings_web_service_enabling": "Web-Dienst wird aktiviert...",
|
||||
"settings_web_service_disabling": "Web-Dienst wird deaktiviert...",
|
||||
"settings_web_service_success_enabled": "Web-Dienst beim Booten aktiviert",
|
||||
"settings_web_service_success_disabled": "Web-Dienst beim Booten deaktiviert",
|
||||
"settings_web_service_error": "Fehler: {0}",
|
||||
"controls_desc_confirm": "Bestätigen (z.B. A/Kreuz)",
|
||||
"controls_desc_cancel": "Abbrechen/Zurück (z.B. B/Kreis)",
|
||||
"controls_desc_up": "UP ↑",
|
||||
"controls_desc_down": "DOWN ↓",
|
||||
"controls_desc_left": "LEFT ←",
|
||||
"controls_desc_right": "RIGHT →",
|
||||
"controls_desc_page_up": "Schnell nach oben (z.B. LT/L2)",
|
||||
"controls_desc_page_down": "Schnell nach unten (z.B. RT/R2)",
|
||||
"controls_desc_history": "Verlauf öffnen (z.B. Y/Dreieck)",
|
||||
"controls_desc_clear_history": "Downloads: Mehrfachauswahl / Verlauf: Leeren (z.B. X/Quadrat)",
|
||||
"controls_desc_filter": "Filtermodus: Öffnen/Bestätigen (z.B. Select)",
|
||||
"controls_desc_delete": "Filtermodus: Zeichen löschen (z.B. LB/L1)",
|
||||
"controls_desc_space": "Filtermodus: Leerzeichen hinzufügen (z.B. RB/R1)",
|
||||
"controls_desc_start": "Pausenmenü öffnen (z.B. Start)",
|
||||
"controls_mapping_title": "Steuerungszuordnung",
|
||||
"controls_mapping_instruction": "Zum Bestätigen gedrückt halten:",
|
||||
"controls_mapping_waiting": "Warte auf eine Taste oder einen Button...",
|
||||
"controls_mapping_press": "Drücke eine Taste oder einen Button",
|
||||
"status_already_present": "Bereits Vorhanden",
|
||||
"footer_joystick": "Joystick: {0}",
|
||||
"history_game_options_title": "Spiel Optionen",
|
||||
"history_option_download_folder": "Datei lokalisieren",
|
||||
"history_option_extract_archive": "Archiv extrahieren",
|
||||
"history_option_scraper": "Metadaten scrapen",
|
||||
"history_option_delete_game": "Spiel löschen",
|
||||
"history_option_error_info": "Fehlerdetails",
|
||||
"history_option_retry": "Download wiederholen",
|
||||
"history_option_back": "Zurück",
|
||||
"history_folder_path_label": "Zielpfad:",
|
||||
"history_scraper_not_implemented": "Scraper noch nicht implementiert",
|
||||
"history_confirm_delete": "Dieses Spiel von der Festplatte löschen?",
|
||||
"history_file_not_found": "Datei nicht gefunden",
|
||||
"history_extracting": "Extrahieren...",
|
||||
"history_extracted": "Extrahiert",
|
||||
"history_delete_success": "Spiel erfolgreich gelöscht",
|
||||
"history_delete_error": "Fehler beim Löschen des Spiels: {0}",
|
||||
"history_error_details_title": "Fehlerdetails",
|
||||
"history_no_error_message": "Keine Fehlermeldung verfügbar",
|
||||
"web_title": "RGSX Web-Oberfläche",
|
||||
"web_tab_platforms": "Systemliste",
|
||||
"web_tab_downloads": "Downloads",
|
||||
"web_tab_history": "Verlauf",
|
||||
"web_tab_settings": "Einstellungen",
|
||||
"web_tab_update": "Liste aktualisieren",
|
||||
"web_tooltip_platforms": "Systemliste",
|
||||
"web_tooltip_downloads": "Downloads",
|
||||
"web_tooltip_history": "Verlauf",
|
||||
"web_tooltip_settings": "Einstellungen",
|
||||
"web_tooltip_update": "Spieleliste aktualisieren",
|
||||
"web_search_platform": "Systeme oder Spiele suchen...",
|
||||
"web_search_game": "Spiel suchen...",
|
||||
"web_search_results": "Ergebnisse für",
|
||||
"web_no_results": "Keine Ergebnisse gefunden",
|
||||
"web_platforms": "Systeme",
|
||||
"web_games": "Spiele",
|
||||
"web_error_search": "Suchfehler",
|
||||
"web_back_platforms": "Zurück zu Plattformen",
|
||||
"web_back": "Zurück",
|
||||
"web_game_count": "{0} ({1} Spiele)",
|
||||
"web_download": "Herunterladen",
|
||||
"web_cancel": "Abbrechen",
|
||||
"web_download_canceled": "Download abgebrochen",
|
||||
"web_confirm_cancel": "Möchten Sie diesen Download wirklich abbrechen?",
|
||||
"web_update_title": "Spieleliste wird aktualisiert...",
|
||||
"web_update_message": "Cache wird gelöscht und Daten neu geladen...",
|
||||
"web_update_wait": "Dies kann 10-30 Sekunden dauern",
|
||||
"web_error": "Fehler",
|
||||
"web_error_unknown": "Unbekannter Fehler",
|
||||
"web_error_update": "Fehler beim Aktualisieren der Liste: {0}",
|
||||
"web_error_download": "Fehler: {0}",
|
||||
"web_history_clear": "Verlauf löschen",
|
||||
"web_history_cleared": "Verlauf erfolgreich gelöscht!",
|
||||
"web_error_clear_history": "Fehler beim Löschen des Verlaufs: {0}",
|
||||
"web_settings_title": "Info & Einstellungen",
|
||||
"web_settings_roms_folder": "Benutzerdefinierter ROMs-Ordner",
|
||||
"web_settings_roms_placeholder": "Leer lassen für Standard",
|
||||
"web_settings_browse": "Durchsuchen",
|
||||
"web_settings_language": "Sprache",
|
||||
"web_settings_font_scale": "Schriftgröße",
|
||||
"web_settings_grid": "Rasterlayout",
|
||||
"web_settings_font_family": "Schriftart",
|
||||
"web_settings_music": "Musik",
|
||||
"web_settings_symlink": "Symlink-Modus",
|
||||
"web_settings_source_mode": "Spielequelle",
|
||||
"web_settings_custom_url": "Benutzerdefinierte URL",
|
||||
"web_settings_custom_url_placeholder": "https://beispiel.com/spiele.zip",
|
||||
"web_settings_save": "Einstellungen speichern",
|
||||
"web_settings_saved": "Einstellungen erfolgreich gespeichert!",
|
||||
"web_settings_saved_restart": "Einstellungen erfolgreich gespeichert!\\n\\n⚠️ Einige Einstellungen erfordern einen Serverneustart:\\n- Benutzerdefinierter ROMs-Ordner\\n- Sprache\\n\\nBitte starten Sie den Webserver neu, um diese Änderungen anzuwenden.",
|
||||
"web_error_save_settings": "Fehler beim Speichern der Einstellungen: {0}",
|
||||
"web_browse_title": "Verzeichnisse durchsuchen",
|
||||
"web_browse_select_drive": "Laufwerk auswählen...",
|
||||
"web_browse_drives": "Laufwerke",
|
||||
"web_browse_parent": "Übergeordnet",
|
||||
"web_browse_select": "Diesen Ordner auswählen",
|
||||
"web_browse_cancel": "Abbrechen",
|
||||
"web_browse_empty": "Keine Unterverzeichnisse gefunden",
|
||||
"web_browse_alert_restart": "Wichtig: Sie müssen die Einstellungen SPEICHERN und dann den Webserver NEUSTARTEN, damit der benutzerdefinierte ROMs-Ordner wirksam wird.\\n\\n📝 Schritte:\\n1. Klicken Sie unten auf 'Einstellungen speichern'\\n2. Stoppen Sie den Webserver (Strg+C im Terminal)\\n3. Starten Sie den Webserver neu\\n\\nAusgewählter Pfad: {0}",
|
||||
"web_error_browse": "Fehler beim Durchsuchen der Verzeichnisse: {0}",
|
||||
"web_loading_platforms": "Lade Plattformen...",
|
||||
"web_loading_games": "Lade Spiele...",
|
||||
"web_no_platforms": "Keine Plattformen gefunden",
|
||||
"web_no_downloads": "Keine Downloads im Gange",
|
||||
"web_history_empty": "Keine abgeschlossenen Downloads",
|
||||
"web_history_platform": "Plattform",
|
||||
"web_history_size": "Größe",
|
||||
"web_history_status_completed": "Abgeschlossen",
|
||||
"web_history_status_error": "Fehler",
|
||||
"web_settings_os": "Betriebssystem",
|
||||
"web_settings_platforms_count": "Anzahl der Plattformen",
|
||||
"web_settings_show_unsupported": "Nicht unterstützte Plattformen anzeigen (System fehlt in es_systems.cfg)",
|
||||
"web_settings_allow_unknown": "Unbekannte Erweiterungen erlauben (keine Warnungen anzeigen)",
|
||||
"web_restart_confirm_title": "Anwendung neu starten?",
|
||||
"web_restart_confirm_message": "Die Einstellungen wurden gespeichert. Möchten Sie die Anwendung jetzt neu starten, um die Änderungen anzuwenden?",
|
||||
"web_restart_yes": "Ja, neu starten",
|
||||
"web_restart_no": "Nein, später",
|
||||
"web_restart_success": "Neustart läuft...",
|
||||
"web_restart_error": "Fehler beim Neustart: {0}",
|
||||
"web_support": "Support",
|
||||
"web_support_title": "📦 Support-Datei erstellt",
|
||||
"web_support_message": "Support-Datei erfolgreich erstellt!\\n\\n📁 Inhalt:\\n• Steuerungskonfiguration\\n• Download-Verlauf\\n• RGSX-Einstellungen\\n• Anwendungsprotokolle\\n• Webserver-Protokolle\\n\\n💬 Um Hilfe zu erhalten:\\n1. Trete dem RGSX Discord bei\\n2. Beschreibe dein Problem\\n3. Teile diese ZIP-Datei\\n\\nDownload startet...",
|
||||
"web_support_generating": "Support-Datei wird generiert...",
|
||||
"web_support_download": "Support-Datei herunterladen",
|
||||
"web_support_error": "Fehler beim Erstellen der Support-Datei: {0}",
|
||||
"web_tab_queue": "Warteschlange",
|
||||
"web_tooltip_queue": "Download-Warteschlange",
|
||||
"web_queue_active_download": "⏳ Ein Download ist aktiv",
|
||||
"web_queue_no_active": "✓ Kein aktiver Download",
|
||||
"web_queue_title": "Download-Warteschlange",
|
||||
"web_queue_empty": "Keine Elemente in der Warteschlange",
|
||||
"web_queue_clear": "Warteschlange löschen",
|
||||
"web_queue_cleared": "Warteschlange erfolgreich gelöscht!",
|
||||
"web_confirm_remove_queue": "Dieses Element aus der Warteschlange entfernen?",
|
||||
"web_confirm_clear_queue": "Gesamte Warteschlange löschen?",
|
||||
"web_remove": "Entfernen",
|
||||
"web_loading": "Lädt...",
|
||||
"web_sort": "Sortieren nach",
|
||||
"web_sort_name_asc": "A-Z (Name)",
|
||||
"web_sort_name_desc": "Z-A (Name)",
|
||||
"web_sort_size_asc": "Größe +- (Klein zuerst)",
|
||||
"web_sort_size_desc": "Größe -+ (Groß zuerst)",
|
||||
"download_already_present": " (bereits vorhanden)",
|
||||
"network_download_ok": "Download erfolgreich: {0}",
|
||||
"web_filter_region": "Region",
|
||||
"web_filter_hide_non_release": "Demos/Betas/Protos ausblenden",
|
||||
"web_filter_regex_mode": "Regex-Suche aktivieren",
|
||||
"web_filter_one_rom_per_game": "Eine ROM pro Spiel",
|
||||
"web_filter_configure_priority": "Regions-Prioritätsreihenfolge konfigurieren",
|
||||
"filter_all": "Alles auswählen",
|
||||
"filter_none": "Alles abwählen",
|
||||
"filter_apply": "Filter anwenden",
|
||||
"filter_back": "Zurück"
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
"loading_test_connection": "Testing connection...",
|
||||
"loading_download_data": "Downloading initial Data folder...",
|
||||
"loading_progress": "Progress: {0}%",
|
||||
"loading_check_updates": "Checking for updates... Please wait...",
|
||||
"loading_check_updates": "Update in progress... Please wait...",
|
||||
"error_check_updates_failed": "Failed to check updates.",
|
||||
"loading_downloading_games_images": "Downloading games and images...",
|
||||
"loading_extracting_data": "Extracting initial Data folder...",
|
||||
@@ -93,8 +93,8 @@
|
||||
"support_dialog_message": "A support file has been created with all your configuration and log files.\n\nFile: {0}\n\nTo get help:\n1. Join the RGSX Discord server\n2. Describe your issue\n3. Share this ZIP file\n\nPress {1} to return to the menu.",
|
||||
"support_dialog_error": "Error generating support file:\n{0}\n\nPress {1} to return to the menu.",
|
||||
"controls_action_history": "History / Downloads",
|
||||
"controls_action_close_history": "Close History",
|
||||
"network_checking_updates": "Checking for updates...",
|
||||
"controls_action_close_history": "Close History",
|
||||
"network_checking_updates": "Update in progress please wait...",
|
||||
"network_update_available": "Update available: {0}",
|
||||
"network_extracting_update": "Extracting update...",
|
||||
"network_update_completed": "Update completed",
|
||||
@@ -174,174 +174,192 @@
|
||||
"menu_games": "Games",
|
||||
"api_keys_hint_manage": "Put your keys in {path}",
|
||||
"api_key_empty_suffix": "empty",
|
||||
"menu_hide_premium_systems": "Hide Premium systems"
|
||||
,"popup_hide_premium_on": "Premium systems hidden"
|
||||
,"popup_hide_premium_off": "Premium systems visible"
|
||||
,"submenu_display_font_family": "Font"
|
||||
,"popup_font_family_changed": "Font changed: {0}",
|
||||
"menu_hide_premium_systems": "Hide Premium systems",
|
||||
"popup_hide_premium_on": "Premium systems hidden",
|
||||
"popup_hide_premium_off": "Premium systems visible",
|
||||
"submenu_display_font_family": "Font",
|
||||
"popup_font_family_changed": "Font changed: {0}",
|
||||
"instruction_pause_language": "Change the interface language",
|
||||
"instruction_pause_controls": "View control layout or start remapping",
|
||||
"instruction_pause_display": "Configure layout, fonts and system visibility",
|
||||
"instruction_pause_games": "Open history, switch source or refresh list",
|
||||
"instruction_pause_settings": "Music, symlink option & API keys status",
|
||||
"instruction_pause_restart": "Restart RGSX to reload configuration"
|
||||
,"instruction_pause_support": "Generate a diagnostic ZIP file for support"
|
||||
,"instruction_pause_quit": "Exit the RGSX application"
|
||||
,"instruction_controls_help": "Show full controller & keyboard reference"
|
||||
,"instruction_controls_remap": "Change button / key bindings"
|
||||
,"instruction_generic_back": "Return to the previous menu"
|
||||
,"instruction_display_layout": "Cycle grid dimensions (columns × rows)"
|
||||
,"instruction_display_font_size": "Adjust text scale for readability"
|
||||
,"instruction_display_font_family": "Switch between available font families"
|
||||
,"instruction_display_show_unsupported": "Show/hide systems not defined in es_systems.cfg"
|
||||
,"instruction_display_unknown_ext": "Enable/disable warning for file extensions absent from es_systems.cfg"
|
||||
,"instruction_display_hide_premium": "Hide systems requiring premium access via API: {providers}"
|
||||
,"instruction_display_filter_platforms": "Manually choose which systems are visible"
|
||||
,"instruction_games_history": "List past downloads and statuses"
|
||||
,"instruction_games_source_mode": "Switch between RGSX or your own custom list source"
|
||||
,"instruction_games_update_cache": "Redownload & refresh current games list"
|
||||
,"instruction_settings_music": "Enable or disable background music playback"
|
||||
,"instruction_settings_symlink": "Toggle using filesystem symlinks for installs"
|
||||
,"instruction_settings_api_keys": "See detected premium provider API keys"
|
||||
,"controls_desc_confirm": "Confirm (e.g. A/Cross)"
|
||||
,"controls_desc_cancel": "Cancel/Back (e.g. B/Circle)"
|
||||
,"controls_desc_up": "UP ↑"
|
||||
,"controls_desc_down": "DOWN ↓"
|
||||
,"controls_desc_left": "LEFT ←"
|
||||
,"controls_desc_right": "RIGHT →"
|
||||
,"controls_desc_page_up": "Fast scroll up (e.g. LT/L2)"
|
||||
,"controls_desc_page_down": "Fast scroll down (e.g. RT/R2)"
|
||||
,"controls_desc_history": "Open history (e.g. Y/Triangle)"
|
||||
,"controls_desc_clear_history": "Downloads: Multi-select / History: Clear (e.g. X/Square)"
|
||||
,"controls_desc_filter": "Filter mode: Open/Confirm (e.g. Select)"
|
||||
,"controls_desc_delete": "Filter mode: Delete character (e.g. LB/L1)"
|
||||
,"controls_desc_space": "Filter mode: Add space (e.g. RB/R1)"
|
||||
,"controls_desc_start": "Open pause menu (e.g. Start)"
|
||||
,"controls_mapping_title": "Controls mapping"
|
||||
,"controls_mapping_instruction": "Hold to confirm the mapping:"
|
||||
,"controls_mapping_waiting": "Waiting for a key or button..."
|
||||
,"controls_mapping_press": "Press a key or a button"
|
||||
,"status_already_present": "Already Present"
|
||||
,"footer_joystick": "Joystick: {0}"
|
||||
,"history_game_options_title": "Game Options"
|
||||
,"history_option_download_folder": "Locate file"
|
||||
,"history_option_extract_archive": "Extract archive"
|
||||
,"history_option_scraper": "Scrape metadata"
|
||||
,"history_option_delete_game": "Delete game"
|
||||
,"history_option_error_info": "Error details"
|
||||
,"history_option_retry": "Retry download"
|
||||
,"history_option_back": "Back"
|
||||
,"history_folder_path_label": "Destination path:"
|
||||
,"history_scraper_not_implemented": "Scraper not yet implemented"
|
||||
,"history_confirm_delete": "Delete this game from disk?"
|
||||
,"history_file_not_found": "File not found"
|
||||
,"history_extracting": "Extracting..."
|
||||
,"history_extracted": "Extracted"
|
||||
,"history_delete_success": "Game deleted successfully"
|
||||
,"history_delete_error": "Error deleting game: {0}"
|
||||
,"history_error_details_title": "Error Details"
|
||||
,"history_no_error_message": "No error message available"
|
||||
,"web_title": "RGSX Web Interface"
|
||||
,"web_tab_platforms": "Platforms List"
|
||||
,"web_tab_downloads": "Downloads"
|
||||
,"web_tab_history": "History"
|
||||
,"web_tab_settings": "Settings"
|
||||
,"web_tab_update": "Update games list"
|
||||
,"web_tooltip_platforms": "Platforms list"
|
||||
,"web_tooltip_downloads": "Downloads"
|
||||
,"web_tooltip_history": "History"
|
||||
,"web_tooltip_settings": "Settings"
|
||||
,"web_tooltip_update": "Update games list"
|
||||
,"web_search_platform": "Search platforms or games..."
|
||||
,"web_search_game": "Search a game..."
|
||||
,"web_search_results": "results for"
|
||||
,"web_no_results": "No results found"
|
||||
,"web_platforms": "Platforms"
|
||||
,"web_games": "Games"
|
||||
,"web_error_search": "Search error"
|
||||
,"web_back_platforms": "Back to platforms"
|
||||
,"web_back": "Back"
|
||||
,"web_game_count": "{0} ({1} games)"
|
||||
,"web_download": "Download"
|
||||
,"web_cancel": "Cancel"
|
||||
,"web_download_canceled": "Download canceled"
|
||||
,"web_confirm_cancel": "Do you really want to cancel this download?"
|
||||
,"web_update_title": "Updating games list..."
|
||||
,"web_update_message": "Clearing cache and reloading data..."
|
||||
,"web_update_wait": "This may take 10-30 seconds"
|
||||
,"web_error": "Error"
|
||||
,"web_error_unknown": "Unknown error"
|
||||
,"web_error_update": "Error updating games list: {0}"
|
||||
,"web_error_download": "Error: {0}"
|
||||
,"web_history_clear": "Clear History"
|
||||
,"web_history_cleared": "History cleared successfully!"
|
||||
,"web_error_clear_history": "Error clearing history: {0}"
|
||||
,"web_settings_title": "Info & Settings"
|
||||
,"web_settings_roms_folder": "Custom ROMs folder"
|
||||
,"web_settings_roms_placeholder": "Leave empty for default"
|
||||
,"web_settings_browse": "Browse"
|
||||
,"web_settings_language": "Language"
|
||||
,"web_settings_font_scale": "Font scale"
|
||||
,"web_settings_grid": "Grid layout"
|
||||
,"web_settings_font_family": "Font family"
|
||||
,"web_settings_music": "Music"
|
||||
,"web_settings_symlink": "Symlink mode"
|
||||
,"web_settings_source_mode": "Games source"
|
||||
,"web_settings_custom_url": "Custom URL"
|
||||
,"web_settings_custom_url_placeholder": "Let empty for local /saves/ports/rgsx/games.zip or use a direct URL like https://example.com/games.zip"
|
||||
,"web_settings_save": "Save Settings"
|
||||
,"web_settings_saved": "Settings saved successfully!"
|
||||
,"web_settings_saved_restart": "Settings saved successfully!\\n\\n⚠️ Some settings require a server restart:\\n- Custom ROMs folder\\n- Language\\n\\nPlease restart the web server to apply these changes."
|
||||
,"web_error_save_settings": "Error saving settings: {0}"
|
||||
,"web_browse_title": "Browse Directories"
|
||||
,"web_browse_select_drive": "Select a drive..."
|
||||
,"web_browse_drives": "Drives"
|
||||
,"web_browse_parent": "Parent"
|
||||
,"web_browse_select": "Select this folder"
|
||||
,"web_browse_cancel": "Cancel"
|
||||
,"web_browse_empty": "No subdirectories found"
|
||||
,"web_browse_alert_restart": "Important: You need to SAVE the settings and then RESTART the web server/application for the custom ROMs folder to take effect.\\n\\n📝 Steps:\\n1. Click 'Save Settings' button below\\n2. Stop the web server (Ctrl+C in terminal)\\n3. Restart the web server\\n\\nSelected path: {0}"
|
||||
,"web_error_browse": "Error browsing directories: {0}"
|
||||
,"web_loading_platforms": "Loading platforms..."
|
||||
,"web_loading_games": "Loading games..."
|
||||
,"web_no_platforms": "No platforms found"
|
||||
,"web_no_downloads": "No downloads in progress"
|
||||
,"web_history_empty": "No completed downloads"
|
||||
,"web_history_platform": "Platform"
|
||||
,"web_history_size": "Size"
|
||||
,"web_history_status_completed": "Completed"
|
||||
,"web_history_status_error": "Error"
|
||||
,"web_settings_os": "Operating System"
|
||||
,"web_settings_platforms_count": "Number of platforms"
|
||||
,"web_settings_show_unsupported": "Show unsupported platforms (system not found in es_systems.cfg)"
|
||||
,"web_settings_allow_unknown": "Allow unknown extensions (don't show warnings)"
|
||||
,"web_restart_confirm_title": "Restart application?"
|
||||
,"web_restart_confirm_message": "Settings have been saved. Do you want to restart the application now to apply the changes?"
|
||||
,"web_restart_yes": "Yes, restart"
|
||||
,"web_restart_no": "No, later"
|
||||
,"web_restart_success": "Restarting..."
|
||||
,"web_restart_error": "Restart error: {0}"
|
||||
,"web_support": "Support"
|
||||
,"web_support_title": "📦 Support File Generated"
|
||||
,"web_support_message": "Support file created successfully!\\n\\n📁 Contents:\\n• Controls configuration\\n• Download history\\n• RGSX settings\\n• Application logs\\n• Web server logs\\n\\n💬 To get help:\\n1. Join RGSX Discord\\n2. Describe your issue\\n3. Share this ZIP file\\n\\nDownload will start..."
|
||||
,"web_support_generating": "Generating support file..."
|
||||
,"web_support_download": "Download support file"
|
||||
,"web_support_error": "Error generating support file: {0}"
|
||||
,"web_tab_queue": "Queue"
|
||||
,"web_tooltip_queue": "Download queue"
|
||||
,"web_queue_active_download": "⏳ A download is currently active"
|
||||
,"web_queue_no_active": "✓ No active download"
|
||||
,"web_queue_title": "Download Queue"
|
||||
,"web_queue_empty": "No items in queue"
|
||||
,"web_queue_clear": "Clear Queue"
|
||||
,"web_queue_cleared": "Queue cleared successfully!"
|
||||
,"web_confirm_remove_queue": "Remove this item from the queue?"
|
||||
,"web_confirm_clear_queue": "Clear the entire queue?"
|
||||
,"web_remove": "Remove"
|
||||
,"web_loading": "Loading..."
|
||||
,"web_sort": "Sort by"
|
||||
,"web_sort_name_asc": "A-Z (Name)"
|
||||
,"web_sort_name_desc": "Z-A (Name)"
|
||||
,"web_sort_size_asc": "Size +- (Small first)"
|
||||
,"web_sort_size_desc": "Size -+ (Large first)"
|
||||
"instruction_pause_restart": "Restart RGSX to reload configuration",
|
||||
"instruction_pause_support": "Generate a diagnostic ZIP file for support",
|
||||
"instruction_pause_quit": "Exit the RGSX application",
|
||||
"instruction_controls_help": "Show full controller & keyboard reference",
|
||||
"instruction_controls_remap": "Change button / key bindings",
|
||||
"instruction_generic_back": "Return to the previous menu",
|
||||
"instruction_display_layout": "Cycle grid dimensions (columns × rows)",
|
||||
"instruction_display_font_size": "Adjust text scale for readability",
|
||||
"instruction_display_font_family": "Switch between available font families",
|
||||
"instruction_display_show_unsupported": "Show/hide systems not defined in es_systems.cfg",
|
||||
"instruction_display_unknown_ext": "Enable/disable warning for file extensions absent from es_systems.cfg",
|
||||
"instruction_display_hide_premium": "Hide systems requiring premium access via API: {providers}",
|
||||
"instruction_display_filter_platforms": "Manually choose which systems are visible",
|
||||
"instruction_games_history": "List past downloads and statuses",
|
||||
"instruction_games_source_mode": "Switch between RGSX or your own custom list source",
|
||||
"instruction_games_update_cache": "Redownload & refresh current games list",
|
||||
"instruction_settings_music": "Enable or disable background music playback",
|
||||
"instruction_settings_symlink": "Toggle using filesystem symlinks for installs",
|
||||
"instruction_settings_api_keys": "See detected premium provider API keys",
|
||||
"instruction_settings_web_service": "Enable/disable web service auto-start at boot",
|
||||
"settings_web_service": "Web Service at Boot",
|
||||
"settings_web_service_enabled": "Enabled",
|
||||
"settings_web_service_disabled": "Disabled",
|
||||
"settings_web_service_enabling": "Enabling web service...",
|
||||
"settings_web_service_disabling": "Disabling web service...",
|
||||
"settings_web_service_success_enabled": "Web service enabled at boot",
|
||||
"settings_web_service_success_disabled": "Web service disabled at boot",
|
||||
"settings_web_service_error": "Error: {0}",
|
||||
"controls_desc_confirm": "Confirm (e.g. A/Cross)",
|
||||
"controls_desc_cancel": "Cancel/Back (e.g. B/Circle)",
|
||||
"controls_desc_up": "UP ↑",
|
||||
"controls_desc_down": "DOWN ↓",
|
||||
"controls_desc_left": "LEFT ←",
|
||||
"controls_desc_right": "RIGHT →",
|
||||
"controls_desc_page_up": "Fast scroll up (e.g. LT/L2)",
|
||||
"controls_desc_page_down": "Fast scroll down (e.g. RT/R2)",
|
||||
"controls_desc_history": "Open history (e.g. Y/Triangle)",
|
||||
"controls_desc_clear_history": "Downloads: Multi-select / History: Clear (e.g. X/Square)",
|
||||
"controls_desc_filter": "Filter mode: Open/Confirm (e.g. Select)",
|
||||
"controls_desc_delete": "Filter mode: Delete character (e.g. LB/L1)",
|
||||
"controls_desc_space": "Filter mode: Add space (e.g. RB/R1)",
|
||||
"controls_desc_start": "Open pause menu (e.g. Start)",
|
||||
"controls_mapping_title": "Controls mapping",
|
||||
"controls_mapping_instruction": "Hold to confirm the mapping:",
|
||||
"controls_mapping_waiting": "Waiting for a key or button...",
|
||||
"controls_mapping_press": "Press a key or a button",
|
||||
"status_already_present": "Already Present",
|
||||
"footer_joystick": "Joystick: {0}",
|
||||
"history_game_options_title": "Game Options",
|
||||
"history_option_download_folder": "Locate file",
|
||||
"history_option_extract_archive": "Extract archive",
|
||||
"history_option_scraper": "Scrape metadata",
|
||||
"history_option_delete_game": "Delete game",
|
||||
"history_option_error_info": "Error details",
|
||||
"history_option_retry": "Retry download",
|
||||
"history_option_back": "Back",
|
||||
"history_folder_path_label": "Destination path:",
|
||||
"history_scraper_not_implemented": "Scraper not yet implemented",
|
||||
"history_confirm_delete": "Delete this game from disk?",
|
||||
"history_file_not_found": "File not found",
|
||||
"history_extracting": "Extracting...",
|
||||
"history_extracted": "Extracted",
|
||||
"history_delete_success": "Game deleted successfully",
|
||||
"history_delete_error": "Error deleting game: {0}",
|
||||
"history_error_details_title": "Error Details",
|
||||
"history_no_error_message": "No error message available",
|
||||
"web_title": "RGSX Web Interface",
|
||||
"web_tab_platforms": "Platforms List",
|
||||
"web_tab_downloads": "Downloads",
|
||||
"web_tab_history": "History",
|
||||
"web_tab_settings": "Settings",
|
||||
"web_tab_update": "Update games list",
|
||||
"web_tooltip_platforms": "Platforms list",
|
||||
"web_tooltip_downloads": "Downloads",
|
||||
"web_tooltip_history": "History",
|
||||
"web_tooltip_settings": "Settings",
|
||||
"web_tooltip_update": "Update games list",
|
||||
"web_search_platform": "Search platforms or games...",
|
||||
"web_search_game": "Search a game...",
|
||||
"web_search_results": "results for",
|
||||
"web_no_results": "No results found",
|
||||
"web_platforms": "Platforms",
|
||||
"web_games": "Games",
|
||||
"web_error_search": "Search error",
|
||||
"web_back_platforms": "Back to platforms",
|
||||
"web_back": "Back",
|
||||
"web_game_count": "{0} ({1} games)",
|
||||
"web_download": "Download",
|
||||
"web_cancel": "Cancel",
|
||||
"web_download_canceled": "Download canceled",
|
||||
"web_confirm_cancel": "Do you really want to cancel this download?",
|
||||
"web_update_title": "Updating games list...",
|
||||
"web_update_message": "Clearing cache and reloading data...",
|
||||
"web_update_wait": "This may take 10-30 seconds",
|
||||
"web_error": "Error",
|
||||
"web_error_unknown": "Unknown error",
|
||||
"web_error_update": "Error updating games list: {0}",
|
||||
"web_error_download": "Error: {0}",
|
||||
"web_history_clear": "Clear History",
|
||||
"web_history_cleared": "History cleared successfully!",
|
||||
"web_error_clear_history": "Error clearing history: {0}",
|
||||
"web_settings_title": "Info & Settings",
|
||||
"web_settings_roms_folder": "Custom ROMs folder",
|
||||
"web_settings_roms_placeholder": "Leave empty for default",
|
||||
"web_settings_browse": "Browse",
|
||||
"web_settings_language": "Language",
|
||||
"web_settings_font_scale": "Font scale",
|
||||
"web_settings_grid": "Grid layout",
|
||||
"web_settings_font_family": "Font family",
|
||||
"web_settings_music": "Music",
|
||||
"web_settings_symlink": "Symlink mode",
|
||||
"web_settings_source_mode": "Games source",
|
||||
"web_settings_custom_url": "Custom URL",
|
||||
"web_settings_custom_url_placeholder": "Let empty for local /saves/ports/rgsx/games.zip or use a direct URL like https://example.com/games.zip",
|
||||
"web_settings_save": "Save Settings",
|
||||
"web_settings_saved": "Settings saved successfully!",
|
||||
"web_settings_saved_restart": "Settings saved successfully!\\n\\n⚠️ Some settings require a server restart:\\n- Custom ROMs folder\\n- Language\\n\\nPlease restart the web server to apply these changes.",
|
||||
"web_error_save_settings": "Error saving settings: {0}",
|
||||
"web_browse_title": "Browse Directories",
|
||||
"web_browse_select_drive": "Select a drive...",
|
||||
"web_browse_drives": "Drives",
|
||||
"web_browse_parent": "Parent",
|
||||
"web_browse_select": "Select this folder",
|
||||
"web_browse_cancel": "Cancel",
|
||||
"web_browse_empty": "No subdirectories found",
|
||||
"web_browse_alert_restart": "Important: You need to SAVE the settings and then RESTART the web server/application for the custom ROMs folder to take effect.\\n\\n📝 Steps:\\n1. Click 'Save Settings' button below\\n2. Stop the web server (Ctrl+C in terminal)\\n3. Restart the web server\\n\\nSelected path: {0}",
|
||||
"web_error_browse": "Error browsing directories: {0}",
|
||||
"web_loading_platforms": "Loading platforms...",
|
||||
"web_loading_games": "Loading games...",
|
||||
"web_no_platforms": "No platforms found",
|
||||
"web_no_downloads": "No downloads in progress",
|
||||
"web_history_empty": "No completed downloads",
|
||||
"web_history_platform": "Platform",
|
||||
"web_history_size": "Size",
|
||||
"web_history_status_completed": "Completed",
|
||||
"web_history_status_error": "Error",
|
||||
"web_settings_os": "Operating System",
|
||||
"web_settings_platforms_count": "Number of platforms",
|
||||
"web_settings_show_unsupported": "Show unsupported platforms (system not found in es_systems.cfg)",
|
||||
"web_settings_allow_unknown": "Allow unknown extensions (don't show warnings)",
|
||||
"web_restart_confirm_title": "Restart application?",
|
||||
"web_restart_confirm_message": "Settings have been saved. Do you want to restart the application now to apply the changes?",
|
||||
"web_restart_yes": "Yes, restart",
|
||||
"web_restart_no": "No, later",
|
||||
"web_restart_success": "Restarting...",
|
||||
"web_restart_error": "Restart error: {0}",
|
||||
"web_support": "Support",
|
||||
"web_support_title": "📦 Support File Generated",
|
||||
"web_support_message": "Support file created successfully!\\n\\n📁 Contents:\\n• Controls configuration\\n• Download history\\n• RGSX settings\\n• Application logs\\n• Web server logs\\n\\n💬 To get help:\\n1. Join RGSX Discord\\n2. Describe your issue\\n3. Share this ZIP file\\n\\nDownload will start...",
|
||||
"web_support_generating": "Generating support file...",
|
||||
"web_support_download": "Download support file",
|
||||
"web_support_error": "Error generating support file: {0}",
|
||||
"web_tab_queue": "Queue",
|
||||
"web_tooltip_queue": "Download queue",
|
||||
"web_queue_active_download": "⏳ A download is currently active",
|
||||
"web_queue_no_active": "✓ No active download",
|
||||
"web_queue_title": "Download Queue",
|
||||
"web_queue_empty": "No items in queue",
|
||||
"web_queue_clear": "Clear Queue",
|
||||
"web_queue_cleared": "Queue cleared successfully!",
|
||||
"web_confirm_remove_queue": "Remove this item from the queue?",
|
||||
"web_confirm_clear_queue": "Clear the entire queue?",
|
||||
"web_remove": "Remove",
|
||||
"web_loading": "Loading...",
|
||||
"web_sort": "Sort by",
|
||||
"web_sort_name_asc": "A-Z (Name)",
|
||||
"web_sort_name_desc": "Z-A (Name)",
|
||||
"web_sort_size_asc": "Size +- (Small first)",
|
||||
"web_sort_size_desc": "Size -+ (Large first)",
|
||||
"web_filter_region": "Region",
|
||||
"web_filter_hide_non_release": "Hide Demos/Betas/Protos",
|
||||
"web_filter_regex_mode": "Enable Regex Search",
|
||||
"web_filter_one_rom_per_game": "One ROM Per Game",
|
||||
"web_filter_configure_priority": "Configure region priority order",
|
||||
"filter_all": "Check All",
|
||||
"filter_none": "Uncheck All",
|
||||
"filter_apply": "Apply Filter",
|
||||
"filter_back": "Back"
|
||||
}
|
||||
@@ -89,7 +89,7 @@
|
||||
"popup_restarting": "Reiniciando...",
|
||||
"controls_action_clear_history": "Vaciar historial",
|
||||
"controls_action_history": "Historial / Descargas",
|
||||
"controls_action_close_history": "Cerrar Historial",
|
||||
"controls_action_close_history": "Cerrar Historial",
|
||||
"controls_action_delete": "Eliminar",
|
||||
"controls_action_space": "Espacio",
|
||||
"controls_action_start": "Ayuda / Configuración",
|
||||
@@ -140,6 +140,7 @@
|
||||
"download_in_progress": "Descarga en curso...",
|
||||
"download_queued": "En cola de descarga",
|
||||
"download_started": "Descarga iniciada",
|
||||
"network_download_already_queued": "Esta descarga ya está en curso",
|
||||
"utils_extracted": "Extraído: {0}",
|
||||
"utils_corrupt_zip": "Archivo ZIP corrupto: {0}",
|
||||
"utils_permission_denied": "Permiso denegado durante la extracción: {0}",
|
||||
@@ -175,172 +176,190 @@
|
||||
"api_key_empty_suffix": "vacío",
|
||||
"menu_hide_premium_systems": "Ocultar sistemas Premium",
|
||||
"popup_hide_premium_on": "Sistemas Premium ocultos",
|
||||
"popup_hide_premium_off": "Sistemas Premium visibles"
|
||||
,"submenu_display_font_family": "Fuente"
|
||||
,"popup_font_family_changed": "Fuente cambiada: {0}"
|
||||
,"instruction_pause_language": "Cambiar el idioma de la interfaz"
|
||||
,"instruction_pause_controls": "Ver esquema de controles o remapear"
|
||||
,"instruction_pause_display": "Configurar distribución, fuentes y visibilidad de sistemas"
|
||||
,"instruction_pause_games": "Abrir historial, cambiar fuente o refrescar lista"
|
||||
,"instruction_pause_settings": "Música, opción symlink y estado de claves API"
|
||||
,"instruction_pause_restart": "Reiniciar RGSX para recargar configuración"
|
||||
,"instruction_pause_support": "Generar un archivo ZIP de diagnóstico para soporte"
|
||||
,"instruction_pause_quit": "Salir de la aplicación RGSX"
|
||||
,"instruction_controls_help": "Mostrar referencia completa de mando y teclado"
|
||||
,"instruction_controls_remap": "Cambiar asignación de botones / teclas"
|
||||
,"instruction_generic_back": "Volver al menú anterior"
|
||||
,"instruction_display_layout": "Alternar dimensiones de la cuadrícula (columnas × filas)"
|
||||
,"instruction_display_font_size": "Ajustar tamaño del texto para mejor legibilidad"
|
||||
,"instruction_display_font_family": "Cambiar entre familias de fuentes disponibles"
|
||||
,"instruction_display_show_unsupported": "Mostrar/ocultar sistemas no definidos en es_systems.cfg"
|
||||
,"instruction_display_unknown_ext": "Activar/desactivar aviso para extensiones no presentes en es_systems.cfg"
|
||||
,"instruction_display_hide_premium": "Ocultar sistemas que requieren acceso premium vía API: {providers}"
|
||||
,"instruction_display_filter_platforms": "Elegir manualmente qué sistemas son visibles"
|
||||
,"instruction_games_history": "Ver descargas pasadas y su estado"
|
||||
,"instruction_games_source_mode": "Cambiar entre lista RGSX o fuente personalizada"
|
||||
,"instruction_games_update_cache": "Volver a descargar y refrescar la lista de juegos"
|
||||
,"instruction_settings_music": "Activar o desactivar música de fondo"
|
||||
,"instruction_settings_symlink": "Alternar uso de symlinks en instalaciones"
|
||||
,"instruction_settings_api_keys": "Ver claves API premium detectadas"
|
||||
,"controls_desc_confirm": "Confirmar (ej. A/Cruz)"
|
||||
,"controls_desc_cancel": "Cancelar/Volver (ej. B/Círculo)"
|
||||
,"controls_desc_up": "UP ↑"
|
||||
,"controls_desc_down": "DOWN ↓"
|
||||
,"controls_desc_left": "LEFT ←"
|
||||
,"controls_desc_right": "RIGHT →"
|
||||
,"controls_desc_page_up": "Desplazamiento rápido - (ej. LT/L2)"
|
||||
,"controls_desc_page_down": "Desplazamiento rápido + (ej. RT/R2)"
|
||||
,"controls_desc_history": "Abrir historial (ej. Y/Triángulo)"
|
||||
,"controls_desc_clear_history": "Descargas: Selección múltiple / Historial: Limpiar (ej. X/Cuadrado)"
|
||||
,"controls_desc_filter": "Modo filtro: Abrir/Confirmar (ej. Select)"
|
||||
,"controls_desc_delete": "Modo filtro: Eliminar carácter (ej. LB/L1)"
|
||||
,"controls_desc_space": "Modo filtro: Añadir espacio (ej. RB/R1)"
|
||||
,"controls_desc_start": "Abrir menú pausa (ej. Start)"
|
||||
,"controls_mapping_title": "Asignación de controles"
|
||||
,"controls_mapping_instruction": "Mantén para confirmar la asignación:"
|
||||
,"controls_mapping_waiting": "Esperando una tecla o botón..."
|
||||
,"controls_mapping_press": "Pulsa una tecla o un botón"
|
||||
,"status_already_present": "Ya Presente"
|
||||
,"footer_joystick": "Joystick: {0}"
|
||||
,"history_game_options_title": "Opciones del juego"
|
||||
,"history_option_download_folder": "Localizar archivo"
|
||||
,"history_option_extract_archive": "Extraer archivo"
|
||||
,"history_option_scraper": "Scraper metadatos"
|
||||
,"history_option_delete_game": "Eliminar juego"
|
||||
,"history_option_error_info": "Detalles del error"
|
||||
,"history_option_retry": "Reintentar descarga"
|
||||
,"history_option_back": "Volver"
|
||||
,"history_folder_path_label": "Ruta de destino:"
|
||||
,"history_scraper_not_implemented": "Scraper aún no implementado"
|
||||
,"history_confirm_delete": "¿Eliminar este juego del disco?"
|
||||
,"history_file_not_found": "Archivo no encontrado"
|
||||
,"history_extracting": "Extrayendo..."
|
||||
,"history_extracted": "Extraído"
|
||||
,"history_delete_success": "Juego eliminado con éxito"
|
||||
,"history_delete_error": "Error al eliminar juego: {0}"
|
||||
,"history_error_details_title": "Detalles del error"
|
||||
,"history_no_error_message": "No hay mensaje de error disponible"
|
||||
,"web_title": "Interfaz Web RGSX"
|
||||
,"web_tab_platforms": "Lista de sistemas"
|
||||
,"web_tab_downloads": "Descargas"
|
||||
,"web_tab_history": "Historial"
|
||||
,"web_tab_settings": "Configuración"
|
||||
,"web_tab_update": "Actualizar lista"
|
||||
,"web_tooltip_platforms": "Lista de sistemas"
|
||||
,"web_tooltip_downloads": "Descargas"
|
||||
,"web_tooltip_history": "Historial"
|
||||
,"web_tooltip_settings": "Configuración"
|
||||
,"web_tooltip_update": "Actualizar lista de juegos"
|
||||
,"web_search_platform": "Buscar sistemas o juegos..."
|
||||
,"web_search_game": "Buscar un juego..."
|
||||
,"web_search_results": "resultados para"
|
||||
,"web_no_results": "No se encontraron resultados"
|
||||
,"web_platforms": "Sistemas"
|
||||
,"web_games": "Juegos"
|
||||
,"web_error_search": "Error de búsqueda"
|
||||
,"web_back_platforms": "Volver a plataformas"
|
||||
,"web_back": "Volver"
|
||||
,"web_game_count": "{0} ({1} juegos)"
|
||||
,"web_download": "Descargar"
|
||||
,"web_cancel": "Cancelar"
|
||||
,"web_download_canceled": "Descarga cancelada"
|
||||
,"web_confirm_cancel": "¿Realmente desea cancelar esta descarga?"
|
||||
,"web_update_title": "Actualizando lista de juegos..."
|
||||
,"web_update_message": "Limpiando caché y recargando datos..."
|
||||
,"web_update_wait": "Esto puede tardar 10-30 segundos"
|
||||
,"web_error": "Error"
|
||||
,"web_error_unknown": "Error desconocido"
|
||||
,"web_error_update": "Error al actualizar la lista: {0}"
|
||||
,"web_error_download": "Error: {0}"
|
||||
,"web_history_clear": "Limpiar historial"
|
||||
,"web_history_cleared": "¡Historial limpiado con éxito!"
|
||||
,"web_error_clear_history": "Error al limpiar historial: {0}"
|
||||
,"web_settings_title": "Información y Configuración"
|
||||
,"web_settings_roms_folder": "Carpeta ROMs personalizada"
|
||||
,"web_settings_roms_placeholder": "Dejar vacío para predeterminado"
|
||||
,"web_settings_browse": "Explorar"
|
||||
,"web_settings_language": "Idioma"
|
||||
,"web_settings_font_scale": "Escala de fuente"
|
||||
,"web_settings_grid": "Diseño de cuadrícula"
|
||||
,"web_settings_font_family": "Familia de fuente"
|
||||
,"web_settings_music": "Música"
|
||||
,"web_settings_symlink": "Modo symlink"
|
||||
,"web_settings_source_mode": "Fuente de juegos"
|
||||
,"web_settings_custom_url": "URL personalizada"
|
||||
,"web_settings_custom_url_placeholder": "Dejar vacío para /saves/ports/rgsx/games.zip o usar una URL directa como https://ejemplo.com/juegos.zip"
|
||||
,"web_settings_save": "Guardar configuración"
|
||||
,"web_settings_saved": "¡Configuración guardada con éxito!"
|
||||
,"web_settings_saved_restart": "¡Configuración guardada con éxito!\\n\\n⚠️ Algunos ajustes requieren reiniciar el servidor:\\n- Carpeta ROMs personalizada\\n- Idioma\\n\\nPor favor, reinicie el servidor web para aplicar estos cambios."
|
||||
,"web_error_save_settings": "Error al guardar configuración: {0}"
|
||||
,"web_browse_title": "Explorar directorios"
|
||||
,"web_browse_select_drive": "Seleccione una unidad..."
|
||||
,"web_browse_drives": "Unidades"
|
||||
,"web_browse_parent": "Arriba"
|
||||
,"web_browse_select": "Seleccionar esta carpeta"
|
||||
,"web_browse_cancel": "Cancelar"
|
||||
,"web_browse_empty": "No se encontraron subdirectorios"
|
||||
,"web_browse_alert_restart": "Importante: Debe GUARDAR la configuración y luego REINICIAR el servidor web para que la carpeta ROMs personalizada tenga efecto.\\n\\n📝 Pasos:\\n1. Haga clic en 'Guardar configuración' abajo\\n2. Detenga el servidor web (Ctrl+C en terminal)\\n3. Reinicie el servidor web\\n\\nRuta seleccionada: {0}"
|
||||
,"web_error_browse": "Error al explorar directorios: {0}"
|
||||
,"web_loading_platforms": "Cargando plataformas..."
|
||||
,"web_loading_games": "Cargando juegos..."
|
||||
,"web_no_platforms": "No se encontraron plataformas"
|
||||
,"web_no_downloads": "No hay descargas en curso"
|
||||
,"web_history_empty": "No hay descargas completadas"
|
||||
,"web_history_platform": "Plataforma"
|
||||
,"web_history_size": "Tamaño"
|
||||
,"web_history_status_completed": "Completado"
|
||||
,"web_history_status_error": "Error"
|
||||
,"web_settings_os": "Sistema operativo"
|
||||
,"web_settings_platforms_count": "Número de plataformas"
|
||||
,"web_settings_show_unsupported": "Mostrar plataformas no compatibles (sistema ausente en es_systems.cfg)"
|
||||
,"web_settings_allow_unknown": "Permitir extensiones desconocidas (no mostrar advertencias)"
|
||||
,"web_restart_confirm_title": "¿Reiniciar aplicación?"
|
||||
,"web_restart_confirm_message": "Los parámetros se han guardado. ¿Desea reiniciar la aplicación ahora para aplicar los cambios?"
|
||||
,"web_restart_yes": "Sí, reiniciar"
|
||||
,"web_restart_no": "No, más tarde"
|
||||
,"web_restart_success": "Reiniciando..."
|
||||
,"web_restart_error": "Error al reiniciar: {0}"
|
||||
,"web_support": "Soporte"
|
||||
,"web_support_title": "📦 Archivo de soporte generado"
|
||||
,"web_support_message": "¡Archivo de soporte creado con éxito!\\n\\n📁 Contenido:\\n• Configuración de controles\\n• Historial de descargas\\n• Configuración RGSX\\n• Registros de la aplicación\\n• Registros del servidor web\\n\\n💬 Para obtener ayuda:\\n1. Únete al Discord de RGSX\\n2. Describe tu problema\\n3. Comparte este archivo ZIP\\n\\nLa descarga comenzará..."
|
||||
,"web_support_generating": "Generando archivo de soporte..."
|
||||
,"web_support_download": "Descargar archivo de soporte"
|
||||
,"web_support_error": "Error al generar el archivo de soporte: {0}"
|
||||
,"web_tab_queue": "Cola"
|
||||
,"web_tooltip_queue": "Cola de descargas"
|
||||
,"web_queue_active_download": "⏳ Una descarga está activa"
|
||||
,"web_queue_no_active": "✓ Sin descargas activas"
|
||||
,"web_queue_title": "Cola de Descargas"
|
||||
,"web_queue_empty": "No hay elementos en la cola"
|
||||
,"web_queue_clear": "Limpiar cola"
|
||||
,"web_queue_cleared": "¡Cola limpiada con éxito!"
|
||||
,"web_confirm_remove_queue": "¿Eliminar este elemento de la cola?"
|
||||
,"web_confirm_clear_queue": "¿Limpiar toda la cola?"
|
||||
,"web_remove": "Eliminar"
|
||||
,"web_loading": "Cargando..."
|
||||
,"web_sort": "Ordenar por"
|
||||
,"web_sort_name_asc": "A-Z (Nombre)"
|
||||
,"web_sort_name_desc": "Z-A (Nombre)"
|
||||
,"web_sort_size_asc": "Tamaño +- (Menor primero)"
|
||||
,"web_sort_size_desc": "Tamaño -+ (Mayor primero)"
|
||||
"popup_hide_premium_off": "Sistemas Premium visibles",
|
||||
"submenu_display_font_family": "Fuente",
|
||||
"popup_font_family_changed": "Fuente cambiada: {0}",
|
||||
"instruction_pause_language": "Cambiar el idioma de la interfaz",
|
||||
"instruction_pause_controls": "Ver esquema de controles o remapear",
|
||||
"instruction_pause_display": "Configurar distribución, fuentes y visibilidad de sistemas",
|
||||
"instruction_pause_games": "Abrir historial, cambiar fuente o refrescar lista",
|
||||
"instruction_pause_settings": "Música, opción symlink y estado de claves API",
|
||||
"instruction_pause_restart": "Reiniciar RGSX para recargar configuración",
|
||||
"instruction_pause_support": "Generar un archivo ZIP de diagnóstico para soporte",
|
||||
"instruction_pause_quit": "Salir de la aplicación RGSX",
|
||||
"instruction_controls_help": "Mostrar referencia completa de mando y teclado",
|
||||
"instruction_controls_remap": "Cambiar asignación de botones / teclas",
|
||||
"instruction_generic_back": "Volver al menú anterior",
|
||||
"instruction_display_layout": "Alternar dimensiones de la cuadrícula (columnas × filas)",
|
||||
"instruction_display_font_size": "Ajustar tamaño del texto para mejor legibilidad",
|
||||
"instruction_display_font_family": "Cambiar entre familias de fuentes disponibles",
|
||||
"instruction_display_show_unsupported": "Mostrar/ocultar sistemas no definidos en es_systems.cfg",
|
||||
"instruction_display_unknown_ext": "Activar/desactivar aviso para extensiones no presentes en es_systems.cfg",
|
||||
"instruction_display_hide_premium": "Ocultar sistemas que requieren acceso premium vía API: {providers}",
|
||||
"instruction_display_filter_platforms": "Elegir manualmente qué sistemas son visibles",
|
||||
"instruction_games_history": "Ver descargas pasadas y su estado",
|
||||
"instruction_games_source_mode": "Cambiar entre lista RGSX o fuente personalizada",
|
||||
"instruction_games_update_cache": "Volver a descargar y refrescar la lista de juegos",
|
||||
"instruction_settings_music": "Activar o desactivar música de fondo",
|
||||
"instruction_settings_symlink": "Alternar uso de symlinks en instalaciones",
|
||||
"instruction_settings_api_keys": "Ver claves API premium detectadas",
|
||||
"instruction_settings_web_service": "Activar/desactivar inicio automático del servicio web",
|
||||
"settings_web_service": "Servicio Web al Inicio",
|
||||
"settings_web_service_enabled": "Activado",
|
||||
"settings_web_service_disabled": "Desactivado",
|
||||
"settings_web_service_enabling": "Activando servicio web...",
|
||||
"settings_web_service_disabling": "Desactivando servicio web...",
|
||||
"settings_web_service_success_enabled": "Servicio web activado al inicio",
|
||||
"settings_web_service_success_disabled": "Servicio web desactivado al inicio",
|
||||
"settings_web_service_error": "Error: {0}",
|
||||
"controls_desc_confirm": "Confirmar (ej. A/Cruz)",
|
||||
"controls_desc_cancel": "Cancelar/Volver (ej. B/Círculo)",
|
||||
"controls_desc_up": "UP ↑",
|
||||
"controls_desc_down": "DOWN ↓",
|
||||
"controls_desc_left": "LEFT ←",
|
||||
"controls_desc_right": "RIGHT →",
|
||||
"controls_desc_page_up": "Desplazamiento rápido - (ej. LT/L2)",
|
||||
"controls_desc_page_down": "Desplazamiento rápido + (ej. RT/R2)",
|
||||
"controls_desc_history": "Abrir historial (ej. Y/Triángulo)",
|
||||
"controls_desc_clear_history": "Descargas: Selección múltiple / Historial: Limpiar (ej. X/Cuadrado)",
|
||||
"controls_desc_filter": "Modo filtro: Abrir/Confirmar (ej. Select)",
|
||||
"controls_desc_delete": "Modo filtro: Eliminar carácter (ej. LB/L1)",
|
||||
"controls_desc_space": "Modo filtro: Añadir espacio (ej. RB/R1)",
|
||||
"controls_desc_start": "Abrir menú pausa (ej. Start)",
|
||||
"controls_mapping_title": "Asignación de controles",
|
||||
"controls_mapping_instruction": "Mantén para confirmar la asignación:",
|
||||
"controls_mapping_waiting": "Esperando una tecla o botón...",
|
||||
"controls_mapping_press": "Pulsa una tecla o un botón",
|
||||
"status_already_present": "Ya Presente",
|
||||
"footer_joystick": "Joystick: {0}",
|
||||
"history_game_options_title": "Opciones del juego",
|
||||
"history_option_download_folder": "Localizar archivo",
|
||||
"history_option_extract_archive": "Extraer archivo",
|
||||
"history_option_scraper": "Scraper metadatos",
|
||||
"history_option_delete_game": "Eliminar juego",
|
||||
"history_option_error_info": "Detalles del error",
|
||||
"history_option_retry": "Reintentar descarga",
|
||||
"history_option_back": "Volver",
|
||||
"history_folder_path_label": "Ruta de destino:",
|
||||
"history_scraper_not_implemented": "Scraper aún no implementado",
|
||||
"history_confirm_delete": "¿Eliminar este juego del disco?",
|
||||
"history_file_not_found": "Archivo no encontrado",
|
||||
"history_extracting": "Extrayendo...",
|
||||
"history_extracted": "Extraído",
|
||||
"history_delete_success": "Juego eliminado con éxito",
|
||||
"history_delete_error": "Error al eliminar juego: {0}",
|
||||
"history_error_details_title": "Detalles del error",
|
||||
"history_no_error_message": "No hay mensaje de error disponible",
|
||||
"web_title": "Interfaz Web RGSX",
|
||||
"web_tab_platforms": "Lista de sistemas",
|
||||
"web_tab_downloads": "Descargas",
|
||||
"web_tab_history": "Historial",
|
||||
"web_tab_settings": "Configuración",
|
||||
"web_tab_update": "Actualizar lista",
|
||||
"web_tooltip_platforms": "Lista de sistemas",
|
||||
"web_tooltip_downloads": "Descargas",
|
||||
"web_tooltip_history": "Historial",
|
||||
"web_tooltip_settings": "Configuración",
|
||||
"web_tooltip_update": "Actualizar lista de juegos",
|
||||
"web_search_platform": "Buscar sistemas o juegos...",
|
||||
"web_search_game": "Buscar un juego...",
|
||||
"web_search_results": "resultados para",
|
||||
"web_no_results": "No se encontraron resultados",
|
||||
"web_platforms": "Sistemas",
|
||||
"web_games": "Juegos",
|
||||
"web_error_search": "Error de búsqueda",
|
||||
"web_back_platforms": "Volver a plataformas",
|
||||
"web_back": "Volver",
|
||||
"web_game_count": "{0} ({1} juegos)",
|
||||
"web_download": "Descargar",
|
||||
"web_cancel": "Cancelar",
|
||||
"web_download_canceled": "Descarga cancelada",
|
||||
"web_confirm_cancel": "¿Realmente desea cancelar esta descarga?",
|
||||
"web_update_title": "Actualizando lista de juegos...",
|
||||
"web_update_message": "Limpiando caché y recargando datos...",
|
||||
"web_update_wait": "Esto puede tardar 10-30 segundos",
|
||||
"web_error": "Error",
|
||||
"web_error_unknown": "Error desconocido",
|
||||
"web_error_update": "Error al actualizar la lista: {0}",
|
||||
"web_error_download": "Error: {0}",
|
||||
"web_history_clear": "Limpiar historial",
|
||||
"web_history_cleared": "¡Historial limpiado con éxito!",
|
||||
"web_error_clear_history": "Error al limpiar historial: {0}",
|
||||
"web_settings_title": "Información y Configuración",
|
||||
"web_settings_roms_folder": "Carpeta ROMs personalizada",
|
||||
"web_settings_roms_placeholder": "Dejar vacío para predeterminado",
|
||||
"web_settings_browse": "Explorar",
|
||||
"web_settings_language": "Idioma",
|
||||
"web_settings_font_scale": "Escala de fuente",
|
||||
"web_settings_grid": "Diseño de cuadrícula",
|
||||
"web_settings_font_family": "Familia de fuente",
|
||||
"web_settings_music": "Música",
|
||||
"web_settings_symlink": "Modo symlink",
|
||||
"web_settings_source_mode": "Fuente de juegos",
|
||||
"web_settings_custom_url": "URL personalizada",
|
||||
"web_settings_custom_url_placeholder": "Dejar vacío para /saves/ports/rgsx/games.zip o usar una URL directa como https://ejemplo.com/juegos.zip",
|
||||
"web_settings_save": "Guardar configuración",
|
||||
"web_settings_saved": "¡Configuración guardada con éxito!",
|
||||
"web_settings_saved_restart": "¡Configuración guardada con éxito!\\n\\n⚠️ Algunos ajustes requieren reiniciar el servidor:\\n- Carpeta ROMs personalizada\\n- Idioma\\n\\nPor favor, reinicie el servidor web para aplicar estos cambios.",
|
||||
"web_error_save_settings": "Error al guardar configuración: {0}",
|
||||
"web_browse_title": "Explorar directorios",
|
||||
"web_browse_select_drive": "Seleccione una unidad...",
|
||||
"web_browse_drives": "Unidades",
|
||||
"web_browse_parent": "Arriba",
|
||||
"web_browse_select": "Seleccionar esta carpeta",
|
||||
"web_browse_cancel": "Cancelar",
|
||||
"web_browse_empty": "No se encontraron subdirectorios",
|
||||
"web_browse_alert_restart": "Importante: Debe GUARDAR la configuración y luego REINICIAR el servidor web para que la carpeta ROMs personalizada tenga efecto.\\n\\n📝 Pasos:\\n1. Haga clic en 'Guardar configuración' abajo\\n2. Detenga el servidor web (Ctrl+C en terminal)\\n3. Reinicie el servidor web\\n\\nRuta seleccionada: {0}",
|
||||
"web_error_browse": "Error al explorar directorios: {0}",
|
||||
"web_loading_platforms": "Cargando plataformas...",
|
||||
"web_loading_games": "Cargando juegos...",
|
||||
"web_no_platforms": "No se encontraron plataformas",
|
||||
"web_no_downloads": "No hay descargas en curso",
|
||||
"web_history_empty": "No hay descargas completadas",
|
||||
"web_history_platform": "Plataforma",
|
||||
"web_history_size": "Tamaño",
|
||||
"web_history_status_completed": "Completado",
|
||||
"web_history_status_error": "Error",
|
||||
"web_settings_os": "Sistema operativo",
|
||||
"web_settings_platforms_count": "Número de plataformas",
|
||||
"web_settings_show_unsupported": "Mostrar plataformas no compatibles (sistema ausente en es_systems.cfg)",
|
||||
"web_settings_allow_unknown": "Permitir extensiones desconocidas (no mostrar advertencias)",
|
||||
"web_restart_confirm_title": "¿Reiniciar aplicación?",
|
||||
"web_restart_confirm_message": "Los parámetros se han guardado. ¿Desea reiniciar la aplicación ahora para aplicar los cambios?",
|
||||
"web_restart_yes": "Sí, reiniciar",
|
||||
"web_restart_no": "No, más tarde",
|
||||
"web_restart_success": "Reiniciando...",
|
||||
"web_restart_error": "Error al reiniciar: {0}",
|
||||
"web_support": "Soporte",
|
||||
"web_support_title": "📦 Archivo de soporte generado",
|
||||
"web_support_message": "¡Archivo de soporte creado con éxito!\\n\\n📁 Contenido:\\n• Configuración de controles\\n• Historial de descargas\\n• Configuración RGSX\\n• Registros de la aplicación\\n• Registros del servidor web\\n\\n💬 Para obtener ayuda:\\n1. Únete al Discord de RGSX\\n2. Describe tu problema\\n3. Comparte este archivo ZIP\\n\\nLa descarga comenzará...",
|
||||
"web_support_generating": "Generando archivo de soporte...",
|
||||
"web_support_download": "Descargar archivo de soporte",
|
||||
"web_support_error": "Error al generar el archivo de soporte: {0}",
|
||||
"web_tab_queue": "Cola",
|
||||
"web_tooltip_queue": "Cola de descargas",
|
||||
"web_queue_active_download": "⏳ Una descarga está activa",
|
||||
"web_queue_no_active": "✓ Sin descargas activas",
|
||||
"web_queue_title": "Cola de Descargas",
|
||||
"web_queue_empty": "No hay elementos en la cola",
|
||||
"web_queue_clear": "Limpiar cola",
|
||||
"web_queue_cleared": "¡Cola limpiada con éxito!",
|
||||
"web_confirm_remove_queue": "¿Eliminar este elemento de la cola?",
|
||||
"web_confirm_clear_queue": "¿Limpiar toda la cola?",
|
||||
"web_remove": "Eliminar",
|
||||
"web_loading": "Cargando...",
|
||||
"web_sort": "Ordenar por",
|
||||
"web_sort_name_asc": "A-Z (Nombre)",
|
||||
"web_sort_name_desc": "Z-A (Nombre)",
|
||||
"web_sort_size_asc": "Tamaño +- (Menor primero)",
|
||||
"web_sort_size_desc": "Tamaño -+ (Mayor primero)",
|
||||
"web_filter_region": "Región",
|
||||
"web_filter_hide_non_release": "Ocultar Demos/Betas/Protos",
|
||||
"web_filter_regex_mode": "Activar búsqueda Regex",
|
||||
"web_filter_one_rom_per_game": "Una ROM por juego",
|
||||
"web_filter_configure_priority": "Configurar orden de prioridad de regiones",
|
||||
"filter_all": "Marcar todo",
|
||||
"filter_none": "Desmarcar todo",
|
||||
"filter_apply": "Aplicar filtro",
|
||||
"filter_back": "Volver"
|
||||
}
|
||||
@@ -141,7 +141,7 @@
|
||||
"download_queued": "En file d'attente",
|
||||
"download_started": "Téléchargement démarré",
|
||||
"network_download_already_queued": "Ce téléchargement est déjà en cours",
|
||||
"utils_extracted": "Extracted: {0}",
|
||||
"utils_extracted": "Extrait: {0}",
|
||||
"utils_corrupt_zip": "Archive ZIP corrompue: {0}",
|
||||
"utils_permission_denied": "Permission refusée lors de l'extraction: {0}",
|
||||
"utils_extraction_failed": "Échec de l'extraction: {0}",
|
||||
@@ -176,172 +176,190 @@
|
||||
"api_key_empty_suffix": "vide",
|
||||
"menu_hide_premium_systems": "Masquer systèmes Premium",
|
||||
"popup_hide_premium_on": "Systèmes Premium masqués",
|
||||
"popup_hide_premium_off": "Systèmes Premium visibles"
|
||||
,"submenu_display_font_family": "Police"
|
||||
,"popup_font_family_changed": "Police changée : {0}"
|
||||
,"instruction_pause_language": "Changer la langue de l'interface"
|
||||
,"instruction_pause_controls": "Afficher la configuration ou remapper"
|
||||
,"instruction_pause_display": "Agencer l'affichage, polices et systèmes visibles"
|
||||
,"instruction_pause_games": "Historique, source de liste ou rafraîchissement"
|
||||
,"instruction_pause_settings": "Musique, option symlink & statut des clés API"
|
||||
,"instruction_pause_restart": "Redémarrer RGSX pour recharger la configuration"
|
||||
,"instruction_pause_support": "Générer un fichier ZIP de diagnostic pour l'assistance"
|
||||
,"instruction_pause_quit": "Quitter l'application RGSX"
|
||||
,"instruction_controls_help": "Afficher la référence complète manette & clavier"
|
||||
,"instruction_controls_remap": "Modifier l'association boutons / touches"
|
||||
,"instruction_generic_back": "Revenir au menu précédent"
|
||||
,"instruction_display_layout": "Changer les dimensions de la grille"
|
||||
,"instruction_display_font_size": "Ajuster la taille du texte pour la lisibilité"
|
||||
,"instruction_display_font_family": "Basculer entre les polices disponibles"
|
||||
,"instruction_display_show_unsupported": "Afficher/masquer systèmes absents de es_systems.cfg"
|
||||
,"instruction_display_unknown_ext": "Avertir ou non pour extensions absentes de es_systems.cfg"
|
||||
,"instruction_display_hide_premium": "Masquer les systèmes nécessitant un accès premium via API: {providers}"
|
||||
,"instruction_display_filter_platforms": "Choisir manuellement les systèmes visibles"
|
||||
,"instruction_games_history": "Lister les téléchargements passés et leur statut"
|
||||
,"instruction_games_source_mode": "Basculer entre liste RGSX ou source personnalisée"
|
||||
,"instruction_games_update_cache": "Retélécharger & rafraîchir la liste des jeux"
|
||||
,"instruction_settings_music": "Activer ou désactiver la lecture musicale"
|
||||
,"instruction_settings_symlink": "Basculer l'utilisation de symlinks pour l'installation"
|
||||
,"instruction_settings_api_keys": "Voir les clés API détectées des services premium"
|
||||
,"controls_desc_confirm": "Valider (ex: A/Croix)"
|
||||
,"controls_desc_cancel": "Annuler/Retour (ex: B/Rond)"
|
||||
,"controls_desc_up": "UP ↑"
|
||||
,"controls_desc_down": "DOWN ↓"
|
||||
,"controls_desc_left": "LEFT ←"
|
||||
,"controls_desc_right": "RIGHT →"
|
||||
,"controls_desc_page_up": "Défilement Rapide - (ex: LT/L2)"
|
||||
,"controls_desc_page_down": "Défilement Rapide + (ex: RT/R2)"
|
||||
,"controls_desc_history": "Ouvrir l'historique (ex: Y/Triangle)"
|
||||
,"controls_desc_clear_history": "Téléchargements : Sélection multiple / Historique : Vider (ex: X/Carré)"
|
||||
,"controls_desc_filter": "Mode Filtre : Ouvrir/Valider (ex: Select)"
|
||||
,"controls_desc_delete": "Mode Filtre : Supprimer caractère (ex: LB/L1)"
|
||||
,"controls_desc_space": "Mode Filtre : Ajouter espace (ex: RB/R1)"
|
||||
,"controls_desc_start": "Ouvrir le menu pause (ex: Start)"
|
||||
,"controls_mapping_title": "Configuration des contrôles"
|
||||
,"controls_mapping_instruction": "Maintenez pour confirmer l'association :"
|
||||
,"controls_mapping_waiting": "En attente d'une touche ou d'un bouton..."
|
||||
,"controls_mapping_press": "Appuyez sur une touche ou un bouton"
|
||||
,"status_already_present": "Déjà Présent"
|
||||
,"footer_joystick": "Joystick : {0}"
|
||||
,"history_game_options_title": "Options du jeu"
|
||||
,"history_option_download_folder": "Localiser le fichier"
|
||||
,"history_option_extract_archive": "Extraire l'archive"
|
||||
,"history_option_scraper": "Scraper métadonnées"
|
||||
,"history_option_delete_game": "Supprimer le jeu"
|
||||
,"history_option_error_info": "Détails de l'erreur"
|
||||
,"history_option_retry": "Réessayer le téléchargement"
|
||||
,"history_option_back": "Retour"
|
||||
,"history_folder_path_label": "Chemin de destination :"
|
||||
,"history_scraper_not_implemented": "Scraper pas encore implémenté"
|
||||
,"history_confirm_delete": "Supprimer ce jeu du disque ?"
|
||||
,"history_file_not_found": "Fichier introuvable"
|
||||
,"history_extracting": "Extraction en cours..."
|
||||
,"history_extracted": "Extrait"
|
||||
,"history_delete_success": "Jeu supprimé avec succès"
|
||||
,"history_delete_error": "Erreur lors de la suppression du jeu : {0}"
|
||||
,"history_error_details_title": "Détails de l'erreur"
|
||||
,"history_no_error_message": "Aucun message d'erreur disponible"
|
||||
,"web_title": "Interface Web RGSX"
|
||||
,"web_tab_platforms": "Liste des systèmes"
|
||||
,"web_tab_downloads": "Téléchargements"
|
||||
,"web_tab_history": "Historique"
|
||||
,"web_tab_settings": "Paramètres"
|
||||
,"web_tab_update": "Mettre à jour la liste"
|
||||
,"web_tooltip_platforms": "Liste des systèmes"
|
||||
,"web_tooltip_downloads": "Téléchargements"
|
||||
,"web_tooltip_history": "Historique"
|
||||
,"web_tooltip_settings": "Paramètres"
|
||||
,"web_tooltip_update": "Mettre à jour la liste des jeux"
|
||||
,"web_search_platform": "Rechercher des systèmes ou jeux..."
|
||||
,"web_search_game": "Rechercher un jeu..."
|
||||
,"web_search_results": "résultats pour"
|
||||
,"web_no_results": "Aucun résultat trouvé"
|
||||
,"web_platforms": "Systèmes"
|
||||
,"web_games": "Jeux"
|
||||
,"web_error_search": "Erreur de recherche"
|
||||
,"web_back_platforms": "Retour aux plateformes"
|
||||
,"web_back": "Retour"
|
||||
,"web_game_count": "{0} ({1} jeux)"
|
||||
,"web_download": "Télécharger"
|
||||
,"web_cancel": "Annuler"
|
||||
,"web_download_canceled": "Téléchargement annulé"
|
||||
,"web_confirm_cancel": "Voulez-vous vraiment annuler ce téléchargement ?"
|
||||
,"web_update_title": "Mise à jour de la liste des jeux..."
|
||||
,"web_update_message": "Nettoyage du cache et rechargement des données..."
|
||||
,"web_update_wait": "Cela peut prendre 10-30 secondes"
|
||||
,"web_error": "Erreur"
|
||||
,"web_error_unknown": "Erreur inconnue"
|
||||
,"web_error_update": "Erreur lors de la mise à jour de la liste : {0}"
|
||||
,"web_error_download": "Erreur : {0}"
|
||||
,"web_history_clear": "Vider l'historique"
|
||||
,"web_history_cleared": "Historique vidé avec succès !"
|
||||
,"web_error_clear_history": "Erreur lors du vidage de l'historique : {0}"
|
||||
,"web_settings_title": "Informations & Paramètres"
|
||||
,"web_settings_roms_folder": "Dossier ROMs personnalisé"
|
||||
,"web_settings_roms_placeholder": "Laisser vide pour le dossier par défaut"
|
||||
,"web_settings_browse": "Parcourir"
|
||||
,"web_settings_language": "Langue"
|
||||
,"web_settings_font_scale": "Échelle de police"
|
||||
,"web_settings_grid": "Grille d'affichage"
|
||||
,"web_settings_font_family": "Police de caractères"
|
||||
,"web_settings_music": "Musique"
|
||||
,"web_settings_symlink": "Mode symlink"
|
||||
,"web_settings_source_mode": "Source des jeux"
|
||||
,"web_settings_custom_url": "URL personnalisée"
|
||||
,"web_settings_custom_url_placeholder": "Laisser vide pour /saves/ports/rgsx/games.zip ou utiliser une URL directe comme https://exemple.com/jeux.zip"
|
||||
,"web_settings_save": "Enregistrer les paramètres"
|
||||
,"web_settings_saved": "Paramètres enregistrés avec succès !"
|
||||
,"web_settings_saved_restart": "Paramètres enregistrés avec succès !\\n\\n⚠️ Certains paramètres nécessitent un redémarrage du serveur :\\n- Dossier ROMs personnalisé\\n- Langue\\n\\nVeuillez redémarrer le serveur web pour appliquer ces changements."
|
||||
,"web_error_save_settings": "Erreur lors de l'enregistrement : {0}"
|
||||
,"web_browse_title": "Parcourir les dossiers"
|
||||
,"web_browse_select_drive": "Sélectionnez un lecteur..."
|
||||
,"web_browse_drives": "Lecteurs"
|
||||
,"web_browse_parent": "Parent"
|
||||
,"web_browse_select": "Sélectionner ce dossier"
|
||||
,"web_browse_cancel": "Annuler"
|
||||
,"web_browse_empty": "Aucun sous-dossier trouvé"
|
||||
,"web_browse_alert_restart": "Important : Vous devez ENREGISTRER les paramètres puis REDÉMARRER le serveur web pour que le dossier ROMs personnalisé soit pris en compte.\\n\\n📝 Étapes :\\n1. Cliquez sur 'Enregistrer les paramètres' ci-dessous\\n2. Arrêtez le serveur web (Ctrl+C dans le terminal)\\n3. Redémarrez le serveur web\\n\\nChemin sélectionné : {0}"
|
||||
,"web_error_browse": "Erreur lors de la navigation : {0}"
|
||||
,"web_loading_platforms": "Chargement des plateformes..."
|
||||
,"web_loading_games": "Chargement des jeux..."
|
||||
,"web_no_platforms": "Aucune plateforme trouvée"
|
||||
,"web_no_downloads": "Aucun téléchargement en cours"
|
||||
,"web_history_empty": "Aucun téléchargement terminé"
|
||||
,"web_history_platform": "Plateforme"
|
||||
,"web_history_size": "Taille"
|
||||
,"web_history_status_completed": "Terminé"
|
||||
,"web_history_status_error": "Erreur"
|
||||
,"web_settings_os": "Système d'exploitation"
|
||||
,"web_settings_platforms_count": "Nombre de plateformes"
|
||||
,"web_settings_show_unsupported": "Afficher les systèmes non supportés (absents de es_systems.cfg)"
|
||||
,"web_settings_allow_unknown": "Autoriser les extensions inconnues (ne pas afficher d'avertissement)"
|
||||
,"web_restart_confirm_title": "Redémarrer l'application ?"
|
||||
,"web_restart_confirm_message": "Les paramètres ont été enregistrés. Voulez-vous redémarrer l'application maintenant pour appliquer les changements ?"
|
||||
,"web_restart_yes": "Oui, redémarrer"
|
||||
,"web_restart_no": "Non, plus tard"
|
||||
,"web_restart_success": "Redémarrage en cours..."
|
||||
,"web_restart_error": "Erreur lors du redémarrage : {0}"
|
||||
,"web_support": "Support"
|
||||
,"web_support_title": "📦 Fichier de support généré"
|
||||
,"web_support_message": "Le fichier de support a été créé avec succès !\\n\\n📁 Contenu :\\n• Configuration des contrôles\\n• Historique des téléchargements\\n• Paramètres RGSX\\n• Logs de l'application\\n• Logs du serveur web\\n\\n💬 Pour obtenir de l'aide :\\n1. Rejoignez le Discord RGSX\\n2. Décrivez votre problème\\n3. Partagez ce fichier ZIP\\n\\nLe téléchargement va démarrer..."
|
||||
,"web_support_generating": "Génération du fichier de support..."
|
||||
,"web_support_download": "Télécharger le fichier de support"
|
||||
,"web_support_error": "Erreur lors de la génération du fichier de support : {0}"
|
||||
,"web_tab_queue": "File d'attente"
|
||||
,"web_tooltip_queue": "File d'attente des téléchargements"
|
||||
,"web_queue_active_download": "⏳ Un téléchargement est actuellement en cours"
|
||||
,"web_queue_no_active": "✓ Aucun téléchargement actif"
|
||||
,"web_queue_title": "File d'attente des téléchargements"
|
||||
,"web_queue_empty": "Aucun élément en attente"
|
||||
,"web_queue_clear": "Vider la file d'attente"
|
||||
,"web_queue_cleared": "File d'attente vidée avec succès !"
|
||||
,"web_confirm_remove_queue": "Supprimer cet élément de la file d'attente ?"
|
||||
,"web_confirm_clear_queue": "Vider toute la file d'attente ?"
|
||||
,"web_remove": "Supprimer"
|
||||
,"web_loading": "Chargement..."
|
||||
,"web_sort": "Trier par"
|
||||
,"web_sort_name_asc": "A-Z (Nom)"
|
||||
,"web_sort_name_desc": "Z-A (Nom)"
|
||||
,"web_sort_size_asc": "Taille +- (Petit d'abord)"
|
||||
,"web_sort_size_desc": "Taille -+ (Grand d'abord)"
|
||||
"popup_hide_premium_off": "Systèmes Premium visibles",
|
||||
"submenu_display_font_family": "Police",
|
||||
"popup_font_family_changed": "Police changée : {0}",
|
||||
"instruction_pause_language": "Changer la langue de l'interface",
|
||||
"instruction_pause_controls": "Afficher la configuration ou remapper",
|
||||
"instruction_pause_display": "Agencer l'affichage, polices et systèmes visibles",
|
||||
"instruction_pause_games": "Historique, source de liste ou rafraîchissement",
|
||||
"instruction_pause_settings": "Musique, option symlink & statut des clés API",
|
||||
"instruction_pause_restart": "Redémarrer RGSX pour recharger la configuration",
|
||||
"instruction_pause_support": "Générer un fichier ZIP de diagnostic pour l'assistance",
|
||||
"instruction_pause_quit": "Quitter l'application RGSX",
|
||||
"instruction_controls_help": "Afficher la référence complète manette & clavier",
|
||||
"instruction_controls_remap": "Modifier l'association boutons / touches",
|
||||
"instruction_generic_back": "Revenir au menu précédent",
|
||||
"instruction_display_layout": "Changer les dimensions de la grille",
|
||||
"instruction_display_font_size": "Ajuster la taille du texte pour la lisibilité",
|
||||
"instruction_display_font_family": "Basculer entre les polices disponibles",
|
||||
"instruction_display_show_unsupported": "Afficher/masquer systèmes absents de es_systems.cfg",
|
||||
"instruction_display_unknown_ext": "Avertir ou non pour extensions absentes de es_systems.cfg",
|
||||
"instruction_display_hide_premium": "Masquer les systèmes nécessitant un accès premium via API: {providers}",
|
||||
"instruction_display_filter_platforms": "Choisir manuellement les systèmes visibles",
|
||||
"instruction_games_history": "Lister les téléchargements passés et leur statut",
|
||||
"instruction_games_source_mode": "Basculer entre liste RGSX ou source personnalisée",
|
||||
"instruction_games_update_cache": "Retélécharger & rafraîchir la liste des jeux",
|
||||
"instruction_settings_music": "Activer ou désactiver la lecture musicale",
|
||||
"instruction_settings_symlink": "Basculer l'utilisation de symlinks pour l'installation",
|
||||
"instruction_settings_api_keys": "Voir les clés API détectées des services premium",
|
||||
"instruction_settings_web_service": "Activer/désactiver le démarrage automatique du service web",
|
||||
"settings_web_service": "Service Web au démarrage",
|
||||
"settings_web_service_enabled": "Activé",
|
||||
"settings_web_service_disabled": "Désactivé",
|
||||
"settings_web_service_enabling": "Activation du service web...",
|
||||
"settings_web_service_disabling": "Désactivation du service web...",
|
||||
"settings_web_service_success_enabled": "Service web activé au démarrage",
|
||||
"settings_web_service_success_disabled": "Service web désactivé au démarrage",
|
||||
"settings_web_service_error": "Erreur : {0}",
|
||||
"controls_desc_confirm": "Valider (ex: A/Croix)",
|
||||
"controls_desc_cancel": "Annuler/Retour (ex: B/Rond)",
|
||||
"controls_desc_up": "UP ↑",
|
||||
"controls_desc_down": "DOWN ↓",
|
||||
"controls_desc_left": "LEFT ←",
|
||||
"controls_desc_right": "RIGHT →",
|
||||
"controls_desc_page_up": "Défilement Rapide - (ex: LT/L2)",
|
||||
"controls_desc_page_down": "Défilement Rapide + (ex: RT/R2)",
|
||||
"controls_desc_history": "Ouvrir l'historique (ex: Y/Triangle)",
|
||||
"controls_desc_clear_history": "Téléchargements : Sélection multiple / Historique : Vider (ex: X/Carré)",
|
||||
"controls_desc_filter": "Mode Filtre : Ouvrir/Valider (ex: Select)",
|
||||
"controls_desc_delete": "Mode Filtre : Supprimer caractère (ex: LB/L1)",
|
||||
"controls_desc_space": "Mode Filtre : Ajouter espace (ex: RB/R1)",
|
||||
"controls_desc_start": "Ouvrir le menu pause (ex: Start)",
|
||||
"controls_mapping_title": "Configuration des contrôles",
|
||||
"controls_mapping_instruction": "Maintenez pour confirmer l'association :",
|
||||
"controls_mapping_waiting": "En attente d'une touche ou d'un bouton...",
|
||||
"controls_mapping_press": "Appuyez sur une touche ou un bouton",
|
||||
"status_already_present": "Déjà Présent",
|
||||
"footer_joystick": "Joystick : {0}",
|
||||
"history_game_options_title": "Options du jeu",
|
||||
"history_option_download_folder": "Localiser le fichier",
|
||||
"history_option_extract_archive": "Extraire l'archive",
|
||||
"history_option_scraper": "Scraper métadonnées",
|
||||
"history_option_delete_game": "Supprimer le jeu",
|
||||
"history_option_error_info": "Détails de l'erreur",
|
||||
"history_option_retry": "Réessayer le téléchargement",
|
||||
"history_option_back": "Retour",
|
||||
"history_folder_path_label": "Chemin de destination :",
|
||||
"history_scraper_not_implemented": "Scraper pas encore implémenté",
|
||||
"history_confirm_delete": "Supprimer ce jeu du disque ?",
|
||||
"history_file_not_found": "Fichier introuvable",
|
||||
"history_extracting": "Extraction en cours...",
|
||||
"history_extracted": "Extrait",
|
||||
"history_delete_success": "Jeu supprimé avec succès",
|
||||
"history_delete_error": "Erreur lors de la suppression du jeu : {0}",
|
||||
"history_error_details_title": "Détails de l'erreur",
|
||||
"history_no_error_message": "Aucun message d'erreur disponible",
|
||||
"web_title": "Interface Web RGSX",
|
||||
"web_tab_platforms": "Liste des systèmes",
|
||||
"web_tab_downloads": "Téléchargements",
|
||||
"web_tab_history": "Historique",
|
||||
"web_tab_settings": "Paramètres",
|
||||
"web_tab_update": "Mettre à jour la liste",
|
||||
"web_tooltip_platforms": "Liste des systèmes",
|
||||
"web_tooltip_downloads": "Téléchargements",
|
||||
"web_tooltip_history": "Historique",
|
||||
"web_tooltip_settings": "Paramètres",
|
||||
"web_tooltip_update": "Mettre à jour la liste des jeux",
|
||||
"web_search_platform": "Rechercher des systèmes ou jeux...",
|
||||
"web_search_game": "Rechercher un jeu...",
|
||||
"web_search_results": "résultats pour",
|
||||
"web_no_results": "Aucun résultat trouvé",
|
||||
"web_platforms": "Systèmes",
|
||||
"web_games": "Jeux",
|
||||
"web_error_search": "Erreur de recherche",
|
||||
"web_back_platforms": "Retour aux plateformes",
|
||||
"web_back": "Retour",
|
||||
"web_game_count": "{0} ({1} jeux)",
|
||||
"web_download": "Télécharger",
|
||||
"web_cancel": "Annuler",
|
||||
"web_download_canceled": "Téléchargement annulé",
|
||||
"web_confirm_cancel": "Voulez-vous vraiment annuler ce téléchargement ?",
|
||||
"web_update_title": "Mise à jour de la liste des jeux...",
|
||||
"web_update_message": "Nettoyage du cache et rechargement des données...",
|
||||
"web_update_wait": "Cela peut prendre 10-30 secondes",
|
||||
"web_error": "Erreur",
|
||||
"web_error_unknown": "Erreur inconnue",
|
||||
"web_error_update": "Erreur lors de la mise à jour de la liste : {0}",
|
||||
"web_error_download": "Erreur : {0}",
|
||||
"web_history_clear": "Vider l'historique",
|
||||
"web_history_cleared": "Historique vidé avec succès !",
|
||||
"web_error_clear_history": "Erreur lors du vidage de l'historique : {0}",
|
||||
"web_settings_title": "Informations & Paramètres",
|
||||
"web_settings_roms_folder": "Dossier ROMs personnalisé",
|
||||
"web_settings_roms_placeholder": "Laisser vide pour le dossier par défaut",
|
||||
"web_settings_browse": "Parcourir",
|
||||
"web_settings_language": "Langue",
|
||||
"web_settings_font_scale": "Échelle de police",
|
||||
"web_settings_grid": "Grille d'affichage",
|
||||
"web_settings_font_family": "Police de caractères",
|
||||
"web_settings_music": "Musique",
|
||||
"web_settings_symlink": "Mode symlink",
|
||||
"web_settings_source_mode": "Source des jeux",
|
||||
"web_settings_custom_url": "URL personnalisée",
|
||||
"web_settings_custom_url_placeholder": "Laisser vide pour /saves/ports/rgsx/games.zip ou utiliser une URL directe comme https://exemple.com/jeux.zip",
|
||||
"web_settings_save": "Enregistrer les paramètres",
|
||||
"web_settings_saved": "Paramètres enregistrés avec succès !",
|
||||
"web_settings_saved_restart": "Paramètres enregistrés avec succès !\\n\\n⚠️ Certains paramètres nécessitent un redémarrage du serveur :\\n- Dossier ROMs personnalisé\\n- Langue\\n\\nVeuillez redémarrer le serveur web pour appliquer ces changements.",
|
||||
"web_error_save_settings": "Erreur lors de l'enregistrement : {0}",
|
||||
"web_browse_title": "Parcourir les dossiers",
|
||||
"web_browse_select_drive": "Sélectionnez un lecteur...",
|
||||
"web_browse_drives": "Lecteurs",
|
||||
"web_browse_parent": "Parent",
|
||||
"web_browse_select": "Sélectionner ce dossier",
|
||||
"web_browse_cancel": "Annuler",
|
||||
"web_browse_empty": "Aucun sous-dossier trouvé",
|
||||
"web_browse_alert_restart": "Important : Vous devez ENREGISTRER les paramètres puis REDÉMARRER le serveur web pour que le dossier ROMs personnalisé soit pris en compte.\\n\\n📝 Étapes :\\n1. Cliquez sur 'Enregistrer les paramètres' ci-dessous\\n2. Arrêtez le serveur web (Ctrl+C dans le terminal)\\n3. Redémarrez le serveur web\\n\\nChemin sélectionné : {0}",
|
||||
"web_error_browse": "Erreur lors de la navigation : {0}",
|
||||
"web_loading_platforms": "Chargement des plateformes...",
|
||||
"web_loading_games": "Chargement des jeux...",
|
||||
"web_no_platforms": "Aucune plateforme trouvée",
|
||||
"web_no_downloads": "Aucun téléchargement en cours",
|
||||
"web_history_empty": "Aucun téléchargement terminé",
|
||||
"web_history_platform": "Plateforme",
|
||||
"web_history_size": "Taille",
|
||||
"web_history_status_completed": "Terminé",
|
||||
"web_history_status_error": "Erreur",
|
||||
"web_settings_os": "Système d'exploitation",
|
||||
"web_settings_platforms_count": "Nombre de plateformes",
|
||||
"web_settings_show_unsupported": "Afficher les systèmes non supportés (absents de es_systems.cfg)",
|
||||
"web_settings_allow_unknown": "Autoriser les extensions inconnues (ne pas afficher d'avertissement)",
|
||||
"web_restart_confirm_title": "Redémarrer l'application ?",
|
||||
"web_restart_confirm_message": "Les paramètres ont été enregistrés. Voulez-vous redémarrer l'application maintenant pour appliquer les changements ?",
|
||||
"web_restart_yes": "Oui, redémarrer",
|
||||
"web_restart_no": "Non, plus tard",
|
||||
"web_restart_success": "Redémarrage en cours...",
|
||||
"web_restart_error": "Erreur lors du redémarrage : {0}",
|
||||
"web_support": "Support",
|
||||
"web_support_title": "📦 Fichier de support généré",
|
||||
"web_support_message": "Le fichier de support a été créé avec succès !\\n\\n📁 Contenu :\\n• Configuration des contrôles\\n• Historique des téléchargements\\n• Paramètres RGSX\\n• Logs de l'application\\n• Logs du serveur web\\n\\n💬 Pour obtenir de l'aide :\\n1. Rejoignez le Discord RGSX\\n2. Décrivez votre problème\\n3. Partagez ce fichier ZIP\\n\\nLe téléchargement va démarrer...",
|
||||
"web_support_generating": "Génération du fichier de support...",
|
||||
"web_support_download": "Télécharger le fichier de support",
|
||||
"web_support_error": "Erreur lors de la génération du fichier de support : {0}",
|
||||
"web_tab_queue": "File d'attente",
|
||||
"web_tooltip_queue": "File d'attente des téléchargements",
|
||||
"web_queue_active_download": "⏳ Un téléchargement est actuellement en cours",
|
||||
"web_queue_no_active": "✓ Aucun téléchargement actif",
|
||||
"web_queue_title": "File d'attente des téléchargements",
|
||||
"web_queue_empty": "Aucun élément en attente",
|
||||
"web_queue_clear": "Vider la file d'attente",
|
||||
"web_queue_cleared": "File d'attente vidée avec succès !",
|
||||
"web_confirm_remove_queue": "Supprimer cet élément de la file d'attente ?",
|
||||
"web_confirm_clear_queue": "Vider toute la file d'attente ?",
|
||||
"web_remove": "Supprimer",
|
||||
"web_loading": "Chargement...",
|
||||
"web_sort": "Trier par",
|
||||
"web_sort_name_asc": "A-Z (Nom)",
|
||||
"web_sort_name_desc": "Z-A (Nom)",
|
||||
"web_sort_size_asc": "Taille +- (Petit d'abord)",
|
||||
"web_sort_size_desc": "Taille -+ (Grand d'abord)",
|
||||
"web_filter_region": "Région",
|
||||
"web_filter_hide_non_release": "Masquer Démos/Betas/Protos",
|
||||
"web_filter_regex_mode": "Activer recherche Regex",
|
||||
"web_filter_one_rom_per_game": "Une ROM par jeu",
|
||||
"web_filter_configure_priority": "Configurer l'ordre de priorité des régions",
|
||||
"filter_all": "Tout cocher",
|
||||
"filter_none": "Tout décocher",
|
||||
"filter_apply": "Appliquer filtre",
|
||||
"filter_back": "Retour"
|
||||
}
|
||||
@@ -89,7 +89,7 @@
|
||||
"popup_restarting": "Riavvio...",
|
||||
"controls_action_clear_history": "Cancella cronologia",
|
||||
"controls_action_history": "Cronologia / Downloads",
|
||||
"controls_action_close_history": "Chiudi Cronologia",
|
||||
"controls_action_close_history": "Chiudi Cronologia",
|
||||
"controls_action_delete": "Elimina",
|
||||
"controls_action_space": "Spazio",
|
||||
"controls_action_start": "Aiuto / Impostazioni",
|
||||
@@ -128,9 +128,16 @@
|
||||
"download_in_progress": "Download in corso...",
|
||||
"download_queued": "In coda di download",
|
||||
"download_started": "Download iniziato",
|
||||
"accessibility_font_size": "Dimensione carattere: {0}",
|
||||
"confirm_cancel_download": "Annullare il download corrente?",
|
||||
"controls_help_title": "Guida ai controlli",
|
||||
"network_download_already_queued": "Questo download è già in corso",
|
||||
"utils_extracted": "Estratto: {0}",
|
||||
"utils_corrupt_zip": "Archivio ZIP corrotto: {0}",
|
||||
"utils_permission_denied": "Permesso negato durante l'estrazione: {0}",
|
||||
"utils_extraction_failed": "Estrazione fallita: {0}",
|
||||
"utils_unrar_unavailable": "Comando unrar non disponibile",
|
||||
"utils_rar_list_failed": "Impossibile elencare i file RAR: {0}",
|
||||
"symlink_option_enabled": "Opzione symlink abilitata",
|
||||
"symlink_option_disabled": "Opzione symlink disabilitata",
|
||||
"menu_games_source_prefix": "Sorgente giochi",
|
||||
"controls_category_navigation": "Navigazione",
|
||||
"controls_category_main_actions": "Azioni principali",
|
||||
"controls_category_downloads": "Download",
|
||||
@@ -140,9 +147,6 @@
|
||||
"controls_confirm_select": "Conferma/Seleziona",
|
||||
"controls_cancel_back": "Annulla/Indietro",
|
||||
"controls_filter_search": "Filtro/Ricerca",
|
||||
"symlink_option_enabled": "Opzione symlink abilitata",
|
||||
"symlink_option_disabled": "Opzione symlink disabilitata",
|
||||
"menu_games_source_prefix": "Sorgente giochi",
|
||||
"games_source_rgsx": "RGSX",
|
||||
"sources_mode_rgsx_select_info": "RGSX: aggiorna l'elenco dei giochi",
|
||||
"games_source_custom": "Personalizzato",
|
||||
@@ -169,172 +173,193 @@
|
||||
"api_key_empty_suffix": "vuoto",
|
||||
"menu_hide_premium_systems": "Nascondi sistemi Premium",
|
||||
"popup_hide_premium_on": "Sistemi Premium nascosti",
|
||||
"popup_hide_premium_off": "Sistemi Premium visibili"
|
||||
,"submenu_display_font_family": "Font"
|
||||
,"popup_font_family_changed": "Font cambiato: {0}"
|
||||
,"instruction_pause_language": "Cambiare la lingua dell'interfaccia"
|
||||
,"instruction_pause_controls": "Vedere schema controlli o avviare rimappatura"
|
||||
,"instruction_pause_display": "Configurare layout, font e visibilità sistemi"
|
||||
,"instruction_pause_games": "Aprire cronologia, cambiare sorgente o aggiornare elenco"
|
||||
,"instruction_pause_settings": "Musica, opzione symlink e stato chiavi API"
|
||||
,"instruction_pause_restart": "Riavvia RGSX per ricaricare la configurazione"
|
||||
,"instruction_pause_support": "Genera un file ZIP diagnostico per il supporto"
|
||||
,"instruction_pause_quit": "Uscire dall'applicazione RGSX"
|
||||
,"instruction_controls_help": "Mostrare riferimento completo controller & tastiera"
|
||||
,"instruction_controls_remap": "Modificare associazione pulsanti / tasti"
|
||||
,"instruction_generic_back": "Tornare al menu precedente"
|
||||
,"instruction_display_layout": "Scorrere dimensioni griglia (colonne × righe)"
|
||||
,"instruction_display_font_size": "Regolare dimensione testo per leggibilità"
|
||||
,"instruction_display_font_family": "Cambiare famiglia di font disponibile"
|
||||
,"instruction_display_show_unsupported": "Mostrare/nascondere sistemi non definiti in es_systems.cfg"
|
||||
,"instruction_display_unknown_ext": "Attivare/disattivare avviso per estensioni assenti in es_systems.cfg"
|
||||
,"instruction_display_hide_premium": "Nascondere sistemi che richiedono accesso premium via API: {providers}"
|
||||
,"instruction_display_filter_platforms": "Scegliere manualmente quali sistemi sono visibili"
|
||||
,"instruction_games_history": "Elencare download passati e stato"
|
||||
,"instruction_games_source_mode": "Passare tra elenco RGSX o sorgente personalizzata"
|
||||
,"instruction_games_update_cache": "Riscaria e aggiorna l'elenco dei giochi"
|
||||
,"instruction_settings_music": "Abilitare o disabilitare musica di sottofondo"
|
||||
,"instruction_settings_symlink": "Abilitare/disabilitare uso symlink per installazioni"
|
||||
,"instruction_settings_api_keys": "Mostrare chiavi API premium rilevate"
|
||||
,"controls_desc_confirm": "Confermare (es. A/Croce)"
|
||||
,"controls_desc_cancel": "Annullare/Indietro (es. B/Cerchio)"
|
||||
,"controls_desc_up": "UP ↑"
|
||||
,"controls_desc_down": "DOWN ↓"
|
||||
,"controls_desc_left": "LEFT ←"
|
||||
,"controls_desc_right": "RIGHT →"
|
||||
,"controls_desc_page_up": "Scorrimento rapido su (es. LT/L2)"
|
||||
,"controls_desc_page_down": "Scorrimento rapido giù (es. RT/R2)"
|
||||
,"controls_desc_history": "Aprire cronologia (es. Y/Triangolo)"
|
||||
,"controls_desc_clear_history": "Download: Selezione multipla / Cronologia: Svuotare (es. X/Quadrato)"
|
||||
,"controls_desc_filter": "Modalità filtro: Aprire/Confermare (es. Select)"
|
||||
,"controls_desc_delete": "Modalità filtro: Eliminare carattere (es. LB/L1)"
|
||||
,"controls_desc_space": "Modalità filtro: Aggiungere spazio (es. RB/R1)"
|
||||
,"controls_desc_start": "Aprire menu pausa (es. Start)"
|
||||
,"controls_mapping_title": "Mappatura controlli"
|
||||
,"controls_mapping_instruction": "Tieni premuto per confermare l'associazione:"
|
||||
,"controls_mapping_waiting": "In attesa di un tasto o pulsante..."
|
||||
,"controls_mapping_press": "Premi un tasto o un pulsante"
|
||||
,"status_already_present": "Già Presente"
|
||||
,"footer_joystick": "Joystick: {0}"
|
||||
,"history_game_options_title": "Opzioni gioco"
|
||||
,"history_option_download_folder": "Localizza file"
|
||||
,"history_option_extract_archive": "Estrai archivio"
|
||||
,"history_option_scraper": "Scraper metadati"
|
||||
,"history_option_delete_game": "Elimina gioco"
|
||||
,"history_option_error_info": "Dettagli errore"
|
||||
,"history_option_retry": "Riprova download"
|
||||
,"history_option_back": "Indietro"
|
||||
,"history_folder_path_label": "Percorso destinazione:"
|
||||
,"history_scraper_not_implemented": "Scraper non ancora implementato"
|
||||
,"history_confirm_delete": "Eliminare questo gioco dal disco?"
|
||||
,"history_file_not_found": "File non trovato"
|
||||
,"history_extracting": "Estrazione in corso..."
|
||||
,"history_extracted": "Estratto"
|
||||
,"history_delete_success": "Gioco eliminato con successo"
|
||||
,"history_delete_error": "Errore durante l'eliminazione del gioco: {0}"
|
||||
,"history_error_details_title": "Dettagli errore"
|
||||
,"history_no_error_message": "Nessun messaggio di errore disponibile"
|
||||
,"web_title": "Interfaccia Web RGSX"
|
||||
,"web_tab_platforms": "Elenco sistemi"
|
||||
,"web_tab_downloads": "Download"
|
||||
,"web_tab_history": "Cronologia"
|
||||
,"web_tab_settings": "Impostazioni"
|
||||
,"web_tab_update": "Aggiorna elenco"
|
||||
,"web_tooltip_platforms": "Elenco sistemi"
|
||||
,"web_tooltip_downloads": "Download"
|
||||
,"web_tooltip_history": "Cronologia"
|
||||
,"web_tooltip_settings": "Impostazioni"
|
||||
,"web_tooltip_update": "Aggiorna elenco giochi"
|
||||
,"web_search_platform": "Cerca sistemi o giochi..."
|
||||
,"web_search_game": "Cerca un gioco..."
|
||||
,"web_search_results": "risultati per"
|
||||
,"web_no_results": "Nessun risultato trovato"
|
||||
,"web_platforms": "Sistemi"
|
||||
,"web_games": "Giochi"
|
||||
,"web_error_search": "Errore di ricerca"
|
||||
,"web_back_platforms": "Torna alle piattaforme"
|
||||
,"web_back": "Indietro"
|
||||
,"web_game_count": "{0} ({1} giochi)"
|
||||
,"web_download": "Scarica"
|
||||
,"web_cancel": "Annulla"
|
||||
,"web_download_canceled": "Download annullato"
|
||||
,"web_confirm_cancel": "Vuoi davvero annullare questo download?"
|
||||
,"web_update_title": "Aggiornamento elenco giochi..."
|
||||
,"web_update_message": "Pulizia cache e ricaricamento dati..."
|
||||
,"web_update_wait": "Potrebbe richiedere 10-30 secondi"
|
||||
,"web_error": "Errore"
|
||||
,"web_error_unknown": "Errore sconosciuto"
|
||||
,"web_error_update": "Errore durante l'aggiornamento dell'elenco: {0}"
|
||||
,"web_error_download": "Errore: {0}"
|
||||
,"web_history_clear": "Cancella cronologia"
|
||||
,"web_history_cleared": "Cronologia cancellata con successo!"
|
||||
,"web_error_clear_history": "Errore durante la cancellazione della cronologia: {0}"
|
||||
,"web_settings_title": "Info e Impostazioni"
|
||||
,"web_settings_roms_folder": "Cartella ROMs personalizzata"
|
||||
,"web_settings_roms_placeholder": "Lasciare vuoto per predefinito"
|
||||
,"web_settings_browse": "Sfoglia"
|
||||
,"web_settings_language": "Lingua"
|
||||
,"web_settings_font_scale": "Scala carattere"
|
||||
,"web_settings_grid": "Layout griglia"
|
||||
,"web_settings_font_family": "Famiglia carattere"
|
||||
,"web_settings_music": "Musica"
|
||||
,"web_settings_symlink": "Modalità symlink"
|
||||
,"web_settings_source_mode": "Fonte giochi"
|
||||
,"web_settings_custom_url": "URL personalizzato"
|
||||
,"web_settings_custom_url_placeholder": " Lasciare vuoto per /saves/ports/rgsx/games.zip o usare una URL diretta come https://esempio.com/giochi.zip"
|
||||
,"web_settings_save": "Salva impostazioni"
|
||||
,"web_settings_saved": "Impostazioni salvate con successo!"
|
||||
,"web_settings_saved_restart": "Impostazioni salvate con successo!\\n\\n⚠️ Alcune impostazioni richiedono il riavvio del server:\\n- Cartella ROMs personalizzata\\n- Lingua\\n\\nRiavviare il server web per applicare queste modifiche."
|
||||
,"web_error_save_settings": "Errore durante il salvataggio delle impostazioni: {0}"
|
||||
,"web_browse_title": "Sfoglia directory"
|
||||
,"web_browse_select_drive": "Seleziona un'unità..."
|
||||
,"web_browse_drives": "Unità"
|
||||
,"web_browse_parent": "Superiore"
|
||||
,"web_browse_select": "Seleziona questa cartella"
|
||||
,"web_browse_cancel": "Annulla"
|
||||
,"web_browse_empty": "Nessuna sottodirectory trovata"
|
||||
,"web_browse_alert_restart": "Importante: È necessario SALVARE le impostazioni e poi RIAVVIARE il server web affinché la cartella ROMs personalizzata abbia effetto.\\n\\n📝 Passaggi:\\n1. Fare clic su 'Salva impostazioni' qui sotto\\n2. Arrestare il server web (Ctrl+C nel terminale)\\n3. Riavviare il server web\\n\\nPercorso selezionato: {0}"
|
||||
,"web_error_browse": "Errore durante la navigazione delle directory: {0}"
|
||||
,"web_loading_platforms": "Caricamento piattaforme..."
|
||||
,"web_loading_games": "Caricamento giochi..."
|
||||
,"web_no_platforms": "Nessuna piattaforma trovata"
|
||||
,"web_no_downloads": "Nessun download in corso"
|
||||
,"web_history_empty": "Nessun download completato"
|
||||
,"web_history_platform": "Piattaforma"
|
||||
,"web_history_size": "Dimensione"
|
||||
,"web_history_status_completed": "Completato"
|
||||
,"web_history_status_error": "Errore"
|
||||
,"web_settings_os": "Sistema operativo"
|
||||
,"web_settings_platforms_count": "Numero di piattaforme"
|
||||
,"web_settings_show_unsupported": "Mostra piattaforme non supportate (sistema assente in es_systems.cfg)"
|
||||
,"web_settings_allow_unknown": "Consenti estensioni sconosciute (non mostrare avvisi)"
|
||||
,"web_restart_confirm_title": "Riavviare l'applicazione?"
|
||||
,"web_restart_confirm_message": "Le impostazioni sono state salvate. Vuoi riavviare l'applicazione ora per applicare le modifiche?"
|
||||
,"web_restart_yes": "Sì, riavvia"
|
||||
,"web_restart_no": "No, più tardi"
|
||||
,"web_restart_success": "Riavvio in corso..."
|
||||
,"web_restart_error": "Errore durante il riavvio: {0}"
|
||||
,"web_support": "Supporto"
|
||||
,"web_support_title": "📦 File di supporto generato"
|
||||
,"web_support_message": "File di supporto creato con successo!\\n\\n📁 Contenuto:\\n• Configurazione controlli\\n• Cronologia download\\n• Impostazioni RGSX\\n• Log dell'applicazione\\n• Log del server web\\n\\n💬 Per ottenere aiuto:\\n1. Unisciti al Discord RGSX\\n2. Descrivi il tuo problema\\n3. Condividi questo file ZIP\\n\\nIl download inizierà..."
|
||||
,"web_support_generating": "Generazione file di supporto..."
|
||||
,"web_support_download": "Scarica file di supporto"
|
||||
,"web_support_error": "Errore nella generazione del file di supporto: {0}"
|
||||
,"web_tab_queue": "Coda"
|
||||
,"web_tooltip_queue": "Coda di download"
|
||||
,"web_queue_active_download": "⏳ Un download è attivo"
|
||||
,"web_queue_no_active": "✓ Nessun download attivo"
|
||||
,"web_queue_title": "Coda di Download"
|
||||
,"web_queue_empty": "Nessun elemento in coda"
|
||||
,"web_queue_clear": "Svuota coda"
|
||||
,"web_queue_cleared": "Coda svuotata con successo!"
|
||||
,"web_confirm_remove_queue": "Rimuovere questo elemento dalla coda?"
|
||||
,"web_confirm_clear_queue": "Svuotare l'intera coda?"
|
||||
,"web_remove": "Rimuovi"
|
||||
,"web_loading": "Caricamento..."
|
||||
,"web_sort": "Ordina per"
|
||||
,"web_sort_name_asc": "A-Z (Nome)"
|
||||
,"web_sort_name_desc": "Z-A (Nome)"
|
||||
,"web_sort_size_asc": "Dimensione +- (Piccolo primo)"
|
||||
,"web_sort_size_desc": "Dimensione -+ (Grande primo)"
|
||||
"popup_hide_premium_off": "Sistemi Premium visibili",
|
||||
"submenu_display_font_family": "Font",
|
||||
"popup_font_family_changed": "Font cambiato: {0}",
|
||||
"instruction_pause_language": "Cambiare la lingua dell'interfaccia",
|
||||
"instruction_pause_controls": "Vedere schema controlli o avviare rimappatura",
|
||||
"instruction_pause_display": "Configurare layout, font e visibilità sistemi",
|
||||
"instruction_pause_games": "Aprire cronologia, cambiare sorgente o aggiornare elenco",
|
||||
"instruction_pause_settings": "Musica, opzione symlink e stato chiavi API",
|
||||
"instruction_pause_restart": "Riavvia RGSX per ricaricare la configurazione",
|
||||
"instruction_pause_support": "Genera un file ZIP diagnostico per il supporto",
|
||||
"instruction_pause_quit": "Uscire dall'applicazione RGSX",
|
||||
"instruction_controls_help": "Mostrare riferimento completo controller & tastiera",
|
||||
"instruction_controls_remap": "Modificare associazione pulsanti / tasti",
|
||||
"instruction_generic_back": "Tornare al menu precedente",
|
||||
"instruction_display_layout": "Scorrere dimensioni griglia (colonne × righe)",
|
||||
"instruction_display_font_size": "Regolare dimensione testo per leggibilità",
|
||||
"instruction_display_font_family": "Cambiare famiglia di font disponibile",
|
||||
"instruction_display_show_unsupported": "Mostrare/nascondere sistemi non definiti in es_systems.cfg",
|
||||
"instruction_display_unknown_ext": "Attivare/disattivare avviso per estensioni assenti in es_systems.cfg",
|
||||
"instruction_display_hide_premium": "Nascondere sistemi che richiedono accesso premium via API: {providers}",
|
||||
"instruction_display_filter_platforms": "Scegliere manualmente quali sistemi sono visibili",
|
||||
"instruction_games_history": "Elencare download passati e stato",
|
||||
"instruction_games_source_mode": "Passare tra elenco RGSX o sorgente personalizzata",
|
||||
"instruction_games_update_cache": "Riscaria e aggiorna l'elenco dei giochi",
|
||||
"instruction_settings_music": "Abilitare o disabilitare musica di sottofondo",
|
||||
"instruction_settings_symlink": "Abilitare/disabilitare uso symlink per installazioni",
|
||||
"instruction_settings_api_keys": "Mostrare chiavi API premium rilevate",
|
||||
"instruction_settings_web_service": "Attivare/disattivare avvio automatico servizio web all'avvio",
|
||||
"settings_web_service": "Servizio Web all'Avvio",
|
||||
"settings_web_service_enabled": "Abilitato",
|
||||
"settings_web_service_disabled": "Disabilitato",
|
||||
"settings_web_service_enabling": "Abilitazione servizio web...",
|
||||
"settings_web_service_disabling": "Disabilitazione servizio web...",
|
||||
"settings_web_service_success_enabled": "Servizio web abilitato all'avvio",
|
||||
"settings_web_service_success_disabled": "Servizio web disabilitato all'avvio",
|
||||
"settings_web_service_error": "Errore: {0}",
|
||||
"controls_desc_confirm": "Confermare (es. A/Croce)",
|
||||
"controls_desc_cancel": "Annullare/Indietro (es. B/Cerchio)",
|
||||
"controls_desc_up": "UP ↑",
|
||||
"controls_desc_down": "DOWN ↓",
|
||||
"controls_desc_left": "LEFT ←",
|
||||
"controls_desc_right": "RIGHT →",
|
||||
"controls_desc_page_up": "Scorrimento rapido su (es. LT/L2)",
|
||||
"controls_desc_page_down": "Scorrimento rapido giù (es. RT/R2)",
|
||||
"controls_desc_history": "Aprire cronologia (es. Y/Triangolo)",
|
||||
"controls_desc_clear_history": "Download: Selezione multipla / Cronologia: Svuotare (es. X/Quadrato)",
|
||||
"controls_desc_filter": "Modalità filtro: Aprire/Confermare (es. Select)",
|
||||
"controls_desc_delete": "Modalità filtro: Eliminare carattere (es. LB/L1)",
|
||||
"controls_desc_space": "Modalità filtro: Aggiungere spazio (es. RB/R1)",
|
||||
"controls_desc_start": "Aprire menu pausa (es. Start)",
|
||||
"controls_mapping_title": "Mappatura controlli",
|
||||
"controls_mapping_instruction": "Tieni premuto per confermare l'associazione:",
|
||||
"controls_mapping_waiting": "In attesa di un tasto o pulsante...",
|
||||
"controls_mapping_press": "Premi un tasto o un pulsante",
|
||||
"status_already_present": "Già Presente",
|
||||
"footer_joystick": "Joystick: {0}",
|
||||
"history_game_options_title": "Opzioni gioco",
|
||||
"history_option_download_folder": "Localizza file",
|
||||
"history_option_extract_archive": "Estrai archivio",
|
||||
"history_option_scraper": "Scraper metadati",
|
||||
"history_option_delete_game": "Elimina gioco",
|
||||
"history_option_error_info": "Dettagli errore",
|
||||
"history_option_retry": "Riprova download",
|
||||
"history_option_back": "Indietro",
|
||||
"history_folder_path_label": "Percorso destinazione:",
|
||||
"history_scraper_not_implemented": "Scraper non ancora implementato",
|
||||
"history_confirm_delete": "Eliminare questo gioco dal disco?",
|
||||
"history_file_not_found": "File non trovato",
|
||||
"history_extracting": "Estrazione in corso...",
|
||||
"history_extracted": "Estratto",
|
||||
"history_delete_success": "Gioco eliminato con successo",
|
||||
"history_delete_error": "Errore durante l'eliminazione del gioco: {0}",
|
||||
"history_error_details_title": "Dettagli errore",
|
||||
"history_no_error_message": "Nessun messaggio di errore disponibile",
|
||||
"web_title": "Interfaccia Web RGSX",
|
||||
"web_tab_platforms": "Elenco sistemi",
|
||||
"web_tab_downloads": "Download",
|
||||
"web_tab_history": "Cronologia",
|
||||
"web_tab_settings": "Impostazioni",
|
||||
"web_tab_update": "Aggiorna elenco",
|
||||
"web_tooltip_platforms": "Elenco sistemi",
|
||||
"web_tooltip_downloads": "Download",
|
||||
"web_tooltip_history": "Cronologia",
|
||||
"web_tooltip_settings": "Impostazioni",
|
||||
"web_tooltip_update": "Aggiorna elenco giochi",
|
||||
"web_search_platform": "Cerca sistemi o giochi...",
|
||||
"web_search_game": "Cerca un gioco...",
|
||||
"web_search_results": "risultati per",
|
||||
"web_no_results": "Nessun risultato trovato",
|
||||
"web_platforms": "Sistemi",
|
||||
"web_games": "Giochi",
|
||||
"web_error_search": "Errore di ricerca",
|
||||
"web_back_platforms": "Torna alle piattaforme",
|
||||
"web_back": "Indietro",
|
||||
"web_game_count": "{0} ({1} giochi)",
|
||||
"web_download": "Scarica",
|
||||
"web_cancel": "Annulla",
|
||||
"web_download_canceled": "Download annullato",
|
||||
"web_confirm_cancel": "Vuoi davvero annullare questo download?",
|
||||
"web_update_title": "Aggiornamento elenco giochi...",
|
||||
"web_update_message": "Pulizia cache e ricaricamento dati...",
|
||||
"web_update_wait": "Potrebbe richiedere 10-30 secondi",
|
||||
"web_error": "Errore",
|
||||
"web_error_unknown": "Errore sconosciuto",
|
||||
"web_error_update": "Errore durante l'aggiornamento dell'elenco: {0}",
|
||||
"web_error_download": "Errore: {0}",
|
||||
"web_history_clear": "Cancella cronologia",
|
||||
"web_history_cleared": "Cronologia cancellata con successo!",
|
||||
"web_error_clear_history": "Errore durante la cancellazione della cronologia: {0}",
|
||||
"web_settings_title": "Info e Impostazioni",
|
||||
"web_settings_roms_folder": "Cartella ROMs personalizzata",
|
||||
"web_settings_roms_placeholder": "Lasciare vuoto per predefinito",
|
||||
"web_settings_browse": "Sfoglia",
|
||||
"web_settings_language": "Lingua",
|
||||
"web_settings_font_scale": "Scala carattere",
|
||||
"web_settings_grid": "Layout griglia",
|
||||
"web_settings_font_family": "Famiglia carattere",
|
||||
"web_settings_music": "Musica",
|
||||
"web_settings_symlink": "Modalità symlink",
|
||||
"web_settings_source_mode": "Fonte giochi",
|
||||
"web_settings_custom_url": "URL personalizzato",
|
||||
"web_settings_custom_url_placeholder": " Lasciare vuoto per /saves/ports/rgsx/games.zip o usare una URL diretta come https://esempio.com/giochi.zip",
|
||||
"web_settings_save": "Salva impostazioni",
|
||||
"web_settings_saved": "Impostazioni salvate con successo!",
|
||||
"web_settings_saved_restart": "Impostazioni salvate con successo!\\n\\n⚠️ Alcune impostazioni richiedono il riavvio del server:\\n- Cartella ROMs personalizzata\\n- Lingua\\n\\nRiavviare il server web per applicare queste modifiche.",
|
||||
"web_error_save_settings": "Errore durante il salvataggio delle impostazioni: {0}",
|
||||
"web_browse_title": "Sfoglia directory",
|
||||
"web_browse_select_drive": "Seleziona un'unità...",
|
||||
"web_browse_drives": "Unità",
|
||||
"web_browse_parent": "Superiore",
|
||||
"web_browse_select": "Seleziona questa cartella",
|
||||
"web_browse_cancel": "Annulla",
|
||||
"web_browse_empty": "Nessuna sottodirectory trovata",
|
||||
"web_browse_alert_restart": "Importante: È necessario SALVARE le impostazioni e poi RIAVVIARE il server web affinché la cartella ROMs personalizzata abbia effetto.\\n\\n📝 Passaggi:\\n1. Fare clic su 'Salva impostazioni' qui sotto\\n2. Arrestare il server web (Ctrl+C nel terminale)\\n3. Riavviare il server web\\n\\nPercorso selezionato: {0}",
|
||||
"web_error_browse": "Errore durante la navigazione delle directory: {0}",
|
||||
"web_loading_platforms": "Caricamento piattaforme...",
|
||||
"web_loading_games": "Caricamento giochi...",
|
||||
"web_no_platforms": "Nessuna piattaforma trovata",
|
||||
"web_no_downloads": "Nessun download in corso",
|
||||
"web_history_empty": "Nessun download completato",
|
||||
"web_history_platform": "Piattaforma",
|
||||
"web_history_size": "Dimensione",
|
||||
"web_history_status_completed": "Completato",
|
||||
"web_history_status_error": "Errore",
|
||||
"web_settings_os": "Sistema operativo",
|
||||
"web_settings_platforms_count": "Numero di piattaforme",
|
||||
"web_settings_show_unsupported": "Mostra piattaforme non supportate (sistema assente in es_systems.cfg)",
|
||||
"web_settings_allow_unknown": "Consenti estensioni sconosciute (non mostrare avvisi)",
|
||||
"web_restart_confirm_title": "Riavviare l'applicazione?",
|
||||
"web_restart_confirm_message": "Le impostazioni sono state salvate. Vuoi riavviare l'applicazione ora per applicare le modifiche?",
|
||||
"web_restart_yes": "Sì, riavvia",
|
||||
"web_restart_no": "No, più tardi",
|
||||
"web_restart_success": "Riavvio in corso...",
|
||||
"web_restart_error": "Errore durante il riavvio: {0}",
|
||||
"web_support": "Supporto",
|
||||
"web_support_title": "📦 File di supporto generato",
|
||||
"web_support_message": "File di supporto creato con successo!\\n\\n📁 Contenuto:\\n• Configurazione controlli\\n• Cronologia download\\n• Impostazioni RGSX\\n• Log dell'applicazione\\n• Log del server web\\n\\n💬 Per ottenere aiuto:\\n1. Unisciti al Discord RGSX\\n2. Descrivi il tuo problema\\n3. Condividi questo file ZIP\\n\\nIl download inizierà...",
|
||||
"web_support_generating": "Generazione file di supporto...",
|
||||
"web_support_download": "Scarica file di supporto",
|
||||
"web_support_error": "Errore nella generazione del file di supporto: {0}",
|
||||
"web_tab_queue": "Coda",
|
||||
"web_tooltip_queue": "Coda di download",
|
||||
"web_queue_active_download": "⏳ Un download è attivo",
|
||||
"web_queue_no_active": "✓ Nessun download attivo",
|
||||
"web_queue_title": "Coda di Download",
|
||||
"web_queue_empty": "Nessun elemento in coda",
|
||||
"web_queue_clear": "Svuota coda",
|
||||
"web_queue_cleared": "Coda svuotata con successo!",
|
||||
"web_confirm_remove_queue": "Rimuovere questo elemento dalla coda?",
|
||||
"web_confirm_clear_queue": "Svuotare l'intera coda?",
|
||||
"web_remove": "Rimuovi",
|
||||
"web_loading": "Caricamento...",
|
||||
"web_sort": "Ordina per",
|
||||
"web_sort_name_asc": "A-Z (Nome)",
|
||||
"web_sort_name_desc": "Z-A (Nome)",
|
||||
"web_sort_size_asc": "Dimensione +- (Piccolo primo)",
|
||||
"web_sort_size_desc": "Dimensione -+ (Grande primo)",
|
||||
"accessibility_font_size": "Dimensione carattere: {0}",
|
||||
"confirm_cancel_download": "Annullare il download corrente?",
|
||||
"controls_help_title": "Guida ai controlli",
|
||||
"web_filter_region": "Regione",
|
||||
"web_filter_hide_non_release": "Nascondi Demo/Beta/Proto",
|
||||
"web_filter_regex_mode": "Attiva ricerca Regex",
|
||||
"web_filter_one_rom_per_game": "Una ROM per gioco",
|
||||
"web_filter_configure_priority": "Configura ordine di priorità delle regioni",
|
||||
"filter_all": "Seleziona tutto",
|
||||
"filter_none": "Deseleziona tutto",
|
||||
"filter_apply": "Applica filtro",
|
||||
"filter_back": "Indietro"
|
||||
}
|
||||
@@ -89,7 +89,7 @@
|
||||
"popup_restarting": "Reiniciando...",
|
||||
"controls_action_clear_history": "Limpar histórico",
|
||||
"controls_action_history": "Histórico / Downloads",
|
||||
"controls_action_close_history": "Fechar Histórico",
|
||||
"controls_action_close_history": "Fechar Histórico",
|
||||
"controls_action_delete": "Deletar",
|
||||
"controls_action_space": "Espaço",
|
||||
"controls_action_start": "Ajuda / Configurações",
|
||||
@@ -128,7 +128,13 @@
|
||||
"download_in_progress": "Download em andamento...",
|
||||
"download_queued": "Na fila de download",
|
||||
"download_started": "Download iniciado",
|
||||
"accessibility_font_size": "Tamanho da fonte: {0}",
|
||||
"network_download_already_queued": "Este download já está em andamento",
|
||||
"utils_extracted": "Extraído: {0}",
|
||||
"utils_corrupt_zip": "Arquivo ZIP corrupto: {0}",
|
||||
"utils_permission_denied": "Permissão negada durante a extração: {0}",
|
||||
"utils_extraction_failed": "Falha na extração: {0}",
|
||||
"utils_unrar_unavailable": "Comando unrar não disponível",
|
||||
"utils_rar_list_failed": "Falha ao listar arquivos RAR: {0}",
|
||||
"confirm_cancel_download": "Cancelar download atual?",
|
||||
"controls_help_title": "Ajuda de Controles",
|
||||
"controls_category_navigation": "Navegação",
|
||||
@@ -169,172 +175,191 @@
|
||||
"api_key_empty_suffix": "vazio",
|
||||
"menu_hide_premium_systems": "Ocultar sistemas Premium",
|
||||
"popup_hide_premium_on": "Sistemas Premium ocultos",
|
||||
"popup_hide_premium_off": "Sistemas Premium visíveis"
|
||||
,"submenu_display_font_family": "Fonte"
|
||||
,"popup_font_family_changed": "Fonte alterada: {0}"
|
||||
,"instruction_pause_language": "Alterar o idioma da interface"
|
||||
,"instruction_pause_controls": "Ver esquema de controles ou iniciar remapeamento"
|
||||
,"instruction_pause_display": "Configurar layout, fontes e visibilidade de sistemas"
|
||||
,"instruction_pause_games": "Abrir histórico, mudar fonte ou atualizar lista"
|
||||
,"instruction_pause_settings": "Música, opção symlink e status das chaves API"
|
||||
,"instruction_pause_restart": "Reiniciar RGSX para recarregar configuração"
|
||||
,"instruction_pause_support": "Gerar um arquivo ZIP de diagnóstico para suporte"
|
||||
,"instruction_pause_quit": "Sair da aplicação RGSX"
|
||||
,"instruction_controls_help": "Mostrar referência completa de controle e teclado"
|
||||
,"instruction_controls_remap": "Modificar associação de botões / teclas"
|
||||
,"instruction_generic_back": "Voltar ao menu anterior"
|
||||
,"instruction_display_layout": "Alternar dimensões da grade (colunas × linhas)"
|
||||
,"instruction_display_font_size": "Ajustar tamanho do texto para legibilidade"
|
||||
,"instruction_display_font_family": "Alternar entre famílias de fontes disponíveis"
|
||||
,"instruction_display_show_unsupported": "Mostrar/ocultar sistemas não definidos em es_systems.cfg"
|
||||
,"instruction_display_unknown_ext": "Ativar/desativar aviso para extensões ausentes em es_systems.cfg"
|
||||
,"instruction_display_hide_premium": "Ocultar sistemas que exigem acesso premium via API: {providers}"
|
||||
,"instruction_display_filter_platforms": "Escolher manualmente quais sistemas são visíveis"
|
||||
,"instruction_games_history": "Listar downloads anteriores e status"
|
||||
,"instruction_games_source_mode": "Alternar entre lista RGSX ou fonte personalizada"
|
||||
,"instruction_games_update_cache": "Baixar novamente e atualizar a lista de jogos"
|
||||
,"instruction_settings_music": "Ativar ou desativar música de fundo"
|
||||
,"instruction_settings_symlink": "Ativar/desativar uso de symlinks para instalações"
|
||||
,"instruction_settings_api_keys": "Ver chaves API premium detectadas"
|
||||
,"controls_desc_confirm": "Confirmar (ex. A/Cruz)"
|
||||
,"controls_desc_cancel": "Cancelar/Voltar (ex. B/Círculo)"
|
||||
,"controls_desc_up": "UP ↑"
|
||||
,"controls_desc_down": "DOWN ↓"
|
||||
,"controls_desc_left": "LEFT ←"
|
||||
,"controls_desc_right": "RIGHT →"
|
||||
,"controls_desc_page_up": "Rolagem rápida para cima (ex. LT/L2)"
|
||||
,"controls_desc_page_down": "Rolagem rápida para baixo (ex. RT/R2)"
|
||||
,"controls_desc_history": "Abrir histórico (ex. Y/Triângulo)"
|
||||
,"controls_desc_clear_history": "Downloads: Seleção múltipla / Histórico: Limpar (ex. X/Quadrado)"
|
||||
,"controls_desc_filter": "Modo filtro: Abrir/Confirmar (ex. Select)"
|
||||
,"controls_desc_delete": "Modo filtro: Deletar caractere (ex. LB/L1)"
|
||||
,"controls_desc_space": "Modo filtro: Adicionar espaço (ex. RB/R1)"
|
||||
,"controls_desc_start": "Abrir menu pausa (ex. Start)"
|
||||
,"controls_mapping_title": "Mapeamento de controles"
|
||||
,"controls_mapping_instruction": "Mantenha para confirmar o mapeamento:"
|
||||
,"controls_mapping_waiting": "Aguardando uma tecla ou botão..."
|
||||
,"controls_mapping_press": "Pressione uma tecla ou um botão"
|
||||
,"status_already_present": "Já Presente"
|
||||
,"footer_joystick": "Joystick: {0}"
|
||||
,"history_game_options_title": "Opções do jogo"
|
||||
,"history_option_download_folder": "Localizar arquivo"
|
||||
,"history_option_extract_archive": "Extrair arquivo"
|
||||
,"history_option_scraper": "Scraper metadados"
|
||||
,"history_option_delete_game": "Excluir jogo"
|
||||
,"history_option_error_info": "Detalhes do erro"
|
||||
,"history_option_retry": "Tentar novamente"
|
||||
,"history_option_back": "Voltar"
|
||||
,"history_folder_path_label": "Caminho de destino:"
|
||||
,"history_scraper_not_implemented": "Scraper ainda não implementado"
|
||||
,"history_confirm_delete": "Excluir este jogo do disco?"
|
||||
,"history_file_not_found": "Arquivo não encontrado"
|
||||
,"history_extracting": "Extraindo..."
|
||||
,"history_extracted": "Extraído"
|
||||
,"history_delete_success": "Jogo excluído com sucesso"
|
||||
,"history_delete_error": "Erro ao excluir jogo: {0}"
|
||||
,"history_error_details_title": "Detalhes do erro"
|
||||
,"history_no_error_message": "Nenhuma mensagem de erro disponível"
|
||||
,"web_title": "Interface Web RGSX"
|
||||
,"web_tab_platforms": "Lista de sistemas"
|
||||
,"web_tab_downloads": "Downloads"
|
||||
,"web_tab_history": "Histórico"
|
||||
,"web_tab_settings": "Configurações"
|
||||
,"web_tab_update": "Atualizar lista"
|
||||
,"web_tooltip_platforms": "Lista de sistemas"
|
||||
,"web_tooltip_downloads": "Downloads"
|
||||
,"web_tooltip_history": "Histórico"
|
||||
,"web_tooltip_settings": "Configurações"
|
||||
,"web_tooltip_update": "Atualizar lista de jogos"
|
||||
,"web_search_platform": "Pesquisar sistemas ou jogos..."
|
||||
,"web_search_game": "Pesquisar um jogo..."
|
||||
,"web_search_results": "resultados para"
|
||||
,"web_no_results": "Nenhum resultado encontrado"
|
||||
,"web_platforms": "Sistemas"
|
||||
,"web_games": "Jogos"
|
||||
,"web_error_search": "Erro de pesquisa"
|
||||
,"web_back_platforms": "Voltar às plataformas"
|
||||
,"web_back": "Voltar"
|
||||
,"web_game_count": "{0} ({1} jogos)"
|
||||
,"web_download": "Baixar"
|
||||
,"web_cancel": "Cancelar"
|
||||
,"web_download_canceled": "Download cancelado"
|
||||
,"web_confirm_cancel": "Você realmente deseja cancelar este download?"
|
||||
,"web_update_title": "Atualizando lista de jogos..."
|
||||
,"web_update_message": "Limpando cache e recarregando dados..."
|
||||
,"web_update_wait": "Isso pode levar 10-30 segundos"
|
||||
,"web_error": "Erro"
|
||||
,"web_error_unknown": "Erro desconhecido"
|
||||
,"web_error_update": "Erro ao atualizar a lista: {0}"
|
||||
,"web_error_download": "Erro: {0}"
|
||||
,"web_history_clear": "Limpar histórico"
|
||||
,"web_history_cleared": "Histórico limpo com sucesso!"
|
||||
,"web_error_clear_history": "Erro ao limpar histórico: {0}"
|
||||
,"web_settings_title": "Informações e Configurações"
|
||||
,"web_settings_roms_folder": "Pasta ROMs personalizada"
|
||||
,"web_settings_roms_placeholder": "Deixar vazio para padrão"
|
||||
,"web_settings_browse": "Procurar"
|
||||
,"web_settings_language": "Idioma"
|
||||
,"web_settings_font_scale": "Escala de fonte"
|
||||
,"web_settings_grid": "Layout de grade"
|
||||
,"web_settings_font_family": "Família de fonte"
|
||||
,"web_settings_music": "Música"
|
||||
,"web_settings_symlink": "Modo symlink"
|
||||
,"web_settings_source_mode": "Fonte de jogos"
|
||||
,"web_settings_custom_url": "URL personalizada"
|
||||
,"web_settings_custom_url_placeholder": "Deixar vazio para /saves/ports/rgsx/games.zip ou usar uma URL direta como https://example.com/games.zip"
|
||||
,"web_settings_save": "Salvar configurações"
|
||||
,"web_settings_saved": "Configurações salvas com sucesso!"
|
||||
,"web_settings_saved_restart": "Configurações salvas com sucesso!\\n\\n⚠️ Algumas configurações exigem reiniciar o servidor:\\n- Pasta ROMs personalizada\\n- Idioma\\n\\nPor favor, reinicie o servidor web para aplicar essas alterações."
|
||||
,"web_error_save_settings": "Erro ao salvar configurações: {0}"
|
||||
,"web_browse_title": "Procurar diretórios"
|
||||
,"web_browse_select_drive": "Selecione uma unidade..."
|
||||
,"web_browse_drives": "Unidades"
|
||||
,"web_browse_parent": "Acima"
|
||||
,"web_browse_select": "Selecionar esta pasta"
|
||||
,"web_browse_cancel": "Cancelar"
|
||||
,"web_browse_empty": "Nenhum subdiretório encontrado"
|
||||
,"web_browse_alert_restart": "Importante: Você precisa SALVAR as configurações e então REINICIAR o servidor web para que a pasta ROMs personalizada tenha efeito.\\n\\n📝 Passos:\\n1. Clique em 'Salvar configurações' abaixo\\n2. Pare o servidor web (Ctrl+C no terminal)\\n3. Reinicie o servidor web\\n\\nCaminho selecionado: {0}"
|
||||
,"web_error_browse": "Erro ao procurar diretórios: {0}"
|
||||
,"web_loading_platforms": "Carregando plataformas..."
|
||||
,"web_loading_games": "Carregando jogos..."
|
||||
,"web_no_platforms": "Nenhuma plataforma encontrada"
|
||||
,"web_no_downloads": "Nenhum download em andamento"
|
||||
,"web_history_empty": "Nenhum download concluído"
|
||||
,"web_history_platform": "Plataforma"
|
||||
,"web_history_size": "Tamanho"
|
||||
,"web_history_status_completed": "Concluído"
|
||||
,"web_history_status_error": "Erro"
|
||||
,"web_settings_os": "Sistema operacional"
|
||||
,"web_settings_platforms_count": "Número de plataformas"
|
||||
,"web_settings_show_unsupported": "Mostrar plataformas não suportadas (sistema ausente em es_systems.cfg)"
|
||||
,"web_settings_allow_unknown": "Permitir extensões desconhecidas (não mostrar avisos)"
|
||||
,"web_restart_confirm_title": "Reiniciar aplicação?"
|
||||
,"web_restart_confirm_message": "As configurações foram salvas. Deseja reiniciar a aplicação agora para aplicar as alterações?"
|
||||
,"web_restart_yes": "Sim, reiniciar"
|
||||
,"web_restart_no": "Não, mais tarde"
|
||||
,"web_restart_success": "Reiniciando..."
|
||||
,"web_restart_error": "Erro ao reiniciar: {0}"
|
||||
,"web_support": "Suporte"
|
||||
,"web_support_title": "📦 Arquivo de suporte gerado"
|
||||
,"web_support_message": "Arquivo de suporte criado com sucesso!\\n\\n📁 Conteúdo:\\n• Configuração de controles\\n• Histórico de downloads\\n• Configurações RGSX\\n• Logs da aplicação\\n• Logs do servidor web\\n\\n💬 Para obter ajuda:\\n1. Entre no Discord RGSX\\n2. Descreva seu problema\\n3. Compartilhe este arquivo ZIP\\n\\nO download vai começar..."
|
||||
,"web_support_generating": "Gerando arquivo de suporte..."
|
||||
,"web_support_download": "Baixar arquivo de suporte"
|
||||
,"web_support_error": "Erro ao gerar arquivo de suporte: {0}"
|
||||
,"web_tab_queue": "Fila"
|
||||
,"web_tooltip_queue": "Fila de downloads"
|
||||
,"web_queue_active_download": "⏳ Um download está ativo"
|
||||
,"web_queue_no_active": "✓ Sem downloads ativos"
|
||||
,"web_queue_title": "Fila de Downloads"
|
||||
,"web_queue_empty": "Nenhum item na fila"
|
||||
,"web_queue_clear": "Limpar fila"
|
||||
,"web_queue_cleared": "Fila limpa com sucesso!"
|
||||
,"web_confirm_remove_queue": "Remover este item da fila?"
|
||||
,"web_confirm_clear_queue": "Limpar toda a fila?"
|
||||
,"web_remove": "Remover"
|
||||
,"web_loading": "Carregando..."
|
||||
,"web_sort": "Ordenar por"
|
||||
,"web_sort_name_asc": "A-Z (Nome)"
|
||||
,"web_sort_name_desc": "Z-A (Nome)"
|
||||
,"web_sort_size_asc": "Tamanho +- (Menor primeiro)"
|
||||
,"web_sort_size_desc": "Tamanho -+ (Maior primeiro)"
|
||||
"popup_hide_premium_off": "Sistemas Premium visíveis",
|
||||
"submenu_display_font_family": "Fonte",
|
||||
"popup_font_family_changed": "Fonte alterada: {0}",
|
||||
"instruction_pause_language": "Alterar o idioma da interface",
|
||||
"instruction_pause_controls": "Ver esquema de controles ou iniciar remapeamento",
|
||||
"instruction_pause_display": "Configurar layout, fontes e visibilidade de sistemas",
|
||||
"instruction_pause_games": "Abrir histórico, mudar fonte ou atualizar lista",
|
||||
"instruction_pause_settings": "Música, opção symlink e status das chaves API",
|
||||
"instruction_pause_restart": "Reiniciar RGSX para recarregar configuração",
|
||||
"instruction_pause_support": "Gerar um arquivo ZIP de diagnóstico para suporte",
|
||||
"instruction_pause_quit": "Sair da aplicação RGSX",
|
||||
"instruction_controls_help": "Mostrar referência completa de controle e teclado",
|
||||
"instruction_controls_remap": "Modificar associação de botões / teclas",
|
||||
"instruction_generic_back": "Voltar ao menu anterior",
|
||||
"instruction_display_layout": "Alternar dimensões da grade (colunas × linhas)",
|
||||
"instruction_display_font_size": "Ajustar tamanho do texto para legibilidade",
|
||||
"instruction_display_font_family": "Alternar entre famílias de fontes disponíveis",
|
||||
"instruction_display_show_unsupported": "Mostrar/ocultar sistemas não definidos em es_systems.cfg",
|
||||
"instruction_display_unknown_ext": "Ativar/desativar aviso para extensões ausentes em es_systems.cfg",
|
||||
"instruction_display_hide_premium": "Ocultar sistemas que exigem acesso premium via API: {providers}",
|
||||
"instruction_display_filter_platforms": "Escolher manualmente quais sistemas são visíveis",
|
||||
"instruction_games_history": "Listar downloads anteriores e status",
|
||||
"instruction_games_source_mode": "Alternar entre lista RGSX ou fonte personalizada",
|
||||
"instruction_games_update_cache": "Baixar novamente e atualizar a lista de jogos",
|
||||
"instruction_settings_music": "Ativar ou desativar música de fundo",
|
||||
"instruction_settings_symlink": "Ativar/desativar uso de symlinks para instalações",
|
||||
"instruction_settings_api_keys": "Ver chaves API premium detectadas",
|
||||
"instruction_settings_web_service": "Ativar/desativar início automático do serviço web na inicialização",
|
||||
"settings_web_service": "Serviço Web na Inicialização",
|
||||
"settings_web_service_enabled": "Ativado",
|
||||
"settings_web_service_disabled": "Desativado",
|
||||
"settings_web_service_enabling": "Ativando serviço web...",
|
||||
"settings_web_service_disabling": "Desativando serviço web...",
|
||||
"settings_web_service_success_enabled": "Serviço web ativado na inicialização",
|
||||
"settings_web_service_success_disabled": "Serviço web desativado na inicialização",
|
||||
"settings_web_service_error": "Erro: {0}",
|
||||
"controls_desc_confirm": "Confirmar (ex. A/Cruz)",
|
||||
"controls_desc_cancel": "Cancelar/Voltar (ex. B/Círculo)",
|
||||
"controls_desc_up": "UP ↑",
|
||||
"controls_desc_down": "DOWN ↓",
|
||||
"controls_desc_left": "LEFT ←",
|
||||
"controls_desc_right": "RIGHT →",
|
||||
"controls_desc_page_up": "Rolagem rápida para cima (ex. LT/L2)",
|
||||
"controls_desc_page_down": "Rolagem rápida para baixo (ex. RT/R2)",
|
||||
"controls_desc_history": "Abrir histórico (ex. Y/Triângulo)",
|
||||
"controls_desc_clear_history": "Downloads: Seleção múltipla / Histórico: Limpar (ex. X/Quadrado)",
|
||||
"controls_desc_filter": "Modo filtro: Abrir/Confirmar (ex. Select)",
|
||||
"controls_desc_delete": "Modo filtro: Deletar caractere (ex. LB/L1)",
|
||||
"controls_desc_space": "Modo filtro: Adicionar espaço (ex. RB/R1)",
|
||||
"controls_desc_start": "Abrir menu pausa (ex. Start)",
|
||||
"controls_mapping_title": "Mapeamento de controles",
|
||||
"controls_mapping_instruction": "Mantenha para confirmar o mapeamento:",
|
||||
"controls_mapping_waiting": "Aguardando uma tecla ou botão...",
|
||||
"controls_mapping_press": "Pressione uma tecla ou um botão",
|
||||
"status_already_present": "Já Presente",
|
||||
"footer_joystick": "Joystick: {0}",
|
||||
"history_game_options_title": "Opções do jogo",
|
||||
"history_option_download_folder": "Localizar arquivo",
|
||||
"history_option_extract_archive": "Extrair arquivo",
|
||||
"history_option_scraper": "Scraper metadados",
|
||||
"history_option_delete_game": "Excluir jogo",
|
||||
"history_option_error_info": "Detalhes do erro",
|
||||
"history_option_retry": "Tentar novamente",
|
||||
"history_option_back": "Voltar",
|
||||
"history_folder_path_label": "Caminho de destino:",
|
||||
"history_scraper_not_implemented": "Scraper ainda não implementado",
|
||||
"history_confirm_delete": "Excluir este jogo do disco?",
|
||||
"history_file_not_found": "Arquivo não encontrado",
|
||||
"history_extracting": "Extraindo...",
|
||||
"history_extracted": "Extraído",
|
||||
"history_delete_success": "Jogo excluído com sucesso",
|
||||
"history_delete_error": "Erro ao excluir jogo: {0}",
|
||||
"history_error_details_title": "Detalhes do erro",
|
||||
"history_no_error_message": "Nenhuma mensagem de erro disponível",
|
||||
"web_title": "Interface Web RGSX",
|
||||
"web_tab_platforms": "Lista de sistemas",
|
||||
"web_tab_downloads": "Downloads",
|
||||
"web_tab_history": "Histórico",
|
||||
"web_tab_settings": "Configurações",
|
||||
"web_tab_update": "Atualizar lista",
|
||||
"web_tooltip_platforms": "Lista de sistemas",
|
||||
"web_tooltip_downloads": "Downloads",
|
||||
"web_tooltip_history": "Histórico",
|
||||
"web_tooltip_settings": "Configurações",
|
||||
"web_tooltip_update": "Atualizar lista de jogos",
|
||||
"web_search_platform": "Pesquisar sistemas ou jogos...",
|
||||
"web_search_game": "Pesquisar um jogo...",
|
||||
"web_search_results": "resultados para",
|
||||
"web_no_results": "Nenhum resultado encontrado",
|
||||
"web_platforms": "Sistemas",
|
||||
"web_games": "Jogos",
|
||||
"web_error_search": "Erro de pesquisa",
|
||||
"web_back_platforms": "Voltar às plataformas",
|
||||
"web_back": "Voltar",
|
||||
"web_game_count": "{0} ({1} jogos)",
|
||||
"web_download": "Baixar",
|
||||
"web_cancel": "Cancelar",
|
||||
"web_download_canceled": "Download cancelado",
|
||||
"web_confirm_cancel": "Você realmente deseja cancelar este download?",
|
||||
"web_update_title": "Atualizando lista de jogos...",
|
||||
"web_update_message": "Limpando cache e recarregando dados...",
|
||||
"web_update_wait": "Isso pode levar 10-30 segundos",
|
||||
"web_error": "Erro",
|
||||
"web_error_unknown": "Erro desconhecido",
|
||||
"web_error_update": "Erro ao atualizar a lista: {0}",
|
||||
"web_error_download": "Erro: {0}",
|
||||
"web_history_clear": "Limpar histórico",
|
||||
"web_history_cleared": "Histórico limpo com sucesso!",
|
||||
"web_error_clear_history": "Erro ao limpar histórico: {0}",
|
||||
"web_settings_title": "Informações e Configurações",
|
||||
"web_settings_roms_folder": "Pasta ROMs personalizada",
|
||||
"web_settings_roms_placeholder": "Deixar vazio para padrão",
|
||||
"web_settings_browse": "Procurar",
|
||||
"web_settings_language": "Idioma",
|
||||
"web_settings_font_scale": "Escala de fonte",
|
||||
"web_settings_grid": "Layout de grade",
|
||||
"web_settings_font_family": "Família de fonte",
|
||||
"web_settings_music": "Música",
|
||||
"web_settings_symlink": "Modo symlink",
|
||||
"web_settings_source_mode": "Fonte de jogos",
|
||||
"web_settings_custom_url": "URL personalizada",
|
||||
"web_settings_custom_url_placeholder": "Deixar vazio para /saves/ports/rgsx/games.zip ou usar uma URL direta como https://example.com/games.zip",
|
||||
"web_settings_save": "Salvar configurações",
|
||||
"web_settings_saved": "Configurações salvas com sucesso!",
|
||||
"web_settings_saved_restart": "Configurações salvas com sucesso!\\n\\n⚠️ Algumas configurações exigem reiniciar o servidor:\\n- Pasta ROMs personalizada\\n- Idioma\\n\\nPor favor, reinicie o servidor web para aplicar essas alterações.",
|
||||
"web_error_save_settings": "Erro ao salvar configurações: {0}",
|
||||
"web_browse_title": "Procurar diretórios",
|
||||
"web_browse_select_drive": "Selecione uma unidade...",
|
||||
"web_browse_drives": "Unidades",
|
||||
"web_browse_parent": "Acima",
|
||||
"web_browse_select": "Selecionar esta pasta",
|
||||
"web_browse_cancel": "Cancelar",
|
||||
"web_browse_empty": "Nenhum subdiretório encontrado",
|
||||
"web_browse_alert_restart": "Importante: Você precisa SALVAR as configurações e então REINICIAR o servidor web para que a pasta ROMs personalizada tenha efeito.\\n\\n📝 Passos:\\n1. Clique em 'Salvar configurações' abaixo\\n2. Pare o servidor web (Ctrl+C no terminal)\\n3. Reinicie o servidor web\\n\\nCaminho selecionado: {0}",
|
||||
"web_error_browse": "Erro ao procurar diretórios: {0}",
|
||||
"web_loading_platforms": "Carregando plataformas...",
|
||||
"web_loading_games": "Carregando jogos...",
|
||||
"web_no_platforms": "Nenhuma plataforma encontrada",
|
||||
"web_no_downloads": "Nenhum download em andamento",
|
||||
"web_history_empty": "Nenhum download concluído",
|
||||
"web_history_platform": "Plataforma",
|
||||
"web_history_size": "Tamanho",
|
||||
"web_history_status_completed": "Concluído",
|
||||
"web_history_status_error": "Erro",
|
||||
"web_settings_os": "Sistema operacional",
|
||||
"web_settings_platforms_count": "Número de plataformas",
|
||||
"web_settings_show_unsupported": "Mostrar plataformas não suportadas (sistema ausente em es_systems.cfg)",
|
||||
"web_settings_allow_unknown": "Permitir extensões desconhecidas (não mostrar avisos)",
|
||||
"web_restart_confirm_title": "Reiniciar aplicação?",
|
||||
"web_restart_confirm_message": "As configurações foram salvas. Deseja reiniciar a aplicação agora para aplicar as alterações?",
|
||||
"web_restart_yes": "Sim, reiniciar",
|
||||
"web_restart_no": "Não, mais tarde",
|
||||
"web_restart_success": "Reiniciando...",
|
||||
"web_restart_error": "Erro ao reiniciar: {0}",
|
||||
"web_support": "Suporte",
|
||||
"web_support_title": "📦 Arquivo de suporte gerado",
|
||||
"web_support_message": "Arquivo de suporte criado com sucesso!\\n\\n📁 Conteúdo:\\n• Configuração de controles\\n• Histórico de downloads\\n• Configurações RGSX\\n• Logs da aplicação\\n• Logs do servidor web\\n\\n💬 Para obter ajuda:\\n1. Entre no Discord RGSX\\n2. Descreva seu problema\\n3. Compartilhe este arquivo ZIP\\n\\nO download vai começar...",
|
||||
"web_support_generating": "Gerando arquivo de suporte...",
|
||||
"web_support_download": "Baixar arquivo de suporte",
|
||||
"web_support_error": "Erro ao gerar arquivo de suporte: {0}",
|
||||
"web_tab_queue": "Fila",
|
||||
"web_tooltip_queue": "Fila de downloads",
|
||||
"web_queue_active_download": "⏳ Um download está ativo",
|
||||
"web_queue_no_active": "✓ Sem downloads ativos",
|
||||
"web_queue_title": "Fila de Downloads",
|
||||
"web_queue_empty": "Nenhum item na fila",
|
||||
"web_queue_clear": "Limpar fila",
|
||||
"web_queue_cleared": "Fila limpa com sucesso!",
|
||||
"web_confirm_remove_queue": "Remover este item da fila?",
|
||||
"web_confirm_clear_queue": "Limpar toda a fila?",
|
||||
"web_remove": "Remover",
|
||||
"web_loading": "Carregando...",
|
||||
"web_sort": "Ordenar por",
|
||||
"web_sort_name_asc": "A-Z (Nome)",
|
||||
"web_sort_name_desc": "Z-A (Nome)",
|
||||
"web_sort_size_asc": "Tamanho +- (Menor primeiro)",
|
||||
"web_sort_size_desc": "Tamanho -+ (Maior primeiro)",
|
||||
"accessibility_font_size": "Tamanho da fonte: {0}",
|
||||
"web_filter_region": "Região",
|
||||
"web_filter_hide_non_release": "Ocultar Demos/Betas/Protos",
|
||||
"web_filter_regex_mode": "Ativar pesquisa Regex",
|
||||
"web_filter_one_rom_per_game": "Uma ROM por jogo",
|
||||
"web_filter_configure_priority": "Configurar ordem de prioridade das regiões",
|
||||
"filter_all": "Marcar tudo",
|
||||
"filter_none": "Desmarcar tudo",
|
||||
"filter_apply": "Aplicar filtro",
|
||||
"filter_back": "Voltar"
|
||||
}
|
||||
@@ -338,7 +338,7 @@ def download_1fichier_free_mode(url, dest_dir, session, log_callback=None, progr
|
||||
return (True, filepath, None)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error downloading with free mode: {str(e)}"
|
||||
error_msg = f"Error Downloading with free mode: {str(e)}"
|
||||
_log(error_msg)
|
||||
logger.error(error_msg, exc_info=True)
|
||||
return (False, None, error_msg)
|
||||
@@ -455,10 +455,15 @@ async def check_for_updates():
|
||||
|
||||
response = requests.get(OTA_VERSION_ENDPOINT, timeout=5)
|
||||
response.raise_for_status()
|
||||
if response.headers.get("content-type") != "application/json":
|
||||
|
||||
# Accepter différents content-types (application/json, text/plain, text/html)
|
||||
content_type = response.headers.get("content-type", "")
|
||||
allowed_types = ["application/json", "text/plain", "text/html"]
|
||||
if not any(allowed in content_type for allowed in allowed_types):
|
||||
raise ValueError(
|
||||
f"Le fichier version.json n'est pas un JSON valide (type de contenu : {response.headers.get('content-type')})"
|
||||
f"Le fichier version.json n'est pas un JSON valide (type de contenu : {content_type})"
|
||||
)
|
||||
|
||||
version_data = response.json()
|
||||
latest_version = version_data.get("version")
|
||||
logger.debug(f"Version distante : {latest_version}, version locale : {config.app_version}")
|
||||
@@ -495,7 +500,7 @@ async def check_for_updates():
|
||||
|
||||
# Créer le dossier UPDATE_FOLDER s'il n'existe pas
|
||||
os.makedirs(UPDATE_FOLDER, exist_ok=True)
|
||||
update_zip_path = os.path.join(UPDATE_FOLDER, f"RGSX_v{latest_version}.zip")
|
||||
update_zip_path = os.path.join(UPDATE_FOLDER, f"RGSX_update_v{latest_version}.zip")
|
||||
logger.debug(f"Téléchargement de {UPDATE_ZIP} vers {update_zip_path}")
|
||||
|
||||
# Télécharger le ZIP
|
||||
@@ -679,7 +684,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
|
||||
def download_thread():
|
||||
try:
|
||||
# IMPORTANT: Créer l'entrée dans config.history dès le début avec status "downloading"
|
||||
# IMPORTANT: Créer l'entrée dans config.history dès le début avec status "Downloading"
|
||||
# pour que l'interface web puisse afficher le téléchargement en cours
|
||||
|
||||
# TOUJOURS charger l'historique existant depuis le fichier pour éviter d'écraser les anciennes entrées
|
||||
@@ -690,8 +695,8 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
for entry in config.history:
|
||||
if entry.get("url") == url:
|
||||
entry_exists = True
|
||||
# Réinitialiser le status à "downloading"
|
||||
entry["status"] = "downloading"
|
||||
# Réinitialiser le status à "Downloading"
|
||||
entry["status"] = "Downloading"
|
||||
entry["progress"] = 0
|
||||
entry["downloaded_size"] = 0
|
||||
entry["platform"] = platform
|
||||
@@ -706,7 +711,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
"platform": platform,
|
||||
"game_name": game_name,
|
||||
"url": url,
|
||||
"status": "downloading",
|
||||
"status": "Downloading",
|
||||
"progress": 0,
|
||||
"downloaded_size": 0,
|
||||
"total_size": 0,
|
||||
@@ -1126,7 +1131,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
try:
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
|
||||
entry["status"] = "Extracting"
|
||||
entry["progress"] = 0
|
||||
entry["message"] = "Préparation de l'extraction..."
|
||||
@@ -1212,7 +1217,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement", "Extracting"]:
|
||||
entry["status"] = "Download_OK" if success else "Erreur"
|
||||
entry["progress"] = 100 if success else 0
|
||||
entry["message"] = message
|
||||
@@ -1252,7 +1257,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
# NOTE: On ne touche PAS au timestamp qui doit rester celui de création
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
|
||||
entry["downloaded_size"] = downloaded
|
||||
entry["total_size"] = total_size
|
||||
entry["speed"] = speed
|
||||
@@ -1284,7 +1289,7 @@ async def download_rom(url, platform, game_name, is_zip_non_supported=False, tas
|
||||
logger.debug(f"[DRAIN_QUEUE] Processing final message: success={success}, message={message[:100] if message else 'None'}")
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement", "Extracting"]:
|
||||
entry["status"] = "Download_OK" if success else "Erreur"
|
||||
entry["progress"] = 100 if success else 0
|
||||
entry["message"] = message
|
||||
@@ -1406,7 +1411,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
||||
link = url.split('&af=')[0]
|
||||
logger.debug(f"URL nettoyée: {link}")
|
||||
|
||||
# IMPORTANT: Créer l'entrée dans config.history dès le début avec status "downloading"
|
||||
# IMPORTANT: Créer l'entrée dans config.history dès le début avec status "Downloading"
|
||||
# pour que l'interface web puisse afficher le téléchargement en cours
|
||||
|
||||
# Charger l'historique existant depuis le fichier
|
||||
@@ -1418,8 +1423,8 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
||||
for entry in config.history:
|
||||
if entry.get("url") == url:
|
||||
entry_exists = True
|
||||
# Réinitialiser le status à "downloading"
|
||||
entry["status"] = "downloading"
|
||||
# Réinitialiser le status à "Downloading"
|
||||
entry["status"] = "Downloading"
|
||||
entry["progress"] = 0
|
||||
entry["downloaded_size"] = 0
|
||||
entry["platform"] = platform
|
||||
@@ -1434,7 +1439,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
||||
"platform": platform,
|
||||
"game_name": game_name,
|
||||
"url": url,
|
||||
"status": "downloading",
|
||||
"status": "Downloading",
|
||||
"progress": 0,
|
||||
"downloaded_size": 0,
|
||||
"total_size": 0,
|
||||
@@ -1857,7 +1862,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
||||
with free_lock:
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] == "downloading":
|
||||
if "url" in entry and entry["url"] == url and entry["status"] == "Downloading":
|
||||
entry["progress"] = int(pct) if pct else 0
|
||||
entry["downloaded_size"] = downloaded
|
||||
entry["total_size"] = total
|
||||
@@ -1928,7 +1933,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
||||
else:
|
||||
logger.error(f"Échec téléchargement gratuit: {error_msg}")
|
||||
result[0] = False
|
||||
result[1] = f"Error downloading with free mode: {error_msg}"
|
||||
result[1] = f"Error Downloading with free mode: {error_msg}"
|
||||
return
|
||||
|
||||
except Exception as e:
|
||||
@@ -2061,7 +2066,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
||||
with lock:
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] == "downloading":
|
||||
if "url" in entry and entry["url"] == url and entry["status"] == "Downloading":
|
||||
entry["total_size"] = total_size
|
||||
config.needs_redraw = True
|
||||
break
|
||||
@@ -2099,7 +2104,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
||||
with lock:
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] == "downloading":
|
||||
if "url" in entry and entry["url"] == url and entry["status"] == "Downloading":
|
||||
progress_percent = int(downloaded / total_size * 100) if total_size > 0 else 0
|
||||
progress_percent = max(0, min(100, progress_percent))
|
||||
entry["progress"] = progress_percent
|
||||
@@ -2226,7 +2231,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
||||
success, message = data[1], data[2]
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement", "Extracting"]:
|
||||
entry["status"] = "Download_OK" if success else "Erreur"
|
||||
entry["progress"] = 100 if success else 0
|
||||
entry["message"] = message
|
||||
@@ -2251,7 +2256,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
||||
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement"]:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement"]:
|
||||
entry["progress"] = progress_percent
|
||||
entry["status"] = "Téléchargement"
|
||||
entry["downloaded_size"] = downloaded
|
||||
@@ -2281,7 +2286,7 @@ async def download_from_1fichier(url, platform, game_name, is_zip_non_supported=
|
||||
logger.debug(f"[1F_DRAIN_QUEUE] Processing final message: success={success}, message={message[:100] if message else 'None'}")
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["downloading", "Téléchargement", "Extracting"]:
|
||||
if "url" in entry and entry["url"] == url and entry["status"] in ["Downloading", "Téléchargement", "Extracting"]:
|
||||
entry["status"] = "Download_OK" if success else "Erreur"
|
||||
entry["progress"] = 100 if success else 0
|
||||
entry["message"] = message
|
||||
|
||||
@@ -103,7 +103,7 @@ def ensure_data_present(verbose: bool = False):
|
||||
headers = {"User-Agent": "Mozilla/5.0"}
|
||||
# Always show progress when we're in the 'missing data' path
|
||||
show = True or verbose
|
||||
print("Source data not found, downloading...")
|
||||
print("Source data not found, Downloading...")
|
||||
print(f"Downloading data from {url}...")
|
||||
try:
|
||||
with requests.get(url, stream=True, headers=headers, timeout=60) as r:
|
||||
@@ -394,7 +394,7 @@ async def _run_download_with_progress(url: str, platform_name: str, game_name: s
|
||||
try:
|
||||
if isinstance(config.history, list):
|
||||
for e in config.history:
|
||||
if e.get('url') == url and e.get('status') in ("downloading", "Téléchargement", "Extracting"):
|
||||
if e.get('url') == url and e.get('status') in ("Downloading", "Téléchargement", "Extracting"):
|
||||
downloaded = int(e.get('downloaded_size') or 0)
|
||||
total = int(e.get('total_size') or 0)
|
||||
speed = e.get('speed')
|
||||
@@ -646,7 +646,7 @@ def cmd_download(args):
|
||||
hist.append({
|
||||
"platform": platform.get('platform_name') or platform.get('platform') or args.platform,
|
||||
"game_name": title,
|
||||
"status": "downloading",
|
||||
"status": "Downloading",
|
||||
"url": url,
|
||||
"progress": 0,
|
||||
"message": "Téléchargement en cours",
|
||||
|
||||
@@ -69,7 +69,8 @@ def load_rgsx_settings():
|
||||
},
|
||||
"show_unsupported_platforms": False,
|
||||
"allow_unknown_extensions": False,
|
||||
"roms_folder": ""
|
||||
"roms_folder": "",
|
||||
"web_service_at_boot": False
|
||||
}
|
||||
|
||||
try:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Module de scraping pour récupérer les métadonnées des jeux depuis TheGamesDB.net
|
||||
Module de scraping pour récupérer les métadonnées des jeux depuis TheGamesDB.net API v1
|
||||
"""
|
||||
import logging
|
||||
import requests
|
||||
@@ -9,138 +9,167 @@ import pygame
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Mapping des noms de plateformes vers leurs IDs sur TheGamesDB
|
||||
# Les noms correspondent exactement à ceux utilisés dans systems_list.json
|
||||
# Clé API publique pour TheGamesDB
|
||||
API_KEY = "bdbb4a1ce5f1c12c1bcc119aeb4d4923d3887e22ad336d576e9b9e5da5ecaa3c"
|
||||
API_BASE_URL = "https://api.thegamesdb.net/v1"
|
||||
|
||||
# Mapping des noms de plateformes vers leurs IDs sur TheGamesDB API
|
||||
# Documentation: https://api.thegamesdb.net/#/Platforms
|
||||
PLATFORM_MAPPING = {
|
||||
# Noms exacts du systems_list.json
|
||||
"3DO Interactive Multiplayer": "25",
|
||||
"3DS": "4912",
|
||||
"Adventure Vision": "4974",
|
||||
"Amiga CD32": "4947",
|
||||
"Amiga CDTV": "4947", # Même ID que CD32
|
||||
"Amiga OCS ECS": "4911",
|
||||
"Apple II": "4942",
|
||||
"Apple IIGS": "4942", # Même famille
|
||||
"Arcadia 2001": "4963",
|
||||
"Archimedes": "4944",
|
||||
"Astrocade": "4968",
|
||||
"Atari 2600": "22",
|
||||
"Atari 5200": "26",
|
||||
"Atari 7800": "27",
|
||||
"Atari Lynx": "4924",
|
||||
"Atari ST": "4937",
|
||||
"Atom": "5014",
|
||||
"Channel-F": "4928",
|
||||
"ColecoVision": "31",
|
||||
"Commodore 64": "40",
|
||||
"Commodore Plus4": "5007",
|
||||
"Commodore VIC-20": "4945",
|
||||
"CreatiVision": "5005",
|
||||
"Dos (x86)": "1",
|
||||
"Dreamcast": "16",
|
||||
"Family Computer Disk System": "4936",
|
||||
"Final Burn Neo": "23", # Arcade
|
||||
"FM-TOWNS": "4932",
|
||||
"Gamate": "5004",
|
||||
"Game Boy": "4",
|
||||
"Game Boy Advance": "5",
|
||||
"Game Boy Color": "41",
|
||||
"Game Cube": "2",
|
||||
"Game Gear": "20",
|
||||
"Game Master": "4948", # Mega Duck
|
||||
"Game.com": "4940",
|
||||
"Jaguar": "28",
|
||||
"Macintosh": "37",
|
||||
"Master System": "35",
|
||||
"Mattel Intellivision": "32",
|
||||
"Mega CD": "21",
|
||||
"Mega Drive": "36",
|
||||
"Mega Duck Cougar Boy": "4948",
|
||||
"MSX1": "4929",
|
||||
"MSX2+": "4929",
|
||||
"Namco System 246 256": "23", # Arcade
|
||||
"Naomi": "23", # Arcade
|
||||
"Naomi 2": "23", # Arcade
|
||||
"Neo-Geo CD": "4956",
|
||||
"Neo-Geo Pocket": "4922",
|
||||
"Neo-Geo Pocket Color": "4923",
|
||||
"Neo-Geo": "24",
|
||||
"Nintendo 64": "3",
|
||||
"Nintendo 64 Disk Drive": "3",
|
||||
"Nintendo DS": "8",
|
||||
"Nintendo DSi": "8",
|
||||
"Nintendo Entertainment System": "7",
|
||||
"Odyssey2": "4927",
|
||||
"PC Engine": "34",
|
||||
"PC Engine CD": "4955",
|
||||
"PC Engine SuperGrafx": "34",
|
||||
"PC-9800": "4934",
|
||||
"PlayStation": "10",
|
||||
"PlayStation 2": "11",
|
||||
"PlayStation 3": "12",
|
||||
"PlayStation Portable": "13",
|
||||
"PlayStation Vita": "39",
|
||||
"Pokemon Mini": "4957",
|
||||
"PV-1000": "4964",
|
||||
"Satellaview": "6", # SNES addon
|
||||
"Saturn": "17",
|
||||
"ScummVM": "1", # PC
|
||||
"Sega 32X": "33",
|
||||
"Sega Chihiro": "23", # Arcade
|
||||
"Sega Pico": "4958",
|
||||
"SG-1000": "4949",
|
||||
"Sharp X1": "4977",
|
||||
"SuFami Turbo": "6", # SNES addon
|
||||
"Super A'Can": "4918", # Pas d'ID exact, utilise Virtual Boy
|
||||
"Super Cassette Vision": "4966",
|
||||
"Super Nintendo Entertainment System": "6",
|
||||
"Supervision": "4959",
|
||||
"Switch (1Fichier)": "4971",
|
||||
"TI-99": "4953",
|
||||
"V.Smile": "4988",
|
||||
"Vectrex": "4939",
|
||||
"Virtual Boy": "4918",
|
||||
"Wii": "9",
|
||||
"Wii (Virtual Console)": "9",
|
||||
"Wii U": "38",
|
||||
"Windows (1Fichier)": "1",
|
||||
"WonderSwan": "4925",
|
||||
"WonderSwan Color": "4926",
|
||||
"Xbox": "14",
|
||||
"Xbox 360": "15",
|
||||
"ZX Spectrum": "4913",
|
||||
"Game and Watch": "4950",
|
||||
"Nintendo Famicom Disk System": "4936",
|
||||
"3DO Interactive Multiplayer": 25,
|
||||
"3DS": 4912,
|
||||
"Adventure Vision": 4974,
|
||||
"Amiga CD32": 4947,
|
||||
"Amiga CDTV": 4947,
|
||||
"Amiga OCS ECS": 4911,
|
||||
"Apple II": 4942,
|
||||
"Apple IIGS": 4942,
|
||||
"Arcadia 2001": 4963,
|
||||
"Archimedes": 4944,
|
||||
"Astrocade": 4968,
|
||||
"Atari 2600": 22,
|
||||
"Atari 5200": 26,
|
||||
"Atari 7800": 27,
|
||||
"Atari Lynx": 4924,
|
||||
"Atari ST": 4937,
|
||||
"Atom": 5014,
|
||||
"Channel-F": 4928,
|
||||
"ColecoVision": 31,
|
||||
"Commodore 64": 40,
|
||||
"Commodore Plus4": 5007,
|
||||
"Commodore VIC-20": 4945,
|
||||
"CreatiVision": 5005,
|
||||
"Dos (x86)": 1,
|
||||
"Dreamcast": 16,
|
||||
"Family Computer Disk System": 4936,
|
||||
"Final Burn Neo": 23,
|
||||
"FM-TOWNS": 4932,
|
||||
"Gamate": 5004,
|
||||
"Game Boy": 4,
|
||||
"Game Boy Advance": 5,
|
||||
"Game Boy Color": 41,
|
||||
"Game Cube": 2,
|
||||
"Game Gear": 20,
|
||||
"Game Master": 4948,
|
||||
"Game.com": 4940,
|
||||
"Jaguar": 28,
|
||||
"Macintosh": 37,
|
||||
"Master System": 35,
|
||||
"Mattel Intellivision": 32,
|
||||
"Mega CD": 21,
|
||||
"Mega Drive": 36,
|
||||
"Mega Duck Cougar Boy": 4948,
|
||||
"MSX1": 4929,
|
||||
"MSX2+": 4929,
|
||||
"Namco System 246 256": 23,
|
||||
"Naomi": 23,
|
||||
"Naomi 2": 23,
|
||||
"Neo-Geo CD": 4956,
|
||||
"Neo-Geo Pocket": 4922,
|
||||
"Neo-Geo Pocket Color": 4923,
|
||||
"Neo-Geo": 24,
|
||||
"Nintendo 64": 3,
|
||||
"Nintendo 64 Disk Drive": 3,
|
||||
"Nintendo DS": 8,
|
||||
"Nintendo DSi": 8,
|
||||
"Nintendo Entertainment System": 7,
|
||||
"Odyssey2": 4927,
|
||||
"PC Engine": 34,
|
||||
"PC Engine CD": 4955,
|
||||
"PC Engine SuperGrafx": 34,
|
||||
"PC-9800": 4934,
|
||||
"PlayStation": 10,
|
||||
"PlayStation 2": 11,
|
||||
"PlayStation 3": 12,
|
||||
"PlayStation Portable": 13,
|
||||
"PlayStation Vita": 39,
|
||||
"Pokemon Mini": 4957,
|
||||
"PV-1000": 4964,
|
||||
"Satellaview": 6,
|
||||
"Saturn": 17,
|
||||
"ScummVM": 1,
|
||||
"Sega 32X": 33,
|
||||
"Sega Chihiro": 23,
|
||||
"Sega Pico": 4958,
|
||||
"SG-1000": 4949,
|
||||
"Sharp X1": 4977,
|
||||
"SuFami Turbo": 6,
|
||||
"Super A'Can": 4918,
|
||||
"Super Cassette Vision": 4966,
|
||||
"Super Nintendo Entertainment System": 6,
|
||||
"Supervision": 4959,
|
||||
"Switch (1Fichier)": 4971,
|
||||
"TI-99": 4953,
|
||||
"V.Smile": 4988,
|
||||
"Vectrex": 4939,
|
||||
"Virtual Boy": 4918,
|
||||
"Wii": 9,
|
||||
"Wii (Virtual Console)": 9,
|
||||
"Wii U": 38,
|
||||
"Windows (1Fichier)": 1,
|
||||
"WonderSwan": 4925,
|
||||
"WonderSwan Color": 4926,
|
||||
"Xbox": 14,
|
||||
"Xbox 360": 15,
|
||||
"ZX Spectrum": 4913,
|
||||
"Game and Watch": 4950,
|
||||
"Nintendo Famicom Disk System": 4936,
|
||||
|
||||
# Aliases communs (pour compatibilité)
|
||||
"3DO": "25",
|
||||
"NES": "7",
|
||||
"SNES": "6",
|
||||
"GBA": "5",
|
||||
"GBC": "41",
|
||||
"GameCube": "2",
|
||||
"N64": "3",
|
||||
"NDS": "8",
|
||||
"PSX": "10",
|
||||
"PS1": "10",
|
||||
"PS2": "11",
|
||||
"PS3": "12",
|
||||
"PSP": "13",
|
||||
"PS Vita": "39",
|
||||
"Genesis": "18",
|
||||
"32X": "33",
|
||||
"Game & Watch": "4950",
|
||||
"PC-98": "4934",
|
||||
"TurboGrafx 16": "34",
|
||||
"TurboGrafx CD": "4955",
|
||||
"Mega Duck": "4948",
|
||||
"Amiga": "4911"
|
||||
"3DO": 25,
|
||||
"NES": 7,
|
||||
"SNES": 6,
|
||||
"GBA": 5,
|
||||
"GBC": 41,
|
||||
"GameCube": 2,
|
||||
"N64": 3,
|
||||
"NDS": 8,
|
||||
"PSX": 10,
|
||||
"PS1": 10,
|
||||
"PS2": 11,
|
||||
"PS3": 12,
|
||||
"PSP": 13,
|
||||
"PS Vita": 39,
|
||||
"Genesis": 18,
|
||||
"32X": 33,
|
||||
"Game & Watch": 4950,
|
||||
"PC-98": 4934,
|
||||
"TurboGrafx 16": 34,
|
||||
"TurboGrafx CD": 4955,
|
||||
"Mega Duck": 4948,
|
||||
"Amiga": 4911
|
||||
}
|
||||
|
||||
|
||||
def clean_game_name(game_name):
|
||||
"""
|
||||
Nettoie le nom du jeu en supprimant les extensions et tags
|
||||
|
||||
Args:
|
||||
game_name (str): Nom brut du jeu
|
||||
|
||||
Returns:
|
||||
str: Nom nettoyé
|
||||
"""
|
||||
clean_name = game_name
|
||||
|
||||
# Supprimer les extensions communes
|
||||
extensions = ['.zip', '.7z', '.rar', '.iso', '.chd', '.cue', '.bin', '.gdi', '.cdi',
|
||||
'.nsp', '.xci', '.wbfs', '.rvz', '.gcz', '.wad', '.3ds', '.cia']
|
||||
for ext in extensions:
|
||||
if clean_name.lower().endswith(ext):
|
||||
clean_name = clean_name[:-len(ext)]
|
||||
|
||||
# Supprimer les tags entre parenthèses et crochets
|
||||
clean_name = re.sub(r'\s*[\(\[].*?[\)\]]', '', clean_name)
|
||||
|
||||
return clean_name.strip()
|
||||
|
||||
|
||||
def get_game_metadata(game_name, platform_name):
|
||||
"""
|
||||
Récupère les métadonnées complètes d'un jeu depuis TheGamesDB.net
|
||||
Récupère les métadonnées complètes d'un jeu depuis TheGamesDB.net API
|
||||
|
||||
Args:
|
||||
game_name (str): Nom du jeu à rechercher
|
||||
@@ -150,100 +179,128 @@ def get_game_metadata(game_name, platform_name):
|
||||
dict: Dictionnaire contenant les métadonnées ou message d'erreur
|
||||
Keys: image_url, game_page_url, description, genre, release_date, error
|
||||
"""
|
||||
# Nettoyer le nom du jeu
|
||||
clean_game_name = game_name
|
||||
for ext in ['.zip', '.7z', '.rar', '.iso', '.chd', '.cue', '.bin', '.gdi', '.cdi']:
|
||||
if clean_game_name.lower().endswith(ext):
|
||||
clean_game_name = clean_game_name[:-len(ext)]
|
||||
clean_game_name = re.sub(r'\s*[\(\[].*?[\)\]]', '', clean_game_name)
|
||||
clean_game_name = clean_game_name.strip()
|
||||
|
||||
logger.info(f"Recherche métadonnées pour: '{clean_game_name}' sur plateforme '{platform_name}'")
|
||||
clean_name = clean_game_name(game_name)
|
||||
logger.info(f"Recherche métadonnées pour: '{clean_name}' sur plateforme '{platform_name}'")
|
||||
|
||||
# Obtenir l'ID de la plateforme
|
||||
platform_id = PLATFORM_MAPPING.get(platform_name)
|
||||
if not platform_id:
|
||||
logger.warning(f"Plateforme '{platform_name}' non trouvée dans le mapping")
|
||||
return {"error": f"Plateforme '{platform_name}' non supportée"}
|
||||
|
||||
# Construire l'URL de recherche
|
||||
base_url = "https://thegamesdb.net/search.php"
|
||||
params = {
|
||||
"name": clean_game_name,
|
||||
"platform_id[]": platform_id
|
||||
}
|
||||
|
||||
try:
|
||||
# Envoyer la requête GET pour la recherche
|
||||
logger.debug(f"Recherche sur TheGamesDB: {base_url} avec params={params}")
|
||||
response = requests.get(base_url, params=params, timeout=10)
|
||||
# Endpoint: Games/ByGameName
|
||||
# Documentation: https://api.thegamesdb.net/#/Games/GamesbyName
|
||||
url = f"{API_BASE_URL}/Games/ByGameName"
|
||||
params = {
|
||||
"apikey": API_KEY,
|
||||
"name": clean_name,
|
||||
"filter[platform]": platform_id,
|
||||
"fields": "players,publishers,genres,overview,last_updated,rating,platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates",
|
||||
"include": "boxart"
|
||||
}
|
||||
|
||||
logger.debug(f"Requête API: {url} avec name='{clean_name}', platform={platform_id}")
|
||||
response = requests.get(url, params=params, timeout=15)
|
||||
|
||||
if response.status_code != 200:
|
||||
logger.error(f"Erreur HTTP {response.status_code}: {response.text}")
|
||||
return {"error": f"Erreur HTTP {response.status_code}"}
|
||||
|
||||
html_content = response.text
|
||||
data = response.json()
|
||||
|
||||
# Trouver la première carte avec class 'card border-primary'
|
||||
card_start = html_content.find('div class="card border-primary"')
|
||||
if card_start == -1:
|
||||
# Vérifier si des résultats ont été trouvés
|
||||
if "data" not in data or "games" not in data["data"] or not data["data"]["games"]:
|
||||
logger.warning(f"Aucun résultat trouvé pour '{clean_name}'")
|
||||
return {"error": "Aucun résultat trouvé"}
|
||||
|
||||
# Extraire l'URL de la page du jeu
|
||||
href_match = re.search(r'<a href="(\.\/game\.php\?id=\d+)">', html_content[card_start-100:card_start+500])
|
||||
game_page_url = None
|
||||
if href_match:
|
||||
game_page_url = f"https://thegamesdb.net/{href_match.group(1)[2:]}" # Enlever le ./
|
||||
logger.info(f"Page du jeu trouvée: {game_page_url}")
|
||||
# Prendre le premier résultat (meilleure correspondance)
|
||||
games = data["data"]["games"]
|
||||
game = games[0]
|
||||
game_id = game.get("id")
|
||||
|
||||
# Extraire l'URL de l'image
|
||||
img_start = html_content.find('<img class="card-img-top"', card_start)
|
||||
image_url = None
|
||||
if img_start != -1:
|
||||
src_match = re.search(r'src="([^"]+)"', html_content[img_start:img_start+200])
|
||||
if src_match:
|
||||
image_url = src_match.group(1)
|
||||
if not image_url.startswith("https://"):
|
||||
image_url = f"https://thegamesdb.net{image_url}"
|
||||
logger.info(f"Image trouvée: {image_url}")
|
||||
logger.info(f"Jeu trouvé: '{game.get('game_title')}' (ID: {game_id})")
|
||||
|
||||
# Extraire la date de sortie depuis les résultats de recherche
|
||||
release_date = None
|
||||
card_footer_start = html_content.find('class="card-footer', card_start)
|
||||
if card_footer_start != -1:
|
||||
# Chercher une date au format YYYY-MM-DD
|
||||
date_match = re.search(r'<p>(\d{4}-\d{2}-\d{2})</p>', html_content[card_footer_start:card_footer_start+300])
|
||||
if date_match:
|
||||
release_date = date_match.group(1)
|
||||
logger.info(f"Date de sortie trouvée: {release_date}")
|
||||
# Construire l'URL de la page du jeu
|
||||
game_page_url = f"https://thegamesdb.net/game.php?id={game_id}"
|
||||
|
||||
# Si on a l'URL de la page, récupérer la description et le genre
|
||||
description = None
|
||||
# Extraire les métadonnées de base
|
||||
description = game.get("overview", "").strip() or None
|
||||
release_date = game.get("release_date", "").strip() or None
|
||||
|
||||
# Extraire les genres
|
||||
genre = None
|
||||
if game_page_url:
|
||||
try:
|
||||
logger.debug(f"Récupération de la page du jeu: {game_page_url}")
|
||||
game_response = requests.get(game_page_url, timeout=10)
|
||||
|
||||
if game_response.status_code == 200:
|
||||
game_html = game_response.text
|
||||
|
||||
# Extraire la description
|
||||
desc_match = re.search(r'<p class="game-overview">(.*?)</p>', game_html, re.DOTALL)
|
||||
if desc_match:
|
||||
description = desc_match.group(1).strip()
|
||||
# Nettoyer les entités HTML
|
||||
description = description.replace(''', "'")
|
||||
description = description.replace('"', '"')
|
||||
description = description.replace('&', '&')
|
||||
logger.info(f"Description trouvée ({len(description)} caractères)")
|
||||
|
||||
# Extraire le genre
|
||||
genre_match = re.search(r'<p>Genre\(s\): (.*?)</p>', game_html)
|
||||
if genre_match:
|
||||
genre = genre_match.group(1).strip()
|
||||
logger.info(f"Genre trouvé: {genre}")
|
||||
if "genres" in game and game["genres"]:
|
||||
genre_ids = game["genres"]
|
||||
# Les noms des genres sont dans data.genres
|
||||
if "genres" in data["data"]:
|
||||
genre_names = []
|
||||
for gid in genre_ids:
|
||||
if str(gid) in data["data"]["genres"]:
|
||||
genre_names.append(data["data"]["genres"][str(gid)]["name"])
|
||||
if genre_names:
|
||||
genre = ", ".join(genre_names)
|
||||
|
||||
# Extraire l'image de couverture (boxart)
|
||||
# Utiliser l'endpoint dédié /v1/Games/Images pour récupérer les images du jeu
|
||||
image_url = None
|
||||
try:
|
||||
images_url = f"{API_BASE_URL}/Games/Images"
|
||||
images_params = {
|
||||
"apikey": API_KEY,
|
||||
"games_id": game_id,
|
||||
"filter[type]": "boxart"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Erreur lors de la récupération de la page du jeu: {e}")
|
||||
logger.debug(f"Récupération des images pour game_id={game_id}")
|
||||
images_response = requests.get(images_url, params=images_params, timeout=10)
|
||||
|
||||
if images_response.status_code == 200:
|
||||
images_data = images_response.json()
|
||||
|
||||
# Récupérer l'URL de base
|
||||
base_url_original = ""
|
||||
if "data" in images_data and "base_url" in images_data["data"]:
|
||||
base_url_original = images_data["data"]["base_url"].get("original", "")
|
||||
|
||||
# Parcourir les images
|
||||
if "data" in images_data and "images" in images_data["data"]:
|
||||
images_dict = images_data["data"]["images"]
|
||||
|
||||
# Les images sont organisées par game_id
|
||||
if str(game_id) in images_dict:
|
||||
game_images = images_dict[str(game_id)]
|
||||
|
||||
# Chercher front boxart en priorité
|
||||
for img in game_images:
|
||||
if img.get("type") == "boxart" and img.get("side") == "front":
|
||||
filename = img.get("filename")
|
||||
if filename:
|
||||
image_url = f"{base_url_original}{filename}"
|
||||
logger.info(f"Image front trouvée: {image_url}")
|
||||
break
|
||||
|
||||
# Si pas de front, prendre n'importe quelle boxart
|
||||
if not image_url:
|
||||
for img in game_images:
|
||||
if img.get("type") == "boxart":
|
||||
filename = img.get("filename")
|
||||
if filename:
|
||||
image_url = f"{base_url_original}{filename}"
|
||||
logger.info(f"Image boxart trouvée: {image_url}")
|
||||
break
|
||||
|
||||
# Si toujours rien, prendre la première image
|
||||
if not image_url and game_images:
|
||||
filename = game_images[0].get("filename")
|
||||
if filename:
|
||||
image_url = f"{base_url_original}{filename}"
|
||||
logger.info(f"Première image trouvée: {image_url}")
|
||||
else:
|
||||
logger.warning(f"Erreur lors de la récupération des images: HTTP {images_response.status_code}")
|
||||
|
||||
except Exception as img_error:
|
||||
logger.warning(f"Erreur lors de la récupération des images: {img_error}")
|
||||
|
||||
# Construire le résultat
|
||||
result = {
|
||||
@@ -254,15 +311,16 @@ def get_game_metadata(game_name, platform_name):
|
||||
"release_date": release_date
|
||||
}
|
||||
|
||||
# Vérifier qu'on a au moins quelque chose
|
||||
if not any([image_url, description, genre]):
|
||||
result["error"] = "Métadonnées incomplètes"
|
||||
logger.info(f"Métadonnées récupérées: image={bool(image_url)}, desc={bool(description)}, genre={bool(genre)}, date={bool(release_date)}")
|
||||
|
||||
return result
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.error(f"Erreur lors de la requête: {str(e)}")
|
||||
logger.error(f"Erreur lors de la requête API: {str(e)}")
|
||||
return {"error": f"Erreur réseau: {str(e)}"}
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur inattendue: {str(e)}", exc_info=True)
|
||||
return {"error": f"Erreur: {str(e)}"}
|
||||
|
||||
|
||||
def download_image_to_surface(image_url):
|
||||
|
||||
241
ports/RGSX/static/css/accessibility.css
Normal file
241
ports/RGSX/static/css/accessibility.css
Normal file
@@ -0,0 +1,241 @@
|
||||
/* ===== ACCESSIBILITY & FOCUS MANAGEMENT ===== */
|
||||
|
||||
/* Focus visible - show focus for keyboard navigation only */
|
||||
:focus {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline: 3px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* Skip to main content link (keyboard accessible) */
|
||||
.skip-to-main {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
left: 0;
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
text-decoration: none;
|
||||
z-index: var(--z-tooltip);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.skip-to-main:focus {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
/* Visually hidden but accessible to screen readers */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
/* ===== LOADING SKELETONS ===== */
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -1000px 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 1000px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--color-bg-secondary) 0%,
|
||||
var(--color-bg-tertiary) 50%,
|
||||
var(--color-bg-secondary) 100%
|
||||
);
|
||||
background-size: 1000px 100%;
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
.skeleton-card {
|
||||
background: var(--color-bg-primary);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-lg);
|
||||
border: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
.skeleton-card::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background: var(--color-bg-secondary);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--spacing-md);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
.skeleton-card::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 80%;
|
||||
height: 16px;
|
||||
background: var(--color-bg-secondary);
|
||||
border-radius: var(--radius-sm);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
.skeleton-text {
|
||||
height: 1em;
|
||||
background: var(--color-bg-secondary);
|
||||
border-radius: var(--radius-sm);
|
||||
margin: var(--spacing-md) 0;
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
.skeleton-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-bg-secondary);
|
||||
display: inline-block;
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
/* Grid of skeleton cards */
|
||||
.skeleton-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: var(--spacing-lg);
|
||||
margin-bottom: var(--spacing-2xl);
|
||||
}
|
||||
|
||||
/* ===== BUTTON & LINK ACCESSIBILITY ===== */
|
||||
|
||||
button, a[role="button"], input[type="button"], input[type="submit"] {
|
||||
position: relative;
|
||||
transition: all var(--transition-base);
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Disabled state */
|
||||
button:disabled,
|
||||
[role="button"][aria-disabled="true"],
|
||||
input[type="button"]:disabled,
|
||||
input[type="submit"]:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Ensure buttons have minimum touch target (44x44px WCAG) */
|
||||
button, a[role="button"] {
|
||||
min-height: 44px;
|
||||
min-width: 44px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-md) var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* ===== FORM ACCESSIBILITY ===== */
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: var(--spacing-sm);
|
||||
font-weight: 500;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
padding: var(--spacing-md);
|
||||
border: 2px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
transition: border-color var(--transition-base);
|
||||
}
|
||||
|
||||
input:focus-visible,
|
||||
select:focus-visible,
|
||||
textarea:focus-visible {
|
||||
border-color: var(--color-primary);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Error state for inputs */
|
||||
input[aria-invalid="true"],
|
||||
select[aria-invalid="true"],
|
||||
textarea[aria-invalid="true"] {
|
||||
border-color: var(--color-danger);
|
||||
}
|
||||
|
||||
/* ===== MODAL & DIALOG ACCESSIBILITY ===== */
|
||||
|
||||
[role="dialog"],
|
||||
.modal {
|
||||
z-index: var(--z-modal);
|
||||
}
|
||||
|
||||
[role="dialog"]::backdrop,
|
||||
.modal-backdrop {
|
||||
background-color: var(--color-overlay-bg);
|
||||
z-index: var(--z-modal-backdrop);
|
||||
}
|
||||
|
||||
/* Ensure modal is positioned and styled appropriately */
|
||||
[role="dialog"] {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-bg-primary);
|
||||
padding: var(--spacing-2xl);
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
box-shadow: var(--shadow-xl);
|
||||
}
|
||||
|
||||
/* ===== HIGH CONTRAST SUPPORT ===== */
|
||||
|
||||
@media (prefers-contrast: more) {
|
||||
button,
|
||||
a[role="button"],
|
||||
input[type="button"],
|
||||
input[type="submit"] {
|
||||
border: 3px solid currentColor;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
:focus-visible {
|
||||
outline-width: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== REDUCED MOTION SUPPORT ===== */
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.skeleton,
|
||||
.skeleton-card::before,
|
||||
.skeleton-card::after,
|
||||
.skeleton-text,
|
||||
.skeleton-avatar {
|
||||
animation: none;
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
}
|
||||
426
ports/RGSX/static/css/app.css
Normal file
426
ports/RGSX/static/css/app.css
Normal file
@@ -0,0 +1,426 @@
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: #333;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
.container {
|
||||
max-width: 90%;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||||
overflow: hidden;
|
||||
}
|
||||
header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
header h1 { font-size: 2.5em; margin-bottom: 10px; }
|
||||
header p { opacity: 0.9; font-size: 1.1em; }
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Navigation mobile dans le header */
|
||||
.mobile-tabs {
|
||||
display: none;
|
||||
justify-content: space-around;
|
||||
padding: 15px 10px 10px 10px;
|
||||
gap: 5px;
|
||||
}
|
||||
.mobile-tab {
|
||||
flex: 1;
|
||||
background: rgba(255,255,255,0.2);
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 12px 5px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 24px;
|
||||
transition: all 0.3s;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
.mobile-tab:hover {
|
||||
background: rgba(255,255,255,0.3);
|
||||
}
|
||||
.mobile-tab.active {
|
||||
background: rgba(255,255,255,0.4);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
background: #f5f5f5;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
.tab {
|
||||
flex: 1;
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background: #f5f5f5;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
.tab:hover { background: #e0e0e0; }
|
||||
.tab.active {
|
||||
background: white;
|
||||
border-bottom: 3px solid #667eea;
|
||||
font-weight: bold;
|
||||
}
|
||||
.tab.support-btn {
|
||||
margin-left: auto;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 20px;
|
||||
padding: 8px 20px;
|
||||
}
|
||||
.tab.support-btn:hover {
|
||||
background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 30px;
|
||||
min-height: 400px;
|
||||
}
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 50px;
|
||||
font-size: 1.2em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.platform-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
.platform-card {
|
||||
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
text-align: center;
|
||||
}
|
||||
.platform-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.4);
|
||||
}
|
||||
.platform-card img {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
object-fit: contain;
|
||||
margin-bottom: 15px;
|
||||
filter: drop-shadow(0 4px 6px rgba(0,0,0,0.3));
|
||||
}
|
||||
.platform-card h3 {
|
||||
margin-bottom: 10px;
|
||||
color: white;
|
||||
font-size: 1.1em;
|
||||
min-height: 2.5em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.platform-card .count {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 5px 15px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
margin-bottom: 20px;
|
||||
position: relative;
|
||||
}
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
padding: 12px 45px 12px 15px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
transition: border-color 0.3s;
|
||||
background: white;
|
||||
color: #333;
|
||||
}
|
||||
.search-box input:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
}
|
||||
.search-box .search-icon {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
color: #999;
|
||||
font-size: 18px;
|
||||
}
|
||||
.search-box .clear-search {
|
||||
position: absolute;
|
||||
right: 45px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
.search-box .clear-search:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.sort-btn {
|
||||
background: #e0e0e0;
|
||||
color: #333;
|
||||
border: 2px solid #999;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.sort-btn:hover {
|
||||
background: #d0d0d0;
|
||||
border-color: #666;
|
||||
}
|
||||
.sort-btn.active {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border-color: #667eea;
|
||||
}
|
||||
|
||||
.filter-section {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 12px;
|
||||
padding: 12px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.filter-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.filter-label {
|
||||
font-weight: bold;
|
||||
margin-right: 4px;
|
||||
|
||||
}
|
||||
.region-btn {
|
||||
background: #e0e0e0;
|
||||
color: #333;
|
||||
border: 2px solid #999;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85em;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.region-btn:hover {
|
||||
background: #d0d0d0;
|
||||
border-color: #666;
|
||||
}
|
||||
.region-btn.active {
|
||||
background: #28a745;
|
||||
color: white;
|
||||
border-color: #28a745;
|
||||
}
|
||||
.region-btn.excluded {
|
||||
background: #dc3545;
|
||||
color: white;
|
||||
border-color: #dc3545;
|
||||
}
|
||||
.filter-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
color: #333;
|
||||
}
|
||||
.filter-checkbox input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.games-list {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.game-item {
|
||||
background: #f9f9f9;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.game-item:hover { background: #f0f0f0; }
|
||||
.game-name { font-weight: 500; flex: 1; }
|
||||
.game-size {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
font-size: 0.9em;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.download-btn {
|
||||
background: transparent;
|
||||
color: #28a745;
|
||||
border: none;
|
||||
padding: 8px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 1.5em;
|
||||
transition: all 0.2s;
|
||||
min-width: 40px;
|
||||
}
|
||||
.download-btn:hover {
|
||||
background: rgba(40, 167, 69, 0.1);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
.download-btn:disabled {
|
||||
color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
background: #667eea;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.back-btn:hover { background: #5568d3; }
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
gap: 15px;
|
||||
}
|
||||
.info-item {
|
||||
background: #f9f9f9;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
.info-item strong { display: block; margin-bottom: 5px; color: #667eea; }
|
||||
|
||||
.history-item {
|
||||
background: #f9f9f9;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid #28a745;
|
||||
}
|
||||
.history-item.error { border-left-color: #dc3545; }
|
||||
|
||||
/* Media Queries pour responsive */
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
padding: 10px;
|
||||
}
|
||||
.container {
|
||||
border-radius: 0;
|
||||
}
|
||||
header {
|
||||
padding: 20px 20px 10px 20px;
|
||||
}
|
||||
header h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
.tabs {
|
||||
display: none;
|
||||
}
|
||||
.mobile-tabs {
|
||||
display: flex;
|
||||
}
|
||||
.content {
|
||||
padding: 15px;
|
||||
}
|
||||
.platform-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 15px;
|
||||
}
|
||||
.platform-card {
|
||||
padding: 15px;
|
||||
}
|
||||
.platform-card img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
.platform-card h3 {
|
||||
font-size: 0.9em;
|
||||
min-height: 2em;
|
||||
}
|
||||
.game-item {
|
||||
flex-wrap: wrap;
|
||||
padding: 10px;
|
||||
}
|
||||
.game-name {
|
||||
font-size: 0.9em;
|
||||
flex: 1 1 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.download-btn-group {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
header h1 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.platform-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.platform-card {
|
||||
padding: 10px;
|
||||
}
|
||||
.platform-card img {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.platform-card h3 {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.platform-card .count {
|
||||
font-size: 0.8em;
|
||||
padding: 3px 10px;
|
||||
}
|
||||
}
|
||||
120
ports/RGSX/static/css/theme.css
Normal file
120
ports/RGSX/static/css/theme.css
Normal file
@@ -0,0 +1,120 @@
|
||||
/* ===== THEMING SYSTEM WITH CSS CUSTOM PROPERTIES ===== */
|
||||
/* This file defines the color scheme and can be easily swapped for alternative themes */
|
||||
|
||||
:root {
|
||||
/* Primary Colors - Brand Identity */
|
||||
--color-primary: #667eea;
|
||||
--color-primary-dark: #764ba2;
|
||||
--color-primary-light: #8b9dff;
|
||||
--color-primary-rgb: 102, 126, 234;
|
||||
|
||||
/* Secondary Colors - Accents */
|
||||
--color-success: #28a745;
|
||||
--color-warning: #ffc107;
|
||||
--color-danger: #dc3545;
|
||||
--color-info: #17a2b8;
|
||||
|
||||
/* Neutral Colors - Layout & Typography */
|
||||
--color-bg-primary: #ffffff;
|
||||
--color-bg-secondary: #f5f5f5;
|
||||
--color-bg-tertiary: #f9f9f9;
|
||||
--color-text-primary: #333333;
|
||||
--color-text-secondary: #666666;
|
||||
--color-text-muted: #999999;
|
||||
--color-text-inverse: #ffffff;
|
||||
|
||||
/* Border & Divider Colors */
|
||||
--color-border: #dddddd;
|
||||
--color-border-light: #eeeeee;
|
||||
--color-divider: #e0e0e0;
|
||||
|
||||
/* Semantic Colors */
|
||||
--color-card-bg: #2c3e50;
|
||||
--color-card-border: #34495e;
|
||||
--color-overlay-bg: rgba(0, 0, 0, 0.7);
|
||||
--color-overlay-light: rgba(0, 0, 0, 0.3);
|
||||
|
||||
/* Gradients */
|
||||
--gradient-primary: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
|
||||
--gradient-reverse: linear-gradient(135deg, var(--color-primary-dark) 0%, var(--color-primary) 100%);
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12);
|
||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.4);
|
||||
--shadow-xl: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||
|
||||
/* Spacing Scale */
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 12px;
|
||||
--spacing-lg: 16px;
|
||||
--spacing-xl: 20px;
|
||||
--spacing-2xl: 30px;
|
||||
--spacing-3xl: 40px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-sm: 4px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 0.2s ease-in-out;
|
||||
--transition-base: 0.3s ease-in-out;
|
||||
--transition-slow: 0.5s ease-in-out;
|
||||
|
||||
/* Typography */
|
||||
--font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
--font-size-xs: 0.75rem;
|
||||
--font-size-sm: 0.875rem;
|
||||
--font-size-base: 1rem;
|
||||
--font-size-lg: 1.125rem;
|
||||
--font-size-xl: 1.25rem;
|
||||
--font-size-2xl: 1.5rem;
|
||||
--font-size-3xl: 2.5rem;
|
||||
|
||||
/* Z-index Scale */
|
||||
--z-dropdown: 100;
|
||||
--z-sticky: 200;
|
||||
--z-fixed: 300;
|
||||
--z-modal-backdrop: 400;
|
||||
--z-modal: 500;
|
||||
--z-notification: 600;
|
||||
--z-tooltip: 700;
|
||||
}
|
||||
|
||||
/* ===== DARK MODE SUPPORT ===== */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-bg-primary: #1a1a1a;
|
||||
--color-bg-secondary: #2d2d2d;
|
||||
--color-bg-tertiary: #3a3a3a;
|
||||
--color-text-primary: #e0e0e0;
|
||||
--color-text-secondary: #b0b0b0;
|
||||
--color-text-muted: #808080;
|
||||
--color-border: #404040;
|
||||
--color-border-light: #3a3a3a;
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== HIGH CONTRAST MODE SUPPORT ===== */
|
||||
@media (prefers-contrast: more) {
|
||||
:root {
|
||||
--color-primary: #0043b3;
|
||||
--color-primary-dark: #002e7a;
|
||||
--color-text-primary: #000000;
|
||||
--color-text-secondary: #333333;
|
||||
--color-border: #000000;
|
||||
--shadow-lg: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== REDUCED MOTION SUPPORT ===== */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
194
ports/RGSX/static/js/accessibility.js
Normal file
194
ports/RGSX/static/js/accessibility.js
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Accessibility Utilities Module
|
||||
* Handles keyboard navigation, focus management, and ARIA updates
|
||||
*/
|
||||
|
||||
const A11y = {
|
||||
/**
|
||||
* Manage focus trap for modals (keep focus within modal)
|
||||
*/
|
||||
trapFocus(element) {
|
||||
const focusableElements = element.querySelectorAll(
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
);
|
||||
const firstElement = focusableElements[0];
|
||||
const lastElement = focusableElements[focusableElements.length - 1];
|
||||
|
||||
element.addEventListener('keydown', (e) => {
|
||||
if (e.key !== 'Tab') return;
|
||||
|
||||
if (e.shiftKey) {
|
||||
if (document.activeElement === firstElement) {
|
||||
lastElement.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
} else {
|
||||
if (document.activeElement === lastElement) {
|
||||
firstElement.focus();
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Set initial focus to first element
|
||||
firstElement?.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Restore focus to element after modal closes
|
||||
*/
|
||||
savedFocusElement: null,
|
||||
|
||||
saveFocus() {
|
||||
this.savedFocusElement = document.activeElement;
|
||||
},
|
||||
|
||||
restoreFocus() {
|
||||
if (this.savedFocusElement && this.savedFocusElement.focus) {
|
||||
this.savedFocusElement.focus();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Announce changes to screen readers with live regions
|
||||
*/
|
||||
announceToScreenReader(message, priority = 'polite') {
|
||||
let liveRegion = document.querySelector(`[role="status"][aria-live="${priority}"]`);
|
||||
|
||||
if (!liveRegion) {
|
||||
liveRegion = document.createElement('div');
|
||||
liveRegion.setAttribute('role', 'status');
|
||||
liveRegion.setAttribute('aria-live', priority);
|
||||
liveRegion.className = 'sr-only';
|
||||
document.body.appendChild(liveRegion);
|
||||
}
|
||||
|
||||
liveRegion.textContent = message;
|
||||
|
||||
// Clear after announcement
|
||||
setTimeout(() => {
|
||||
liveRegion.textContent = '';
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle Enter/Space key on clickable elements
|
||||
*/
|
||||
makeKeyboardClickable(element, callback) {
|
||||
element.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Arrow key navigation for grids
|
||||
*/
|
||||
setupGridNavigation(gridSelector) {
|
||||
const grid = document.querySelector(gridSelector);
|
||||
if (!grid) return;
|
||||
|
||||
const items = Array.from(grid.children);
|
||||
const colCount = Math.ceil(Math.sqrt(items.length));
|
||||
|
||||
items.forEach((item, index) => {
|
||||
item.setAttribute('tabindex', index === 0 ? '0' : '-1');
|
||||
|
||||
item.addEventListener('keydown', (e) => {
|
||||
let newIndex = index;
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowLeft':
|
||||
newIndex = index === 0 ? items.length - 1 : index - 1;
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
newIndex = index === items.length - 1 ? 0 : index + 1;
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
newIndex = Math.max(0, index - colCount);
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
newIndex = Math.min(items.length - 1, index + colCount);
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'Home':
|
||||
newIndex = 0;
|
||||
e.preventDefault();
|
||||
break;
|
||||
case 'End':
|
||||
newIndex = items.length - 1;
|
||||
e.preventDefault();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
items[index].setAttribute('tabindex', '-1');
|
||||
items[newIndex].setAttribute('tabindex', '0');
|
||||
items[newIndex].focus();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update ARIA attributes for dynamic content
|
||||
*/
|
||||
updateAriaLabel(element, label) {
|
||||
element.setAttribute('aria-label', label);
|
||||
},
|
||||
|
||||
updateAriaLive(element, region = 'polite') {
|
||||
element.setAttribute('aria-live', region);
|
||||
element.setAttribute('aria-atomic', 'true');
|
||||
},
|
||||
|
||||
/**
|
||||
* Set loading state with ARIA
|
||||
*/
|
||||
setLoadingState(element, isLoading) {
|
||||
if (isLoading) {
|
||||
element.setAttribute('aria-busy', 'true');
|
||||
element.setAttribute('disabled', 'true');
|
||||
} else {
|
||||
element.removeAttribute('aria-busy');
|
||||
element.removeAttribute('disabled');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Create accessible loading skeleton
|
||||
*/
|
||||
createSkeletonLoader(containerId, itemCount = 6) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = '';
|
||||
for (let i = 0; i < itemCount; i++) {
|
||||
const skeleton = document.createElement('div');
|
||||
skeleton.className = 'skeleton-card';
|
||||
skeleton.setAttribute('aria-hidden', 'true');
|
||||
container.appendChild(skeleton);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove skeleton loader
|
||||
*/
|
||||
removeSkeletonLoader(containerId) {
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
|
||||
const skeletons = container.querySelectorAll('.skeleton-card');
|
||||
skeletons.forEach(s => s.remove());
|
||||
}
|
||||
};
|
||||
|
||||
// Export for use in app.js
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = A11y;
|
||||
}
|
||||
2199
ports/RGSX/static/js/app.js
Normal file
2199
ports/RGSX/static/js/app.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ except Exception:
|
||||
pygame = None # type: ignore
|
||||
import glob
|
||||
import threading
|
||||
from rgsx_settings import load_rgsx_settings, save_rgsx_settings
|
||||
from rgsx_settings import load_rgsx_settings, save_rgsx_settings, get_allow_unknown_extensions
|
||||
import zipfile
|
||||
import time
|
||||
import random
|
||||
@@ -25,6 +25,7 @@ from history import save_history
|
||||
from language import _
|
||||
from datetime import datetime
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -85,9 +86,7 @@ def generate_support_zip():
|
||||
Returns:
|
||||
tuple: (success: bool, message: str, zip_path: str ou None)
|
||||
"""
|
||||
import zipfile
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
try:
|
||||
# Créer un fichier ZIP temporaire
|
||||
@@ -162,6 +161,146 @@ DO NOT share this file publicly as it may contain sensitive information.
|
||||
logger.error(f"Erreur lors de la génération du fichier de support: {e}")
|
||||
return (False, str(e), None)
|
||||
|
||||
|
||||
def toggle_web_service_at_boot(enable: bool):
|
||||
"""Active ou désactive le service web au démarrage de Batocera.
|
||||
|
||||
Args:
|
||||
enable: True pour activer, False pour désactiver
|
||||
|
||||
Returns:
|
||||
tuple: (success: bool, message: str)
|
||||
"""
|
||||
|
||||
|
||||
try:
|
||||
# Vérifier si on est sur un système compatible (Linux avec batocera-services)
|
||||
if config.OPERATING_SYSTEM != "Linux":
|
||||
return (False, "Web service auto-start is only available on Batocera/Linux systems")
|
||||
|
||||
services_dir = "/userdata/system/services"
|
||||
service_file = os.path.join(services_dir, "rgsx_web")
|
||||
source_file = os.path.join(config.APP_FOLDER, "assets", "progs", "rgsx_web")
|
||||
|
||||
if enable:
|
||||
# Mode ENABLE
|
||||
logger.debug("Activation du service web au démarrage...")
|
||||
|
||||
# 1. Créer le dossier services s'il n'existe pas
|
||||
try:
|
||||
os.makedirs(services_dir, exist_ok=True)
|
||||
logger.debug(f"Dossier services vérifié/créé: {services_dir}")
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to create services directory: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
|
||||
# 2. Copier le fichier rgsx_web
|
||||
try:
|
||||
if not os.path.exists(source_file):
|
||||
error_msg = f"Source service file not found: {source_file}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
|
||||
shutil.copy2(source_file, service_file)
|
||||
os.chmod(service_file, 0o755) # Rendre exécutable
|
||||
logger.debug(f"Fichier service copié et rendu exécutable: {service_file}")
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to copy service file: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
|
||||
# 3. Activer le service avec batocera-services
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['batocera-services', 'enable', 'rgsx_web'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
if result.returncode != 0:
|
||||
error_msg = f"batocera-services enable failed: {result.stderr}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
logger.debug(f"Service activé: {result.stdout}")
|
||||
except FileNotFoundError:
|
||||
error_msg = "batocera-services command not found"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to enable service: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
|
||||
# 4. Démarrer le service immédiatement
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['batocera-services', 'start', 'rgsx_web'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
if result.returncode != 0:
|
||||
# Le service peut ne pas démarrer si déjà en cours, ce n'est pas grave
|
||||
logger.warning(f"batocera-services start warning: {result.stderr}")
|
||||
else:
|
||||
logger.debug(f"Service démarré: {result.stdout}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to start service (non-critical): {str(e)}")
|
||||
|
||||
success_msg = _("settings_web_service_success_enabled") if _ else "Web service enabled at boot"
|
||||
logger.info(success_msg)
|
||||
|
||||
# Sauvegarder l'état dans rgsx_settings.json
|
||||
settings = load_rgsx_settings()
|
||||
settings["web_service_at_boot"] = True
|
||||
save_rgsx_settings(settings)
|
||||
|
||||
return (True, success_msg)
|
||||
|
||||
else:
|
||||
# Mode DISABLE
|
||||
logger.debug("Désactivation du service web au démarrage...")
|
||||
|
||||
# 1. Désactiver le service avec batocera-services
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['batocera-services', 'disable', 'rgsx_web'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10
|
||||
)
|
||||
if result.returncode != 0:
|
||||
error_msg = f"batocera-services disable failed: {result.stderr}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
logger.debug(f"Service désactivé: {result.stdout}")
|
||||
except FileNotFoundError:
|
||||
error_msg = "batocera-services command not found"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
except Exception as e:
|
||||
error_msg = f"Failed to disable service: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return (False, error_msg)
|
||||
|
||||
success_msg = _("settings_web_service_success_disabled") if _ else "✓ Web service disabled at boot"
|
||||
logger.info(success_msg)
|
||||
|
||||
# Sauvegarder l'état dans rgsx_settings.json
|
||||
settings = load_rgsx_settings()
|
||||
settings["web_service_at_boot"] = False
|
||||
save_rgsx_settings(settings)
|
||||
|
||||
return (True, success_msg)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Unexpected error: {str(e)}"
|
||||
logger.exception(error_msg)
|
||||
return (False, error_msg)
|
||||
|
||||
|
||||
|
||||
_extensions_cache = None # type: ignore
|
||||
_extensions_json_regenerated = False
|
||||
|
||||
@@ -385,7 +524,6 @@ def check_extension_before_download(url, platform, game_name):
|
||||
# Autoriser si l'utilisateur a choisi d'autoriser les extensions inconnues
|
||||
allow_unknown = False
|
||||
try:
|
||||
from rgsx_settings import get_allow_unknown_extensions
|
||||
allow_unknown = get_allow_unknown_extensions()
|
||||
except Exception:
|
||||
allow_unknown = False
|
||||
@@ -849,7 +987,7 @@ def _update_extraction_progress(url, extracted_size, total_size, lock, last_save
|
||||
with lock:
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if "status" in entry and entry["status"] in ["Téléchargement", "Extracting", "downloading"]:
|
||||
if "status" in entry and entry["status"] in ["Téléchargement", "Extracting", "Downloading"]:
|
||||
if "url" in entry and entry["url"] == url:
|
||||
progress_percent = int(extracted_size / total_size * 100) if total_size > 0 else 0
|
||||
progress_percent = max(0, min(100, progress_percent))
|
||||
@@ -907,8 +1045,12 @@ def _handle_special_platforms(dest_dir, archive_path, before_dirs, iso_before=No
|
||||
before_items: Set de tous les éléments (fichiers+dossiers) avant extraction (pour DOS)
|
||||
"""
|
||||
# Xbox: conversion ISO
|
||||
xbox_dir = os.path.join(config.ROMS_FOLDER, "xbox")
|
||||
if dest_dir == xbox_dir and iso_before is not None:
|
||||
# Gérer les deux cas: symlink activé (xbox/xbox) ou désactivé (xbox)
|
||||
xbox_dir_normal = os.path.join(config.ROMS_FOLDER, "xbox")
|
||||
xbox_dir_symlink = os.path.join(config.ROMS_FOLDER, "xbox", "xbox")
|
||||
is_xbox = (dest_dir == xbox_dir_normal or dest_dir == xbox_dir_symlink)
|
||||
|
||||
if is_xbox and iso_before is not None:
|
||||
iso_after = set()
|
||||
for root, dirs, files in os.walk(dest_dir):
|
||||
for file in files:
|
||||
@@ -963,6 +1105,16 @@ def _handle_special_platforms(dest_dir, archive_path, before_dirs, iso_before=No
|
||||
if not success:
|
||||
return False, error_msg
|
||||
|
||||
# ScummVM: organisation en dossiers + fichier .scummvm
|
||||
scummvm_dir = os.path.join(config.ROMS_FOLDER, "scummvm")
|
||||
if dest_dir == scummvm_dir:
|
||||
expected_base = os.path.splitext(os.path.basename(archive_path))[0]
|
||||
# Utiliser before_items si fourni, sinon before_dirs pour rétro-compatibilité
|
||||
items_before = before_items if before_items is not None else before_dirs
|
||||
success, error_msg = handle_scummvm(dest_dir, items_before, extracted_basename=expected_base)
|
||||
if not success:
|
||||
return False, error_msg
|
||||
|
||||
return True, None
|
||||
|
||||
def extract_zip(zip_path, dest_dir, url):
|
||||
@@ -1052,7 +1204,6 @@ def extract_zip(zip_path, dest_dir, url):
|
||||
if os.path.isdir(file_path):
|
||||
logger.warning(f"Conflit: dossier existant avec le même nom que le fichier {file_path}, suppression du dossier")
|
||||
try:
|
||||
import shutil
|
||||
shutil.rmtree(file_path)
|
||||
except Exception as rm_err:
|
||||
logger.error(f"Impossible de supprimer le dossier {file_path}: {rm_err}")
|
||||
@@ -1547,6 +1698,88 @@ def handle_dos(dest_dir, before_items, extracted_basename=None):
|
||||
return False, error_msg
|
||||
|
||||
|
||||
def handle_scummvm(dest_dir, before_items, extracted_basename=None):
|
||||
"""Gère l'organisation spécifique des jeux ScummVM extraits.
|
||||
|
||||
- Crée un sous-dossier avec le nom du jeu (sans extension)
|
||||
- Extrait/déplace le contenu du ZIP dans ce dossier
|
||||
- Crée un fichier .scummvm vide avec le même nom
|
||||
|
||||
Exemple: Freddi_fish_1.zip -> dossier Freddi_fish_1/ + fichier Freddi_fish_1.scummvm
|
||||
|
||||
Args:
|
||||
dest_dir: Dossier de destination (scummvm)
|
||||
before_items: Set des éléments présents avant extraction
|
||||
extracted_basename: Nom de base du ZIP extrait (sans extension)
|
||||
"""
|
||||
logger.debug(f"Traitement spécifique ScummVM dans: {dest_dir}")
|
||||
time.sleep(2) # Petite latence post-extraction
|
||||
|
||||
try:
|
||||
# Déterminer les nouveaux éléments extraits
|
||||
after_items = set(os.listdir(dest_dir))
|
||||
except Exception:
|
||||
after_items = set()
|
||||
|
||||
ignore_names = {"scummvm", "images", "videos", "manuals", "media"}
|
||||
# Filtrer les nouveaux éléments (fichiers ou dossiers)
|
||||
new_items = [item for item in (after_items - before_items)
|
||||
if item not in ignore_names and not item.endswith('.scummvm')]
|
||||
|
||||
if not new_items:
|
||||
logger.warning("Aucun nouveau contenu ScummVM détecté après extraction")
|
||||
return True, None
|
||||
|
||||
if not extracted_basename:
|
||||
logger.warning("Nom de base du ZIP non fourni pour le traitement ScummVM")
|
||||
return True, None
|
||||
|
||||
# Nom du dossier et du fichier .scummvm
|
||||
game_folder_name = extracted_basename
|
||||
game_folder_path = os.path.join(dest_dir, game_folder_name)
|
||||
scummvm_file_path = os.path.join(game_folder_path, f"{game_folder_name}.scummvm")
|
||||
|
||||
try:
|
||||
# Créer le dossier du jeu s'il n'existe pas
|
||||
if os.path.exists(game_folder_path):
|
||||
logger.warning(f"Le dossier {game_folder_path} existe déjà, il sera utilisé")
|
||||
else:
|
||||
os.makedirs(game_folder_path, exist_ok=True)
|
||||
logger.debug(f"Dossier créé: {game_folder_path}")
|
||||
|
||||
# Déplacer tous les nouveaux éléments dans le dossier du jeu
|
||||
for item in new_items:
|
||||
src_path = os.path.join(dest_dir, item)
|
||||
dst_path = os.path.join(game_folder_path, item)
|
||||
|
||||
try:
|
||||
if os.path.isdir(src_path):
|
||||
shutil.move(src_path, dst_path)
|
||||
else:
|
||||
shutil.move(src_path, dst_path)
|
||||
logger.debug(f"Déplacé: {item} -> {game_folder_name}/{item}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur déplacement {item}: {e}")
|
||||
return False, f"Erreur lors du déplacement de {item}: {str(e)}"
|
||||
|
||||
# Créer le fichier .scummvm vide dans le sous-dossier
|
||||
try:
|
||||
with open(scummvm_file_path, 'w', encoding='utf-8') as f:
|
||||
pass # Fichier vide
|
||||
logger.info(f"Fichier .scummvm créé: {scummvm_file_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur création fichier .scummvm: {e}")
|
||||
return False, f"Erreur lors de la création du fichier .scummvm: {str(e)}"
|
||||
|
||||
logger.info(f"Contenu ScummVM organisé avec succès: dossier {game_folder_name}/ avec fichier {game_folder_name}.scummvm à l'intérieur")
|
||||
return True, None
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Erreur lors de l'organisation ScummVM dans {game_folder_path}: {str(e)}"
|
||||
logger.error(error_msg)
|
||||
return False, error_msg
|
||||
|
||||
|
||||
def handle_xbox(dest_dir, iso_files, url=None):
|
||||
"""Gère la conversion des fichiers Xbox extraits et met à jour l'UI (Converting)."""
|
||||
logger.debug(f"Traitement spécifique Xbox dans: {dest_dir}")
|
||||
@@ -1555,31 +1788,31 @@ def handle_xbox(dest_dir, iso_files, url=None):
|
||||
time.sleep(2)
|
||||
if config.OPERATING_SYSTEM == "Windows":
|
||||
# Sur Windows; telecharger le fichier exe
|
||||
XDVDFS_EXE = config.XDVDFS_EXE
|
||||
xdvdfs_cmd = [XDVDFS_EXE, "pack"] # Liste avec 2 éléments
|
||||
XISO_EXE = config.XISO_EXE
|
||||
extract_xiso_cmd = [XISO_EXE, "-r"] # Liste avec 2 éléments
|
||||
|
||||
else:
|
||||
# Linux/Batocera : télécharger le fichier xdvdfs
|
||||
XDVDFS_LINUX = config.XDVDFS_LINUX
|
||||
XISO_LINUX = config.XISO_LINUX
|
||||
try:
|
||||
stat_info = os.stat(XDVDFS_LINUX)
|
||||
stat_info = os.stat(XISO_LINUX)
|
||||
mode = stat_info.st_mode
|
||||
logger.debug(f"Permissions de {XDVDFS_LINUX}: {oct(mode)}")
|
||||
logger.debug(f"Permissions de {XISO_LINUX}: {oct(mode)}")
|
||||
logger.debug(f"Propriétaire: {stat_info.st_uid}, Groupe: {stat_info.st_gid}")
|
||||
|
||||
# Vérifier si le fichier est exécutable
|
||||
if not os.access(XDVDFS_LINUX, os.X_OK):
|
||||
logger.error(f"Le fichier {XDVDFS_LINUX} n'est pas exécutable")
|
||||
if not os.access(XISO_LINUX, os.X_OK):
|
||||
logger.error(f"Le fichier {XISO_LINUX} n'est pas exécutable")
|
||||
try:
|
||||
os.chmod(XDVDFS_LINUX, 0o755)
|
||||
logger.info(f"Permissions corrigées pour {XDVDFS_LINUX}")
|
||||
os.chmod(XISO_LINUX, 0o755)
|
||||
logger.info(f"Permissions corrigées pour {XISO_LINUX}")
|
||||
except Exception as e:
|
||||
logger.error(f"Impossible de modifier les permissions: {str(e)}")
|
||||
return False, "Erreur de permissions sur xdvdfs"
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la vérification des permissions: {str(e)}")
|
||||
|
||||
xdvdfs_cmd = [XDVDFS_LINUX, "pack"] # Liste avec 2 éléments
|
||||
extract_xiso_cmd = [XISO_LINUX, "-r"] # Liste avec 2 éléments
|
||||
|
||||
try:
|
||||
# Utiliser uniquement la liste fournie (nouveaux ISO extraits). Fallback scan uniquement si liste vide.
|
||||
@@ -1617,7 +1850,7 @@ def handle_xbox(dest_dir, iso_files, url=None):
|
||||
# Historique
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if entry.get("url") == url and entry.get("status") in ["Extracting", "Téléchargement", "downloading"]:
|
||||
if entry.get("url") == url and entry.get("status") in ["Extracting", "Téléchargement", "Downloading"]:
|
||||
entry["status"] = "Converting"
|
||||
entry["progress"] = 0
|
||||
entry["message"] = "Xbox conversion in progress"
|
||||
@@ -1629,16 +1862,21 @@ def handle_xbox(dest_dir, iso_files, url=None):
|
||||
logger.info(f"Démarrage conversion Xbox: {total} ISO(s)")
|
||||
for idx, iso_xbox_source in enumerate(iso_files, start=1):
|
||||
logger.debug(f"Traitement de l'ISO Xbox: {iso_xbox_source}")
|
||||
xiso_dest = os.path.splitext(iso_xbox_source)[0] + "_xbox.iso"
|
||||
|
||||
# Construction de la commande avec des arguments distincts
|
||||
cmd = xdvdfs_cmd + [iso_xbox_source, xiso_dest]
|
||||
logger.debug(f"Exécution de la commande: {' '.join(cmd)}")
|
||||
|
||||
# extract-xiso -r repackage l'ISO en place
|
||||
# Il faut exécuter la commande depuis le dossier contenant l'ISO
|
||||
iso_dir = os.path.dirname(iso_xbox_source)
|
||||
iso_filename = os.path.basename(iso_xbox_source)
|
||||
|
||||
# Utiliser le nom de fichier relatif et définir le répertoire de travail
|
||||
cmd = extract_xiso_cmd + [iso_filename]
|
||||
logger.debug(f"Exécution de la commande: {' '.join(cmd)} (cwd: {iso_dir})")
|
||||
|
||||
process = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True
|
||||
text=True,
|
||||
cwd=iso_dir
|
||||
)
|
||||
|
||||
if process.returncode != 0:
|
||||
@@ -1650,27 +1888,34 @@ def handle_xbox(dest_dir, iso_files, url=None):
|
||||
if url not in config.download_progress:
|
||||
config.download_progress[url] = {}
|
||||
config.download_progress[url]["status"] = "Error"
|
||||
config.download_progress[url]["message"] = err_msg
|
||||
config.download_progress[url]["message"] = process.stderr
|
||||
config.download_progress[url]["progress_percent"] = 0
|
||||
config.needs_redraw = True
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if entry.get("url") == url and entry.get("status") in ("Converting", "Extracting", "Téléchargement", "downloading"):
|
||||
if entry.get("url") == url and entry.get("status") in ("Converting", "Extracting", "Téléchargement", "Downloading"):
|
||||
entry["status"] = "Error"
|
||||
entry["message"] = err_msg
|
||||
entry["message"] = process.stderr
|
||||
save_history(config.history)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
return False, err_msg
|
||||
|
||||
# Vérifier que l'ISO converti a été créé
|
||||
if os.path.exists(xiso_dest):
|
||||
logger.info(f"ISO converti avec succès: {xiso_dest}")
|
||||
# Remplacer l'ISO original par l'ISO converti
|
||||
os.remove(iso_xbox_source)
|
||||
os.rename(xiso_dest, iso_xbox_source)
|
||||
logger.debug(f"ISO original remplacé par la version convertie")
|
||||
# Vérifier que l'ISO existe toujours (extract-xiso le modifie en place)
|
||||
if os.path.exists(iso_xbox_source):
|
||||
logger.info(f"ISO repackagé avec succès: {iso_xbox_source}")
|
||||
logger.debug(f"ISO converti au format XISO en place")
|
||||
|
||||
# Supprimer le fichier .old créé par extract-xiso (backup)
|
||||
old_file = iso_xbox_source + ".old"
|
||||
if os.path.exists(old_file):
|
||||
try:
|
||||
os.remove(old_file)
|
||||
logger.debug(f"Fichier backup .old supprimé: {old_file}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Impossible de supprimer le fichier .old: {e}")
|
||||
|
||||
# Mise à jour progression de conversion (coarse-grain)
|
||||
try:
|
||||
percent = int(idx / total * 100) if total > 0 else 100
|
||||
@@ -1689,7 +1934,7 @@ def handle_xbox(dest_dir, iso_files, url=None):
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
err_msg = f"L'ISO converti n'a pas été créé: {xiso_dest}"
|
||||
err_msg = f"L'ISO source a disparu après conversion: {iso_xbox_source}"
|
||||
logger.error(err_msg)
|
||||
try:
|
||||
if url:
|
||||
@@ -1701,7 +1946,7 @@ def handle_xbox(dest_dir, iso_files, url=None):
|
||||
config.needs_redraw = True
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if entry.get("url") == url and entry.get("status") in ("Converting", "Extracting", "Téléchargement", "downloading"):
|
||||
if entry.get("url") == url and entry.get("status") in ("Converting", "Extracting", "Téléchargement", "Downloading"):
|
||||
entry["status"] = "Error"
|
||||
entry["message"] = err_msg
|
||||
save_history(config.history)
|
||||
@@ -1710,6 +1955,25 @@ def handle_xbox(dest_dir, iso_files, url=None):
|
||||
pass
|
||||
return False, "Échec de la conversion de l'ISO"
|
||||
|
||||
# Conversion terminée avec succès - mettre à jour le statut final
|
||||
try:
|
||||
if url:
|
||||
if url not in config.download_progress:
|
||||
config.download_progress[url] = {}
|
||||
config.download_progress[url]["status"] = "Download_OK"
|
||||
config.download_progress[url]["progress_percent"] = 100
|
||||
config.needs_redraw = True
|
||||
if isinstance(config.history, list):
|
||||
for entry in config.history:
|
||||
if entry.get("url") == url and entry.get("status") == "Converting":
|
||||
entry["status"] = "Download_OK"
|
||||
entry["progress"] = 100
|
||||
entry["message"] = "Xbox conversion completed successfully"
|
||||
save_history(config.history)
|
||||
break
|
||||
except Exception as e:
|
||||
logger.debug(f"MAJ statut final conversion ignorée: {e}")
|
||||
|
||||
return True, "Conversion Xbox terminée avec succès"
|
||||
|
||||
except Exception as e:
|
||||
@@ -1890,6 +2154,26 @@ def save_music_config():
|
||||
except Exception as e:
|
||||
logger.error(f"Erreur lors de la sauvegarde de la configuration musique: {str(e)}")
|
||||
|
||||
def check_web_service_status():
|
||||
"""Vérifie si le service web est activé au démarrage.
|
||||
|
||||
Returns:
|
||||
bool: True si activé, False sinon
|
||||
"""
|
||||
try:
|
||||
if config.OPERATING_SYSTEM != "Linux":
|
||||
return False
|
||||
|
||||
# Lire l'état depuis rgsx_settings.json
|
||||
|
||||
settings = load_rgsx_settings()
|
||||
return settings.get("web_service_at_boot", False)
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to check web service status: {e}")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def normalize_platform_name(platform):
|
||||
"""Normalise un nom de plateforme en supprimant espaces et convertissant en minuscules."""
|
||||
|
||||
@@ -1,406 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import re
|
||||
import traceback
|
||||
from typing import Any, Dict, Tuple, List, Optional
|
||||
|
||||
try:
|
||||
import pygame # type: ignore
|
||||
except Exception as e:
|
||||
print("Pygame is required. Install with: pip install pygame")
|
||||
raise
|
||||
|
||||
|
||||
PROMPTS = [
|
||||
# Face buttons
|
||||
"SOUTH_BUTTON - CONFIRM", # A on Xbox
|
||||
"EAST_BUTTON - CANCEL", # B on Xbox
|
||||
"WEST_BUTTON - CLEAR HISTORY / SELECT GAMES", # X on Xbox
|
||||
"NORTH_BUTTON - HISTORY", # Y on Xbox
|
||||
# Meta
|
||||
"START - PAUSE",
|
||||
"SELECT - FILTER",
|
||||
# D-Pad
|
||||
"DPAD_UP - MOVE UP",
|
||||
"DPAD_DOWN - MOVE DOWN",
|
||||
"DPAD_LEFT - MOVE LEFT",
|
||||
"DPAD_RIGHT - MOVE RIGHT",
|
||||
# Bumpers
|
||||
"LEFT_BUMPER - LB/L1 - Delete last char",
|
||||
"RIGHT_BUMPER - RB/R1 - Add space",
|
||||
# Triggers
|
||||
"LEFT_TRIGGER - LT/L2 - Page +",
|
||||
"RIGHT_TRIGGER - RT/R2 - Page -",
|
||||
# Left stick directions
|
||||
"JOYSTICK_LEFT_UP - MOVE UP",
|
||||
"JOYSTICK_LEFT_DOWN - MOVE DOWN",
|
||||
"JOYSTICK_LEFT_LEFT - MOVE LEFT",
|
||||
"JOYSTICK_LEFT_RIGHT - MOVE RIGHT",
|
||||
]
|
||||
|
||||
INPUT_TIMEOUT_SECONDS = 10 # Temps max par entrée avant "ignored"
|
||||
|
||||
# --- Minimal on-screen console (Pygame window) ---
|
||||
SURFACE = None # type: ignore
|
||||
FONT = None # type: ignore
|
||||
LOG_LINES: List[str] = []
|
||||
MAX_LOG = 300
|
||||
|
||||
|
||||
def init_screen(width: int = 900, height: int = 600) -> None:
|
||||
global SURFACE, FONT
|
||||
try:
|
||||
pygame.display.init()
|
||||
SURFACE = pygame.display.set_mode((width, height))
|
||||
pygame.display.set_caption("Controller Tester")
|
||||
pygame.font.init()
|
||||
FONT = pygame.font.SysFont("Consolas", 20) or pygame.font.Font(None, 20)
|
||||
except Exception:
|
||||
# If display init fails, stay headless but continue
|
||||
SURFACE = None
|
||||
FONT = None
|
||||
|
||||
|
||||
def log(msg: str) -> None:
|
||||
# Print to real console and on-screen log
|
||||
try:
|
||||
print(msg)
|
||||
except Exception:
|
||||
pass
|
||||
LOG_LINES.append(str(msg))
|
||||
if len(LOG_LINES) > MAX_LOG:
|
||||
del LOG_LINES[: len(LOG_LINES) - MAX_LOG]
|
||||
draw_log()
|
||||
|
||||
|
||||
def draw_log() -> None:
|
||||
if SURFACE is None or FONT is None:
|
||||
return
|
||||
try:
|
||||
SURFACE.fill((12, 12, 12))
|
||||
margin = 12
|
||||
line_h = FONT.get_height() + 4
|
||||
# Show the last N lines that fit on screen
|
||||
max_lines = (SURFACE.get_height() - margin * 2) // line_h
|
||||
to_draw = LOG_LINES[-max_lines:]
|
||||
y = margin
|
||||
for line in to_draw:
|
||||
surf = FONT.render(line, True, (220, 220, 220))
|
||||
SURFACE.blit(surf, (margin, y))
|
||||
y += line_h
|
||||
pygame.display.flip()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def init_joystick() -> pygame.joystick.Joystick:
|
||||
pygame.init()
|
||||
pygame.joystick.init()
|
||||
if pygame.joystick.get_count() == 0:
|
||||
log("No joystick detected. Connect a controller and try again.")
|
||||
sys.exit(1)
|
||||
js = pygame.joystick.Joystick(0)
|
||||
js.init()
|
||||
name = js.get_name()
|
||||
log(f"Using joystick 0: {name}")
|
||||
log("")
|
||||
log(f"Note: each input will auto-ignore after {INPUT_TIMEOUT_SECONDS}s if not present (e.g. missing L2/R2)")
|
||||
return js
|
||||
|
||||
|
||||
def wait_for_stable(js: pygame.joystick.Joystick, settle_ms: int = 250, deadband: float = 0.05, timeout_ms: int = 2000) -> bool:
|
||||
"""Wait until axes stop moving (change < deadband) continuously for settle_ms.
|
||||
|
||||
Unlike a traditional neutral check, this doesn't assume axes center at 0.
|
||||
Hats are required to be (0,0) to avoid capturing D-Pad releases.
|
||||
Returns True if stability achieved, False on timeout.
|
||||
"""
|
||||
start = pygame.time.get_ticks()
|
||||
last = [js.get_axis(i) for i in range(js.get_numaxes())]
|
||||
stable_since = None
|
||||
while True:
|
||||
# Handle window close only (avoid quitting on keyboard here)
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
log("Window closed. Exiting.")
|
||||
sys.exit(0)
|
||||
moved = False
|
||||
for i in range(js.get_numaxes()):
|
||||
cur = js.get_axis(i)
|
||||
if abs(cur - last[i]) > deadband:
|
||||
moved = True
|
||||
last[i] = cur
|
||||
hats_ok = all(js.get_hat(i) == (0, 0) for i in range(js.get_numhats()))
|
||||
if not moved and hats_ok:
|
||||
if stable_since is None:
|
||||
stable_since = pygame.time.get_ticks()
|
||||
elif pygame.time.get_ticks() - stable_since >= settle_ms:
|
||||
return True
|
||||
else:
|
||||
stable_since = None
|
||||
if pygame.time.get_ticks() - start > timeout_ms:
|
||||
return False
|
||||
draw_log()
|
||||
pygame.time.wait(10)
|
||||
|
||||
|
||||
def wait_for_event(js: pygame.joystick.Joystick, logical_name: str, axis_threshold: float = 0.6, timeout_sec: int = INPUT_TIMEOUT_SECONDS) -> Tuple[str, Any]:
|
||||
"""Wait for a joystick event for the given logical control.
|
||||
|
||||
Returns a tuple of (kind, data):
|
||||
- ("button", button_index)
|
||||
- ("hat", (x, y)) where x,y in {-1,0,1}
|
||||
- ("axis", {"axis": index, "direction": -1|1})
|
||||
"""
|
||||
# Ensure prior motion has settled to avoid capturing a release
|
||||
wait_for_stable(js)
|
||||
log("")
|
||||
deadline = time.time() + max(1, int(timeout_sec))
|
||||
log(f"Press {logical_name} (Wait {timeout_sec}s to skip/ignore) if not present")
|
||||
# Flush old events
|
||||
pygame.event.clear()
|
||||
while True:
|
||||
# Update window title with countdown if we have a surface
|
||||
try:
|
||||
remaining = int(max(0, deadline - time.time()))
|
||||
if SURFACE is not None:
|
||||
pygame.display.set_caption(f"Controller Tester — {logical_name} — {remaining}s left")
|
||||
except Exception:
|
||||
pass
|
||||
for event in pygame.event.get():
|
||||
# Keyboard helpers
|
||||
if event.type == pygame.KEYDOWN:
|
||||
if event.key == pygame.K_ESCAPE:
|
||||
log(f"Skipped {logical_name}")
|
||||
return ("skipped", None)
|
||||
# No keyboard quit here to avoid accidental exits when using controllers
|
||||
if event.type == pygame.QUIT:
|
||||
log("Window closed. Exiting.")
|
||||
sys.exit(0)
|
||||
|
||||
# Buttons
|
||||
if event.type == pygame.JOYBUTTONDOWN:
|
||||
log(f"Captured {logical_name}: BUTTON {event.button}")
|
||||
return ("button", event.button)
|
||||
|
||||
# D-Pad (HAT)
|
||||
if event.type == pygame.JOYHATMOTION:
|
||||
val = event.value # (x, y)
|
||||
if val != (0, 0):
|
||||
log(f"Captured {logical_name}: HAT {val}")
|
||||
return ("hat", val)
|
||||
|
||||
# Axes (sticks, triggers)
|
||||
if event.type == pygame.JOYAXISMOTION:
|
||||
axis = event.axis
|
||||
value = float(event.value)
|
||||
if abs(value) >= axis_threshold:
|
||||
direction = 1 if value > 0 else -1
|
||||
log(f"Captured {logical_name}: AXIS {axis} dir {direction} (raw {value:.2f})")
|
||||
return ("axis", {"axis": axis, "direction": direction, "raw": value})
|
||||
|
||||
draw_log()
|
||||
# Timeout?
|
||||
if time.time() >= deadline:
|
||||
log(f"Ignored {logical_name} (timeout {timeout_sec}s)")
|
||||
return ("ignored", None)
|
||||
time.sleep(0.005)
|
||||
|
||||
|
||||
def write_log(path: str, mapping: Dict[str, Tuple[str, Any]], device_name: str) -> None:
|
||||
lines = []
|
||||
lines.append("# Controller mapping log\n")
|
||||
lines.append(f"# Device: {device_name}\n\n")
|
||||
for name, (kind, data) in mapping.items():
|
||||
if kind == "button":
|
||||
lines.append(f"{name} = BUTTON {data}\n")
|
||||
elif kind == "hat":
|
||||
lines.append(f"{name} = HAT {data}\n")
|
||||
elif kind == "axis":
|
||||
ax = data.get("axis")
|
||||
direction = data.get("direction")
|
||||
lines.append(f"{name} = AXIS {ax} dir {direction}\n")
|
||||
elif kind == "skipped":
|
||||
lines.append(f"{name} = SKIPPED\n")
|
||||
elif kind == "ignored":
|
||||
lines.append(f"{name} = IGNORED\n")
|
||||
else:
|
||||
lines.append(f"{name} = UNKNOWN {data}\n")
|
||||
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.writelines(lines)
|
||||
log("")
|
||||
log(f"Saved mapping to: {path}")
|
||||
|
||||
|
||||
# --- JSON preset generation ---
|
||||
def sanitize_device_name(name: str) -> str:
|
||||
s = name.strip().lower()
|
||||
# Replace non-alphanumeric with underscore
|
||||
s = re.sub(r"[^a-z0-9]+", "_", s)
|
||||
s = re.sub(r"_+", "_", s).strip("_")
|
||||
return s or "controller"
|
||||
|
||||
|
||||
def to_json_binding(kind: str, data: Any, display: Optional[str] = None) -> Optional[Dict[str, Any]]:
|
||||
if kind == "button" and isinstance(data, int):
|
||||
return {"type": "button", "button": data, **({"display": display} if display else {})}
|
||||
if kind == "hat" and isinstance(data, (tuple, list)) and len(data) == 2:
|
||||
val = list(data)
|
||||
return {"type": "hat", "value": val, **({"display": display} if display else {})}
|
||||
if kind == "axis" and isinstance(data, dict):
|
||||
axis = data.get("axis")
|
||||
direction = data.get("direction")
|
||||
if isinstance(axis, int) and direction in (-1, 1):
|
||||
return {"type": "axis", "axis": axis, "direction": int(direction), **({"display": display} if display else {})}
|
||||
return None
|
||||
|
||||
|
||||
def build_controls_json(mapping: Dict[str, Tuple[str, Any]]) -> Dict[str, Any]:
|
||||
# Map logical prompts to action keys and preferred display labels
|
||||
prompt_map = {
|
||||
"SOUTH_BUTTON - CONFIRM": ("confirm", "A"),
|
||||
"EAST_BUTTON - CANCEL": ("cancel", "B"),
|
||||
"WEST_BUTTON - CLEAR HISTORY / SELECT GAMES": ("clear_history", "X"),
|
||||
"NORTH_BUTTON - HISTORY": ("history", "Y"),
|
||||
"START - PAUSE": ("start", "Start"),
|
||||
"SELECT - FILTER": ("filter", "Select"),
|
||||
"DPAD_UP - MOVE UP": ("up", "↑"),
|
||||
"DPAD_DOWN - MOVE DOWN": ("down", "↓"),
|
||||
"DPAD_LEFT - MOVE LEFT": ("left", "←"),
|
||||
"DPAD_RIGHT - MOVE RIGHT": ("right", "→"),
|
||||
"LEFT_BUMPER - LB/L1 - Delete last char": ("delete", "LB"),
|
||||
"RIGHT_BUMPER - RB/R1 - Add space": ("space", "RB"),
|
||||
# Triggers per prompts: LEFT=page_up, RIGHT=page_down
|
||||
"LEFT_TRIGGER - LT/L2 - Page +": ("page_up", "LT"),
|
||||
"RIGHT_TRIGGER - RT/R2 - Page -": ("page_down", "RT"),
|
||||
# Left stick directions (fallbacks for arrows)
|
||||
"JOYSTICK_LEFT_UP - MOVE UP": ("up", "J↑"),
|
||||
"JOYSTICK_LEFT_DOWN - MOVE DOWN": ("down", "J↓"),
|
||||
"JOYSTICK_LEFT_LEFT - MOVE LEFT": ("left", "J←"),
|
||||
"JOYSTICK_LEFT_RIGHT - MOVE RIGHT": ("right", "J→"),
|
||||
}
|
||||
|
||||
result: Dict[str, Any] = {}
|
||||
|
||||
# First pass: take direct DPAD/face/meta/bumper/trigger bindings
|
||||
for prompt, (action, disp) in prompt_map.items():
|
||||
if prompt not in mapping:
|
||||
continue
|
||||
kind, data = mapping[prompt]
|
||||
if kind in ("ignored", "skipped"):
|
||||
continue
|
||||
# Prefer DPAD over JOYSTICK for directions: handle fallback later
|
||||
if action in ("up", "down", "left", "right"):
|
||||
if prompt.startswith("DPAD_"):
|
||||
b = to_json_binding(kind, data, disp)
|
||||
if b:
|
||||
result[action] = b
|
||||
# Joystick handled as fallback if DPAD missing
|
||||
else:
|
||||
b = to_json_binding(kind, data, disp)
|
||||
if b:
|
||||
result[action] = b
|
||||
|
||||
# Second pass: fallback to joystick directions if arrows missing
|
||||
fallbacks = [
|
||||
("JOYSTICK_LEFT_UP - MOVE UP", "up", "J↑"),
|
||||
("JOYSTICK_LEFT_DOWN - MOVE DOWN", "down", "J↓"),
|
||||
("JOYSTICK_LEFT_LEFT - MOVE LEFT", "left", "J←"),
|
||||
("JOYSTICK_LEFT_RIGHT - MOVE RIGHT", "right", "J→"),
|
||||
]
|
||||
for prompt, action, disp in fallbacks:
|
||||
if action in result:
|
||||
continue
|
||||
if prompt in mapping:
|
||||
kind, data = mapping[prompt]
|
||||
if kind in ("ignored", "skipped"):
|
||||
continue
|
||||
b = to_json_binding(kind, data, disp)
|
||||
if b:
|
||||
result[action] = b
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def write_controls_json(device_name: str, controls: Dict[str, Any]) -> str:
|
||||
"""Write the generated controls preset JSON in the same folder as this script.
|
||||
|
||||
Also embeds a JSON-safe comment with the device name under the _comment key.
|
||||
"""
|
||||
# Same folder as the launched script
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
fname = f"{sanitize_device_name(device_name)}_controller.json"
|
||||
out_path = os.path.join(base_dir, fname)
|
||||
# Include the detected device name for auto-preset matching
|
||||
payload = {"device": device_name}
|
||||
payload.update(controls)
|
||||
try:
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
json.dump(payload, f, ensure_ascii=False, indent=4)
|
||||
return out_path
|
||||
except Exception:
|
||||
return out_path
|
||||
|
||||
|
||||
def main() -> None:
|
||||
init_screen()
|
||||
js = init_joystick()
|
||||
# Print device basics
|
||||
try:
|
||||
log(f"Buttons: {js.get_numbuttons()} | Axes: {js.get_numaxes()} | Hats: {js.get_numhats()}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
mapping: Dict[str, Tuple[str, Any]] = {}
|
||||
for logical in PROMPTS:
|
||||
kind, data = wait_for_event(js, logical)
|
||||
mapping[logical] = (kind, data)
|
||||
# Short, consistent debounce for all inputs
|
||||
pygame.event.clear()
|
||||
pygame.time.wait(150)
|
||||
|
||||
log_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "controller_mapping.log")
|
||||
write_log(log_path, mapping, js.get_name())
|
||||
# Build and write ready-to-use JSON controls preset
|
||||
controls = build_controls_json(mapping)
|
||||
if controls:
|
||||
out_json = write_controls_json(js.get_name(), controls)
|
||||
log(f"Saved JSON preset to: {out_json}")
|
||||
else:
|
||||
log("No usable inputs captured to build a JSON preset.")
|
||||
log("Done. Press Q or close the window to exit.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except SystemExit:
|
||||
# Allow intentional exits
|
||||
pass
|
||||
except Exception:
|
||||
# Show traceback on screen and wait for window close
|
||||
tb = traceback.format_exc()
|
||||
try:
|
||||
log("")
|
||||
log("An error occurred:")
|
||||
for line in tb.splitlines():
|
||||
log(line)
|
||||
# Idle until window is closed
|
||||
while True:
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.QUIT:
|
||||
raise SystemExit(1)
|
||||
draw_log()
|
||||
pygame.time.wait(50)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
try:
|
||||
pygame.joystick.quit()
|
||||
pygame.quit()
|
||||
except Exception:
|
||||
pass
|
||||
3
version.json
Normal file
3
version.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"version": "2.3.1.6"
|
||||
}
|
||||
@@ -55,8 +55,8 @@ echo UPDATE_GAMELIST_SCRIPT : !UPDATE_GAMELIST_SCRIPT! >> "%LOG_FILE%"
|
||||
echo Checking python.exe...
|
||||
echo [%DATE% %TIME%] Checking python.exe at !PYTHON_EXE_FULL! >> "%LOG_FILE%"
|
||||
if not exist "!PYTHON_EXE_FULL!" (
|
||||
echo python.exe not found. Preparing download...
|
||||
echo [%DATE% %TIME%] python.exe not found. Preparing download... >> "%LOG_FILE%"
|
||||
echo python.exe not found in system/tools. Preparing to extract..
|
||||
echo [%DATE% %TIME%] python.exe not found in system/tools. Preparing to extract.. >> "%LOG_FILE%"
|
||||
|
||||
:: Créer le dossier Python s'il n'existe pas
|
||||
set "TOOLS_FOLDER_FULL=!ROOT_DIR!\system\tools"
|
||||
@@ -67,29 +67,21 @@ if not exist "!PYTHON_EXE_FULL!" (
|
||||
mkdir "!TOOLS_FOLDER_FULL!\Python"
|
||||
)
|
||||
|
||||
set ZIP_URL=https://retrogamesets.fr/softs/python.zip
|
||||
set "ZIP_FILE=!TOOLS_FOLDER_FULL!\python.zip"
|
||||
echo ZIP_URL : !ZIP_URL!
|
||||
echo [%DATE% %TIME%] ZIP_URL : !ZIP_URL! >> "%LOG_FILE%"
|
||||
echo ZIP_FILE : !ZIP_FILE!
|
||||
set "ZIP_FILE=%ROOT_DIR%\roms\windows\python.zip"
|
||||
echo Extracting ZIP_FILE : !ZIP_FILE! in /system/tools/Python
|
||||
echo [%DATE% %TIME%] ZIP_FILE : !ZIP_FILE! >> "%LOG_FILE%"
|
||||
|
||||
echo Downloading python.zip...
|
||||
echo [%DATE% %TIME%] Downloading python.zip from !ZIP_URL!... >> "%LOG_FILE%"
|
||||
curl -L "!ZIP_URL!" -o "!ZIP_FILE!"
|
||||
|
||||
|
||||
if exist "!ZIP_FILE!" (
|
||||
echo Download complete. Extracting python.zip...
|
||||
echo [%DATE% %TIME%] Download complete. Extracting python.zip to !TOOLS_FOLDER_FULL!... >> "%LOG_FILE%"
|
||||
echo [%DATE% %TIME%] Extracting python.zip to !TOOLS_FOLDER_FULL!... >> "%LOG_FILE%"
|
||||
tar -xf "!ZIP_FILE!" -C "!TOOLS_FOLDER_FULL!\Python" --strip-components=0
|
||||
echo Extraction finished.
|
||||
echo [%DATE% %TIME%] Extraction finished. >> "%LOG_FILE%"
|
||||
del /q "!ZIP_FILE!"
|
||||
del /s /q "!ZIP_FILE!"
|
||||
echo python.zip file deleted.
|
||||
echo [%DATE% %TIME%] python.zip file deleted. >> "%LOG_FILE%"
|
||||
) else (
|
||||
echo Error: Failed to download python.zip.
|
||||
echo [%DATE% %TIME%] Error: Failed to download python.zip. >> "%LOG_FILE%"
|
||||
echo Error: Error python.zip not found please download it from github and put in /roms/windows folder.
|
||||
echo [%DATE% %TIME%] Error: Error python.zip not found please download it from github and put in /roms/windows folder >> "%LOG_FILE%"
|
||||
goto :error
|
||||
)
|
||||
|
||||
|
||||
BIN
windows/python.zip
Normal file
BIN
windows/python.zip
Normal file
Binary file not shown.
Reference in New Issue
Block a user