Compare commits

..

3 commits

Author SHA1 Message Date
dff44f40c1 fetch game pic and Steam AppID 2025-04-21 11:42:20 +00:00
8bc257aec6 new setup.sh 2025-04-21 11:42:20 +00:00
11760988ee first commit - start of project 2025-04-21 11:42:20 +00:00
2 changed files with 764 additions and 0 deletions

71
README.md Normal file
View file

@ -0,0 +1,71 @@
# 🗝️ Game Key Management System 🔑
![Project Logo - ideally a nerdy picture of keys or a gamepad]
**Welcome!** 👋
This project helps you keep track of your collected game keys. Does this sound familiar? You have a key here, a key there, from Humble Bundle, Fanatical, or just given to you, but you can't remember if you redeemed it, gifted it, or if it's still lurking somewhere? Don't panic, this is the solution!
## ✨ Features ✨
* **Key Management:** Enter your game keys, along with the corresponding game, platform (Steam, GOG, etc.) and where you got the key.
* **Status Tracking:** Mark keys as "Redeemed", "Gifted" or "Available". So you always know where you stand.
* **Clear Database:** All your keys in one place, easily searchable and sortable.
* **(Planned Features):**
* Import/Export of Keys (CSV, JSON)
* Integration with Steam API (to automatically check if a key has already been redeemed)
* Dark Mode! (Because who doesn't love Dark Mode?)
## 🚀 Get Started! 🚀
1. **Clone the Repository:**
```bash
git clone [Repository URL]
cd [Project Directory]
```
2. **Installation (if required - e.g. for a web application):**
* Describe the necessary installation steps here. Example:
```bash
npm install # Or yarn install, whatever you prefer!
```
3. **Configuration:**
* Explain here which configuration steps are necessary (e.g. setting up a database connection).
* Example: Create a `.env` file and enter your database access data.
4. **Start the Application:**
```bash
npm start # Or however the application is started
```
5. **Use the Application!** Open your browser and go to `http://localhost:[Port]` (or wherever your application is running).
## 🛠️ Technology Stack 🛠️
* **Frontend:** [Enter the frontend technology here - e.g. React, Vue.js, Angular]
* **Backend:** [Enter the backend technology here - e.g. Node.js, Python/Flask, Java/Spring]
* **Database:** [Enter the database here - e.g. PostgreSQL, MySQL, SQLite]
## 🙌 Contribute! 🙌
This project is open source and thrives on your help! If you find bugs, have suggestions, or want to contribute code yourself, you are welcome!
* **Bug Reports:** Please report bugs as Issues.
* **Feature Requests:** Suggest new features!
* **Pull Requests:** Submit your code changes!
Before contributing code, please read the [CONTRIBUTING.md](CONTRIBUTING.md) file.
## 📜 License 📜
This project is licensed under the [MIT License](LICENSE).
## 💖 Acknowledgements 💖
A big thank you to everyone who supports and contributes to this project!

693
setup.sh Normal file
View file

@ -0,0 +1,693 @@
#!/bin/bash
set -e
# Konfiguration
PROJECT_DIR="steam-gift-manager"
TRANSLATIONS_DIR="$PWD/steam-translations"
DATA_DIR="$PWD/data"
# 1. Projektordner & Übersetzungsordner erstellen
mkdir -p $PROJECT_DIR/{templates,static}
mkdir -p $TRANSLATIONS_DIR
mkdir -p $DATA_DIR
cd $PROJECT_DIR
# 2. requirements.txt
cat <<EOL > requirements.txt
flask
flask-login
werkzeug
python-dotenv
flask-sqlalchemy
flask-babel
jinja2<3.1.0
EOL
# 3. app.py (angepasst für SQLAlchemy 2.x)
cat <<'PYTHON_END' > app.py
from flask import Flask, render_template, request, redirect, url_for, flash, make_response, session, abort
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from flask_babel import Babel, _
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
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)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
babel = Babel(app)
@babel.localeselector
def get_locale():
if 'lang' in session and session['lang'] in app.config['BABEL_SUPPORTED_LOCALES']:
return session['lang']
return request.accept_languages.best_match(app.config['BABEL_SUPPORTED_LOCALES'])
@app.context_processor
def inject_template_vars():
return dict(
get_locale=get_locale,
theme='dark' if request.cookies.get('dark_mode') == 'true' else 'light'
)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(100), unique=True)
password = db.Column(db.String(100))
games = db.relationship('Game', backref='owner', lazy=True)
class Game(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
steam_key = db.Column(db.String(100), nullable=False)
status = db.Column(db.String(50), nullable=False)
recipient = db.Column(db.String(100))
notes = db.Column(db.Text)
url = db.Column(db.String(200))
created_at = db.Column(db.DateTime, default=datetime.utcnow)
redeem_date = db.Column(db.DateTime)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
steam_appid = db.Column(db.String(20))
@login_manager.user_loader
def load_user(user_id):
return db.session.get(User, int(user_id))
@app.route('/')
@login_required
def index():
search_query = request.args.get('q', '')
query = db.session.query(Game).filter_by(user_id=current_user.id)
if search_query:
query = query.filter(Game.name.ilike(f'%{search_query}%'))
games = query.order_by(Game.created_at.desc()).all()
return render_template('index.html',
games=games,
format_date=lambda dt: dt.strftime('%d.%m.%Y') if dt else '',
search_query=search_query)
@app.route('/set-lang/<lang>')
def set_lang(lang):
if lang in app.config['BABEL_SUPPORTED_LOCALES']:
session['lang'] = lang
return redirect(request.referrer or url_for('index'))
@app.route('/set-theme/<theme>')
def set_theme(theme):
resp = make_response('', 204)
resp.set_cookie('dark_mode', 'true' if theme == 'dark' else 'false', max_age=60*60*24*365)
return resp
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password, password):
login_user(user)
return redirect(url_for('index'))
flash(_('Invalid credentials'), 'danger')
return render_template('login.html')
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
password = generate_password_hash(request.form['password'])
if User.query.filter_by(username=username).first():
flash(_('Username already exists'), 'danger')
return redirect(url_for('register'))
new_user = User(username=username, password=password)
db.session.add(new_user)
db.session.commit()
login_user(new_user)
return redirect(url_for('index'))
return render_template('register.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))
import re
def extract_steam_appid(url):
match = re.search(r'store\.steampowered\.com/app/(\d+)', url or '')
if match:
return match.group(1)
return ''
@app.route('/add', methods=['GET', 'POST'])
@login_required
def add_game():
if request.method == 'POST':
try:
url = request.form.get('url', '')
steam_appid = request.form.get('steam_appid', '').strip()
if not steam_appid:
steam_appid = extract_steam_appid(url)
new_game = Game(
name=request.form['name'],
steam_key=request.form['steam_key'],
status=request.form['status'],
recipient=request.form.get('recipient', ''),
notes=request.form.get('notes', ''),
url=url,
steam_appid=steam_appid, # <- jetzt wird sie gesetzt!
redeem_date=datetime.strptime(request.form['redeem_date'], '%Y-%m-%d') if request.form['redeem_date'] else None,
user_id=current_user.id
)
db.session.add(new_game)
db.session.commit()
flash(_('Game added successfully!'), 'success')
return redirect(url_for('index'))
except Exception as e:
db.session.rollback()
flash(_('Error: ') + str(e), 'danger')
return render_template('add_game.html')
@app.route('/edit/<int:game_id>', methods=['GET', 'POST'])
@login_required
def edit_game(game_id):
game = db.session.get(Game, game_id) # SQLAlchemy 2.x-kompatibel
if not game or game.owner != current_user:
return _("Not allowed!"), 403
if request.method == 'POST':
try:
# Steam AppID aus Formular oder URL extrahieren
url = request.form.get('url', '')
steam_appid = request.form.get('steam_appid', '').strip()
if not steam_appid:
steam_appid = extract_steam_appid(url)
# Aktualisiere alle Felder
game.name = request.form['name']
game.steam_key = request.form['steam_key']
game.status = request.form['status']
game.recipient = request.form.get('recipient', '')
game.notes = request.form.get('notes', '')
game.url = url
game.steam_appid = steam_appid # <- FEHLTE HIER
game.redeem_date = datetime.strptime(request.form['redeem_date'], '%Y-%m-%d') if request.form['redeem_date'] else None
db.session.commit()
flash(_('Changes saved!'), 'success')
return redirect(url_for('index'))
except Exception as e:
db.session.rollback()
flash(_('Error: ') + str(e), 'danger')
return render_template('edit_game.html',
game=game,
redeem_date=game.redeem_date.strftime('%Y-%m-%d') if game.redeem_date else '')
@app.route('/delete/<int:game_id>', methods=['POST'])
@login_required
def delete_game(game_id):
game = Game.query.get_or_404(game_id)
if game.owner != current_user:
return _("Not allowed!"), 403
try:
db.session.delete(game)
db.session.commit()
flash(_('Game deleted!'), 'success')
except Exception as e:
db.session.rollback()
flash(_('Error deleting: ') + str(e), 'danger')
return redirect(url_for('index'))
if __name__ == '__main__':
with app.app_context():
db.create_all()
app.run(host='0.0.0.0', port=5000)
PYTHON_END
# 4. babel.cfg
cat <<EOL > babel.cfg
[python: **.py]
[jinja2: templates/**.html]
EOL
# 5. Dockerfile
cat <<DOCKER_END > Dockerfile
FROM python:3.10-slim
# Shell explizit setzen
SHELL ["/bin/bash", "-c"]
# Datenbankordner erstellen und Berechtigungen setzen
RUN mkdir -p /app/data && chmod -R a+rwX /app/data
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ARG UID=1000
ARG GID=1000
RUN groupadd -g \$GID appuser && \
useradd -u \$UID -g \$GID -m appuser && \
chown -R appuser:appuser /app
USER appuser
EXPOSE 5000
CMD ["python", "app.py"]
DOCKER_END
# 6. docker-compose.yml
cat <<COMPOSE_END > docker-compose.yml
version: '3.8'
services:
steam-manager:
build: .
ports:
- "5000:5000"
volumes:
- $DATA_DIR:/app/data
- $TRANSLATIONS_DIR:/app/translations
environment:
- FLASK_DEBUG=0
restart: unless-stopped
COMPOSE_END
# 7. Verzeichnisse und Berechtigungen
mkdir -p ../data ../steam-translations
chmod -R a+rwX ../data ../steam-translations
# 7. Übersetzungs-Workflow-Script
cat <<'SCRIPT_END' > ../translate.sh
#!/bin/bash
set -e
cd "$(dirname "$0")/steam-gift-manager"
# 1. Extrahiere alle Texte
docker-compose exec steam-manager pybabel extract -F babel.cfg -o translations/messages.pot .
# 2. Initialisiere Sprachen (nur einmal nötig, danach auskommentieren)
for lang in de en; do
if [ ! -f "../steam-translations/$lang/LC_MESSAGES/messages.po" ]; then
docker-compose exec steam-manager pybabel init -i translations/messages.pot -d translations -l $lang
fi
done
# 3. Aktualisiere Übersetzungen
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
echo "✅ Übersetzungen extrahiert, aktualisiert und kompiliert!"
SCRIPT_END
chmod +x ../translate.sh
cat <<HTML_END > templates/base.html
<!DOCTYPE html>
<html lang="{{ get_locale() }}" data-bs-theme="{{ theme }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ _('Steam Manager') }}</title>
<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') }}">
</head>
<body>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container">
<a class="navbar-brand" href="/">{{ _('Steam Manager') }}</a>
<div class="d-flex align-items-center gap-3">
<form class="d-flex" action="{{ url_for('index') }}" method="GET">
<input class="form-control me-2"
type="search"
name="q"
placeholder="{{ _('Search') }}"
value="{{ search_query }}">
<button class="btn btn-outline-success" type="submit">🔍</button>
</form>
<div class="form-check form-switch">
<input class="form-check-input"
type="checkbox"
id="darkModeSwitch" {% if theme == 'dark' %}checked{% endif %}>
<label class="form-check-label" for="darkModeSwitch">{{ _('Dark Mode') }}</label>
</div>
<!-- Sprachumschalter -->
<div class="dropdown ms-3">
<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 %}
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item {% if get_locale() == 'de' %}active{% endif %}" href="{{ url_for('set_lang', lang='de') }}">
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>
</div>
{% if current_user.is_authenticated %}
<a href="{{ url_for('logout') }}" class="btn btn-danger ms-3">{{ _('Logout') }}</a>
{% endif %}
</div>
</div>
</nav>
<div class="container mt-4">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
const toggle = document.getElementById('darkModeSwitch');
const html = document.documentElement;
toggle.addEventListener('change', function() {
const theme = this.checked ? 'dark' : 'light';
fetch('/set-theme/' + theme)
.then(() => {
html.setAttribute('data-bs-theme', theme);
});
});
});
</script>
</body>
</html>
HTML_END
cat <<HTML_END > templates/index.html
{% extends "base.html" %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>{{ _('My Games') }}</h1>
<a href="{{ url_for('add_game') }}" class="btn btn-primary">
+ {{ _('Add New Game') }}
</a>
</div>
{% if games %}
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-dark">
<tr>
<th>{{ _('Cover') }}</th>
<th>{{ _('Name') }}</th>
<th>{{ _('Key') }}</th>
<th>{{ _('Status') }}</th>
<th>{{ _('Created') }}</th>
<th>{{ _('Redeem by') }}</th>
<th>{{ _('Shop') }}</th>
<th>{{ _('Actions') }}</th>
</tr>
</thead>
<tbody>
{% for game in games %}
<tr>
<td>
{% if game.steam_appid %}
<img src="https://cdn.cloudflare.steamstatic.com/steam/apps/{{ game.steam_appid }}/header.jpg"
alt="Steam Header" style="height:64px;max-width:120px;object-fit:cover;">
{% endif %}
</td>
<td>{{ game.name }}</td>
<td class="font-monospace">{{ game.steam_key }}</td>
<td>
{% if game.status == 'nicht eingelöst' %}
<span class="badge bg-warning text-dark">{{ _('Not redeemed') }}</span>
{% elif game.status == 'verschenkt' %}
<span class="badge bg-success">{{ _('Gifted') }}</span>
{% elif game.status == 'eingelöst' %}
<span class="badge bg-secondary">{{ _('Redeemed') }}</span>
{% endif %}
</td>
<td>{{ format_date(game.created_at) }}</td>
<td>
{% if game.redeem_date %}
<span class="badge bg-danger">{{ format_date(game.redeem_date) }}</span>
{% endif %}
</td>
<td>
{% if game.url %}
<a href="{{ game.url }}" target="_blank" class="btn btn-sm btn-outline-info">🔗 {{ _('Shop') }}</a>
{% endif %}
</td>
<td class="text-nowrap">
<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">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('{{ _('Really delete?') }}')">🗑️</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-info">{{ _('No games yet') }}</div>
{% endif %}
{% endblock %}
HTML_END
cat <<HTML_END > templates/add_game.html
{% extends "base.html" %}
{% block content %}
<div class="card p-4 shadow-sm">
<h2 class="mb-4">{{ _('Add New Game') }}</h2>
<form method="POST">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">{{ _('Name') }} *</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="col-md-6">
<label class="form-label">{{ _('Steam Key') }} *</label>
<input type="text" name="steam_key" class="form-control" required>
</div>
<div class="col-md-4">
<label class="form-label">{{ _('Status') }} *</label>
<select name="status" class="form-select" required>
<option value="nicht eingelöst">{{ _('Not redeemed') }}</option>
<option value="verschenkt">{{ _('Gifted') }}</option>
<option value="eingelöst">{{ _('Redeemed') }}</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">{{ _('Redeem by') }}</label>
<input type="date" name="redeem_date" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label">{{ _('Recipient') }}</label>
<input type="text" name="recipient" class="form-control">
</div>
<div class="col-12">
<label class="form-label">{{ _('Shop URL') }}</label>
<input type="url" name="url" class="form-control">
</div>
<div class="col-12">
<label class="form-label">{{ _('Notes') }}</label>
<textarea name="notes" class="form-control" rows="3"></textarea>
</div>
<div class="col-12">
<button type="submit" class="btn btn-success">{{ _('Save') }}</button>
<a href="{{ url_for('index') }}" class="btn btn-outline-secondary">{{ _('Cancel') }}</a>
</div>
</div>
</form>
</div>
{% endblock %}
HTML_END
cat <<HTML_END > templates/edit_game.html
{% extends "base.html" %}
{% block content %}
<div class="card p-4 shadow-sm">
<h2 class="mb-4">{{ _('Edit Game') }}</h2>
<form method="POST">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">{{ _('Name') }} *</label>
<input type="text" name="name" class="form-control" value="{{ game.name }}" required>
</div>
<div class="col-md-6">
<label class="form-label">{{ _('Steam Key') }} *</label>
<input type="text" name="steam_key" class="form-control" value="{{ game.steam_key }}" required>
</div>
<div class="col-md-6">
<label class="form-label">{{ _('Steam AppID (optional)') }}</label>
<input type="text" name="steam_appid" class="form-control" value="{{ game.steam_appid or '' }}">
</div>
<div class="col-md-4">
<label class="form-label">{{ _('Status') }} *</label>
<select name="status" class="form-select" required>
<option value="nicht eingelöst" {% if game.status == 'nicht eingelöst' %}selected{% endif %}>{{ _('Not redeemed') }}</option>
<option value="verschenkt" {% if game.status == 'verschenkt' %}selected{% endif %}>{{ _('Gifted') }}</option>
<option value="eingelöst" {% if game.status == 'eingelöst' %}selected{% endif %}>{{ _('Redeemed') }}</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">{{ _('Redeem by') }}</label>
<input type="date" name="redeem_date" class="form-control" value="{{ redeem_date }}">
</div>
<div class="col-md-4">
<label class="form-label">{{ _('Recipient') }}</label>
<input type="text" name="recipient" class="form-control" value="{{ game.recipient }}">
</div>
<div class="col-12">
<label class="form-label">{{ _('Shop URL') }}</label>
<input type="url" name="url" class="form-control" value="{{ game.url }}">
</div>
<div class="col-12">
<label class="form-label">{{ _('Notes') }}</label>
<textarea name="notes" class="form-control" rows="3">{{ game.notes }}</textarea>
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">{{ _('Save') }}</button>
<a href="{{ url_for('index') }}" class="btn btn-outline-secondary">{{ _('Cancel') }}</a>
</div>
</div>
</form>
</div>
{% endblock %}
HTML_END
cat <<HTML_END > templates/login.html
{% extends "base.html" %}
{% block content %}
<div class="row justify-content-center mt-5">
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-body">
<h2 class="card-title mb-4">{{ _('Login') }}</h2>
<form method="POST">
<div class="mb-3">
<label class="form-label">{{ _('Username') }}</label>
<input type="text" name="username" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">{{ _('Password') }}</label>
<input type="password" name="password" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary w-100">{{ _('Login') }}</button>
</form>
<div class="mt-3 text-center">
<a href="{{ url_for('register') }}">{{ _('No account yet? Register') }}</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
HTML_END
cat <<HTML_END > templates/register.html
{% extends "base.html" %}
{% block content %}
<div class="row justify-content-center mt-5">
<div class="col-md-6">
<div class="card shadow-sm">
<div class="card-body">
<h2 class="card-title mb-4">{{ _('Register') }}</h2>
<form method="POST">
<div class="mb-3">
<label class="form-label">{{ _('Username') }}</label>
<input type="text" name="username" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">{{ _('Password') }}</label>
<input type="password" name="password" class="form-control" required>
</div>
<button type="submit" class="btn btn-primary w-100">{{ _('Register') }}</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
HTML_END
# CSS
cat <<CSS_END > static/style.css
:root {
--bs-body-bg: #ffffff;
--bs-body-color: #212529;
}
[data-bs-theme="dark"] {
--bs-body-bg: #1a1a1a;
--bs-body-color: #f8f9fa;
--bs-border-color: #495057;
}
[data-bs-theme="dark"] .table {
--bs-table-bg: #212529;
--bs-table-color: #fff;
--bs-table-border-color: #495057;
}
[data-bs-theme="dark"] .card {
background-color: #2b3035;
border-color: var(--bs-border-color);
}
[data-bs-theme="dark"] .navbar {
background-color: #212529 !important;
}
body {
background-color: var(--bs-body-bg);
color: var(--bs-body-color);
transition: all 0.3s ease;
}
.font-monospace {
font-family: Monaco, Consolas, "Courier New", monospace;
}
.badge {
font-size: 0.9em;
font-weight: 500;
}
CSS_END
echo -e "\n\033[1;32m✅ Setup abgeschlossen!\033[0m"
echo -e "Starten mit:"
echo -e "cd steam-gift-manager"
echo -e "docker-compose build --no-cache && docker-compose up -d"
echo -e "\n\033[1;34mÜbersetzungen initialisieren, bearbeiten und kompilieren:\033[0m"
echo -e "1. ./translate.sh"
echo -e "2. Bearbeite die .po-Dateien in $TRANSLATIONS_DIR/de/LC_MESSAGES/messages.po und .../en/LC_MESSAGES/messages.po"
echo -e "3. ./translate.sh erneut ausführen und Container neustarten, damit Änderungen aktiv werden"