From 2af9cd17eaa83e711a32b777c06c399f61dbaa2c Mon Sep 17 00:00:00 2001 From: Christian Krakau-Louis Date: Mon, 14 Apr 2025 03:27:24 +0200 Subject: [PATCH] Refactor translation handling and remove unused language support in templates --- app/i18n/__init__.py | 31 ++------- app/main.py | 29 +------- app/templates/about.html | 42 ++---------- app/templates/base.html | 115 +++++++++++--------------------- docker-compose.yml.sample | 47 +++++++++++++ scripts/compile_translations.py | 28 +------- 6 files changed, 98 insertions(+), 194 deletions(-) create mode 100644 docker-compose.yml.sample diff --git a/app/i18n/__init__.py b/app/i18n/__init__.py index bedbbf3..ec94f9d 100644 --- a/app/i18n/__init__.py +++ b/app/i18n/__init__.py @@ -1,16 +1,8 @@ import gettext import os -from typing import Dict, List, Callable, Any -from fastapi import Request, Depends +from fastapi import Request from babel.support import Translations 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 @@ -40,12 +32,12 @@ def get_locale_from_request(request: Request) -> str: """ # Check URL parameter lang_param = request.query_params.get('lang') - if lang_param in SUPPORTED_LANGUAGES: + if lang_param: return lang_param # Check session session = request.session.get('language') - if session in SUPPORTED_LANGUAGES: + if session: return session # Check Accept-Language header @@ -54,21 +46,6 @@ def get_locale_from_request(request: Request) -> str: for lang in accept_language.split(','): lang_code = lang.split(';')[0].strip().lower() 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 - -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 - } diff --git a/app/main.py b/app/main.py index a183627..e7794b1 100644 --- a/app/main.py +++ b/app/main.py @@ -12,7 +12,6 @@ from . import models from .templates_config import templates from .views import qr, redeem, teams, admin, leaderboard, dashboard, static, pages from .db_init import seed_db -from app.i18n import get_translator, SUPPORTED_LANGUAGES, _ # Ensure `_` is correctly imported # Create tables on startup init_db() @@ -35,8 +34,6 @@ static.configure_static_files(app) # Setup Jinja2 templates templates = Jinja2Templates(directory="app/templates") -templates.env.globals["SUPPORTED_LANGUAGES"] = SUPPORTED_LANGUAGES -templates.env.globals["_"] = _ # User context middleware to make template globals available @app.middleware("http") @@ -52,35 +49,13 @@ async def add_template_globals(request: Request, call_next): response = await call_next(request) 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) -async def read_root(request: Request, i18n: dict = Depends(get_translator)): +async def read_root(request: Request): return templates.TemplateResponse( "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 app.include_router(pages.router, tags=["Pages"]) # Pages router for index and static pages app.include_router(qr.router, prefix="/qr", tags=["QR"]) diff --git a/app/templates/about.html b/app/templates/about.html index ac15e6d..6a4e249 100644 --- a/app/templates/about.html +++ b/app/templates/about.html @@ -1,61 +1,29 @@ {% extends "base.html" %} {% block content %}
-

{{ _("About LeagueLedger") }}

+

About LeagueLedger

- {% 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. - {% endif %}

- {% 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. - {% endif %}

- {% 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. - {% endif %}

-

- {% if locale == "de" %}Unsere Mission{% else %}Our Mission{% endif %} -

+

Our Mission

- {% 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. - {% endif %}

-

- {% if locale == "de" %}Unser Team{% else %}Our Team{% endif %} -

+

Our Team

- {% 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. - {% endif %}

-

- {% if locale == "de" %}Lizenz{% else %}License{% endif %} -

+

License

- {% 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. - {% endif %}

-

- {% if locale == "de" %}Letzte Aktualisierung: April 2025{% else %}Last updated: April 2025{% endif %} -

+

Last updated: April 2025

{% endblock %} diff --git a/app/templates/base.html b/app/templates/base.html index 9fa2674..a48464c 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -1,9 +1,9 @@ - + - {% block title %}LeagueLedger - Pub Quiz Tracking{% endblock %} + LeagueLedger - Pub Quiz Tracking @@ -65,26 +65,10 @@
- - - @@ -141,19 +111,19 @@ @@ -174,53 +144,46 @@ LeagueLedger Logo LeagueLedger
-

{{ _("Track your pub quiz team's progress.") }}
{{ _("Scan QR codes to earn points.") }}

+

Track your pub quiz team's progress.
Scan QR codes to earn points.

-

{{ _("Navigation") }}

+

Navigation

-

{{ _("Account") }}

+

Account

-

{{ _("Legal") }}

+

Legal

-

© 2025 LeagueLedger. {{ _("Licensed under Apache License 2.0") }}

-
-
- {% for code, name in SUPPORTED_LANGUAGES.items() %} - - {{ name }} - - {% endfor %} +

© 2025 LeagueLedger. Licensed under Apache License 2.0

diff --git a/docker-compose.yml.sample b/docker-compose.yml.sample new file mode 100644 index 0000000..caec116 --- /dev/null +++ b/docker-compose.yml.sample @@ -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: \ No newline at end of file diff --git a/scripts/compile_translations.py b/scripts/compile_translations.py index b7f5557..4dcfe61 100644 --- a/scripts/compile_translations.py +++ b/scripts/compile_translations.py @@ -4,31 +4,6 @@ from pathlib import Path # Define base directory 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(): """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" - for lang_dir in LOCALE_DIR.iterdir(): + for lang_dir in (BASE_DIR / "app" / "i18n" / "locales").iterdir(): if lang_dir.is_dir(): lang_code = lang_dir.name po_file = lang_dir / "LC_MESSAGES" / "messages.po" @@ -97,4 +72,3 @@ if __name__ == "__main__": update_pot_file() update_po_files() - compile_all_translations()