Merge branch 'dev'
This commit is contained in:
		
						commit
						380376c16b
					
				
					 32 changed files with 2786 additions and 994 deletions
				
			
		
							
								
								
									
										
											BIN
										
									
								
								GameManager.png
									
										
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								GameManager.png
									
										
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 190 KiB  | 
							
								
								
									
										97
									
								
								README.md
									
										
									
									
									
								
							
							
						
						
									
										97
									
								
								README.md
									
										
									
									
									
								
							| 
						 | 
					@ -1,34 +1,50 @@
 | 
				
			||||||
# 🔑 Game Key Management System 🔑
 | 
					# 🔑 Game Key Manager 🔑
 | 
				
			||||||
 | 
					
 | 
				
			||||||

 | 
					## 👋 Welcome! 👋
 | 
				
			||||||
 | 
					 | 
				
			||||||
## Welcome! 👋
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
This project helps you keep track of your collected game keys.  
 | 
					This project helps you keep track of your collected game keys.  
 | 
				
			||||||
No more confusion about whether a key is redeemed, gifted, or still unused – now you have everything in one place, with search, status, and even automatic Steam cover images!
 | 
					No more confusion about whether a key is redeemed, gifted, or still unused – now you have everything in one place, with search, status, and even automatic Steam cover images!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It's even possible to gift your keys via a unique website. Just edit the game to "Gifted" and you'll get a option to copy the on your overview page. (maybe HTTPS only)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(the link will also remain in the edit area)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## ✨ Features ✨
 | 
					## ✨ Features ✨
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **Key Management:**  
 | 
					- **Key Management:**  
 | 
				
			||||||
  Enter your game keys, the corresponding game, platform, and where you got the key.
 | 
					  Enter your game keys, the corresponding game, platform, and maybe where you got the key.
 | 
				
			||||||
- **Status Tracking:**  
 | 
					- **Status Tracking:**  
 | 
				
			||||||
  Mark keys as "Redeemed", "Gifted" or "Available" – always know your status.
 | 
					  Mark keys as "Redeemed", "Gifted" or "Available" – always know your status.
 | 
				
			||||||
- **Shop URL & Steam Cover:**  
 | 
					- **Shop URL & Steam Cover:**  
 | 
				
			||||||
  Save the shop URL and (optionally) the Steam AppID. The app will automatically show the official Steam cover image if available.
 | 
					  Save the shop URL and (optionally) the Steam AppID. The app will automatically show the official Steam cover image if available.
 | 
				
			||||||
 | 
					- **Gift your Games:**
 | 
				
			||||||
 | 
					  You can create a unique redeem/gift website, which will expire after 24h.
 | 
				
			||||||
- **Multi-user:**  
 | 
					- **Multi-user:**  
 | 
				
			||||||
  Each user manages their own keys.
 | 
					  Each user manages their own keys.
 | 
				
			||||||
- **Search & Filter:**  
 | 
					- **Enable/Disable Registrations:**
 | 
				
			||||||
 | 
					  Perfect if you want to run the Server just on your own (via .env file)
 | 
				
			||||||
 | 
					- **Search:**  
 | 
				
			||||||
  Find games quickly with the search function.
 | 
					  Find games quickly with the search function.
 | 
				
			||||||
- **Responsive UI:**  
 | 
					- **Responsive UI:**  
 | 
				
			||||||
  Works on desktop and mobile, with Dark Mode toggle.
 | 
					  Works on desktop and mobile, with Dark Mode toggle.
 | 
				
			||||||
- **Multi-language:**  
 | 
					- **Multi-language:**  
 | 
				
			||||||
  Switch between English and German instantly.
 | 
					  Switch between English and German instantly*.
 | 
				
			||||||
 | 
					- **Import/Export (CSV / PDF -only export-):**
 | 
				
			||||||
 | 
					  Easy export and import of your keys. (e.g. in case you have to start over)
 | 
				
			||||||
 | 
					- **Change Password:**
 | 
				
			||||||
 | 
					  Change your Password on the fly.
 | 
				
			||||||
 | 
					- **Website Security:**
 | 
				
			||||||
 | 
					  You can turn on/off CSRF and Secure Cookie via .env file.
 | 
				
			||||||
 | 
					- **Notifications:**
 | 
				
			||||||
 | 
					  If you have key that have to be redeemed before a specific date. You can set up sending messages via, Pushover, Matrix and Gotify
 | 
				
			||||||
- **No key data leaves your server!**
 | 
					- **No key data leaves your server!**
 | 
				
			||||||
- **(Planned):**
 | 
					- **(Planned):**
 | 
				
			||||||
  - Import/Export (CSV, JSON)
 | 
					  - ~~Import/Export (CSV)~~
 | 
				
			||||||
  - Redeem site with unique sharing link
 | 
					  - ~~Redeem site with unique sharing link~~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,13 +53,14 @@ No more confusion about whether a key is redeemed, gifted, or still unused – n
 | 
				
			||||||
### 1. **Clone the Repository**
 | 
					### 1. **Clone the Repository**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
git clone https://git.nocci.it/nocci/GiftGamesDB
 | 
					git clone https://git.nocci.it/nocci/GameKeyManager
 | 
				
			||||||
cd steam-gift-manager
 | 
					cd steam-gift-manager
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 2. **Setup Docker**
 | 
					### 2. **Setup Docker**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Make sure you have [Docker](https://www.docker.com/) and [docker-compose](https://docs.docker.com/compose/) installed.
 | 
					Make sure you have [Docker](https://www.docker.com/) and [docker-compose](https://docs.docker.com/compose/) installed.
 | 
				
			||||||
 | 
					If not, the script will ask you what to do and can install Docker and docker-compose for you. (maybe not if you are running Arch)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 3. **Initial Setup**
 | 
					### 3. **Initial Setup**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -62,54 +79,90 @@ docker-compose build --no-cache
 | 
				
			||||||
docker-compose up -d
 | 
					docker-compose up -d
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 5. **Initialize and Edit Translations (Optional)**
 | 
					### 5. **Edit your .env file to your liking**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It's in your root folder of the installation!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```xml
 | 
				
			||||||
 | 
					# Security
 | 
				
			||||||
 | 
					SESSION_COOKIE_SECURE="True" (only works if you run this app via HTTPS)
 | 
				
			||||||
 | 
					CSRF_ENABLED="True"
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Important after any(!) change of the .env file!**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					cd steam-gift-manager/
 | 
				
			||||||
 | 
					docker-compose down && docker-compose up -d --build
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 6. **Initialize and Edit Translations (Optional)**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
./translate.sh
 | 
					./translate.sh
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Edit the .po files in steam-translations/de/LC_MESSAGES/messages.po and en/LC_MESSAGES/messages.po
 | 
					Edit the .po files in translations/de_DE/LC_MESSAGES/messages.po and en_US/LC_MESSAGES/messages.po
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
./translate.sh
 | 
					./translate.sh
 | 
				
			||||||
cd steam-gift-manager/
 | 
					cd steam-gift-manager/
 | 
				
			||||||
docker-compose restart steam-manager
 | 
					docker-compose down && docker-compose up -d --build
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 6. **Open the App**
 | 
					### 7. **Open the App**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Go to [http://localhost:5000](http://localhost:5000) in your browser.
 | 
					Go to [http://localhost:5000](http://localhost:5000) in your browser.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Register your first user.
 | 
					- Register your first user.
 | 
				
			||||||
- Add your keys, shop URLs, and (optionally) Steam AppIDs.
 | 
					- Add your keys, shop URLs etc.
 | 
				
			||||||
- Enjoy search, status, and automatic Steam cover images!
 | 
					- Enjoy search, status, and automatic Steam cover images!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 🛠️ Technology Stack 🛠️
 | 
					## 🛠️ Technology Stack 🛠️
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **Frontend:** Bootstrap 5, Jinja2 Templates
 | 
					- **Frontend:** Bootstrap 5, Jinja2 Templates ...
 | 
				
			||||||
- **Backend:** Python 3, Flask, Flask-Babel, Flask-Login, Flask-SQLAlchemy
 | 
					- **Backend:** Python 3, Flask, Flask-Babel, Flask-Login, Flask-SQLAlchemy ...
 | 
				
			||||||
- **Database:** SQLite (persisted in `data/`)
 | 
					- **Database:** SQLite (persisted in `data/`)
 | 
				
			||||||
- **Containerization:** Docker, docker-compose
 | 
					- **Containerization:** Docker, docker-compose
 | 
				
			||||||
- **Translations:** Flask-Babel, editable `.po` files in `steam-translations/`
 | 
					- **Translations:** Flask-Babel, editable `.po` files in `translations/`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 🌍 Multi-language
 | 
					## 🌍 Multi-language
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Switch between English and German using the dropdown in the navigation bar.
 | 
					- Switch between English and German using the dropdown in the navigation bar.
 | 
				
			||||||
- All game and menu texts are translated.
 | 
					- All game and menu texts can be translated or individualized.
 | 
				
			||||||
- You can add more languages by editing the `.po` files and running `./translate.sh`.
 | 
					
 | 
				
			||||||
 | 
					## 🔔 Notifications
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Send notifications if a game has to be redeemed by a specific date
 | 
				
			||||||
 | 
					- Gotify, Matrix and Pushover are already available - have a look into the .env file
 | 
				
			||||||
 | 
					- 48 hours before you are running out of time the app will send you a notice
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 🪙 Do you this project? 🪙
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If you’d like to support itme, you can make a donation here:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://ko-fi.com/nocci)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://liberapay.com/nocci/donate)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Thank you!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 🙌 Contribute! 🙌
 | 
					## 🙌 Contribute! 🙌
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This project is open source and thrives on your help!
 | 
					This project is open source!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- **Bug Reports:** Please report bugs as Issues.
 | 
					- **Bug Reports:** Please report bugs as Issues.
 | 
				
			||||||
- **Feature Requests:** Suggest new features!
 | 
					- **Feature Requests:** Suggest new features!
 | 
				
			||||||
- **Pull Requests:** Submit your code changes!
 | 
					- **Pull Requests:** Submit your code changes!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// **only possible after Forgejo opens for federation** \\\
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 📜 License 📜
 | 
					## 📜 License 📜
 | 
				
			||||||
| 
						 | 
					@ -124,4 +177,4 @@ A big thank you to everyone who supports and contributes to this project!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Enjoy your organized Steam key collection!** 🚀
 | 
					**Enjoy your organized Game key collection!** 🚀
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,13 @@ FROM python:3.10-slim
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SHELL ["/bin/bash", "-c"]
 | 
					SHELL ["/bin/bash", "-c"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN mkdir -p /app/data && chmod -R a+rwX /app/data
 | 
					RUN apt-get update && apt-get install -y --no-install-recommends wget     && mkdir -p /app/static     && wget -O /app/static/logo.png "https://git.nocci.it/nocci/GiftGamesDB/raw/branch/main/steam-gift-manager/static/logo.png"     && wget -O /app/static/logo_small.png "https://git.nocci.it/nocci/GiftGamesDB/raw/branch/main/steam-gift-manager/static/logo_small.png"     && wget -O /app/static/forgejo.svg "https://git.nocci.it/nocci/GiftGamesDB/raw/branch/main/steam-gift-manager/static/forgejo.svg"     && rm -rf /var/lib/apt/lists/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RUN mkdir -p /app/data &&     chown -R 1000:1000 /app/data 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ENV TZ=
 | 
				
			||||||
 | 
					RUN ln -snf /usr/share/zoneinfo/ /etc/localtime && echo  > /etc/timezone
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /app
 | 
					WORKDIR /app
 | 
				
			||||||
COPY requirements.txt .
 | 
					COPY requirements.txt .
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,26 +1,90 @@
 | 
				
			||||||
from flask import Flask, render_template, request, redirect, url_for, flash, make_response, session, abort, send_file
 | 
					import os
 | 
				
			||||||
 | 
					import logging
 | 
				
			||||||
 | 
					import warnings
 | 
				
			||||||
 | 
					from sqlalchemy.exc import LegacyAPIWarning
 | 
				
			||||||
 | 
					warnings.simplefilter("ignore", category=LegacyAPIWarning)
 | 
				
			||||||
 | 
					from flask import Flask, render_template, request, redirect, url_for, flash, make_response, session, abort, send_file, jsonify
 | 
				
			||||||
from flask_sqlalchemy import SQLAlchemy
 | 
					from flask_sqlalchemy import SQLAlchemy
 | 
				
			||||||
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
 | 
					from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
 | 
				
			||||||
from flask_babel import Babel, _
 | 
					from flask_babel import Babel, _
 | 
				
			||||||
from werkzeug.security import generate_password_hash, check_password_hash
 | 
					from werkzeug.security import generate_password_hash, check_password_hash
 | 
				
			||||||
from datetime import datetime
 | 
					from datetime import datetime, timedelta
 | 
				
			||||||
import os
 | 
					from flask_wtf import CSRFProtect
 | 
				
			||||||
 | 
					from flask import abort
 | 
				
			||||||
 | 
					import io
 | 
				
			||||||
 | 
					import warnings
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
import io
 | 
					import io
 | 
				
			||||||
import csv
 | 
					import csv
 | 
				
			||||||
 | 
					import secrets
 | 
				
			||||||
 | 
					import requests
 | 
				
			||||||
 | 
					from dotenv import load_dotenv
 | 
				
			||||||
 | 
					load_dotenv(override=True)
 | 
				
			||||||
 | 
					from sqlalchemy.exc import IntegrityError
 | 
				
			||||||
 | 
					from apscheduler.schedulers.background import BackgroundScheduler
 | 
				
			||||||
 | 
					import atexit
 | 
				
			||||||
 | 
					from flask_migrate import Migrate
 | 
				
			||||||
 | 
					from sqlalchemy import MetaData
 | 
				
			||||||
 | 
					from reportlab.pdfgen import canvas
 | 
				
			||||||
 | 
					from reportlab.lib.pagesizes import A4, landscape, letter
 | 
				
			||||||
 | 
					from reportlab.platypus import (
 | 
				
			||||||
 | 
					    SimpleDocTemplate, 
 | 
				
			||||||
 | 
					    Table, 
 | 
				
			||||||
 | 
					    TableStyle, 
 | 
				
			||||||
 | 
					    Paragraph, 
 | 
				
			||||||
 | 
					    Image, 
 | 
				
			||||||
 | 
					    Spacer
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from reportlab.lib import colors
 | 
				
			||||||
 | 
					from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
 | 
				
			||||||
 | 
					from reportlab.lib.utils import ImageReader
 | 
				
			||||||
 | 
					from reportlab.lib.units import cm, inch, mm
 | 
				
			||||||
 | 
					from io import BytesIO
 | 
				
			||||||
 | 
					import reportlab.lib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app = Flask(__name__)
 | 
					app = Flask(__name__)
 | 
				
			||||||
app.config['SECRET_KEY'] = os.urandom(24)
 | 
					csrf = CSRFProtect(app)
 | 
				
			||||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////app/data/games.db'
 | 
					 | 
				
			||||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
 | 
					 | 
				
			||||||
app.config['BABEL_DEFAULT_LOCALE'] = 'de'
 | 
					 | 
				
			||||||
app.config['BABEL_SUPPORTED_LOCALES'] = ['de', 'en']
 | 
					 | 
				
			||||||
app.config['BABEL_TRANSLATION_DIRECTORIES'] = 'translations'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
db = SQLAlchemy(app)
 | 
					convention = {
 | 
				
			||||||
 | 
					    "ix": "ix_%(column_0_label)s",
 | 
				
			||||||
 | 
					    "uq": "uq_%(table_name)s_%(column_0_name)s",
 | 
				
			||||||
 | 
					    "ck": "ck_%(table_name)s_%(constraint_name)s",
 | 
				
			||||||
 | 
					    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
 | 
				
			||||||
 | 
					    "pk": "pk_%(table_name)s"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					metadata = MetaData(naming_convention=convention)
 | 
				
			||||||
 | 
					load_dotenv(override=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Lade Umgebungsvariablen aus .env mit override
 | 
				
			||||||
 | 
					load_dotenv(override=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Konfiguration
 | 
				
			||||||
 | 
					app.config.update(
 | 
				
			||||||
 | 
					    SECRET_KEY=os.getenv('SECRET_KEY'),
 | 
				
			||||||
 | 
					    SQLALCHEMY_DATABASE_URI=('sqlite:////app/data/games.db'),
 | 
				
			||||||
 | 
					    SQLALCHEMY_TRACK_MODIFICATIONS=False,
 | 
				
			||||||
 | 
					    BABEL_DEFAULT_LOCALE=os.getenv('BABEL_DEFAULT_LOCALE'),
 | 
				
			||||||
 | 
					    BABEL_SUPPORTED_LOCALES=os.getenv('BABEL_SUPPORTED_LOCALES').split(','),
 | 
				
			||||||
 | 
					    BABEL_TRANSLATION_DIRECTORIES=os.getenv('BABEL_TRANSLATION_DIRECTORIES'),
 | 
				
			||||||
 | 
					    SESSION_COOKIE_SECURE=os.getenv('SESSION_COOKIE_SECURE') == 'True',
 | 
				
			||||||
 | 
					    WTF_CSRF_ENABLED=os.getenv('CSRF_ENABLED') == 'True',
 | 
				
			||||||
 | 
					    REGISTRATION_ENABLED=os.getenv('REGISTRATION_ENABLED', 'True').lower() == 'true'
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interval_hours = int(os.getenv('CHECK_EXPIRING_KEYS_INTERVAL_HOURS', 12))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Initialisierung
 | 
				
			||||||
 | 
					db = SQLAlchemy(app, metadata=metadata)
 | 
				
			||||||
 | 
					migrate = Migrate(app, db)
 | 
				
			||||||
login_manager = LoginManager(app)
 | 
					login_manager = LoginManager(app)
 | 
				
			||||||
login_manager.login_view = 'login'
 | 
					login_manager.login_view = 'login'
 | 
				
			||||||
babel = Babel(app)
 | 
					babel = Babel(app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Logging
 | 
				
			||||||
 | 
					app.logger.addHandler(logging.StreamHandler())
 | 
				
			||||||
 | 
					app.logger.setLevel(logging.INFO)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@babel.localeselector
 | 
					@babel.localeselector
 | 
				
			||||||
def get_locale():
 | 
					def get_locale():
 | 
				
			||||||
    if 'lang' in session and session['lang'] in app.config['BABEL_SUPPORTED_LOCALES']:
 | 
					    if 'lang' in session and session['lang'] in app.config['BABEL_SUPPORTED_LOCALES']:
 | 
				
			||||||
| 
						 | 
					@ -34,36 +98,63 @@ def inject_template_vars():
 | 
				
			||||||
        theme='dark' if request.cookies.get('dark_mode') == 'true' else 'light'
 | 
					        theme='dark' if request.cookies.get('dark_mode') == 'true' else 'light'
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class User(UserMixin, db.Model):
 | 
					# Datenbankmodelle
 | 
				
			||||||
 | 
					class User(db.Model, UserMixin):
 | 
				
			||||||
 | 
					    __tablename__ = 'users'
 | 
				
			||||||
    id = db.Column(db.Integer, primary_key=True)
 | 
					    id = db.Column(db.Integer, primary_key=True)
 | 
				
			||||||
    username = db.Column(db.String(100), unique=True)
 | 
					    username = db.Column(db.String(80), unique=True, nullable=False)
 | 
				
			||||||
    password = db.Column(db.String(100))
 | 
					    password = db.Column(db.String(256), nullable=False)
 | 
				
			||||||
    games = db.relationship('Game', backref='owner', lazy=True)
 | 
					    games = db.relationship('Game', back_populates='owner', lazy=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Game(db.Model):
 | 
					class Game(db.Model):
 | 
				
			||||||
    id = db.Column(db.Integer, primary_key=True)
 | 
					    id = db.Column(db.Integer, primary_key=True)
 | 
				
			||||||
 | 
					    owner = db.relationship('User', back_populates='games')
 | 
				
			||||||
    name = db.Column(db.String(100), nullable=False)
 | 
					    name = db.Column(db.String(100), nullable=False)
 | 
				
			||||||
    steam_key = db.Column(db.String(100), nullable=False)
 | 
					    steam_key = db.Column(db.String(100), nullable=False, unique=True)
 | 
				
			||||||
    status = db.Column(db.String(50), nullable=False)
 | 
					    status = db.Column(db.String(50), nullable=False)
 | 
				
			||||||
    recipient = db.Column(db.String(100))
 | 
					    recipient = db.Column(db.String(100))
 | 
				
			||||||
    notes = db.Column(db.Text)
 | 
					    notes = db.Column(db.Text)
 | 
				
			||||||
    url = db.Column(db.String(200))
 | 
					    url = db.Column(db.String(200))
 | 
				
			||||||
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
 | 
					    created_at = db.Column(db.DateTime, default=datetime.utcnow)
 | 
				
			||||||
    redeem_date = db.Column(db.DateTime)
 | 
					    redeem_date = db.Column(db.DateTime)
 | 
				
			||||||
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
 | 
					    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
 | 
				
			||||||
    steam_appid = db.Column(db.String(20))
 | 
					    steam_appid = db.Column(db.String(20))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RedeemToken(db.Model):
 | 
				
			||||||
 | 
					    id = db.Column(db.Integer, primary_key=True)
 | 
				
			||||||
 | 
					    token = db.Column(db.String(17), unique=True, nullable=False)
 | 
				
			||||||
 | 
					    game_id = db.Column(db.Integer, db.ForeignKey('game.id'), nullable=False)
 | 
				
			||||||
 | 
					    expires = db.Column(db.DateTime, nullable=False)
 | 
				
			||||||
 | 
					    used = db.Column(db.Boolean, default=False)
 | 
				
			||||||
 | 
					    total_hours = db.Column(db.Integer, nullable=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					with app.app_context():
 | 
				
			||||||
 | 
					    db.create_all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@login_manager.user_loader
 | 
					@login_manager.user_loader
 | 
				
			||||||
def load_user(user_id):
 | 
					def load_user(user_id):
 | 
				
			||||||
    return db.session.get(User, int(user_id))
 | 
					    return db.session.get(User, int(user_id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def extract_steam_appid(url):
 | 
				
			||||||
 | 
					    match = re.search(r'store\.steampowered\.com/app/(\d+)', url or '')
 | 
				
			||||||
 | 
					    return match.group(1) if match else ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 404 
 | 
				
			||||||
 | 
					def get_or_404(model, id):
 | 
				
			||||||
 | 
					    instance = db.session.get(model, id)
 | 
				
			||||||
 | 
					    if not instance:
 | 
				
			||||||
 | 
					        abort(404)
 | 
				
			||||||
 | 
					    return instance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.route('/')
 | 
					@app.route('/')
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def index():
 | 
					def index():
 | 
				
			||||||
    search_query = request.args.get('q', '')
 | 
					    search_query = request.args.get('q', '')
 | 
				
			||||||
    query = db.session.query(Game).filter_by(user_id=current_user.id)
 | 
					    query = Game.query.filter_by(user_id=current_user.id)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    if search_query:
 | 
					    if search_query:
 | 
				
			||||||
        query = query.filter(Game.name.ilike(f'%{search_query}%'))
 | 
					        query = query.filter(Game.name.ilike(f'%{search_query}%'))
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    games = query.order_by(Game.created_at.desc()).all()
 | 
					    games = query.order_by(Game.created_at.desc()).all()
 | 
				
			||||||
    return render_template('index.html',
 | 
					    return render_template('index.html',
 | 
				
			||||||
                         games=games,
 | 
					                         games=games,
 | 
				
			||||||
| 
						 | 
					@ -88,25 +179,34 @@ def login():
 | 
				
			||||||
        username = request.form['username']
 | 
					        username = request.form['username']
 | 
				
			||||||
        password = request.form['password']
 | 
					        password = request.form['password']
 | 
				
			||||||
        user = User.query.filter_by(username=username).first()
 | 
					        user = User.query.filter_by(username=username).first()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        if user and check_password_hash(user.password, password):
 | 
					        if user and check_password_hash(user.password, password):
 | 
				
			||||||
            login_user(user)
 | 
					            login_user(user)
 | 
				
			||||||
            return redirect(url_for('index'))
 | 
					            return redirect(url_for('index'))
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        flash(_('Invalid credentials'), 'danger')
 | 
					        flash(_('Invalid credentials'), 'danger')
 | 
				
			||||||
    return render_template('login.html')
 | 
					    return render_template('login.html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.route('/register', methods=['GET', 'POST'])
 | 
					@app.route('/register', methods=['GET', 'POST'])
 | 
				
			||||||
def register():
 | 
					def register():
 | 
				
			||||||
 | 
					    if not app.config['REGISTRATION_ENABLED']:
 | 
				
			||||||
 | 
					        flash(_('Registrierungen sind deaktiviert'), 'danger')
 | 
				
			||||||
 | 
					        return redirect(url_for('login'))
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
    if request.method == 'POST':
 | 
					    if request.method == 'POST':
 | 
				
			||||||
        username = request.form['username']
 | 
					        username = request.form['username']
 | 
				
			||||||
        password = generate_password_hash(request.form['password'])
 | 
					        password = generate_password_hash(request.form['password'])
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        if User.query.filter_by(username=username).first():
 | 
					        if User.query.filter_by(username=username).first():
 | 
				
			||||||
            flash(_('Username already exists'), 'danger')
 | 
					            flash(_('Username already exists'), 'danger')
 | 
				
			||||||
            return redirect(url_for('register'))
 | 
					            return redirect(url_for('register'))
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        new_user = User(username=username, password=password)
 | 
					        new_user = User(username=username, password=password)
 | 
				
			||||||
        db.session.add(new_user)
 | 
					        db.session.add(new_user)
 | 
				
			||||||
        db.session.commit()
 | 
					        db.session.commit()
 | 
				
			||||||
        login_user(new_user)
 | 
					        login_user(new_user)
 | 
				
			||||||
        return redirect(url_for('index'))
 | 
					        return redirect(url_for('index'))
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    return render_template('register.html')
 | 
					    return render_template('register.html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.route('/logout')
 | 
					@app.route('/logout')
 | 
				
			||||||
| 
						 | 
					@ -115,13 +215,28 @@ def logout():
 | 
				
			||||||
    logout_user()
 | 
					    logout_user()
 | 
				
			||||||
    return redirect(url_for('login'))
 | 
					    return redirect(url_for('login'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import re
 | 
					@app.route('/change-password', methods=['GET', 'POST'])
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					def change_password():
 | 
				
			||||||
 | 
					    if request.method == 'POST':
 | 
				
			||||||
 | 
					        current_password = request.form['current_password']
 | 
				
			||||||
 | 
					        new_password = request.form['new_password']
 | 
				
			||||||
 | 
					        confirm_password = request.form['confirm_password']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def extract_steam_appid(url):
 | 
					        if not check_password_hash(current_user.password, current_password):
 | 
				
			||||||
    match = re.search(r'store\.steampowered\.com/app/(\d+)', url or '')
 | 
					            flash(_('Aktuelles Passwort ist falsch'), 'danger')
 | 
				
			||||||
    if match:
 | 
					            return redirect(url_for('change_password'))
 | 
				
			||||||
        return match.group(1)
 | 
					
 | 
				
			||||||
    return ''
 | 
					        if new_password != confirm_password:
 | 
				
			||||||
 | 
					            flash(_('Neue Passwörter stimmen nicht überein'), 'danger')
 | 
				
			||||||
 | 
					            return redirect(url_for('change_password'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        current_user.password = generate_password_hash(new_password)
 | 
				
			||||||
 | 
					        db.session.commit()
 | 
				
			||||||
 | 
					        flash(_('Passwort erfolgreich geändert'), 'success')
 | 
				
			||||||
 | 
					        return redirect(url_for('index'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return render_template('change_password.html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.route('/add', methods=['GET', 'POST'])
 | 
					@app.route('/add', methods=['GET', 'POST'])
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
| 
						 | 
					@ -130,8 +245,10 @@ def add_game():
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            url = request.form.get('url', '')
 | 
					            url = request.form.get('url', '')
 | 
				
			||||||
            steam_appid = request.form.get('steam_appid', '').strip()
 | 
					            steam_appid = request.form.get('steam_appid', '').strip()
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            if not steam_appid:
 | 
					            if not steam_appid:
 | 
				
			||||||
                steam_appid = extract_steam_appid(url)
 | 
					                steam_appid = extract_steam_appid(url)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            new_game = Game(
 | 
					            new_game = Game(
 | 
				
			||||||
                name=request.form['name'],
 | 
					                name=request.form['name'],
 | 
				
			||||||
                steam_key=request.form['steam_key'],
 | 
					                steam_key=request.form['steam_key'],
 | 
				
			||||||
| 
						 | 
					@ -143,13 +260,19 @@ def add_game():
 | 
				
			||||||
                redeem_date=datetime.strptime(request.form['redeem_date'], '%Y-%m-%d') if request.form['redeem_date'] else None,
 | 
					                redeem_date=datetime.strptime(request.form['redeem_date'], '%Y-%m-%d') if request.form['redeem_date'] else None,
 | 
				
			||||||
                user_id=current_user.id
 | 
					                user_id=current_user.id
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            db.session.add(new_game)
 | 
					            db.session.add(new_game)
 | 
				
			||||||
            db.session.commit()
 | 
					            db.session.commit()
 | 
				
			||||||
            flash(_('Game added successfully!'), 'success')
 | 
					            flash(_('Game added successfully!'), 'success')
 | 
				
			||||||
            return redirect(url_for('index'))
 | 
					            return redirect(url_for('index'))
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        except IntegrityError:
 | 
				
			||||||
 | 
					            db.session.rollback()
 | 
				
			||||||
 | 
					            flash(_('Steam Key already exists!'), 'danger')
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            db.session.rollback()
 | 
					            db.session.rollback()
 | 
				
			||||||
            flash(_('Error: ') + str(e), 'danger')
 | 
					            flash(_('Error: ') + str(e), 'danger')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    return render_template('add_game.html')
 | 
					    return render_template('add_game.html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.route('/edit/<int:game_id>', methods=['GET', 'POST'])
 | 
					@app.route('/edit/<int:game_id>', methods=['GET', 'POST'])
 | 
				
			||||||
| 
						 | 
					@ -157,14 +280,26 @@ def add_game():
 | 
				
			||||||
def edit_game(game_id):
 | 
					def edit_game(game_id):
 | 
				
			||||||
    game = db.session.get(Game, game_id)
 | 
					    game = db.session.get(Game, game_id)
 | 
				
			||||||
    if not game or game.owner != current_user:
 | 
					    if not game or game.owner != current_user:
 | 
				
			||||||
        return _("Not allowed!"), 403
 | 
					        abort(404)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if not game or game.owner != current_user:
 | 
				
			||||||
 | 
					        abort(403)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    active_redeem = RedeemToken.query.filter(
 | 
				
			||||||
 | 
					        RedeemToken.game_id == game_id,
 | 
				
			||||||
 | 
					        RedeemToken.expires > datetime.utcnow()
 | 
				
			||||||
 | 
					    ).first()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    redeem_url = url_for('redeem_page', token=active_redeem.token, _external=True) if active_redeem else None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if request.method == 'POST':
 | 
					    if request.method == 'POST':
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            url = request.form.get('url', '')
 | 
					            url = request.form.get('url', '')
 | 
				
			||||||
            steam_appid = request.form.get('steam_appid', '').strip()
 | 
					            steam_appid = request.form.get('steam_appid', '').strip()
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            if not steam_appid:
 | 
					            if not steam_appid:
 | 
				
			||||||
                steam_appid = extract_steam_appid(url)
 | 
					                steam_appid = extract_steam_appid(url)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            game.name = request.form['name']
 | 
					            game.name = request.form['name']
 | 
				
			||||||
            game.steam_key = request.form['steam_key']
 | 
					            game.steam_key = request.form['steam_key']
 | 
				
			||||||
            game.status = request.form['status']
 | 
					            game.status = request.form['status']
 | 
				
			||||||
| 
						 | 
					@ -173,32 +308,40 @@ def edit_game(game_id):
 | 
				
			||||||
            game.url = url
 | 
					            game.url = url
 | 
				
			||||||
            game.steam_appid = steam_appid
 | 
					            game.steam_appid = steam_appid
 | 
				
			||||||
            game.redeem_date = datetime.strptime(request.form['redeem_date'], '%Y-%m-%d') if request.form['redeem_date'] else None
 | 
					            game.redeem_date = datetime.strptime(request.form['redeem_date'], '%Y-%m-%d') if request.form['redeem_date'] else None
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            db.session.commit()
 | 
					            db.session.commit()
 | 
				
			||||||
            flash(_('Changes saved!'), 'success')
 | 
					            flash(_('Changes saved!'), 'success')
 | 
				
			||||||
            return redirect(url_for('index'))
 | 
					            return redirect(url_for('index'))
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        except Exception as e:
 | 
					        except Exception as e:
 | 
				
			||||||
            db.session.rollback()
 | 
					            db.session.rollback()
 | 
				
			||||||
            flash(_('Error: ') + str(e), 'danger')
 | 
					            flash(_('Error: ') + str(e), 'danger')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    return render_template('edit_game.html',
 | 
					    return render_template('edit_game.html',
 | 
				
			||||||
                         game=game,
 | 
					                         game=game,
 | 
				
			||||||
 | 
					                         redeem_url=redeem_url,
 | 
				
			||||||
 | 
					                         active_redeem=active_redeem,
 | 
				
			||||||
                         redeem_date=game.redeem_date.strftime('%Y-%m-%d') if game.redeem_date else '')
 | 
					                         redeem_date=game.redeem_date.strftime('%Y-%m-%d') if game.redeem_date else '')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.route('/delete/<int:game_id>', methods=['POST'])
 | 
					@app.route('/delete/<int:game_id>', methods=['POST'])
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def delete_game(game_id):
 | 
					def delete_game(game_id):
 | 
				
			||||||
    game = Game.query.get_or_404(game_id)
 | 
					    game = db.session.get(Game, game_id)
 | 
				
			||||||
 | 
					    if not game or game.owner != current_user:
 | 
				
			||||||
 | 
					        abort(404)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    if game.owner != current_user:
 | 
					    if game.owner != current_user:
 | 
				
			||||||
        return _("Not allowed!"), 403
 | 
					        abort(403)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        db.session.delete(game)
 | 
					        db.session.delete(game)
 | 
				
			||||||
        db.session.commit()
 | 
					        db.session.commit()
 | 
				
			||||||
        flash(_('Game deleted!'), 'success')
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					    except Exception as e:
 | 
				
			||||||
        db.session.rollback()
 | 
					        db.session.rollback()
 | 
				
			||||||
        flash(_('Error deleting: ') + str(e), 'danger')
 | 
					    
 | 
				
			||||||
    return redirect(url_for('index'))
 | 
					    return redirect(url_for('index'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# --- Import/Export Funktionen ---
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.route('/export', methods=['GET'])
 | 
					@app.route('/export', methods=['GET'])
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
| 
						 | 
					@ -206,14 +349,22 @@ def export_games():
 | 
				
			||||||
    games = Game.query.filter_by(user_id=current_user.id).all()
 | 
					    games = Game.query.filter_by(user_id=current_user.id).all()
 | 
				
			||||||
    output = io.StringIO()
 | 
					    output = io.StringIO()
 | 
				
			||||||
    writer = csv.writer(output)
 | 
					    writer = csv.writer(output)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    writer.writerow(['Name', 'Steam Key', 'Status', 'Recipient', 'Notes', 'URL', 'Created', 'Redeem by', 'Steam AppID'])
 | 
					    writer.writerow(['Name', 'Steam Key', 'Status', 'Recipient', 'Notes', 'URL', 'Created', 'Redeem by', 'Steam AppID'])
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    for game in games:
 | 
					    for game in games:
 | 
				
			||||||
        writer.writerow([
 | 
					        writer.writerow([
 | 
				
			||||||
            game.name, game.steam_key, game.status, game.recipient, game.notes,
 | 
					            game.name, 
 | 
				
			||||||
            game.url, game.created_at.strftime('%Y-%m-%d %H:%M:%S') if game.created_at else '',
 | 
					            game.steam_key, 
 | 
				
			||||||
 | 
					            game.status, 
 | 
				
			||||||
 | 
					            game.recipient, 
 | 
				
			||||||
 | 
					            game.notes,
 | 
				
			||||||
 | 
					            game.url, 
 | 
				
			||||||
 | 
					            game.created_at.strftime('%Y-%m-%d %H:%M:%S') if game.created_at else '',
 | 
				
			||||||
            game.redeem_date.strftime('%Y-%m-%d') if game.redeem_date else '',
 | 
					            game.redeem_date.strftime('%Y-%m-%d') if game.redeem_date else '',
 | 
				
			||||||
            game.steam_appid
 | 
					            game.steam_appid
 | 
				
			||||||
        ])
 | 
					        ])
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    output.seek(0)
 | 
					    output.seek(0)
 | 
				
			||||||
    return send_file(
 | 
					    return send_file(
 | 
				
			||||||
        io.BytesIO(output.getvalue().encode('utf-8')),
 | 
					        io.BytesIO(output.getvalue().encode('utf-8')),
 | 
				
			||||||
| 
						 | 
					@ -222,18 +373,109 @@ def export_games():
 | 
				
			||||||
        download_name='games_export.csv'
 | 
					        download_name='games_export.csv'
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/export_pdf')
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					def export_pdf():
 | 
				
			||||||
 | 
					    excluded_statuses = ['eingelöst', 'verschenkt']
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    games = Game.query.filter(
 | 
				
			||||||
 | 
					        Game.user_id == current_user.id,
 | 
				
			||||||
 | 
					        Game.status.notin_(excluded_statuses)
 | 
				
			||||||
 | 
					    ).order_by(Game.created_at.desc()).all()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    buffer = io.BytesIO()
 | 
				
			||||||
 | 
					    doc = SimpleDocTemplate(buffer, 
 | 
				
			||||||
 | 
					        pagesize=landscape(A4),
 | 
				
			||||||
 | 
					        leftMargin=40,
 | 
				
			||||||
 | 
					        rightMargin=40,
 | 
				
			||||||
 | 
					        topMargin=40,
 | 
				
			||||||
 | 
					        bottomMargin=40
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    styles = getSampleStyleSheet()
 | 
				
			||||||
 | 
					    elements = []
 | 
				
			||||||
 | 
					    img_height = 2*cm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Titel
 | 
				
			||||||
 | 
					    elements.append(Paragraph(_("Game List (without Keys)"), styles['Title']))
 | 
				
			||||||
 | 
					    elements.append(Spacer(1, 12))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Tabellenkopf
 | 
				
			||||||
 | 
					    col_widths = [
 | 
				
			||||||
 | 
					        5*cm, 10*cm, 6*cm, 3*cm
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    data = [[
 | 
				
			||||||
 | 
					        Paragraph('<b>Cover</b>', styles['Normal']),
 | 
				
			||||||
 | 
					        Paragraph('<b>Name</b>', styles['Normal']),
 | 
				
			||||||
 | 
					        Paragraph('<b>Shop-Link</b>', styles['Normal']),
 | 
				
			||||||
 | 
					        Paragraph('<b>Einlösen bis</b>', styles['Normal'])
 | 
				
			||||||
 | 
					    ]]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for game in games:
 | 
				
			||||||
 | 
					        img = None
 | 
				
			||||||
 | 
					        if game.steam_appid:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                img_url = f"https://cdn.cloudflare.steamstatic.com/steam/apps/{game.steam_appid}/header.jpg"
 | 
				
			||||||
 | 
					                img_data = io.BytesIO(requests.get(img_url, timeout=5).content)
 | 
				
			||||||
 | 
					                img = Image(img_data, width=3*cm, height=img_height)
 | 
				
			||||||
 | 
					            except Exception:
 | 
				
			||||||
 | 
					                img = Paragraph('', styles['Normal'])
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        data.append([
 | 
				
			||||||
 | 
					            img or '',
 | 
				
			||||||
 | 
					            Paragraph(game.name, styles['Normal']),
 | 
				
			||||||
 | 
					            Paragraph(game.url or '', styles['Normal']),
 | 
				
			||||||
 | 
					            game.redeem_date.strftime('%d.%m.%y') if game.redeem_date else ''
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Tabelle formatieren
 | 
				
			||||||
 | 
					    table = Table(data, colWidths=col_widths, repeatRows=1)
 | 
				
			||||||
 | 
					    table.setStyle(TableStyle([
 | 
				
			||||||
 | 
					        ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'),
 | 
				
			||||||
 | 
					        ('FONTSIZE', (0,0), (-1,0), 8),
 | 
				
			||||||
 | 
					        ('FONTSIZE', (0,1), (-1,-1), 8),
 | 
				
			||||||
 | 
					        ('VALIGN', (0,0), (-1,-1), 'MIDDLE'),
 | 
				
			||||||
 | 
					        ('ALIGN', (0,0), (-1,-1), 'LEFT'),
 | 
				
			||||||
 | 
					        ('GRID', (0,0), (-1,-1), 0.5, colors.lightgrey),
 | 
				
			||||||
 | 
					        ('WORDWRAP', (1,1), (1,-1), 'CJK'),
 | 
				
			||||||
 | 
					    ]))
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    elements.append(table)
 | 
				
			||||||
 | 
					    doc.build(elements)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    buffer.seek(0)
 | 
				
			||||||
 | 
					    return send_file(
 | 
				
			||||||
 | 
					        buffer,
 | 
				
			||||||
 | 
					        mimetype='application/pdf',
 | 
				
			||||||
 | 
					        as_attachment=True,
 | 
				
			||||||
 | 
					        download_name=f'game_export_{datetime.now().strftime("%Y%m%d")}.pdf'
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@app.route('/import', methods=['GET', 'POST'])
 | 
					@app.route('/import', methods=['GET', 'POST'])
 | 
				
			||||||
@login_required
 | 
					@login_required
 | 
				
			||||||
def import_games():
 | 
					def import_games():
 | 
				
			||||||
    if request.method == 'POST':
 | 
					    if request.method == 'POST':
 | 
				
			||||||
        file = request.files.get('file')
 | 
					        file = request.files.get('file')
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        if file and file.filename.endswith('.csv'):
 | 
					        if file and file.filename.endswith('.csv'):
 | 
				
			||||||
            stream = io.StringIO(file.stream.read().decode("UTF8"), newline=None)
 | 
					            stream = io.StringIO(file.stream.read().decode("UTF8"))
 | 
				
			||||||
            reader = csv.DictReader(stream)
 | 
					            reader = csv.DictReader(stream)
 | 
				
			||||||
 | 
					            new_games = 0
 | 
				
			||||||
 | 
					            duplicates = 0
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                with db.session.begin_nested():
 | 
				
			||||||
                    for row in reader:
 | 
					                    for row in reader:
 | 
				
			||||||
                new_game = Game(
 | 
					                        steam_key = row['Steam Key'].strip()
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        if Game.query.filter_by(steam_key=steam_key).first():
 | 
				
			||||||
 | 
					                            duplicates += 1
 | 
				
			||||||
 | 
					                            continue
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					                        game = Game(
 | 
				
			||||||
                            name=row['Name'],
 | 
					                            name=row['Name'],
 | 
				
			||||||
                    steam_key=row['Steam Key'],
 | 
					                            steam_key=steam_key,
 | 
				
			||||||
                            status=row['Status'],
 | 
					                            status=row['Status'],
 | 
				
			||||||
                            recipient=row.get('Recipient', ''),
 | 
					                            recipient=row.get('Recipient', ''),
 | 
				
			||||||
                            notes=row.get('Notes', ''),
 | 
					                            notes=row.get('Notes', ''),
 | 
				
			||||||
| 
						 | 
					@ -243,14 +485,194 @@ def import_games():
 | 
				
			||||||
                            steam_appid=row.get('Steam AppID', ''),
 | 
					                            steam_appid=row.get('Steam AppID', ''),
 | 
				
			||||||
                            user_id=current_user.id
 | 
					                            user_id=current_user.id
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                db.session.add(new_game)
 | 
					                        
 | 
				
			||||||
 | 
					                        db.session.add(game)
 | 
				
			||||||
 | 
					                        new_games += 1
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
                    db.session.commit()
 | 
					                    db.session.commit()
 | 
				
			||||||
            flash(_('Import erfolgreich!'), 'success')
 | 
					                
 | 
				
			||||||
 | 
					                flash(_('%(new)d neue Spiele importiert, %(dup)d Duplikate übersprungen', new=new_games, dup=duplicates), 'success')
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            except Exception as e:
 | 
				
			||||||
 | 
					                db.session.rollback()
 | 
				
			||||||
 | 
					                flash(_('Importfehler: %(error)s', error=str(e)), 'danger')
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
            return redirect(url_for('index'))
 | 
					            return redirect(url_for('index'))
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        flash(_('Bitte eine gültige CSV-Datei hochladen.'), 'danger')
 | 
					        flash(_('Bitte eine gültige CSV-Datei hochladen.'), 'danger')
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    return render_template('import.html')
 | 
					    return render_template('import.html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/generate_redeem/<int:game_id>', methods=['POST'])
 | 
				
			||||||
 | 
					@login_required
 | 
				
			||||||
 | 
					def generate_redeem(game_id):
 | 
				
			||||||
 | 
					    game = db.session.get(Game, game_id)
 | 
				
			||||||
 | 
					    if not game or game.owner != current_user:
 | 
				
			||||||
 | 
					        abort(403)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if game.owner != current_user or game.status != 'verschenkt':
 | 
				
			||||||
 | 
					        abort(403)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        token = secrets.token_urlsafe(12)[:17]
 | 
				
			||||||
 | 
					        expires = datetime.utcnow() + timedelta(hours=24)
 | 
				
			||||||
 | 
					        total_hours = 24
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        RedeemToken.query.filter_by(game_id=game_id).delete()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        new_token = RedeemToken(
 | 
				
			||||||
 | 
					            token=token,
 | 
				
			||||||
 | 
					            game_id=game_id,
 | 
				
			||||||
 | 
					            expires=expires,
 | 
				
			||||||
 | 
					            total_hours=24
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        db.session.add(new_token)
 | 
				
			||||||
 | 
					        db.session.commit()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        redeem_url = url_for('redeem_page', token=token, _external=True)
 | 
				
			||||||
 | 
					        return jsonify({'url': redeem_url})
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        app.logger.error(f"Redeem error: {str(e)}")
 | 
				
			||||||
 | 
					        return jsonify({'error': str(e)}), 500
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@app.route('/redeem/<token>')
 | 
				
			||||||
 | 
					def redeem_page(token):
 | 
				
			||||||
 | 
					    redeem_token = RedeemToken.query.filter_by(token=token).first()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if not redeem_token:
 | 
				
			||||||
 | 
					        abort(404)
 | 
				
			||||||
 | 
					    if redeem_token.expires < datetime.utcnow():
 | 
				
			||||||
 | 
					        db.session.delete(redeem_token)
 | 
				
			||||||
 | 
					        db.session.commit()
 | 
				
			||||||
 | 
					        abort(404)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    game = Game.query.get(redeem_token.game_id)
 | 
				
			||||||
 | 
					    redeem_token.used = True
 | 
				
			||||||
 | 
					    db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return render_template('redeem.html',
 | 
				
			||||||
 | 
					                         game=game,
 | 
				
			||||||
 | 
					                         redeem_token=redeem_token,
 | 
				
			||||||
 | 
					                         platform_link='https://store.steampowered.com/account/registerkey?key=' if game.steam_appid else 'https://www.gog.com/redeem')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Benachrichtigungsfunktionen
 | 
				
			||||||
 | 
					def send_pushover_notification(user, game):
 | 
				
			||||||
 | 
					    """Sendet Pushover-Benachrichtigung für ablaufenden Key"""
 | 
				
			||||||
 | 
					    if not app.config['PUSHOVER_APP_TOKEN'] or not app.config['PUSHOVER_USER_KEY']:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    payload = {
 | 
				
			||||||
 | 
					        "token": os.getenv('PUSHOVER_APP_TOKEN'),
 | 
				
			||||||
 | 
					        "user": os.getenv('PUSHOVER_USER_KEY'),
 | 
				
			||||||
 | 
					        "title": "Steam-Key läuft ab!",
 | 
				
			||||||
 | 
					        "message": f"Dein Key für '{game.name}' läuft in weniger als 48 Stunden ab!",
 | 
				
			||||||
 | 
					        "url": url_for('edit_game', game_id=game.id, _external=True),
 | 
				
			||||||
 | 
					        "url_title": "Zum Spiel",
 | 
				
			||||||
 | 
					        "priority": 1
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        response = requests.post(
 | 
				
			||||||
 | 
					            'https://api.pushover.net/1/messages.json', 
 | 
				
			||||||
 | 
					            data=payload
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return response.status_code == 200
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        app.logger.error(f"Pushover error: {str(e)}")
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def send_gotify_notification(user, game):
 | 
				
			||||||
 | 
					    """Sendet Gotify-Benachrichtigung für ablaufenden Key"""
 | 
				
			||||||
 | 
					    if not GOTIFY_URL or not GOTIFY_TOKEN:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    payload = {
 | 
				
			||||||
 | 
					        "title": "Steam-Key läuft ab!",
 | 
				
			||||||
 | 
					        "message": f"Dein Key für '{game.name}' läuft in weniger als 48 Stunden ab!",
 | 
				
			||||||
 | 
					        "priority": 5
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        response = requests.post(
 | 
				
			||||||
 | 
					            f"{GOTIFY_URL}/message?token={GOTIFY_TOKEN}", 
 | 
				
			||||||
 | 
					            json=payload
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return response.status_code == 200
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        app.logger.error(f"Gotify error: {str(e)}")
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def send_matrix_notification(user, game):
 | 
				
			||||||
 | 
					    """Sendet Matrix-Benachrichtigung für ablaufenden Key"""
 | 
				
			||||||
 | 
					    if not MATRIX_HOMESERVER or not MATRIX_ACCESS_TOKEN or not MATRIX_ROOM_ID:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        from matrix_client.client import MatrixClient
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        client = MatrixClient(MATRIX_HOMESERVER, token=MATRIX_ACCESS_TOKEN)
 | 
				
			||||||
 | 
					        room = client.join_room(MATRIX_ROOM_ID)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        message = f"🎮 Dein Key für '{game.name}' läuft in weniger als 48 Stunden ab!"
 | 
				
			||||||
 | 
					        room.send_text(message)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        app.logger.error(f"Matrix error: {str(e)}")
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def send_notification(user, game):
 | 
				
			||||||
 | 
					    """Sendet Benachrichtigung über den bevorzugten Dienst des Benutzers"""
 | 
				
			||||||
 | 
					    if user.notification_service == 'pushover':
 | 
				
			||||||
 | 
					        return send_pushover_notification(user, game)
 | 
				
			||||||
 | 
					    elif user.notification_service == 'gotify':
 | 
				
			||||||
 | 
					        return send_gotify_notification(user, game)
 | 
				
			||||||
 | 
					    elif user.notification_service == 'matrix':
 | 
				
			||||||
 | 
					        return send_matrix_notification(user, game)
 | 
				
			||||||
 | 
					    return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def check_expiring_keys():
 | 
				
			||||||
 | 
					    with app.app_context():
 | 
				
			||||||
 | 
					        now = datetime.utcnow()
 | 
				
			||||||
 | 
					        expiry_threshold = now + timedelta(hours=48)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # Moderner Select-Aufruf
 | 
				
			||||||
 | 
					        stmt = select(Game).where(
 | 
				
			||||||
 | 
					            Game.status != 'eingelöst',
 | 
				
			||||||
 | 
					            Game.redeem_date <= expiry_threshold,
 | 
				
			||||||
 | 
					            Game.redeem_date > now
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        expiring_games = db.session.execute(stmt).scalars().all()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        for game in expiring_games:
 | 
				
			||||||
 | 
					            user = User.query.get(game.user_id)
 | 
				
			||||||
 | 
					            if user.notification_service and user.notification_service != 'none':
 | 
				
			||||||
 | 
					                send_notification(user, game)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Optional: Cleanup-Funktion für regelmäßiges Löschen abgelaufener Tokens
 | 
				
			||||||
 | 
					def cleanup_expired_tokens():
 | 
				
			||||||
 | 
					    now = datetime.utcnow()
 | 
				
			||||||
 | 
					    expired = RedeemToken.query.filter(RedeemToken.expires < now).all()
 | 
				
			||||||
 | 
					    for token in expired:
 | 
				
			||||||
 | 
					        db.session.delete(token)
 | 
				
			||||||
 | 
					    db.session.commit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Scheduler initialisieren und starten
 | 
				
			||||||
 | 
					scheduler = BackgroundScheduler()
 | 
				
			||||||
 | 
					scheduler.add_job(func=check_expiring_keys, trigger="interval", hours=interval_hours)
 | 
				
			||||||
 | 
					scheduler.add_job(func=cleanup_expired_tokens, trigger="interval", hours=1)
 | 
				
			||||||
 | 
					scheduler.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Shutdown des Schedulers bei Beendigung der App
 | 
				
			||||||
 | 
					atexit.register(lambda: scheduler.shutdown())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if __name__ == '__main__':
 | 
					if __name__ == '__main__':
 | 
				
			||||||
    with app.app_context():
 | 
					    with app.app_context():
 | 
				
			||||||
        db.create_all()
 | 
					        db.create_all()
 | 
				
			||||||
    app.run(host='0.0.0.0', port=5000)
 | 
					    app.run(host='0.0.0.0', port=5000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,2 +1,3 @@
 | 
				
			||||||
[python: **.py]
 | 
					[python: **.py]
 | 
				
			||||||
[jinja2: templates/**.html]
 | 
					[jinja2: **/templates/**.html]
 | 
				
			||||||
 | 
					extensions=jinja2.ext.autoescape,jinja2.ext.with_
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,14 @@
 | 
				
			||||||
version: '3.8'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  steam-manager:
 | 
					  steam-manager:
 | 
				
			||||||
    build: .
 | 
					    build: .
 | 
				
			||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
      - "5000:5000"
 | 
					      - "5000:5000"
 | 
				
			||||||
    volumes:
 | 
					 | 
				
			||||||
      - /root/test/data:/app/data
 | 
					 | 
				
			||||||
      - /root/test/steam-translations:/app/translations
 | 
					 | 
				
			||||||
    environment:
 | 
					    environment:
 | 
				
			||||||
      - FLASK_DEBUG=0
 | 
					      - REGISTRATION_ENABLED=True
 | 
				
			||||||
 | 
					      - TZ=
 | 
				
			||||||
 | 
					    volumes:
 | 
				
			||||||
 | 
					      - ../data:/app/data
 | 
				
			||||||
 | 
					      - ../translations:/app/translations
 | 
				
			||||||
 | 
					      - ../.env:/app/.env
 | 
				
			||||||
 | 
					    user: "1000:1000"
 | 
				
			||||||
    restart: unless-stopped
 | 
					    restart: unless-stopped
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,16 @@
 | 
				
			||||||
flask
 | 
					flask
 | 
				
			||||||
flask-login
 | 
					flask-login
 | 
				
			||||||
 | 
					flask-wtf
 | 
				
			||||||
 | 
					flask-migrate
 | 
				
			||||||
werkzeug
 | 
					werkzeug
 | 
				
			||||||
python-dotenv
 | 
					python-dotenv
 | 
				
			||||||
flask-sqlalchemy
 | 
					flask-sqlalchemy
 | 
				
			||||||
flask-babel
 | 
					flask-babel
 | 
				
			||||||
jinja2<3.1.0
 | 
					jinja2<3.1.0
 | 
				
			||||||
 | 
					itsdangerous
 | 
				
			||||||
 | 
					sqlalchemy
 | 
				
			||||||
 | 
					apscheduler
 | 
				
			||||||
 | 
					matrix-client
 | 
				
			||||||
 | 
					reportlab
 | 
				
			||||||
 | 
					requests
 | 
				
			||||||
 | 
					pillow
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										1
									
								
								steam-gift-manager/static/forgejo.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								steam-gift-manager/static/forgejo.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 212 212" width="32" height="32"><style>circle,path{fill:none;stroke:#000;stroke-width:15}path{stroke-width:25}.orange{stroke:#f60}.red{stroke:#d40000}</style><g transform="translate(6 6)"><path d="M58 168V70a50 50 0 0 1 50-50h20" class="orange"/><path d="M58 168v-30a50 50 0 0 1 50-50h20" class="red"/><circle cx="142" cy="20" r="18" class="orange"/><circle cx="142" cy="88" r="18" class="red"/><circle cx="58" cy="180" r="18" class="red"/></g></svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 503 B  | 
							
								
								
									
										
											BIN
										
									
								
								steam-gift-manager/static/logo.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								steam-gift-manager/static/logo.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 52 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								steam-gift-manager/static/logo_small.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								steam-gift-manager/static/logo_small.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 35 KiB  | 
| 
						 | 
					@ -31,3 +31,32 @@ body {
 | 
				
			||||||
    font-size: 0.9em;
 | 
					    font-size: 0.9em;
 | 
				
			||||||
    font-weight: 500;
 | 
					    font-weight: 500;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					#expiry-countdown {
 | 
				
			||||||
 | 
					  font-weight: 600;
 | 
				
			||||||
 | 
					  letter-spacing: 0.05em;
 | 
				
			||||||
 | 
					  color: #dc3545;
 | 
				
			||||||
 | 
					  transition: color 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[data-bs-theme="dark"] #expiry-countdown {
 | 
				
			||||||
 | 
					  color: #ff6b6b;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Progressbar-Animationen */
 | 
				
			||||||
 | 
					#expiry-bar {
 | 
				
			||||||
 | 
					    transition: width 1s linear, background-color 0.5s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.bg-success { background-color: #198754 !important; }
 | 
				
			||||||
 | 
					.bg-warning { background-color: #ffc107 !important; }
 | 
				
			||||||
 | 
					.bg-danger { background-color: #dc3545 !important; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.progress-bar {
 | 
				
			||||||
 | 
					  transition: width 1s linear, background-color 0.3s ease;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.table-pdf {
 | 
				
			||||||
 | 
					    font-size: 0.8em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.table-pdf td, .table-pdf th {
 | 
				
			||||||
 | 
					    padding: 4px 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,13 +3,14 @@
 | 
				
			||||||
<div class="card p-4 shadow-sm">
 | 
					<div class="card p-4 shadow-sm">
 | 
				
			||||||
    <h2 class="mb-4">{{ _('Add New Game') }}</h2>
 | 
					    <h2 class="mb-4">{{ _('Add New Game') }}</h2>
 | 
				
			||||||
    <form method="POST">
 | 
					    <form method="POST">
 | 
				
			||||||
 | 
						<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
 | 
				
			||||||
        <div class="row g-3">
 | 
					        <div class="row g-3">
 | 
				
			||||||
            <div class="col-md-6">
 | 
					            <div class="col-md-6">
 | 
				
			||||||
                <label class="form-label">{{ _('Name') }} *</label>
 | 
					                <label class="form-label">{{ _('Name') }} *</label>
 | 
				
			||||||
                <input type="text" name="name" class="form-control" required>
 | 
					                <input type="text" name="name" class="form-control" required>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="col-md-6">
 | 
					            <div class="col-md-6">
 | 
				
			||||||
                <label class="form-label">{{ _('Steam Key') }} *</label>
 | 
					                <label class="form-label">{{ _('Game Key') }} *</label>
 | 
				
			||||||
                <input type="text" name="steam_key" class="form-control" required>
 | 
					                <input type="text" name="steam_key" class="form-control" required>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="col-md-4">
 | 
					            <div class="col-md-4">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,14 +3,18 @@
 | 
				
			||||||
<head>
 | 
					<head>
 | 
				
			||||||
    <meta charset="UTF-8">
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
    <title>{{ _('Steam Manager') }}</title>
 | 
					    <meta name="csrf-token" content="{{ csrf_token() }}">
 | 
				
			||||||
 | 
					    <title>{{ _('Game Key Manager') }}</title>
 | 
				
			||||||
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
 | 
					    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
 | 
				
			||||||
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
 | 
					    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
 | 
				
			||||||
</head>
 | 
					</head>
 | 
				
			||||||
<body>
 | 
					<body>
 | 
				
			||||||
    <nav class="navbar navbar-expand-lg bg-body-tertiary">
 | 
					    <nav class="navbar navbar-expand-lg bg-body-tertiary">
 | 
				
			||||||
        <div class="container">
 | 
					        <div class="container">
 | 
				
			||||||
            <a class="navbar-brand" href="/">{{ _('Steam Manager') }}</a>
 | 
					            <a class="navbar-brand d-flex align-items-center gap-2" href="/">
 | 
				
			||||||
 | 
					                 <img src="{{ url_for('static', filename='logo_small.png') }}" alt="Logo" width="150" height="116" style="object-fit:contain; border-radius:8px;">
 | 
				
			||||||
 | 
					                 <span>Game Key Manager</span>
 | 
				
			||||||
 | 
					            </a>
 | 
				
			||||||
            <div class="d-flex align-items-center gap-3">
 | 
					            <div class="d-flex align-items-center gap-3">
 | 
				
			||||||
                <form class="d-flex" action="{{ url_for('index') }}" method="GET">
 | 
					                <form class="d-flex" action="{{ url_for('index') }}" method="GET">
 | 
				
			||||||
                    <input class="form-control me-2" 
 | 
					                    <input class="form-control me-2" 
 | 
				
			||||||
| 
						 | 
					@ -26,28 +30,22 @@
 | 
				
			||||||
                           id="darkModeSwitch" {% if theme == 'dark' %}checked{% endif %}>
 | 
					                           id="darkModeSwitch" {% if theme == 'dark' %}checked{% endif %}>
 | 
				
			||||||
                    <label class="form-check-label" for="darkModeSwitch">{{ _('Dark Mode') }}</label>
 | 
					                    <label class="form-check-label" for="darkModeSwitch">{{ _('Dark Mode') }}</label>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <!-- Sprachumschalter -->
 | 
					 | 
				
			||||||
                <div class="dropdown ms-3">
 | 
					                <div class="dropdown ms-3">
 | 
				
			||||||
                    <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
 | 
					                    <button class="btn btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
 | 
				
			||||||
                        {% if get_locale() == 'de' %} Deutsch {% elif get_locale() == 'en' %} English {% else %} Sprache {% endif %}
 | 
					                        {% if get_locale() == 'de' %} Deutsch {% elif get_locale() == 'en' %} English {% else %} Sprache {% endif %}
 | 
				
			||||||
                    </button>
 | 
					                    </button>
 | 
				
			||||||
                    <ul class="dropdown-menu">
 | 
					                    <ul class="dropdown-menu">
 | 
				
			||||||
                    <li>
 | 
					                        <li><a class="dropdown-item {% if get_locale() == 'de' %}active{% endif %}" href="{{ url_for('set_lang', lang='de') }}">Deutsch</a></li>
 | 
				
			||||||
                      <a class="dropdown-item {% if get_locale() == 'de' %}active{% endif %}" href="{{ url_for('set_lang', lang='de') }}">
 | 
					                        <li><a class="dropdown-item {% if get_locale() == 'en' %}active{% endif %}" href="{{ url_for('set_lang', lang='en') }}">English</a></li>
 | 
				
			||||||
                        Deutsch
 | 
					 | 
				
			||||||
                      </a>
 | 
					 | 
				
			||||||
                    </li>
 | 
					 | 
				
			||||||
                    <li>
 | 
					 | 
				
			||||||
                      <a class="dropdown-item {% if get_locale() == 'en' %}active{% endif %}" href="{{ url_for('set_lang', lang='en') }}">
 | 
					 | 
				
			||||||
                        English
 | 
					 | 
				
			||||||
                      </a>
 | 
					 | 
				
			||||||
                    </li>
 | 
					 | 
				
			||||||
                    </ul>
 | 
					                    </ul>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                {% if current_user.is_authenticated %}
 | 
					                {% if current_user.is_authenticated %}
 | 
				
			||||||
					<a href="{{ url_for('export_games') }}" class="btn btn-outline-secondary">⬇️ {{ _('Export') }}</a>
 | 
					                        <li class="nav-item">
 | 
				
			||||||
					<a href="{{ url_for('import_games') }}" class="btn btn-outline-secondary">⬆️ {{ _('Import') }}</a>				
 | 
					                             <a class="nav-link" href="{{ url_for('change_password') }}">🔒 {{ _('Passwort') }}</a>
 | 
				
			||||||
                    <a href="{{ url_for('logout') }}" class="btn btn-danger ms-3">{{ _('Logout') }}</a>
 | 
					                        </li>
 | 
				
			||||||
 | 
					                        <li class="nav-item">
 | 
				
			||||||
 | 
					                             <a class="nav-link" href="{{ url_for('logout') }}">🚪 {{ _('Logout') }}</a>
 | 
				
			||||||
 | 
					                        </li>
 | 
				
			||||||
                {% endif %}
 | 
					                {% endif %}
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
| 
						 | 
					@ -68,16 +66,16 @@
 | 
				
			||||||
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
 | 
					    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
 | 
				
			||||||
    <script>
 | 
					    <script>
 | 
				
			||||||
    document.addEventListener('DOMContentLoaded', function() {
 | 
					    document.addEventListener('DOMContentLoaded', function() {
 | 
				
			||||||
        const toggle = document.getElementById('darkModeSwitch');
 | 
					        const toggle = document.getElementById('darkModeSwitch')
 | 
				
			||||||
        const html = document.documentElement;
 | 
					        const html = document.documentElement
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
        toggle.addEventListener('change', function() {
 | 
					        toggle.addEventListener('change', function() {
 | 
				
			||||||
            const theme = this.checked ? 'dark' : 'light';
 | 
					            const theme = this.checked ? 'dark' : 'light'
 | 
				
			||||||
            fetch('/set-theme/' + theme)
 | 
					            fetch('/set-theme/' + theme)
 | 
				
			||||||
                .then(() => {
 | 
					                .then(() => html.setAttribute('data-bs-theme', theme))
 | 
				
			||||||
                    html.setAttribute('data-bs-theme', theme);
 | 
					        })
 | 
				
			||||||
                });
 | 
					    })
 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
    </script>
 | 
					    </script>
 | 
				
			||||||
 | 
					{% include "footer.html" %}
 | 
				
			||||||
</body>
 | 
					</body>
 | 
				
			||||||
</html>
 | 
					</html>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								steam-gift-manager/templates/change_password.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								steam-gift-manager/templates/change_password.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					{% extends "base.html" %}
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<div class="card p-4 shadow-sm">
 | 
				
			||||||
 | 
					    <h2 class="mb-4">{{ _('Change Password') }}</h2>
 | 
				
			||||||
 | 
					    <form method="POST">
 | 
				
			||||||
 | 
					        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
 | 
				
			||||||
 | 
					        <div class="mb-3">
 | 
				
			||||||
 | 
					            <label class="form-label">{{ _('Current Password') }}</label>
 | 
				
			||||||
 | 
					            <input type="password" name="current_password" class="form-control" required>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="mb-3">
 | 
				
			||||||
 | 
					            <label class="form-label">{{ _('New Password') }}</label>
 | 
				
			||||||
 | 
					            <input type="password" name="new_password" class="form-control" required>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="mb-3">
 | 
				
			||||||
 | 
					            <label class="form-label">{{ _('Confirm New Password') }}</label>
 | 
				
			||||||
 | 
					            <input type="password" name="confirm_password" class="form-control" required>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <button type="submit" class="btn btn-primary">{{ _('Change Password') }}</button>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					@ -3,13 +3,14 @@
 | 
				
			||||||
<div class="card p-4 shadow-sm">
 | 
					<div class="card p-4 shadow-sm">
 | 
				
			||||||
    <h2 class="mb-4">{{ _('Edit Game') }}</h2>
 | 
					    <h2 class="mb-4">{{ _('Edit Game') }}</h2>
 | 
				
			||||||
    <form method="POST">
 | 
					    <form method="POST">
 | 
				
			||||||
 | 
					        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
 | 
				
			||||||
        <div class="row g-3">
 | 
					        <div class="row g-3">
 | 
				
			||||||
            <div class="col-md-6">
 | 
					            <div class="col-md-6">
 | 
				
			||||||
                <label class="form-label">{{ _('Name') }} *</label>
 | 
					                <label class="form-label">{{ _('Name') }} *</label>
 | 
				
			||||||
                <input type="text" name="name" class="form-control" value="{{ game.name }}" required>
 | 
					                <input type="text" name="name" class="form-control" value="{{ game.name }}" required>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="col-md-6">
 | 
					            <div class="col-md-6">
 | 
				
			||||||
                <label class="form-label">{{ _('Steam Key') }} *</label>
 | 
					                <label class="form-label">{{ _('Game Key') }} *</label>
 | 
				
			||||||
                <input type="text" name="steam_key" class="form-control" value="{{ game.steam_key }}" required>
 | 
					                <input type="text" name="steam_key" class="form-control" value="{{ game.steam_key }}" required>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div class="col-md-6">
 | 
					            <div class="col-md-6">
 | 
				
			||||||
| 
						 | 
					@ -40,6 +41,21 @@
 | 
				
			||||||
                <label class="form-label">{{ _('Notes') }}</label>
 | 
					                <label class="form-label">{{ _('Notes') }}</label>
 | 
				
			||||||
                <textarea name="notes" class="form-control" rows="3">{{ game.notes }}</textarea>
 | 
					                <textarea name="notes" class="form-control" rows="3">{{ game.notes }}</textarea>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
 | 
					            <div class="col-12">
 | 
				
			||||||
 | 
					                {% if redeem_url and active_redeem %}
 | 
				
			||||||
 | 
					                <div class="mb-3">
 | 
				
			||||||
 | 
					                    <label class="form-label">{{ _('Active Redeem Link') }}</label>
 | 
				
			||||||
 | 
					                    <input type="text"
 | 
				
			||||||
 | 
					                           class="form-control"
 | 
				
			||||||
 | 
					                           value="{{ redeem_url }}"
 | 
				
			||||||
 | 
					                           readonly
 | 
				
			||||||
 | 
					                           onclick="this.select()">
 | 
				
			||||||
 | 
					                    <small class="text-muted">
 | 
				
			||||||
 | 
					                        {{ _('Expires at') }}: {{ active_redeem.expires.strftime('%d.%m.%Y %H:%M') }}
 | 
				
			||||||
 | 
					                    </small>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					                {% endif %}
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
            <div class="col-12">
 | 
					            <div class="col-12">
 | 
				
			||||||
                <button type="submit" class="btn btn-primary">{{ _('Save') }}</button>
 | 
					                <button type="submit" class="btn btn-primary">{{ _('Save') }}</button>
 | 
				
			||||||
                <a href="{{ url_for('index') }}" class="btn btn-outline-secondary">{{ _('Cancel') }}</a>
 | 
					                <a href="{{ url_for('index') }}" class="btn btn-outline-secondary">{{ _('Cancel') }}</a>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										18
									
								
								steam-gift-manager/templates/footer.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								steam-gift-manager/templates/footer.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					<footer class="mt-5 py-4 bg-body-tertiary border-top">
 | 
				
			||||||
 | 
					  <div class="container text-center small text-muted">
 | 
				
			||||||
 | 
					    <div class="mb-2">
 | 
				
			||||||
 | 
					      <strong>Game Key Manager</strong> — is done by nocci
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div class="mb-2">
 | 
				
			||||||
 | 
					      <a href="https://git.nocci.it/nocci/GiftGamesDB" target="_blank" rel="noopener">
 | 
				
			||||||
 | 
					        <img src="{{ url_for('static', filename='forgejo.svg') }}" alt="forgejo" width="20" style="vertical-align:middle;margin-right:4px;">
 | 
				
			||||||
 | 
					        find the source code on my Forgejo
 | 
				
			||||||
 | 
					      </a>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					      <span>feel free to donate - if you can affort it:</span>
 | 
				
			||||||
 | 
					      <a href="https://ko-fi.com/nocci" target="_blank" rel="noopener">Ko-fi</a> ·
 | 
				
			||||||
 | 
					      <a href="https://liberapay.com/nocci" target="_blank" rel="noopener">Liberapay</a>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</footer>
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@
 | 
				
			||||||
<div class="card p-4 shadow-sm">
 | 
					<div class="card p-4 shadow-sm">
 | 
				
			||||||
    <h2 class="mb-4">{{ _('Import Games') }}</h2>
 | 
					    <h2 class="mb-4">{{ _('Import Games') }}</h2>
 | 
				
			||||||
    <form method="POST" enctype="multipart/form-data">
 | 
					    <form method="POST" enctype="multipart/form-data">
 | 
				
			||||||
 | 
						<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
 | 
				
			||||||
        <div class="mb-3">
 | 
					        <div class="mb-3">
 | 
				
			||||||
            <label class="form-label">{{ _('CSV-Datei auswählen') }}</label>
 | 
					            <label class="form-label">{{ _('CSV-Datei auswählen') }}</label>
 | 
				
			||||||
            <input type="file" name="file" class="form-control" accept=".csv" required>
 | 
					            <input type="file" name="file" class="form-control" accept=".csv" required>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,9 +2,12 @@
 | 
				
			||||||
{% block content %}
 | 
					{% block content %}
 | 
				
			||||||
<div class="d-flex justify-content-between align-items-center mb-4">
 | 
					<div class="d-flex justify-content-between align-items-center mb-4">
 | 
				
			||||||
    <h1>{{ _('My Games') }}</h1>
 | 
					    <h1>{{ _('My Games') }}</h1>
 | 
				
			||||||
    <a href="{{ url_for('add_game') }}" class="btn btn-primary">
 | 
					    <div>
 | 
				
			||||||
        + {{ _('Add New Game') }}
 | 
					        <a href="{{ url_for('export_games') }}" class="btn btn-outline-secondary">⬇️ {{ _('Export CSV') }}</a>
 | 
				
			||||||
    </a>
 | 
					        <a href="{{ url_for('export_pdf') }}" class="btn btn-outline-secondary">⬇️ Export PDF (for sharing)</a>
 | 
				
			||||||
 | 
					        <a href="{{ url_for('import_games') }}" class="btn btn-outline-secondary">⬆️ {{ _('Import CSV') }}</a>
 | 
				
			||||||
 | 
					        <a href="{{ url_for('add_game') }}" class="btn btn-primary">+ {{ _('Add New Game') }}</a>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% if games %}
 | 
					{% if games %}
 | 
				
			||||||
| 
						 | 
					@ -54,8 +57,16 @@
 | 
				
			||||||
                    {% endif %}
 | 
					                    {% endif %}
 | 
				
			||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
                <td class="text-nowrap">
 | 
					                <td class="text-nowrap">
 | 
				
			||||||
 | 
					                    {% if game.status == 'verschenkt' %}
 | 
				
			||||||
 | 
					                    <button class="btn btn-sm btn-success generate-redeem" 
 | 
				
			||||||
 | 
					                            data-game-id="{{ game.id }}"
 | 
				
			||||||
 | 
					                            title="{{ _('Generate redeem link') }}">
 | 
				
			||||||
 | 
					                        🔗
 | 
				
			||||||
 | 
					                    </button>
 | 
				
			||||||
 | 
					                    {% endif %}
 | 
				
			||||||
                    <a href="{{ url_for('edit_game', game_id=game.id) }}" class="btn btn-sm btn-warning">✏️</a>
 | 
					                    <a href="{{ url_for('edit_game', game_id=game.id) }}" class="btn btn-sm btn-warning">✏️</a>
 | 
				
			||||||
                    <form method="POST" action="{{ url_for('delete_game', game_id=game.id) }}" class="d-inline">
 | 
					                    <form method="POST" action="{{ url_for('delete_game', game_id=game.id) }}" class="d-inline">
 | 
				
			||||||
 | 
					                        <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
 | 
				
			||||||
                        <button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('{{ _('Really delete?') }}')">🗑️</button>
 | 
					                        <button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('{{ _('Really delete?') }}')">🗑️</button>
 | 
				
			||||||
                    </form>
 | 
					                    </form>
 | 
				
			||||||
                </td>
 | 
					                </td>
 | 
				
			||||||
| 
						 | 
					@ -64,6 +75,33 @@
 | 
				
			||||||
        </tbody>
 | 
					        </tbody>
 | 
				
			||||||
    </table>
 | 
					    </table>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.querySelectorAll('.generate-redeem').forEach(btn => {
 | 
				
			||||||
 | 
					    btn.addEventListener('click', async function() {
 | 
				
			||||||
 | 
					        const gameId = this.dataset.gameId;
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const response = await fetch('/generate_redeem/' + gameId, {
 | 
				
			||||||
 | 
					                method: 'POST',
 | 
				
			||||||
 | 
					                headers: {
 | 
				
			||||||
 | 
					                    'X-CSRFToken': csrfToken
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            if (!response.ok) throw new Error('Network error');
 | 
				
			||||||
 | 
					            const data = await response.json();
 | 
				
			||||||
 | 
					            if(data.url) {
 | 
				
			||||||
 | 
					                await navigator.clipboard.writeText(data.url);
 | 
				
			||||||
 | 
					                alert('{{ _("Redeem link copied to clipboard!") }}');
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (error) {
 | 
				
			||||||
 | 
					            console.error('Error:', error);
 | 
				
			||||||
 | 
					            alert('{{ _("Error generating link") }}');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
{% else %}
 | 
					{% else %}
 | 
				
			||||||
<div class="alert alert-info">{{ _('No games yet') }}</div>
 | 
					<div class="alert alert-info">{{ _('No games yet') }}</div>
 | 
				
			||||||
{% endif %}
 | 
					{% endif %}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,9 +3,11 @@
 | 
				
			||||||
<div class="row justify-content-center mt-5">
 | 
					<div class="row justify-content-center mt-5">
 | 
				
			||||||
    <div class="col-md-6">
 | 
					    <div class="col-md-6">
 | 
				
			||||||
        <div class="card shadow-sm">
 | 
					        <div class="card shadow-sm">
 | 
				
			||||||
            <div class="card-body">
 | 
					            <div class="card-body text-center">
 | 
				
			||||||
 | 
					                <img src="{{ url_for('static', filename='logo.png') }}" alt="Logo" width="311" height="240" class="mb-4" style="object-fit:contain;">
 | 
				
			||||||
                <h2 class="card-title mb-4">{{ _('Login') }}</h2>
 | 
					                <h2 class="card-title mb-4">{{ _('Login') }}</h2>
 | 
				
			||||||
                <form method="POST">
 | 
					                <form method="POST">
 | 
				
			||||||
 | 
					                    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
 | 
				
			||||||
                    <div class="mb-3">
 | 
					                    <div class="mb-3">
 | 
				
			||||||
                        <label class="form-label">{{ _('Username') }}</label>
 | 
					                        <label class="form-label">{{ _('Username') }}</label>
 | 
				
			||||||
                        <input type="text" name="username" class="form-control" required>
 | 
					                        <input type="text" name="username" class="form-control" required>
 | 
				
			||||||
| 
						 | 
					@ -24,3 +26,4 @@
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
{% endblock %}
 | 
					{% endblock %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										91
									
								
								steam-gift-manager/templates/redeem.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								steam-gift-manager/templates/redeem.html
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,91 @@
 | 
				
			||||||
 | 
					{% extends "base.html" %}
 | 
				
			||||||
 | 
					{% block content %}
 | 
				
			||||||
 | 
					<div class="container mt-5">
 | 
				
			||||||
 | 
					    <div class="card shadow-lg">
 | 
				
			||||||
 | 
					        <div class="row g-0">
 | 
				
			||||||
 | 
					            {% if game.steam_appid %}
 | 
				
			||||||
 | 
					            <div class="col-md-4">
 | 
				
			||||||
 | 
					                <img src="https://cdn.cloudflare.steamstatic.com/steam/apps/{{ game.steam_appid }}/header.jpg" 
 | 
				
			||||||
 | 
					                     class="img-fluid rounded-start" alt="Game Cover">
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					            {% endif %}
 | 
				
			||||||
 | 
					            <div class="col-md-8">
 | 
				
			||||||
 | 
					                <div class="card-body">
 | 
				
			||||||
 | 
					                    <h1 class="card-title mb-4">{{ game.name }}</h1>
 | 
				
			||||||
 | 
					                    <div class="alert alert-success">
 | 
				
			||||||
 | 
					                        <h4>{{ _('Your Key:') }}</h4>
 | 
				
			||||||
 | 
					                        <code class="fs-3">{{ game.steam_key }}</code>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                    <a href="{{ platform_link }}{{ game.steam_key }}" 
 | 
				
			||||||
 | 
					                       class="btn btn-primary btn-lg mb-3"
 | 
				
			||||||
 | 
					                       target="_blank">
 | 
				
			||||||
 | 
					                        {{ _('Redeem now on') }} {% if game.steam_appid %}Steam{% else %}GOG{% endif %}
 | 
				
			||||||
 | 
					                    </a>
 | 
				
			||||||
 | 
					                    <div class="mt-4 text-muted">
 | 
				
			||||||
 | 
					                        <small>
 | 
				
			||||||
 | 
					                            {{ _('This page will expire in') }} 
 | 
				
			||||||
 | 
					                            <span id="expiry-countdown" class="fw-bold"></span>
 | 
				
			||||||
 | 
					                        </small>
 | 
				
			||||||
 | 
					                        <div class="progress mt-2" style="height: 8px;">
 | 
				
			||||||
 | 
					                            <div id="expiry-bar" 
 | 
				
			||||||
 | 
					                                class="progress-bar bg-danger" 
 | 
				
			||||||
 | 
					                                role="progressbar" 
 | 
				
			||||||
 | 
					                                style="width: 100%">
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					const totalDuration = {{ redeem_token.total_hours * 3600 * 1000 }};  // Gesamtdauer in Millisekunden
 | 
				
			||||||
 | 
					const expires = {{ (redeem_token.expires.timestamp() * 1000) | int }};
 | 
				
			||||||
 | 
					const countdownEl = document.getElementById('expiry-countdown');
 | 
				
			||||||
 | 
					const progressBar = document.getElementById('expiry-bar');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function formatTime(unit) {
 | 
				
			||||||
 | 
					    return unit < 10 ? `0${unit}` : unit;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function updateProgressBar(percentage) {
 | 
				
			||||||
 | 
					    // Alle Farbklassen entfernen
 | 
				
			||||||
 | 
					    progressBar.classList.remove('bg-success', 'bg-warning', 'bg-danger');
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (percentage > 75) {
 | 
				
			||||||
 | 
					        progressBar.classList.add('bg-success');
 | 
				
			||||||
 | 
					    } else if (percentage > 25) {
 | 
				
			||||||
 | 
					        progressBar.classList.add('bg-warning');
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        progressBar.classList.add('bg-danger');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function updateCountdown() {
 | 
				
			||||||
 | 
					    const now = Date.now();
 | 
				
			||||||
 | 
					    const remaining = expires - now;
 | 
				
			||||||
 | 
					    const percent = (remaining / totalDuration) * 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (remaining < 0) {
 | 
				
			||||||
 | 
					        countdownEl.innerHTML = "EXPIRED";
 | 
				
			||||||
 | 
					        progressBar.style.width = "0%";
 | 
				
			||||||
 | 
					        clearInterval(timer);
 | 
				
			||||||
 | 
					        setTimeout(() => window.location.reload(), 5000);
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const hours = Math.floor(remaining / (1000 * 60 * 60));
 | 
				
			||||||
 | 
					    const minutes = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60));
 | 
				
			||||||
 | 
					    const seconds = Math.floor((remaining % (1000 * 60)) / 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    countdownEl.innerHTML = `${formatTime(hours)}h ${formatTime(minutes)}m ${formatTime(seconds)}s`;
 | 
				
			||||||
 | 
					    progressBar.style.width = `${percent}%`;
 | 
				
			||||||
 | 
					    updateProgressBar(percent);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Initialisierung
 | 
				
			||||||
 | 
					updateCountdown();
 | 
				
			||||||
 | 
					const timer = setInterval(updateCountdown, 1000);
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					{% endblock %}
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@
 | 
				
			||||||
            <div class="card-body">
 | 
					            <div class="card-body">
 | 
				
			||||||
                <h2 class="card-title mb-4">{{ _('Register') }}</h2>
 | 
					                <h2 class="card-title mb-4">{{ _('Register') }}</h2>
 | 
				
			||||||
                <form method="POST">
 | 
					                <form method="POST">
 | 
				
			||||||
 | 
					                    <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
 | 
				
			||||||
                    <div class="mb-3">
 | 
					                    <div class="mb-3">
 | 
				
			||||||
                        <label class="form-label">{{ _('Username') }}</label>
 | 
					                        <label class="form-label">{{ _('Username') }}</label>
 | 
				
			||||||
                        <input type="text" name="username" class="form-control" required>
 | 
					                        <input type="text" name="username" class="form-control" required>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,216 +0,0 @@
 | 
				
			||||||
# German translations for PROJECT.
 | 
					 | 
				
			||||||
# Copyright (C) 2025 ORGANIZATION
 | 
					 | 
				
			||||||
# This file is distributed under the same license as the PROJECT project.
 | 
					 | 
				
			||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
msgid ""
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
"Project-Id-Version: PROJECT VERSION\n"
 | 
					 | 
				
			||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
 | 
					 | 
				
			||||||
"POT-Creation-Date: 2025-04-22 11:22+0000\n"
 | 
					 | 
				
			||||||
"PO-Revision-Date: 2025-04-22 11:22+0000\n"
 | 
					 | 
				
			||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
					 | 
				
			||||||
"Language: de\n"
 | 
					 | 
				
			||||||
"Language-Team: de <LL@li.org>\n"
 | 
					 | 
				
			||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
 | 
					 | 
				
			||||||
"MIME-Version: 1.0\n"
 | 
					 | 
				
			||||||
"Content-Type: text/plain; charset=utf-8\n"
 | 
					 | 
				
			||||||
"Content-Transfer-Encoding: 8bit\n"
 | 
					 | 
				
			||||||
"Generated-By: Babel 2.17.0\n"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:94
 | 
					 | 
				
			||||||
msgid "Invalid credentials"
 | 
					 | 
				
			||||||
msgstr "Ooops. Falsche Benutzerdaten!"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:103
 | 
					 | 
				
			||||||
msgid "Username already exists"
 | 
					 | 
				
			||||||
msgstr "Benutzer existiert bereits"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:148
 | 
					 | 
				
			||||||
msgid "Game added successfully!"
 | 
					 | 
				
			||||||
msgstr "Spiel erfolgreich hinzugefügt!"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:152 app.py:181
 | 
					 | 
				
			||||||
msgid "Error: "
 | 
					 | 
				
			||||||
msgstr "Ui. Ein Fehler: "
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:160 app.py:191
 | 
					 | 
				
			||||||
msgid "Not allowed!"
 | 
					 | 
				
			||||||
msgstr "Das ist nicht erlaubt!"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:177
 | 
					 | 
				
			||||||
msgid "Changes saved!"
 | 
					 | 
				
			||||||
msgstr "Änderungen gespeichert!"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:195
 | 
					 | 
				
			||||||
msgid "Game deleted!"
 | 
					 | 
				
			||||||
msgstr "Spiel gelöscht!"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:198
 | 
					 | 
				
			||||||
msgid "Error deleting: "
 | 
					 | 
				
			||||||
msgstr "Fehler beim Löschen: "
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:248
 | 
					 | 
				
			||||||
msgid "Import erfolgreich!"
 | 
					 | 
				
			||||||
msgstr "Import erfolgreich!"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:250
 | 
					 | 
				
			||||||
msgid "Bitte eine gültige CSV-Datei hochladen."
 | 
					 | 
				
			||||||
msgstr "Bitte eine gültige CSV-Datei hochladen."
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:4 templates/index.html:6
 | 
					 | 
				
			||||||
msgid "Add New Game"
 | 
					 | 
				
			||||||
msgstr "Spiel hinzufügen"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:8 templates/edit_game.html:8 templates/index.html:16
 | 
					 | 
				
			||||||
msgid "Name"
 | 
					 | 
				
			||||||
msgstr "Name"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:12 templates/edit_game.html:12
 | 
					 | 
				
			||||||
msgid "Steam Key"
 | 
					 | 
				
			||||||
msgstr "Game Key"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:16 templates/edit_game.html:20
 | 
					 | 
				
			||||||
#: templates/index.html:18
 | 
					 | 
				
			||||||
msgid "Status"
 | 
					 | 
				
			||||||
msgstr "Status"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:18 templates/edit_game.html:22
 | 
					 | 
				
			||||||
#: templates/index.html:38
 | 
					 | 
				
			||||||
msgid "Not redeemed"
 | 
					 | 
				
			||||||
msgstr "Nicht eingelöst"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:19 templates/edit_game.html:23
 | 
					 | 
				
			||||||
#: templates/index.html:40
 | 
					 | 
				
			||||||
msgid "Gifted"
 | 
					 | 
				
			||||||
msgstr "Verschenkt"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:20 templates/edit_game.html:24
 | 
					 | 
				
			||||||
#: templates/index.html:42
 | 
					 | 
				
			||||||
msgid "Redeemed"
 | 
					 | 
				
			||||||
msgstr "Eingelöst"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:24 templates/edit_game.html:28
 | 
					 | 
				
			||||||
#: templates/index.html:20
 | 
					 | 
				
			||||||
msgid "Redeem by"
 | 
					 | 
				
			||||||
msgstr "Einzulösen vor"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:28 templates/edit_game.html:32
 | 
					 | 
				
			||||||
msgid "Recipient"
 | 
					 | 
				
			||||||
msgstr "Empfänger*in"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:32 templates/edit_game.html:36
 | 
					 | 
				
			||||||
msgid "Shop URL"
 | 
					 | 
				
			||||||
msgstr "Shop URL"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:36 templates/edit_game.html:40
 | 
					 | 
				
			||||||
msgid "Notes"
 | 
					 | 
				
			||||||
msgstr "Notizen"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:40 templates/edit_game.html:44
 | 
					 | 
				
			||||||
msgid "Save"
 | 
					 | 
				
			||||||
msgstr "Gespeichert"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:41 templates/edit_game.html:45
 | 
					 | 
				
			||||||
msgid "Cancel"
 | 
					 | 
				
			||||||
msgstr "Abbrechen"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:6 templates/base.html:13
 | 
					 | 
				
			||||||
msgid "Steam Manager"
 | 
					 | 
				
			||||||
msgstr "Steam Spiele Manager"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:19
 | 
					 | 
				
			||||||
msgid "Search"
 | 
					 | 
				
			||||||
msgstr "Suche"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:27
 | 
					 | 
				
			||||||
msgid "Dark Mode"
 | 
					 | 
				
			||||||
msgstr "Dark Mode"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:48
 | 
					 | 
				
			||||||
msgid "Export"
 | 
					 | 
				
			||||||
msgstr "Export"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:49
 | 
					 | 
				
			||||||
msgid "Import"
 | 
					 | 
				
			||||||
msgstr "Import"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:50
 | 
					 | 
				
			||||||
msgid "Logout"
 | 
					 | 
				
			||||||
msgstr "Logout"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/edit_game.html:4
 | 
					 | 
				
			||||||
msgid "Edit Game"
 | 
					 | 
				
			||||||
msgstr "Spiel editieren"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/edit_game.html:16
 | 
					 | 
				
			||||||
msgid "Steam AppID (optional)"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/import.html:4
 | 
					 | 
				
			||||||
msgid "Import Games"
 | 
					 | 
				
			||||||
msgstr "Importiere Spiele"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/import.html:7
 | 
					 | 
				
			||||||
msgid "CSV-Datei auswählen"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/import.html:10
 | 
					 | 
				
			||||||
msgid "Importieren"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/import.html:11
 | 
					 | 
				
			||||||
msgid "Abbrechen"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:4
 | 
					 | 
				
			||||||
msgid "My Games"
 | 
					 | 
				
			||||||
msgstr "Meine Spiele"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:15
 | 
					 | 
				
			||||||
msgid "Cover"
 | 
					 | 
				
			||||||
msgstr "Cover"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:17
 | 
					 | 
				
			||||||
msgid "Key"
 | 
					 | 
				
			||||||
msgstr "Key"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:19
 | 
					 | 
				
			||||||
msgid "Created"
 | 
					 | 
				
			||||||
msgstr "Erstellt"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:21 templates/index.html:53
 | 
					 | 
				
			||||||
msgid "Shop"
 | 
					 | 
				
			||||||
msgstr "ShopLink"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:22
 | 
					 | 
				
			||||||
msgid "Actions"
 | 
					 | 
				
			||||||
msgstr "Aktionen"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:59
 | 
					 | 
				
			||||||
msgid "Really delete?"
 | 
					 | 
				
			||||||
msgstr "Wirklich löschen?"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:68
 | 
					 | 
				
			||||||
msgid "No games yet"
 | 
					 | 
				
			||||||
msgstr "Der Kornspeicher ist leer, Sire!"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/login.html:7 templates/login.html:17
 | 
					 | 
				
			||||||
msgid "Login"
 | 
					 | 
				
			||||||
msgstr "Anmelden"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/login.html:10 templates/register.html:10
 | 
					 | 
				
			||||||
msgid "Username"
 | 
					 | 
				
			||||||
msgstr "Benutzername"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/login.html:14 templates/register.html:14
 | 
					 | 
				
			||||||
msgid "Password"
 | 
					 | 
				
			||||||
msgstr "Passwort"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/login.html:20
 | 
					 | 
				
			||||||
msgid "No account yet? Register"
 | 
					 | 
				
			||||||
msgstr "Kein Account? Hier registrieren!"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/register.html:7 templates/register.html:17
 | 
					 | 
				
			||||||
msgid "Register"
 | 
					 | 
				
			||||||
msgstr "Registrieren"
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,217 +0,0 @@
 | 
				
			||||||
# English translations for PROJECT.
 | 
					 | 
				
			||||||
# Copyright (C) 2025 ORGANIZATION
 | 
					 | 
				
			||||||
# This file is distributed under the same license as the PROJECT project.
 | 
					 | 
				
			||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
msgid ""
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
"Project-Id-Version: PROJECT VERSION\n"
 | 
					 | 
				
			||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
 | 
					 | 
				
			||||||
"POT-Creation-Date: 2025-04-22 11:22+0000\n"
 | 
					 | 
				
			||||||
"PO-Revision-Date: 2025-04-22 11:22+0000\n"
 | 
					 | 
				
			||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
					 | 
				
			||||||
"Language: en\n"
 | 
					 | 
				
			||||||
"Language-Team: en <LL@li.org>\n"
 | 
					 | 
				
			||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
 | 
					 | 
				
			||||||
"MIME-Version: 1.0\n"
 | 
					 | 
				
			||||||
"Content-Type: text/plain; charset=utf-8\n"
 | 
					 | 
				
			||||||
"Content-Transfer-Encoding: 8bit\n"
 | 
					 | 
				
			||||||
"Generated-By: Babel 2.17.0\n"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:94
 | 
					 | 
				
			||||||
msgid "Invalid credentials"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:103
 | 
					 | 
				
			||||||
msgid "Username already exists"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:148
 | 
					 | 
				
			||||||
msgid "Game added successfully!"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:152 app.py:181
 | 
					 | 
				
			||||||
msgid "Error: "
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:160 app.py:191
 | 
					 | 
				
			||||||
msgid "Not allowed!"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:177
 | 
					 | 
				
			||||||
msgid "Changes saved!"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:195
 | 
					 | 
				
			||||||
msgid "Game deleted!"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:198
 | 
					 | 
				
			||||||
msgid "Error deleting: "
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:248
 | 
					 | 
				
			||||||
msgid "Import erfolgreich!"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:250
 | 
					 | 
				
			||||||
msgid "Bitte eine gültige CSV-Datei hochladen."
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:4 templates/index.html:6
 | 
					 | 
				
			||||||
msgid "Add New Game"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:8 templates/edit_game.html:8 templates/index.html:16
 | 
					 | 
				
			||||||
msgid "Name"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:12 templates/edit_game.html:12
 | 
					 | 
				
			||||||
msgid "Steam Key"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:16 templates/edit_game.html:20
 | 
					 | 
				
			||||||
#: templates/index.html:18
 | 
					 | 
				
			||||||
msgid "Status"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:18 templates/edit_game.html:22
 | 
					 | 
				
			||||||
#: templates/index.html:38
 | 
					 | 
				
			||||||
msgid "Not redeemed"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:19 templates/edit_game.html:23
 | 
					 | 
				
			||||||
#: templates/index.html:40
 | 
					 | 
				
			||||||
msgid "Gifted"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:20 templates/edit_game.html:24
 | 
					 | 
				
			||||||
#: templates/index.html:42
 | 
					 | 
				
			||||||
msgid "Redeemed"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:24 templates/edit_game.html:28
 | 
					 | 
				
			||||||
#: templates/index.html:20
 | 
					 | 
				
			||||||
msgid "Redeem by"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:28 templates/edit_game.html:32
 | 
					 | 
				
			||||||
msgid "Recipient"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:32 templates/edit_game.html:36
 | 
					 | 
				
			||||||
msgid "Shop URL"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:36 templates/edit_game.html:40
 | 
					 | 
				
			||||||
msgid "Notes"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:40 templates/edit_game.html:44
 | 
					 | 
				
			||||||
msgid "Save"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:41 templates/edit_game.html:45
 | 
					 | 
				
			||||||
msgid "Cancel"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:6 templates/base.html:13
 | 
					 | 
				
			||||||
msgid "Steam Manager"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:19
 | 
					 | 
				
			||||||
msgid "Search"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:27
 | 
					 | 
				
			||||||
msgid "Dark Mode"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:48
 | 
					 | 
				
			||||||
msgid "Export"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:49
 | 
					 | 
				
			||||||
msgid "Import"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:50
 | 
					 | 
				
			||||||
msgid "Logout"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/edit_game.html:4
 | 
					 | 
				
			||||||
msgid "Edit Game"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/edit_game.html:16
 | 
					 | 
				
			||||||
msgid "Steam AppID (optional)"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/import.html:4
 | 
					 | 
				
			||||||
msgid "Import Games"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/import.html:7
 | 
					 | 
				
			||||||
msgid "CSV-Datei auswählen"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/import.html:10
 | 
					 | 
				
			||||||
msgid "Importieren"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/import.html:11
 | 
					 | 
				
			||||||
msgid "Abbrechen"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:4
 | 
					 | 
				
			||||||
msgid "My Games"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:15
 | 
					 | 
				
			||||||
msgid "Cover"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:17
 | 
					 | 
				
			||||||
msgid "Key"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:19
 | 
					 | 
				
			||||||
msgid "Created"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:21 templates/index.html:53
 | 
					 | 
				
			||||||
msgid "Shop"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:22
 | 
					 | 
				
			||||||
msgid "Actions"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:59
 | 
					 | 
				
			||||||
msgid "Really delete?"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:68
 | 
					 | 
				
			||||||
msgid "No games yet"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/login.html:7 templates/login.html:17
 | 
					 | 
				
			||||||
msgid "Login"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/login.html:10 templates/register.html:10
 | 
					 | 
				
			||||||
msgid "Username"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/login.html:14 templates/register.html:14
 | 
					 | 
				
			||||||
msgid "Password"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/login.html:20
 | 
					 | 
				
			||||||
msgid "No account yet? Register"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/register.html:7 templates/register.html:17
 | 
					 | 
				
			||||||
msgid "Register"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,216 +0,0 @@
 | 
				
			||||||
# Translations template for PROJECT.
 | 
					 | 
				
			||||||
# Copyright (C) 2025 ORGANIZATION
 | 
					 | 
				
			||||||
# This file is distributed under the same license as the PROJECT project.
 | 
					 | 
				
			||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
#, fuzzy
 | 
					 | 
				
			||||||
msgid ""
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
"Project-Id-Version: PROJECT VERSION\n"
 | 
					 | 
				
			||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
 | 
					 | 
				
			||||||
"POT-Creation-Date: 2025-04-22 11:22+0000\n"
 | 
					 | 
				
			||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 | 
					 | 
				
			||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
					 | 
				
			||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
					 | 
				
			||||||
"MIME-Version: 1.0\n"
 | 
					 | 
				
			||||||
"Content-Type: text/plain; charset=utf-8\n"
 | 
					 | 
				
			||||||
"Content-Transfer-Encoding: 8bit\n"
 | 
					 | 
				
			||||||
"Generated-By: Babel 2.17.0\n"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:94
 | 
					 | 
				
			||||||
msgid "Invalid credentials"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:103
 | 
					 | 
				
			||||||
msgid "Username already exists"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:148
 | 
					 | 
				
			||||||
msgid "Game added successfully!"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:152 app.py:181
 | 
					 | 
				
			||||||
msgid "Error: "
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:160 app.py:191
 | 
					 | 
				
			||||||
msgid "Not allowed!"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:177
 | 
					 | 
				
			||||||
msgid "Changes saved!"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:195
 | 
					 | 
				
			||||||
msgid "Game deleted!"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:198
 | 
					 | 
				
			||||||
msgid "Error deleting: "
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:248
 | 
					 | 
				
			||||||
msgid "Import erfolgreich!"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: app.py:250
 | 
					 | 
				
			||||||
msgid "Bitte eine gültige CSV-Datei hochladen."
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:4 templates/index.html:6
 | 
					 | 
				
			||||||
msgid "Add New Game"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:8 templates/edit_game.html:8 templates/index.html:16
 | 
					 | 
				
			||||||
msgid "Name"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:12 templates/edit_game.html:12
 | 
					 | 
				
			||||||
msgid "Steam Key"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:16 templates/edit_game.html:20
 | 
					 | 
				
			||||||
#: templates/index.html:18
 | 
					 | 
				
			||||||
msgid "Status"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:18 templates/edit_game.html:22
 | 
					 | 
				
			||||||
#: templates/index.html:38
 | 
					 | 
				
			||||||
msgid "Not redeemed"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:19 templates/edit_game.html:23
 | 
					 | 
				
			||||||
#: templates/index.html:40
 | 
					 | 
				
			||||||
msgid "Gifted"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:20 templates/edit_game.html:24
 | 
					 | 
				
			||||||
#: templates/index.html:42
 | 
					 | 
				
			||||||
msgid "Redeemed"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:24 templates/edit_game.html:28
 | 
					 | 
				
			||||||
#: templates/index.html:20
 | 
					 | 
				
			||||||
msgid "Redeem by"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:28 templates/edit_game.html:32
 | 
					 | 
				
			||||||
msgid "Recipient"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:32 templates/edit_game.html:36
 | 
					 | 
				
			||||||
msgid "Shop URL"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:36 templates/edit_game.html:40
 | 
					 | 
				
			||||||
msgid "Notes"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:40 templates/edit_game.html:44
 | 
					 | 
				
			||||||
msgid "Save"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/add_game.html:41 templates/edit_game.html:45
 | 
					 | 
				
			||||||
msgid "Cancel"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:6 templates/base.html:13
 | 
					 | 
				
			||||||
msgid "Steam Manager"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:19
 | 
					 | 
				
			||||||
msgid "Search"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:27
 | 
					 | 
				
			||||||
msgid "Dark Mode"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:48
 | 
					 | 
				
			||||||
msgid "Export"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:49
 | 
					 | 
				
			||||||
msgid "Import"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/base.html:50
 | 
					 | 
				
			||||||
msgid "Logout"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/edit_game.html:4
 | 
					 | 
				
			||||||
msgid "Edit Game"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/edit_game.html:16
 | 
					 | 
				
			||||||
msgid "Steam AppID (optional)"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/import.html:4
 | 
					 | 
				
			||||||
msgid "Import Games"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/import.html:7
 | 
					 | 
				
			||||||
msgid "CSV-Datei auswählen"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/import.html:10
 | 
					 | 
				
			||||||
msgid "Importieren"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/import.html:11
 | 
					 | 
				
			||||||
msgid "Abbrechen"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:4
 | 
					 | 
				
			||||||
msgid "My Games"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:15
 | 
					 | 
				
			||||||
msgid "Cover"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:17
 | 
					 | 
				
			||||||
msgid "Key"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:19
 | 
					 | 
				
			||||||
msgid "Created"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:21 templates/index.html:53
 | 
					 | 
				
			||||||
msgid "Shop"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:22
 | 
					 | 
				
			||||||
msgid "Actions"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:59
 | 
					 | 
				
			||||||
msgid "Really delete?"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/index.html:68
 | 
					 | 
				
			||||||
msgid "No games yet"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/login.html:7 templates/login.html:17
 | 
					 | 
				
			||||||
msgid "Login"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/login.html:10 templates/register.html:10
 | 
					 | 
				
			||||||
msgid "Username"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/login.html:14 templates/register.html:14
 | 
					 | 
				
			||||||
msgid "Password"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/login.html:20
 | 
					 | 
				
			||||||
msgid "No account yet? Register"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#: templates/register.html:7 templates/register.html:17
 | 
					 | 
				
			||||||
msgid "Register"
 | 
					 | 
				
			||||||
msgstr ""
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							
								
								
									
										24
									
								
								translate.sh
									
										
									
									
									
								
							
							
						
						
									
										24
									
								
								translate.sh
									
										
									
									
									
								
							| 
						 | 
					@ -3,20 +3,26 @@ set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cd "$(dirname "$0")/steam-gift-manager"
 | 
					cd "$(dirname "$0")/steam-gift-manager"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 1. Extrahiere alle Texte
 | 
					declare -A locales=(
 | 
				
			||||||
 | 
					  ["de"]="de"
 | 
				
			||||||
 | 
					  ["en"]="en"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# POT-Datei erstellen
 | 
				
			||||||
docker-compose exec steam-manager pybabel extract -F babel.cfg -o translations/messages.pot .
 | 
					docker-compose exec steam-manager pybabel extract -F babel.cfg -o translations/messages.pot .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 2. Initialisiere Sprachen (nur einmal nötig, danach auskommentieren)
 | 
					# Für jede Sprache prüfen und ggf. initialisieren
 | 
				
			||||||
for lang in de en; do
 | 
					for lang in "${!locales[@]}"; do
 | 
				
			||||||
    if [ ! -f "../steam-translations/$lang/LC_MESSAGES/messages.po" ]; then
 | 
					  if [ ! -f "translations/${locales[$lang]}/LC_MESSAGES/messages.po" ]; then
 | 
				
			||||||
        docker-compose exec steam-manager pybabel init -i translations/messages.pot -d translations -l $lang
 | 
					    docker-compose exec steam-manager pybabel init \
 | 
				
			||||||
 | 
					      -i translations/messages.pot \
 | 
				
			||||||
 | 
					      -d translations \
 | 
				
			||||||
 | 
					      -l "${locales[$lang]}"
 | 
				
			||||||
  fi
 | 
					  fi
 | 
				
			||||||
done
 | 
					done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 3. Aktualisiere Übersetzungen
 | 
					# Übersetzungen aktualisieren und kompilieren
 | 
				
			||||||
docker-compose exec steam-manager pybabel update -i translations/messages.pot -d translations
 | 
					docker-compose exec steam-manager pybabel update -i translations/messages.pot -d translations
 | 
				
			||||||
 | 
					 | 
				
			||||||
# 4. Kompiliere Übersetzungen
 | 
					 | 
				
			||||||
docker-compose exec steam-manager pybabel compile -d translations
 | 
					docker-compose exec steam-manager pybabel compile -d translations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
echo "✅ Übersetzungen extrahiert, aktualisiert und kompiliert!"
 | 
					echo "✅ Übersetzungen aktualisiert!"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										287
									
								
								translations/de/LC_MESSAGES/messages.po
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								translations/de/LC_MESSAGES/messages.po
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,287 @@
 | 
				
			||||||
 | 
					# German translations for PROJECT.
 | 
				
			||||||
 | 
					# Copyright (C) 2025 ORGANIZATION
 | 
				
			||||||
 | 
					# This file is distributed under the same license as the PROJECT project.
 | 
				
			||||||
 | 
					# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					"Project-Id-Version: PROJECT VERSION\n"
 | 
				
			||||||
 | 
					"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
 | 
				
			||||||
 | 
					"POT-Creation-Date: 2025-04-26 11:13+0000\n"
 | 
				
			||||||
 | 
					"PO-Revision-Date: 2025-04-26 11:13+0000\n"
 | 
				
			||||||
 | 
					"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
				
			||||||
 | 
					"Language: de\n"
 | 
				
			||||||
 | 
					"Language-Team: de <LL@li.org>\n"
 | 
				
			||||||
 | 
					"Plural-Forms: nplurals=2; plural=(n != 1);\n"
 | 
				
			||||||
 | 
					"MIME-Version: 1.0\n"
 | 
				
			||||||
 | 
					"Content-Type: text/plain; charset=utf-8\n"
 | 
				
			||||||
 | 
					"Content-Transfer-Encoding: 8bit\n"
 | 
				
			||||||
 | 
					"Generated-By: Babel 2.17.0\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:187
 | 
				
			||||||
 | 
					msgid "Invalid credentials"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:193
 | 
				
			||||||
 | 
					msgid "Registrierungen sind deaktiviert"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:201
 | 
				
			||||||
 | 
					msgid "Username already exists"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:227
 | 
				
			||||||
 | 
					msgid "Aktuelles Passwort ist falsch"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:231
 | 
				
			||||||
 | 
					msgid "Neue Passwörter stimmen nicht überein"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:236
 | 
				
			||||||
 | 
					msgid "Passwort erfolgreich geändert"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:266
 | 
				
			||||||
 | 
					msgid "Game added successfully!"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:271
 | 
				
			||||||
 | 
					msgid "Steam Key already exists!"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:274 app.py:318
 | 
				
			||||||
 | 
					msgid "Error: "
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:313
 | 
				
			||||||
 | 
					msgid "Changes saved!"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:401
 | 
				
			||||||
 | 
					msgid "Game List (without Keys)"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:494
 | 
				
			||||||
 | 
					#, python-format
 | 
				
			||||||
 | 
					msgid "%(new)d neue Spiele importiert, %(dup)d Duplikate übersprungen"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:498
 | 
				
			||||||
 | 
					#, python-format
 | 
				
			||||||
 | 
					msgid "Importfehler: %(error)s"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:502
 | 
				
			||||||
 | 
					msgid "Bitte eine gültige CSV-Datei hochladen."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:4 templates/index.html:9
 | 
				
			||||||
 | 
					msgid "Add New Game"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:9 templates/edit_game.html:9 templates/index.html:19
 | 
				
			||||||
 | 
					msgid "Name"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:13 templates/edit_game.html:13
 | 
				
			||||||
 | 
					msgid "Game Key"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:17 templates/edit_game.html:21
 | 
				
			||||||
 | 
					#: templates/index.html:21
 | 
				
			||||||
 | 
					msgid "Status"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:19 templates/edit_game.html:23
 | 
				
			||||||
 | 
					#: templates/index.html:41
 | 
				
			||||||
 | 
					msgid "Not redeemed"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:20 templates/edit_game.html:24
 | 
				
			||||||
 | 
					#: templates/index.html:43
 | 
				
			||||||
 | 
					msgid "Gifted"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:21 templates/edit_game.html:25
 | 
				
			||||||
 | 
					#: templates/index.html:45
 | 
				
			||||||
 | 
					msgid "Redeemed"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:25 templates/edit_game.html:29
 | 
				
			||||||
 | 
					#: templates/index.html:23
 | 
				
			||||||
 | 
					msgid "Redeem by"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:29 templates/edit_game.html:33
 | 
				
			||||||
 | 
					msgid "Recipient"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:33 templates/edit_game.html:37
 | 
				
			||||||
 | 
					msgid "Shop URL"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:37 templates/edit_game.html:41
 | 
				
			||||||
 | 
					msgid "Notes"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:41 templates/edit_game.html:60
 | 
				
			||||||
 | 
					msgid "Save"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:42 templates/edit_game.html:61
 | 
				
			||||||
 | 
					msgid "Cancel"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:7
 | 
				
			||||||
 | 
					msgid "Game Key Manager"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:23
 | 
				
			||||||
 | 
					msgid "Search"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:31
 | 
				
			||||||
 | 
					msgid "Dark Mode"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:44
 | 
				
			||||||
 | 
					msgid "Passwort"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:47
 | 
				
			||||||
 | 
					msgid "Logout"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/change_password.html:4 templates/change_password.html:19
 | 
				
			||||||
 | 
					msgid "Change Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/change_password.html:8
 | 
				
			||||||
 | 
					msgid "Current Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/change_password.html:12
 | 
				
			||||||
 | 
					msgid "New Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/change_password.html:16
 | 
				
			||||||
 | 
					msgid "Confirm New Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/edit_game.html:4
 | 
				
			||||||
 | 
					msgid "Edit Game"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/edit_game.html:17
 | 
				
			||||||
 | 
					msgid "Steam AppID (optional)"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/edit_game.html:47
 | 
				
			||||||
 | 
					msgid "Active Redeem Link"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/edit_game.html:54
 | 
				
			||||||
 | 
					msgid "Expires at"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/import.html:4
 | 
				
			||||||
 | 
					msgid "Import Games"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/import.html:8
 | 
				
			||||||
 | 
					msgid "CSV-Datei auswählen"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/import.html:11
 | 
				
			||||||
 | 
					msgid "Importieren"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/import.html:12
 | 
				
			||||||
 | 
					msgid "Abbrechen"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:4
 | 
				
			||||||
 | 
					msgid "My Games"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:6
 | 
				
			||||||
 | 
					msgid "Export CSV"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:8
 | 
				
			||||||
 | 
					msgid "Import CSV"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:18
 | 
				
			||||||
 | 
					msgid "Cover"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:20
 | 
				
			||||||
 | 
					msgid "Key"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:22
 | 
				
			||||||
 | 
					msgid "Created"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:24 templates/index.html:56
 | 
				
			||||||
 | 
					msgid "Shop"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:25
 | 
				
			||||||
 | 
					msgid "Actions"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:63
 | 
				
			||||||
 | 
					msgid "Generate redeem link"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:70
 | 
				
			||||||
 | 
					msgid "Really delete?"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:96
 | 
				
			||||||
 | 
					msgid "Redeem link copied to clipboard!"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:100
 | 
				
			||||||
 | 
					msgid "Error generating link"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:106
 | 
				
			||||||
 | 
					msgid "No games yet"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/login.html:8 templates/login.html:19
 | 
				
			||||||
 | 
					msgid "Login"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/login.html:12 templates/register.html:11
 | 
				
			||||||
 | 
					msgid "Username"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/login.html:16 templates/register.html:15
 | 
				
			||||||
 | 
					msgid "Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/login.html:22
 | 
				
			||||||
 | 
					msgid "No account yet? Register"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/redeem.html:16
 | 
				
			||||||
 | 
					msgid "Your Key:"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/redeem.html:22
 | 
				
			||||||
 | 
					msgid "Redeem now on"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/redeem.html:26
 | 
				
			||||||
 | 
					msgid "This page will expire in"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/register.html:7 templates/register.html:18
 | 
				
			||||||
 | 
					msgid "Register"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										287
									
								
								translations/en/LC_MESSAGES/messages.po
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								translations/en/LC_MESSAGES/messages.po
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,287 @@
 | 
				
			||||||
 | 
					# English translations for PROJECT.
 | 
				
			||||||
 | 
					# Copyright (C) 2025 ORGANIZATION
 | 
				
			||||||
 | 
					# This file is distributed under the same license as the PROJECT project.
 | 
				
			||||||
 | 
					# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					"Project-Id-Version: PROJECT VERSION\n"
 | 
				
			||||||
 | 
					"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
 | 
				
			||||||
 | 
					"POT-Creation-Date: 2025-04-26 11:13+0000\n"
 | 
				
			||||||
 | 
					"PO-Revision-Date: 2025-04-26 11:13+0000\n"
 | 
				
			||||||
 | 
					"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
				
			||||||
 | 
					"Language: en\n"
 | 
				
			||||||
 | 
					"Language-Team: en <LL@li.org>\n"
 | 
				
			||||||
 | 
					"Plural-Forms: nplurals=2; plural=(n != 1);\n"
 | 
				
			||||||
 | 
					"MIME-Version: 1.0\n"
 | 
				
			||||||
 | 
					"Content-Type: text/plain; charset=utf-8\n"
 | 
				
			||||||
 | 
					"Content-Transfer-Encoding: 8bit\n"
 | 
				
			||||||
 | 
					"Generated-By: Babel 2.17.0\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:187
 | 
				
			||||||
 | 
					msgid "Invalid credentials"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:193
 | 
				
			||||||
 | 
					msgid "Registrierungen sind deaktiviert"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:201
 | 
				
			||||||
 | 
					msgid "Username already exists"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:227
 | 
				
			||||||
 | 
					msgid "Aktuelles Passwort ist falsch"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:231
 | 
				
			||||||
 | 
					msgid "Neue Passwörter stimmen nicht überein"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:236
 | 
				
			||||||
 | 
					msgid "Passwort erfolgreich geändert"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:266
 | 
				
			||||||
 | 
					msgid "Game added successfully!"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:271
 | 
				
			||||||
 | 
					msgid "Steam Key already exists!"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:274 app.py:318
 | 
				
			||||||
 | 
					msgid "Error: "
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:313
 | 
				
			||||||
 | 
					msgid "Changes saved!"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:401
 | 
				
			||||||
 | 
					msgid "Game List (without Keys)"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:494
 | 
				
			||||||
 | 
					#, python-format
 | 
				
			||||||
 | 
					msgid "%(new)d neue Spiele importiert, %(dup)d Duplikate übersprungen"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:498
 | 
				
			||||||
 | 
					#, python-format
 | 
				
			||||||
 | 
					msgid "Importfehler: %(error)s"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:502
 | 
				
			||||||
 | 
					msgid "Bitte eine gültige CSV-Datei hochladen."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:4 templates/index.html:9
 | 
				
			||||||
 | 
					msgid "Add New Game"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:9 templates/edit_game.html:9 templates/index.html:19
 | 
				
			||||||
 | 
					msgid "Name"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:13 templates/edit_game.html:13
 | 
				
			||||||
 | 
					msgid "Game Key"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:17 templates/edit_game.html:21
 | 
				
			||||||
 | 
					#: templates/index.html:21
 | 
				
			||||||
 | 
					msgid "Status"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:19 templates/edit_game.html:23
 | 
				
			||||||
 | 
					#: templates/index.html:41
 | 
				
			||||||
 | 
					msgid "Not redeemed"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:20 templates/edit_game.html:24
 | 
				
			||||||
 | 
					#: templates/index.html:43
 | 
				
			||||||
 | 
					msgid "Gifted"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:21 templates/edit_game.html:25
 | 
				
			||||||
 | 
					#: templates/index.html:45
 | 
				
			||||||
 | 
					msgid "Redeemed"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:25 templates/edit_game.html:29
 | 
				
			||||||
 | 
					#: templates/index.html:23
 | 
				
			||||||
 | 
					msgid "Redeem by"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:29 templates/edit_game.html:33
 | 
				
			||||||
 | 
					msgid "Recipient"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:33 templates/edit_game.html:37
 | 
				
			||||||
 | 
					msgid "Shop URL"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:37 templates/edit_game.html:41
 | 
				
			||||||
 | 
					msgid "Notes"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:41 templates/edit_game.html:60
 | 
				
			||||||
 | 
					msgid "Save"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:42 templates/edit_game.html:61
 | 
				
			||||||
 | 
					msgid "Cancel"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:7
 | 
				
			||||||
 | 
					msgid "Game Key Manager"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:23
 | 
				
			||||||
 | 
					msgid "Search"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:31
 | 
				
			||||||
 | 
					msgid "Dark Mode"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:44
 | 
				
			||||||
 | 
					msgid "Passwort"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:47
 | 
				
			||||||
 | 
					msgid "Logout"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/change_password.html:4 templates/change_password.html:19
 | 
				
			||||||
 | 
					msgid "Change Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/change_password.html:8
 | 
				
			||||||
 | 
					msgid "Current Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/change_password.html:12
 | 
				
			||||||
 | 
					msgid "New Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/change_password.html:16
 | 
				
			||||||
 | 
					msgid "Confirm New Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/edit_game.html:4
 | 
				
			||||||
 | 
					msgid "Edit Game"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/edit_game.html:17
 | 
				
			||||||
 | 
					msgid "Steam AppID (optional)"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/edit_game.html:47
 | 
				
			||||||
 | 
					msgid "Active Redeem Link"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/edit_game.html:54
 | 
				
			||||||
 | 
					msgid "Expires at"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/import.html:4
 | 
				
			||||||
 | 
					msgid "Import Games"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/import.html:8
 | 
				
			||||||
 | 
					msgid "CSV-Datei auswählen"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/import.html:11
 | 
				
			||||||
 | 
					msgid "Importieren"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/import.html:12
 | 
				
			||||||
 | 
					msgid "Abbrechen"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:4
 | 
				
			||||||
 | 
					msgid "My Games"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:6
 | 
				
			||||||
 | 
					msgid "Export CSV"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:8
 | 
				
			||||||
 | 
					msgid "Import CSV"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:18
 | 
				
			||||||
 | 
					msgid "Cover"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:20
 | 
				
			||||||
 | 
					msgid "Key"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:22
 | 
				
			||||||
 | 
					msgid "Created"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:24 templates/index.html:56
 | 
				
			||||||
 | 
					msgid "Shop"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:25
 | 
				
			||||||
 | 
					msgid "Actions"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:63
 | 
				
			||||||
 | 
					msgid "Generate redeem link"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:70
 | 
				
			||||||
 | 
					msgid "Really delete?"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:96
 | 
				
			||||||
 | 
					msgid "Redeem link copied to clipboard!"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:100
 | 
				
			||||||
 | 
					msgid "Error generating link"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:106
 | 
				
			||||||
 | 
					msgid "No games yet"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/login.html:8 templates/login.html:19
 | 
				
			||||||
 | 
					msgid "Login"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/login.html:12 templates/register.html:11
 | 
				
			||||||
 | 
					msgid "Username"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/login.html:16 templates/register.html:15
 | 
				
			||||||
 | 
					msgid "Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/login.html:22
 | 
				
			||||||
 | 
					msgid "No account yet? Register"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/redeem.html:16
 | 
				
			||||||
 | 
					msgid "Your Key:"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/redeem.html:22
 | 
				
			||||||
 | 
					msgid "Redeem now on"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/redeem.html:26
 | 
				
			||||||
 | 
					msgid "This page will expire in"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/register.html:7 templates/register.html:18
 | 
				
			||||||
 | 
					msgid "Register"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										286
									
								
								translations/messages.pot
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								translations/messages.pot
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,286 @@
 | 
				
			||||||
 | 
					# Translations template for PROJECT.
 | 
				
			||||||
 | 
					# Copyright (C) 2025 ORGANIZATION
 | 
				
			||||||
 | 
					# This file is distributed under the same license as the PROJECT project.
 | 
				
			||||||
 | 
					# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#, fuzzy
 | 
				
			||||||
 | 
					msgid ""
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					"Project-Id-Version: PROJECT VERSION\n"
 | 
				
			||||||
 | 
					"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
 | 
				
			||||||
 | 
					"POT-Creation-Date: 2025-04-26 11:13+0000\n"
 | 
				
			||||||
 | 
					"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 | 
				
			||||||
 | 
					"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
				
			||||||
 | 
					"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
				
			||||||
 | 
					"MIME-Version: 1.0\n"
 | 
				
			||||||
 | 
					"Content-Type: text/plain; charset=utf-8\n"
 | 
				
			||||||
 | 
					"Content-Transfer-Encoding: 8bit\n"
 | 
				
			||||||
 | 
					"Generated-By: Babel 2.17.0\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:187
 | 
				
			||||||
 | 
					msgid "Invalid credentials"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:193
 | 
				
			||||||
 | 
					msgid "Registrierungen sind deaktiviert"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:201
 | 
				
			||||||
 | 
					msgid "Username already exists"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:227
 | 
				
			||||||
 | 
					msgid "Aktuelles Passwort ist falsch"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:231
 | 
				
			||||||
 | 
					msgid "Neue Passwörter stimmen nicht überein"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:236
 | 
				
			||||||
 | 
					msgid "Passwort erfolgreich geändert"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:266
 | 
				
			||||||
 | 
					msgid "Game added successfully!"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:271
 | 
				
			||||||
 | 
					msgid "Steam Key already exists!"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:274 app.py:318
 | 
				
			||||||
 | 
					msgid "Error: "
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:313
 | 
				
			||||||
 | 
					msgid "Changes saved!"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:401
 | 
				
			||||||
 | 
					msgid "Game List (without Keys)"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:494
 | 
				
			||||||
 | 
					#, python-format
 | 
				
			||||||
 | 
					msgid "%(new)d neue Spiele importiert, %(dup)d Duplikate übersprungen"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:498
 | 
				
			||||||
 | 
					#, python-format
 | 
				
			||||||
 | 
					msgid "Importfehler: %(error)s"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: app.py:502
 | 
				
			||||||
 | 
					msgid "Bitte eine gültige CSV-Datei hochladen."
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:4 templates/index.html:9
 | 
				
			||||||
 | 
					msgid "Add New Game"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:9 templates/edit_game.html:9 templates/index.html:19
 | 
				
			||||||
 | 
					msgid "Name"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:13 templates/edit_game.html:13
 | 
				
			||||||
 | 
					msgid "Game Key"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:17 templates/edit_game.html:21
 | 
				
			||||||
 | 
					#: templates/index.html:21
 | 
				
			||||||
 | 
					msgid "Status"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:19 templates/edit_game.html:23
 | 
				
			||||||
 | 
					#: templates/index.html:41
 | 
				
			||||||
 | 
					msgid "Not redeemed"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:20 templates/edit_game.html:24
 | 
				
			||||||
 | 
					#: templates/index.html:43
 | 
				
			||||||
 | 
					msgid "Gifted"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:21 templates/edit_game.html:25
 | 
				
			||||||
 | 
					#: templates/index.html:45
 | 
				
			||||||
 | 
					msgid "Redeemed"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:25 templates/edit_game.html:29
 | 
				
			||||||
 | 
					#: templates/index.html:23
 | 
				
			||||||
 | 
					msgid "Redeem by"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:29 templates/edit_game.html:33
 | 
				
			||||||
 | 
					msgid "Recipient"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:33 templates/edit_game.html:37
 | 
				
			||||||
 | 
					msgid "Shop URL"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:37 templates/edit_game.html:41
 | 
				
			||||||
 | 
					msgid "Notes"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:41 templates/edit_game.html:60
 | 
				
			||||||
 | 
					msgid "Save"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/add_game.html:42 templates/edit_game.html:61
 | 
				
			||||||
 | 
					msgid "Cancel"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:7
 | 
				
			||||||
 | 
					msgid "Game Key Manager"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:23
 | 
				
			||||||
 | 
					msgid "Search"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:31
 | 
				
			||||||
 | 
					msgid "Dark Mode"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:44
 | 
				
			||||||
 | 
					msgid "Passwort"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/base.html:47
 | 
				
			||||||
 | 
					msgid "Logout"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/change_password.html:4 templates/change_password.html:19
 | 
				
			||||||
 | 
					msgid "Change Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/change_password.html:8
 | 
				
			||||||
 | 
					msgid "Current Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/change_password.html:12
 | 
				
			||||||
 | 
					msgid "New Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/change_password.html:16
 | 
				
			||||||
 | 
					msgid "Confirm New Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/edit_game.html:4
 | 
				
			||||||
 | 
					msgid "Edit Game"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/edit_game.html:17
 | 
				
			||||||
 | 
					msgid "Steam AppID (optional)"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/edit_game.html:47
 | 
				
			||||||
 | 
					msgid "Active Redeem Link"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/edit_game.html:54
 | 
				
			||||||
 | 
					msgid "Expires at"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/import.html:4
 | 
				
			||||||
 | 
					msgid "Import Games"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/import.html:8
 | 
				
			||||||
 | 
					msgid "CSV-Datei auswählen"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/import.html:11
 | 
				
			||||||
 | 
					msgid "Importieren"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/import.html:12
 | 
				
			||||||
 | 
					msgid "Abbrechen"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:4
 | 
				
			||||||
 | 
					msgid "My Games"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:6
 | 
				
			||||||
 | 
					msgid "Export CSV"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:8
 | 
				
			||||||
 | 
					msgid "Import CSV"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:18
 | 
				
			||||||
 | 
					msgid "Cover"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:20
 | 
				
			||||||
 | 
					msgid "Key"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:22
 | 
				
			||||||
 | 
					msgid "Created"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:24 templates/index.html:56
 | 
				
			||||||
 | 
					msgid "Shop"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:25
 | 
				
			||||||
 | 
					msgid "Actions"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:63
 | 
				
			||||||
 | 
					msgid "Generate redeem link"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:70
 | 
				
			||||||
 | 
					msgid "Really delete?"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:96
 | 
				
			||||||
 | 
					msgid "Redeem link copied to clipboard!"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:100
 | 
				
			||||||
 | 
					msgid "Error generating link"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/index.html:106
 | 
				
			||||||
 | 
					msgid "No games yet"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/login.html:8 templates/login.html:19
 | 
				
			||||||
 | 
					msgid "Login"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/login.html:12 templates/register.html:11
 | 
				
			||||||
 | 
					msgid "Username"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/login.html:16 templates/register.html:15
 | 
				
			||||||
 | 
					msgid "Password"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/login.html:22
 | 
				
			||||||
 | 
					msgid "No account yet? Register"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/redeem.html:16
 | 
				
			||||||
 | 
					msgid "Your Key:"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/redeem.html:22
 | 
				
			||||||
 | 
					msgid "Redeem now on"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/redeem.html:26
 | 
				
			||||||
 | 
					msgid "This page will expire in"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#: templates/register.html:7 templates/register.html:18
 | 
				
			||||||
 | 
					msgid "Register"
 | 
				
			||||||
 | 
					msgstr ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										22
									
								
								upgrade.sh
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								upgrade.sh
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,22 @@
 | 
				
			||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					set -e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Setze das Arbeitsverzeichnis auf das Projektverzeichnis
 | 
				
			||||||
 | 
					cd "$(dirname "$0")/steam-gift-manager"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Setze FLASK_APP, falls nötig
 | 
				
			||||||
 | 
					export FLASK_APP=app.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Initialisiere migrations, falls noch nicht vorhanden
 | 
				
			||||||
 | 
					if [ ! -d migrations ]; then
 | 
				
			||||||
 | 
					  echo "Starting Flask-Migrate..."
 | 
				
			||||||
 | 
					  docker-compose exec steam-manager flask db init
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Erzeuge Migration (nur wenn sich Modelle geändert haben)
 | 
				
			||||||
 | 
					docker-compose exec steam-manager flask db migrate -m "Automatic Migration"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Wende Migration an
 | 
				
			||||||
 | 
					docker-compose exec steam-manager flask db upgrade
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo "✅ Database-Migration abgeschlossen!"
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue