Refactor translation handling and remove unused language support in templates
This commit is contained in:
+4
-27
@@ -1,16 +1,8 @@
|
|||||||
import gettext
|
import gettext
|
||||||
import os
|
import os
|
||||||
from typing import Dict, List, Callable, Any
|
from fastapi import Request
|
||||||
from fastapi import Request, Depends
|
|
||||||
from babel.support import Translations
|
from babel.support import Translations
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from gettext import gettext as _ # Ensure `_` is imported for translations
|
|
||||||
|
|
||||||
# Define supported languages
|
|
||||||
SUPPORTED_LANGUAGES = {
|
|
||||||
'en': 'English',
|
|
||||||
'de': 'Deutsch',
|
|
||||||
}
|
|
||||||
|
|
||||||
DEFAULT_LANGUAGE = 'de' # Default is German
|
DEFAULT_LANGUAGE = 'de' # Default is German
|
||||||
|
|
||||||
@@ -40,12 +32,12 @@ def get_locale_from_request(request: Request) -> str:
|
|||||||
"""
|
"""
|
||||||
# Check URL parameter
|
# Check URL parameter
|
||||||
lang_param = request.query_params.get('lang')
|
lang_param = request.query_params.get('lang')
|
||||||
if lang_param in SUPPORTED_LANGUAGES:
|
if lang_param:
|
||||||
return lang_param
|
return lang_param
|
||||||
|
|
||||||
# Check session
|
# Check session
|
||||||
session = request.session.get('language')
|
session = request.session.get('language')
|
||||||
if session in SUPPORTED_LANGUAGES:
|
if session:
|
||||||
return session
|
return session
|
||||||
|
|
||||||
# Check Accept-Language header
|
# Check Accept-Language header
|
||||||
@@ -54,21 +46,6 @@ def get_locale_from_request(request: Request) -> str:
|
|||||||
for lang in accept_language.split(','):
|
for lang in accept_language.split(','):
|
||||||
lang_code = lang.split(';')[0].strip().lower()
|
lang_code = lang.split(';')[0].strip().lower()
|
||||||
lang_code = lang_code.split('-')[0] # Convert 'en-US' to 'en'
|
lang_code = lang_code.split('-')[0] # Convert 'en-US' to 'en'
|
||||||
if lang_code in SUPPORTED_LANGUAGES:
|
return lang_code
|
||||||
return lang_code
|
|
||||||
|
|
||||||
return DEFAULT_LANGUAGE
|
return DEFAULT_LANGUAGE
|
||||||
|
|
||||||
def get_translator(locale: str = Depends(get_locale_from_request)):
|
|
||||||
"""
|
|
||||||
Return a FastAPI dependency that provides the translation function
|
|
||||||
"""
|
|
||||||
translations = get_translation(locale)
|
|
||||||
gettext_func = translations.gettext
|
|
||||||
|
|
||||||
# Make the gettext function available with both _ and gettext names
|
|
||||||
return {
|
|
||||||
"_": gettext_func,
|
|
||||||
"gettext": gettext_func,
|
|
||||||
"locale": locale
|
|
||||||
}
|
|
||||||
|
|||||||
+2
-27
@@ -12,7 +12,6 @@ from . import models
|
|||||||
from .templates_config import templates
|
from .templates_config import templates
|
||||||
from .views import qr, redeem, teams, admin, leaderboard, dashboard, static, pages
|
from .views import qr, redeem, teams, admin, leaderboard, dashboard, static, pages
|
||||||
from .db_init import seed_db
|
from .db_init import seed_db
|
||||||
from app.i18n import get_translator, SUPPORTED_LANGUAGES, _ # Ensure `_` is correctly imported
|
|
||||||
|
|
||||||
# Create tables on startup
|
# Create tables on startup
|
||||||
init_db()
|
init_db()
|
||||||
@@ -35,8 +34,6 @@ static.configure_static_files(app)
|
|||||||
|
|
||||||
# Setup Jinja2 templates
|
# Setup Jinja2 templates
|
||||||
templates = Jinja2Templates(directory="app/templates")
|
templates = Jinja2Templates(directory="app/templates")
|
||||||
templates.env.globals["SUPPORTED_LANGUAGES"] = SUPPORTED_LANGUAGES
|
|
||||||
templates.env.globals["_"] = _
|
|
||||||
|
|
||||||
# User context middleware to make template globals available
|
# User context middleware to make template globals available
|
||||||
@app.middleware("http")
|
@app.middleware("http")
|
||||||
@@ -52,35 +49,13 @@ async def add_template_globals(request: Request, call_next):
|
|||||||
response = await call_next(request)
|
response = await call_next(request)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
# Add translation context processor to Jinja templates
|
|
||||||
@app.middleware("http")
|
|
||||||
async def add_translation_context(request: Request, call_next):
|
|
||||||
response = await call_next(request)
|
|
||||||
return response
|
|
||||||
|
|
||||||
@app.get("/", response_class=HTMLResponse)
|
@app.get("/", response_class=HTMLResponse)
|
||||||
async def read_root(request: Request, i18n: dict = Depends(get_translator)):
|
async def read_root(request: Request):
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"index.html",
|
"index.html",
|
||||||
{"request": request, "user": None, **i18n}
|
{"request": request, "user": None}
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.api_route("/set-language/{language_code}", methods=["GET", "POST"])
|
|
||||||
async def set_language(request: Request, language_code: str):
|
|
||||||
if request.method == "POST":
|
|
||||||
form = await request.form()
|
|
||||||
language = form.get("language", "en")
|
|
||||||
else:
|
|
||||||
language = language_code
|
|
||||||
|
|
||||||
if language in SUPPORTED_LANGUAGES:
|
|
||||||
request.session["language"] = language
|
|
||||||
response = RedirectResponse(url=request.headers.get("referer", "/"))
|
|
||||||
response.set_cookie(key="language", value=language, max_age=31536000) # 1 year
|
|
||||||
return response
|
|
||||||
|
|
||||||
return {"message": f"Invalid language code: {language}"}
|
|
||||||
|
|
||||||
# Routers
|
# Routers
|
||||||
app.include_router(pages.router, tags=["Pages"]) # Pages router for index and static pages
|
app.include_router(pages.router, tags=["Pages"]) # Pages router for index and static pages
|
||||||
app.include_router(qr.router, prefix="/qr", tags=["QR"])
|
app.include_router(qr.router, prefix="/qr", tags=["QR"])
|
||||||
|
|||||||
@@ -1,61 +1,29 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mx-auto p-4">
|
<div class="container mx-auto p-4">
|
||||||
<h1 class="text-2xl font-bold text-irish-green mb-4">{{ _("About LeagueLedger") }}</h1>
|
<h1 class="text-2xl font-bold text-irish-green mb-4">About LeagueLedger</h1>
|
||||||
<p class="mb-4">
|
<p class="mb-4">
|
||||||
{% if locale == "de" %}
|
|
||||||
LeagueLedger ist Ihr ultimativer Begleiter für die Verfolgung von Pub-Quiz-Team-Erfolgen. Wir möchten eine unterhaltsame und ansprechende Plattform für Quiz-Enthusiasten bieten, um sich zu vernetzen, zu wetteifern und ihr Wissen zu feiern.
|
|
||||||
{% else %}
|
|
||||||
LeagueLedger is your ultimate companion for tracking pub quiz team achievements. We aim to provide a fun and engaging platform for quiz enthusiasts to connect, compete, and celebrate their knowledge.
|
LeagueLedger is your ultimate companion for tracking pub quiz team achievements. We aim to provide a fun and engaging platform for quiz enthusiasts to connect, compete, and celebrate their knowledge.
|
||||||
{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-4">
|
<p class="mb-4">
|
||||||
{% if locale == "de" %}
|
|
||||||
Als Pub-Quiz-Meister können Sie QR-Codes für Ihre bestplatzierten Teams generieren, diese verteilen und Teams diese auf unserer Website für Punkte einlösen lassen.
|
|
||||||
{% else %}
|
|
||||||
As a pub quiz master, you can generate printout QR codes for your top-ranking teams, distribute them, and let teams redeem them for points on our website.
|
As a pub quiz master, you can generate printout QR codes for your top-ranking teams, distribute them, and let teams redeem them for points on our website.
|
||||||
{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-4">
|
<p class="mb-4">
|
||||||
{% if locale == "de" %}
|
|
||||||
Als Pub-Quiz-Teammitglied können Sie QR-Codes einlösen, einen Teamnamen erstellen, andere Mitglieder einladen und Social-Logins für einen einfachen Zugang nutzen.
|
|
||||||
{% else %}
|
|
||||||
As a pub quiz team member, you can redeem QR codes, create a team name, invite other members, and use social logins for easy access.
|
As a pub quiz team member, you can redeem QR codes, create a team name, invite other members, and use social logins for easy access.
|
||||||
{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
<h2 class="text-xl font-semibold text-irish-green mt-6 mb-2">
|
<h2 class="text-xl font-semibold text-irish-green mt-6 mb-2">Our Mission</h2>
|
||||||
{% if locale == "de" %}Unsere Mission{% else %}Our Mission{% endif %}
|
|
||||||
</h2>
|
|
||||||
<p class="mb-4">
|
<p class="mb-4">
|
||||||
{% if locale == "de" %}
|
|
||||||
Die Pub-Quiz-Erfahrung zu verbessern, indem wir eine nahtlose und intuitive Plattform für die Verfolgung des Teamfortschritts bieten, freundlichen Wettbewerb fördern und den Geist des Quiz feiern.
|
|
||||||
{% else %}
|
|
||||||
To enhance the pub quiz experience by providing a seamless and intuitive platform for tracking team progress, fostering friendly competition, and celebrating the spirit of trivia.
|
To enhance the pub quiz experience by providing a seamless and intuitive platform for tracking team progress, fostering friendly competition, and celebrating the spirit of trivia.
|
||||||
{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
<h2 class="text-xl font-semibold text-irish-green mt-6 mb-2">
|
<h2 class="text-xl font-semibold text-irish-green mt-6 mb-2">Our Team</h2>
|
||||||
{% if locale == "de" %}Unser Team{% else %}Our Team{% endif %}
|
|
||||||
</h2>
|
|
||||||
<p class="mb-4">
|
<p class="mb-4">
|
||||||
{% if locale == "de" %}
|
|
||||||
LeagueLedger ist eine Open-Source-Initiative und Teil der KaufDeinQuiz-Plattform. Es wird von Christian Louis IT Beratung und Medienproduktion entwickelt, unter der Leitung von Christian Krakau-Louis, einem Team von engagierten Quiz-Enthusiasten und Softwareentwicklern, die leidenschaftlich daran arbeiten, innovative Lösungen für die Pub-Quiz-Community zu schaffen.
|
|
||||||
{% else %}
|
|
||||||
LeagueLedger is an Open-Source initiative and part of the KaufDeinQuiz platform. It is brought to you by Christian Louis IT Beratung und Medienproduktion, led by Christian Krakau-Louis, a team of dedicated quiz enthusiasts and software developers passionate about creating innovative solutions for the pub quiz community.
|
LeagueLedger is an Open-Source initiative and part of the KaufDeinQuiz platform. It is brought to you by Christian Louis IT Beratung und Medienproduktion, led by Christian Krakau-Louis, a team of dedicated quiz enthusiasts and software developers passionate about creating innovative solutions for the pub quiz community.
|
||||||
{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
<h2 class="text-xl font-semibold text-irish-green mt-6 mb-2">
|
<h2 class="text-xl font-semibold text-irish-green mt-6 mb-2">License</h2>
|
||||||
{% if locale == "de" %}Lizenz{% else %}License{% endif %}
|
|
||||||
</h2>
|
|
||||||
<p class="mb-4">
|
<p class="mb-4">
|
||||||
{% if locale == "de" %}
|
|
||||||
LeagueLedger ist unter der Apache License 2.0 lizenziert. Die vollständige Lizenz finden Sie in unserer GitHub-Repository oder auf Anfrage.
|
|
||||||
{% else %}
|
|
||||||
LeagueLedger is licensed under the Apache License 2.0. You can find the full license in our GitHub repository or upon request.
|
LeagueLedger is licensed under the Apache License 2.0. You can find the full license in our GitHub repository or upon request.
|
||||||
{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="mt-8 text-sm text-gray-600">
|
<p class="mt-8 text-sm text-gray-600">Last updated: April 2025</p>
|
||||||
{% if locale == "de" %}Letzte Aktualisierung: April 2025{% else %}Last updated: April 2025{% endif %}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
+39
-76
@@ -1,9 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ locale }}">
|
<html lang="en">
|
||||||
<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>{% block title %}LeagueLedger - Pub Quiz Tracking{% endblock %}</title>
|
<title>LeagueLedger - Pub Quiz Tracking</title>
|
||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
|
|
||||||
@@ -65,26 +65,10 @@
|
|||||||
|
|
||||||
<!-- Desktop Navigation -->
|
<!-- Desktop Navigation -->
|
||||||
<div class="hidden md:flex space-x-6 items-center">
|
<div class="hidden md:flex space-x-6 items-center">
|
||||||
<a href="/" class="hover:text-golden-ale transition">{{ _("Home") }}</a>
|
<a href="/" class="hover:text-golden-ale transition">Home</a>
|
||||||
<a href="/teams" class="hover:text-golden-ale transition">{{ _("Teams") }}</a>
|
<a href="/teams" class="hover:text-golden-ale transition">Teams</a>
|
||||||
<a href="/leaderboard" class="hover:text-golden-ale transition">{{ _("Leaderboard") }}</a>
|
<a href="/leaderboard" class="hover:text-golden-ale transition">Leaderboard</a>
|
||||||
<a href="/scan" class="hover:text-golden-ale transition">{{ _("Scan QR Code") }}</a>
|
<a href="/scan" class="hover:text-golden-ale transition">Scan QR Code</a>
|
||||||
|
|
||||||
<!-- Language Selector -->
|
|
||||||
<div class="relative dropdown">
|
|
||||||
<button class="dropdown-toggle flex items-center space-x-1 hover:text-golden-ale transition">
|
|
||||||
<i class="fas fa-globe text-lg"></i>
|
|
||||||
<span>{{ SUPPORTED_LANGUAGES[locale] }}</span>
|
|
||||||
<i class="fas fa-chevron-down text-xs"></i>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50">
|
|
||||||
{% for code, name in SUPPORTED_LANGUAGES.items() %}
|
|
||||||
<a href="/set-language/{{ code }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 {% if locale == code %}font-bold{% endif %}">
|
|
||||||
{{ name }}
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if user %}
|
{% if user %}
|
||||||
<div class="relative dropdown">
|
<div class="relative dropdown">
|
||||||
@@ -95,43 +79,29 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50">
|
<div class="dropdown-menu hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50">
|
||||||
<a href="/profile" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
<a href="/profile" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||||
<i class="fas fa-user mr-2"></i> {{ _("Profile") }}
|
<i class="fas fa-user mr-2"></i> Profile
|
||||||
</a>
|
</a>
|
||||||
<a href="/dashboard" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
<a href="/dashboard" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||||
<i class="fas fa-tachometer-alt mr-2"></i> {{ _("Dashboard") }}
|
<i class="fas fa-tachometer-alt mr-2"></i> Dashboard
|
||||||
</a>
|
</a>
|
||||||
{% if user.is_admin %}
|
{% if user.is_admin %}
|
||||||
<a href="/admin" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
<a href="/admin" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||||
<i class="fas fa-cog mr-2"></i> {{ _("Admin") }}
|
<i class="fas fa-cog mr-2"></i> Admin
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="border-t border-gray-100 my-1"></div>
|
<div class="border-t border-gray-100 my-1"></div>
|
||||||
<a href="/logout" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
<a href="/logout" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||||
<i class="fas fa-sign-out-alt mr-2"></i> {{ _("Logout") }}
|
<i class="fas fa-sign-out-alt mr-2"></i> Logout
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/login" class="bg-golden-ale hover:bg-opacity-90 text-black-stout px-4 py-2 rounded-md transition">{{ _("Sign In") }}</a>
|
<a href="/login" class="bg-golden-ale hover:bg-opacity-90 text-black-stout px-4 py-2 rounded-md transition">Sign In</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile menu button -->
|
<!-- Mobile menu button -->
|
||||||
<div class="md:hidden flex items-center space-x-4">
|
<div class="md:hidden flex items-center space-x-4">
|
||||||
<!-- Mobile Language Selector -->
|
|
||||||
<div class="relative dropdown">
|
|
||||||
<button class="dropdown-toggle flex items-center hover:text-golden-ale transition">
|
|
||||||
<i class="fas fa-globe text-lg"></i>
|
|
||||||
</button>
|
|
||||||
<div class="dropdown-menu hidden absolute right-0 mt-2 w-32 bg-white rounded-md shadow-lg py-1 z-50">
|
|
||||||
{% for code, name in SUPPORTED_LANGUAGES.items() %}
|
|
||||||
<a href="/set-language/{{ code }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 {% if locale == code %}font-bold{% endif %}">
|
|
||||||
{{ name }}
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="mobile-menu-button" class="text-white hover:text-golden-ale transition">
|
<button id="mobile-menu-button" class="text-white hover:text-golden-ale transition">
|
||||||
<i class="fas fa-bars text-2xl"></i>
|
<i class="fas fa-bars text-2xl"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -141,19 +111,19 @@
|
|||||||
<!-- Mobile Navigation -->
|
<!-- Mobile Navigation -->
|
||||||
<div id="mobile-menu" class="hidden md:hidden mt-3 pb-3 border-t border-irish-green border-opacity-30">
|
<div id="mobile-menu" class="hidden md:hidden mt-3 pb-3 border-t border-irish-green border-opacity-30">
|
||||||
<div class="flex flex-col space-y-2 mt-3">
|
<div class="flex flex-col space-y-2 mt-3">
|
||||||
<a href="/" class="hover:text-golden-ale transition py-2">{{ _("Home") }}</a>
|
<a href="/" class="hover:text-golden-ale transition py-2">Home</a>
|
||||||
<a href="/teams" class="hover:text-golden-ale transition py-2">{{ _("Teams") }}</a>
|
<a href="/teams" class="hover:text-golden-ale transition py-2">Teams</a>
|
||||||
<a href="/leaderboard" class="hover:text-golden-ale transition py-2">{{ _("Leaderboard") }}</a>
|
<a href="/leaderboard" class="hover:text-golden-ale transition py-2">Leaderboard</a>
|
||||||
<a href="/scan" class="hover:text-golden-ale transition py-2">{{ _("Scan QR Code") }}</a>
|
<a href="/scan" class="hover:text-golden-ale transition py-2">Scan QR Code</a>
|
||||||
{% if user %}
|
{% if user %}
|
||||||
<a href="/profile" class="hover:text-golden-ale transition py-2">{{ _("Profile") }}</a>
|
<a href="/profile" class="hover:text-golden-ale transition py-2">Profile</a>
|
||||||
<a href="/dashboard" class="hover:text-golden-ale transition py-2">{{ _("Dashboard") }}</a>
|
<a href="/dashboard" class="hover:text-golden-ale transition py-2">Dashboard</a>
|
||||||
{% if user.is_admin %}
|
{% if user.is_admin %}
|
||||||
<a href="/admin" class="hover:text-golden-ale transition py-2">{{ _("Admin") }}</a>
|
<a href="/admin" class="hover:text-golden-ale transition py-2">Admin</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="/logout" class="hover:text-golden-ale transition py-2">{{ _("Logout") }}</a>
|
<a href="/logout" class="hover:text-golden-ale transition py-2">Logout</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="/login" class="bg-golden-ale hover:bg-opacity-90 text-black-stout px-4 py-2 rounded-md transition text-center">{{ _("Sign In") }}</a>
|
<a href="/login" class="bg-golden-ale hover:bg-opacity-90 text-black-stout px-4 py-2 rounded-md transition text-center">Sign In</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -174,53 +144,46 @@
|
|||||||
<img src="{{ url_for('static', path='images/logos/monogram.png') }}" alt="LeagueLedger Logo" class="h-12">
|
<img src="{{ url_for('static', path='images/logos/monogram.png') }}" alt="LeagueLedger Logo" class="h-12">
|
||||||
<span class="ml-2 text-xl font-bold">LeagueLedger</span>
|
<span class="ml-2 text-xl font-bold">LeagueLedger</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm">{{ _("Track your pub quiz team's progress.") }}<br>{{ _("Scan QR codes to earn points.") }}</p>
|
<p class="text-sm">Track your pub quiz team's progress.<br>Scan QR codes to earn points.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-8">
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-8">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-golden-ale font-bold mb-4">{{ _("Navigation") }}</h3>
|
<h3 class="text-golden-ale font-bold mb-4">Navigation</h3>
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
<li><a href="/" class="hover:text-golden-ale transition">{{ _("Home") }}</a></li>
|
<li><a href="/" class="hover:text-golden-ale transition">Home</a></li>
|
||||||
<li><a href="/teams" class="hover:text-golden-ale transition">{{ _("Teams") }}</a></li>
|
<li><a href="/teams" class="hover:text-golden-ale transition">Teams</a></li>
|
||||||
<li><a href="/leaderboard" class="hover:text-golden-ale transition">{{ _("Leaderboard") }}</a></li>
|
<li><a href="/leaderboard" class="hover:text-golden-ale transition">Leaderboard</a></li>
|
||||||
<li><a href="/scan" class="hover:text-golden-ale transition">{{ _("Scan QR Code") }}</a></li>
|
<li><a href="/scan" class="hover:text-golden-ale transition">Scan QR Code</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-golden-ale font-bold mb-4">{{ _("Account") }}</h3>
|
<h3 class="text-golden-ale font-bold mb-4">Account</h3>
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
<li><a href="/login" class="hover:text-golden-ale transition">{{ _("Sign In") }}</a></li>
|
<li><a href="/login" class="hover:text-golden-ale transition">Sign In</a></li>
|
||||||
<li><a href="/register" class="hover:text-golden-ale transition">{{ _("Register") }}</a></li>
|
<li><a href="/register" class="hover:text-golden-ale transition">Register</a></li>
|
||||||
<li><a href="/profile" class="hover:text-golden-ale transition">{{ _("Profile") }}</a></li>
|
<li><a href="/profile" class="hover:text-golden-ale transition">Profile</a></li>
|
||||||
<li><a href="/dashboard" class="hover:text-golden-ale transition">{{ _("Dashboard") }}</a></li>
|
<li><a href="/dashboard" class="hover:text-golden-ale transition">Dashboard</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-golden-ale font-bold mb-4">{{ _("Legal") }}</h3>
|
<h3 class="text-golden-ale font-bold mb-4">Legal</h3>
|
||||||
<ul class="space-y-2">
|
<ul class="space-y-2">
|
||||||
<li><a href="/about" class="hover:text-golden-ale transition">{{ _("About") }}</a></li>
|
<li><a href="/about" class="hover:text-golden-ale transition">About</a></li>
|
||||||
<li><a href="/contact" class="hover:text-golden-ale transition">{{ _("Contact") }}</a></li>
|
<li><a href="/contact" class="hover:text-golden-ale transition">Contact</a></li>
|
||||||
<li><a href="/terms" class="hover:text-golden-ale transition">{{ _("Terms of Service") }}</a></li>
|
<li><a href="/terms" class="hover:text-golden-ale transition">Terms of Service</a></li>
|
||||||
<li><a href="/privacy" class="hover:text-golden-ale transition">{{ _("Privacy Policy") }}</a></li>
|
<li><a href="/privacy" class="hover:text-golden-ale transition">Privacy Policy</a></li>
|
||||||
<li><a href="/cookies" class="hover:text-golden-ale transition">{{ _("Cookie Policy") }}</a></li>
|
<li><a href="/cookies" class="hover:text-golden-ale transition">Cookie Policy</a></li>
|
||||||
<li><a href="/impressum" class="hover:text-golden-ale transition">{{ _("Imprint") }}</a></li>
|
<li><a href="/impressum" class="hover:text-golden-ale transition">Imprint</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-t border-gray-800 mt-8 pt-8 flex flex-col md:flex-row justify-between items-center">
|
<div class="border-t border-gray-800 mt-8 pt-8 flex flex-col md:flex-row justify-between items-center">
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
<p class="text-sm">© 2025 LeagueLedger. {{ _("Licensed under Apache License 2.0") }}</p>
|
<p class="text-sm">© 2025 LeagueLedger. Licensed under Apache License 2.0</p>
|
||||||
</div>
|
|
||||||
<div class="flex space-x-4 mt-4 md:mt-0">
|
|
||||||
{% for code, name in SUPPORTED_LANGUAGES.items() %}
|
|
||||||
<a href="/set-language/{{ code }}" class="hover:text-golden-ale transition {% if locale == code %}font-bold{% endif %}">
|
|
||||||
{{ name }}
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: leagueledger:latest
|
||||||
|
deploy:
|
||||||
|
replicas: 3
|
||||||
|
update_config:
|
||||||
|
parallelism: 2
|
||||||
|
delay: 10s
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
networks:
|
||||||
|
- traefik-public
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgresql://user:password@db:5432/leagueledger
|
||||||
|
- SECRET_KEY=your-very-secret-session-key
|
||||||
|
- TRAEFIK_ENTRYPOINT=http
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.leagueledger.rule=Host(`yourdomain.com`)"
|
||||||
|
- "traefik.http.services.leagueledger.loadbalancer.server.port=8000"
|
||||||
|
volumes:
|
||||||
|
- ./app:/app
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: postgres:13
|
||||||
|
deploy:
|
||||||
|
restart_policy:
|
||||||
|
condition: on-failure
|
||||||
|
networks:
|
||||||
|
- traefik-public
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: user
|
||||||
|
POSTGRES_PASSWORD: password
|
||||||
|
POSTGRES_DB: leagueledger
|
||||||
|
volumes:
|
||||||
|
- db_data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
networks:
|
||||||
|
traefik-public:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
db_data:
|
||||||
@@ -4,31 +4,6 @@ from pathlib import Path
|
|||||||
|
|
||||||
# Define base directory
|
# Define base directory
|
||||||
BASE_DIR = Path(__file__).parent.parent
|
BASE_DIR = Path(__file__).parent.parent
|
||||||
LOCALE_DIR = BASE_DIR / "app" / "i18n" / "locales"
|
|
||||||
|
|
||||||
def compile_all_translations():
|
|
||||||
"""Compile .po files into .mo files for all languages"""
|
|
||||||
print("Compiling translations...")
|
|
||||||
|
|
||||||
for lang_dir in LOCALE_DIR.iterdir():
|
|
||||||
if lang_dir.is_dir():
|
|
||||||
lang_code = lang_dir.name
|
|
||||||
po_file = lang_dir / "LC_MESSAGES" / "messages.po"
|
|
||||||
|
|
||||||
if po_file.exists():
|
|
||||||
print(f"Compiling {lang_code} translations...")
|
|
||||||
try:
|
|
||||||
subprocess.run([
|
|
||||||
"pybabel", "compile",
|
|
||||||
"-f", "-i", str(po_file),
|
|
||||||
"-o", str(po_file.parent / "messages.mo"),
|
|
||||||
"--statistics"
|
|
||||||
], check=True)
|
|
||||||
print(f"Successfully compiled {lang_code} translations")
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"Error compiling {lang_code} translations: {e}")
|
|
||||||
else:
|
|
||||||
print(f"No .po file found for {lang_code}")
|
|
||||||
|
|
||||||
def update_pot_file():
|
def update_pot_file():
|
||||||
"""Extract translatable strings from templates and create a POT file"""
|
"""Extract translatable strings from templates and create a POT file"""
|
||||||
@@ -54,7 +29,7 @@ def update_po_files():
|
|||||||
|
|
||||||
pot_file = BASE_DIR / "app" / "i18n" / "messages.pot"
|
pot_file = BASE_DIR / "app" / "i18n" / "messages.pot"
|
||||||
|
|
||||||
for lang_dir in LOCALE_DIR.iterdir():
|
for lang_dir in (BASE_DIR / "app" / "i18n" / "locales").iterdir():
|
||||||
if lang_dir.is_dir():
|
if lang_dir.is_dir():
|
||||||
lang_code = lang_dir.name
|
lang_code = lang_dir.name
|
||||||
po_file = lang_dir / "LC_MESSAGES" / "messages.po"
|
po_file = lang_dir / "LC_MESSAGES" / "messages.po"
|
||||||
@@ -97,4 +72,3 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
update_pot_file()
|
update_pot_file()
|
||||||
update_po_files()
|
update_po_files()
|
||||||
compile_all_translations()
|
|
||||||
|
|||||||
Reference in New Issue
Block a user