From b46691fee34e31b6f4361ed4673fcc29832f6b4d Mon Sep 17 00:00:00 2001 From: agentbox Date: Sat, 1 Aug 2026 06:00:29 +0000 Subject: [PATCH] Add i18n support for French and German with browser language detection --- app.py | 3 + auth.py | 23 +-- i18n.py | 401 +++++++++++++++++++++++++++++++++++++++ meals.py | 33 ++-- static/style.css | 22 +++ templates/base.html | 18 +- templates/dashboard.html | 57 +++--- templates/login.html | 12 +- templates/register.html | 28 +-- 9 files changed, 519 insertions(+), 78 deletions(-) create mode 100644 i18n.py diff --git a/app.py b/app.py index 46426af..c80c0f1 100644 --- a/app.py +++ b/app.py @@ -7,6 +7,7 @@ from flask import Flask from models import init_db from auth import auth_bp from meals import meals_bp +from i18n import init_i18n def create_app() -> Flask: @@ -19,6 +20,8 @@ def create_app() -> Flask: with app.app_context(): init_db() + init_i18n(app) + return app diff --git a/auth.py b/auth.py index dcea213..c53087f 100644 --- a/auth.py +++ b/auth.py @@ -12,6 +12,7 @@ from models import ( get_all_households, count_household_users, ) +from i18n import t auth_bp = Blueprint("auth", __name__) @@ -23,12 +24,12 @@ def login(): password = request.form.get("password", "") if not username or not password: - flash("Username and password are required.", "error") + flash(t("Username and password are required."), "error") return render_template("login.html") user = get_user_by_username(username) if user is None or not check_password_hash(user["password_hash"], password): - flash("Invalid username or password.", "error") + flash(t("Invalid username or password."), "error") return render_template("login.html") household_id = user["household_id"] @@ -59,32 +60,32 @@ def register(): errors = [] if not username or len(username) < 2: - errors.append("Username must be at least 2 characters.") + errors.append(t("Username must be at least 2 characters.")) if not password or len(password) < 4: - errors.append("Password must be at least 4 characters.") + errors.append(t("Password must be at least 4 characters.")) if password != confirm: - errors.append("Passwords do not match.") + errors.append(t("Passwords do not match.")) if get_user_by_username(username): - errors.append("Username already taken.") + errors.append(t("Username already taken.")) target_household_id = None if household_action == "create": if not new_household_name: - errors.append("Household name is required.") + errors.append(t("Household name is required.")) elif get_household_by_name(new_household_name): - errors.append("A household with that name already exists.") + errors.append(t("A household with that name already exists.")) else: household = create_household(new_household_name) target_household_id = household["id"] is_admin = True # First user in a new household is admin else: if not household_id: - errors.append("Please select a household.") + errors.append(t("Please select a household.")) else: hh = get_household_by_id(int(household_id)) if hh is None: - errors.append("Selected household does not exist.") + errors.append(t("Selected household does not exist.")) else: target_household_id = hh["id"] is_admin = False @@ -104,7 +105,7 @@ def register(): session["household_id"] = user["household_id"] session["household_name"] = get_household_by_id(target_household_id)["name"] - flash("Account created! Welcome to your household.", "success") + flash(t("Account created! Welcome to your household."), "success") return redirect(url_for("meals.dashboard")) return render_template("register.html", households=households) diff --git a/i18n.py b/i18n.py new file mode 100644 index 0000000..95d478f --- /dev/null +++ b/i18n.py @@ -0,0 +1,401 @@ +"""Lightweight i18n — French and German translations, browser-language detection.""" + +from __future__ import annotations + +from flask import request, session, g + +# ── Supported languages ─────────────────────────────────────────────────────── +LANGUAGES = { + "en": "English", + "fr": "Français", + "de": "Deutsch", +} + +FALLBACK = "en" + + +# ── Translations ────────────────────────────────────────────────────────────── +# Keys are English strings; values are dicts of language → translated string. +# Only non-English translations are stored; English is the key itself. + +TRANSLATIONS: dict[str, dict[str, str]] = { + # ── base.html ── + "Meal Tracker": { + "fr": "Suivi des Repas", + "de": "Mahlzeiten-Tracker", + }, + "admin": { + "fr": "administrateur", + "de": "Administrator", + }, + "Logout": { + "fr": "Déconnexion", + "de": "Abmelden", + }, + + # ── login.html ── + "Log In": { + "fr": "Se connecter", + "de": "Anmelden", + }, + "Username": { + "fr": "Nom d'utilisateur", + "de": "Benutzername", + }, + "Password": { + "fr": "Mot de passe", + "de": "Passwort", + }, + "Don't have an account?": { + "fr": "Pas de compte ?", + "de": "Kein Konto?", + }, + "Register": { + "fr": "S'inscrire", + "de": "Registrieren", + }, + + # ── register.html ── + "Create Account": { + "fr": "Créer un compte", + "de": "Konto erstellen", + }, + "Confirm Password": { + "fr": "Confirmer le mot de passe", + "de": "Passwort bestätigen", + }, + "Household": { + "fr": "Foyer", + "de": "Haushalt", + }, + "Join an existing household": { + "fr": "Rejoindre un foyer existant", + "de": "Einem bestehenden Haushalt beitreten", + }, + "— Select —": { + "fr": "— Sélectionner —", + "de": "— Auswählen —", + }, + "No households yet. Create one below!": { + "fr": "Pas encore de foyer. Créez-en un ci-dessous !", + "de": "Noch keine Haushalte. Erstellen Sie unten einen!", + }, + "Create a new household": { + "fr": "Créer un nouveau foyer", + "de": "Neuen Haushalt erstellen", + }, + "Household name (e.g. The Smiths)": { + "fr": "Nom du foyer (ex. Les Dupont)", + "de": "Haushaltsname (z.B. Familie Müller)", + }, + "You'll be the admin of this household.": { + "fr": "Vous serez l'administrateur de ce foyer.", + "de": "Sie werden der Administrator dieses Haushalts sein.", + }, + "Already have an account?": { + "fr": "Vous avez déjà un compte ?", + "de": "Haben Sie bereits ein Konto?", + }, + "Log in": { + "fr": "Se connecter", + "de": "Anmelden", + }, + + # ── dashboard.html ── + "Today's Meals": { + "fr": "Repas du jour", + "de": "Heutige Mahlzeiten", + }, + "History — {date}": { + "fr": "Historique — {date}", + "de": "Verlauf — {date}", + }, + "◀ Prev": { + "fr": "◀ Préc.", + "de": "◀ Zurück", + }, + "Next ▶": { + "fr": "Suiv. ▶", + "de": "Weiter ▶", + }, + "Today": { + "fr": "Aujourd'hui", + "de": "Heute", + }, + "No registered users yet. Ask household members to register!": { + "fr": "Pas encore d'utilisateurs. Invitez les membres du foyer à s'inscrire !", + "de": "Noch keine registrierten Benutzer. Bitten Sie Haushaltsmitglieder, sich zu registrieren!", + }, + "User": { + "fr": "Utilisateur", + "de": "Benutzer", + }, + "Lunch": { + "fr": "Déjeuner", + "de": "Mittagessen", + }, + "Dinner": { + "fr": "Dîner", + "de": "Abendessen", + }, + "you": { + "fr": "vous", + "de": "du", + }, + "Home": { + "fr": "Présent", + "de": "Zuhause", + }, + "Out": { + "fr": "Absent", + "de": "Auswärts", + }, + "—": { + "fr": "—", + "de": "—", + }, + "Remove": { + "fr": "Retirer", + "de": "Entfernen", + }, + "Changed mind at {time}": { + "fr": "A changé d'avis à {time}", + "de": "Meinung geändert um {time}", + }, + "⚙ Account Settings": { + "fr": "⚙ Paramètres du compte", + "de": "⚙ Kontoeinstellungen", + }, + "Delete My Account": { + "fr": "Supprimer mon compte", + "de": "Mein Konto löschen", + }, + "Enter your password to confirm": { + "fr": "Entrez votre mot de passe pour confirmer", + "de": "Geben Sie Ihr Passwort zur Bestätigung ein", + }, + "Delete Household": { + "fr": "Supprimer le foyer", + "de": "Haushalt löschen", + }, + "This will remove all users and all data in this household.": { + "fr": "Cela supprimera tous les utilisateurs et toutes les données de ce foyer.", + "de": "Dies entfernt alle Benutzer und alle Daten in diesem Haushalt.", + }, + + # ── JS confirm strings ── + "Delete YOUR account? This cannot be undone.": { + "fr": "Supprimer VOTRE compte ? Cette action est irréversible.", + "de": "IHR Konto löschen? Dies kann nicht rückgängig gemacht werden.", + }, + "Remove {name} from this household?": { + "fr": "Retirer {name} de ce foyer ?", + "de": "{name} aus diesem Haushalt entfernen?", + }, + "Delete the ENTIRE household and ALL members? This cannot be undone.": { + "fr": "Supprimer le foyer ENTIER et TOUS ses membres ? Cette action est irréversible.", + "de": "Den GESAMTEN Haushalt und ALLE Mitglieder löschen? Dies kann nicht rückgängig gemacht werden.", + }, + + # ── Page titles ── + "Login — Meal Tracker": { + "fr": "Connexion — Suivi des Repas", + "de": "Anmeldung — Mahlzeiten-Tracker", + }, + "Register — Meal Tracker": { + "fr": "Inscription — Suivi des Repas", + "de": "Registrierung — Mahlzeiten-Tracker", + }, + "Dashboard — Meal Tracker": { + "fr": "Tableau de bord — Suivi des Repas", + "de": "Übersicht — Mahlzeiten-Tracker", + }, + + # ── Flash / error messages ── + "Username and password are required.": { + "fr": "Le nom d'utilisateur et le mot de passe sont requis.", + "de": "Benutzername und Passwort sind erforderlich.", + }, + "Invalid username or password.": { + "fr": "Nom d'utilisateur ou mot de passe invalide.", + "de": "Ungültiger Benutzername oder Passwort.", + }, + "Username must be at least 2 characters.": { + "fr": "Le nom d'utilisateur doit comporter au moins 2 caractères.", + "de": "Der Benutzername muss mindestens 2 Zeichen lang sein.", + }, + "Password must be at least 4 characters.": { + "fr": "Le mot de passe doit comporter au moins 4 caractères.", + "de": "Das Passwort muss mindestens 4 Zeichen lang sein.", + }, + "Passwords do not match.": { + "fr": "Les mots de passe ne correspondent pas.", + "de": "Die Passwörter stimmen nicht überein.", + }, + "Username already taken.": { + "fr": "Ce nom d'utilisateur est déjà pris.", + "de": "Dieser Benutzername ist bereits vergeben.", + }, + "Household name is required.": { + "fr": "Le nom du foyer est requis.", + "de": "Der Haushaltsname ist erforderlich.", + }, + "A household with that name already exists.": { + "fr": "Un foyer avec ce nom existe déjà.", + "de": "Ein Haushalt mit diesem Namen existiert bereits.", + }, + "Please select a household.": { + "fr": "Veuillez sélectionner un foyer.", + "de": "Bitte wählen Sie einen Haushalt aus.", + }, + "Selected household does not exist.": { + "fr": "Le foyer sélectionné n'existe pas.", + "de": "Der ausgewählte Haushalt existiert nicht.", + }, + "Account created! Welcome to your household.": { + "fr": "Compte créé ! Bienvenue dans votre foyer.", + "de": "Konto erstellt! Willkommen in Ihrem Haushalt.", + }, + "Invalid meal type.": { + "fr": "Type de repas invalide.", + "de": "Ungültige Mahlzeitart.", + }, + "Invalid status.": { + "fr": "Statut invalide.", + "de": "Ungültiger Status.", + }, + "Invalid date.": { + "fr": "Date invalide.", + "de": "Ungültiges Datum.", + }, + "Cannot change responses for past dates.": { + "fr": "Impossible de modifier les réponses pour les dates passées.", + "de": "Antworten für vergangene Daten können nicht geändert werden.", + }, + "Meal period not found.": { + "fr": "Période de repas introuvable.", + "de": "Mahlzeitenzeitraum nicht gefunden.", + }, + "Invalid date format. Use YYYY-MM-DD.": { + "fr": "Format de date invalide. Utilisez AAAA-MM-JJ.", + "de": "Ungültiges Datumsformat. Verwenden Sie JJJJ-MM-TT.", + }, + "Incorrect password. Account not deleted.": { + "fr": "Mot de passe incorrect. Compte non supprimé.", + "de": "Falsches Passwort. Konto nicht gelöscht.", + }, + "Your account and household have been deleted.": { + "fr": "Votre compte et votre foyer ont été supprimés.", + "de": "Ihr Konto und Ihr Haushalt wurden gelöscht.", + }, + "Your account has been deleted.": { + "fr": "Votre compte a été supprimé.", + "de": "Ihr Konto wurde gelöscht.", + }, + "Only admins can remove users.": { + "fr": "Seuls les administrateurs peuvent retirer des utilisateurs.", + "de": "Nur Administratoren können Benutzer entfernen.", + }, + "User not found in your household.": { + "fr": "Utilisateur introuvable dans votre foyer.", + "de": "Benutzer nicht in Ihrem Haushalt gefunden.", + }, + "Cannot remove the admin. Transfer admin role first or delete the household.": { + "fr": "Impossible de retirer l'administrateur. Transférez d'abord le rôle admin ou supprimez le foyer.", + "de": "Der Administrator kann nicht entfernt werden. Übertragen Sie zuerst die Admin-Rolle oder löschen Sie den Haushalt.", + }, + "User '{name}' has been removed.": { + "fr": "L'utilisateur '{name}' a été retiré.", + "de": "Benutzer '{name}' wurde entfernt.", + }, + "Only admins can delete the household.": { + "fr": "Seuls les administrateurs peuvent supprimer le foyer.", + "de": "Nur Administratoren können den Haushalt löschen.", + }, + "Incorrect password. Household not deleted.": { + "fr": "Mot de passe incorrect. Foyer non supprimé.", + "de": "Falsches Passwort. Haushalt nicht gelöscht.", + }, + "Household and all members have been deleted.": { + "fr": "Le foyer et tous ses membres ont été supprimés.", + "de": "Haushalt und alle Mitglieder wurden gelöscht.", + }, +} + + +# ── Helpers ─────────────────────────────────────────────────────────────────── + +def parse_accept_language(header: str | None) -> str: + """Parse Accept-Language header and return the best supported language code.""" + if not header: + return FALLBACK + + best = FALLBACK + best_quality = 0.0 + + for part in header.split(","): + chunk = part.strip().split(";") + tag = chunk[0].strip().split("-")[0].lower() # "fr-FR" → "fr" + quality = 1.0 + if len(chunk) > 1 and chunk[1].strip().startswith("q="): + try: + quality = float(chunk[1].strip()[2:]) + except ValueError: + pass + + if tag in LANGUAGES and quality > best_quality: + best_quality = quality + best = tag + + return best + + +def get_lang() -> str: + """Return current language code (from session, header, or fallback).""" + # Session override (user manually selected a language) + lang = session.get("lang") + if lang and lang in LANGUAGES: + return lang + + # Parse Accept-Language header + lang = parse_accept_language(request.headers.get("Accept-Language", "")) + session["lang"] = lang + return lang + + +def t(key: str, **kwargs) -> str: + """Translate a key into the current language. + + Usage in templates: {{ t('Log In') }} + With placeholders: {{ t("User '{name}' has been removed.", name=username) }} + """ + lang = g.get("lang") or session.get("lang") or FALLBACK + + if lang == FALLBACK: + result = key + else: + result = TRANSLATIONS.get(key, {}).get(lang, key) + + if kwargs: + result = result.format(**kwargs) + return result + + +def init_i18n(app): + """Register before_request handler and template globals on a Flask app.""" + + @app.before_request + def _set_lang(): + # Allow ?lang= override in query string + qs_lang = request.args.get("lang") + if qs_lang and qs_lang in LANGUAGES: + session["lang"] = qs_lang + g.lang = get_lang() + + @app.context_processor + def _inject_i18n(): + return { + "t": t, + "lang": g.get("lang", FALLBACK), + "lang_name": LANGUAGES.get(g.get("lang", FALLBACK), "English"), + } diff --git a/meals.py b/meals.py index 2a601ab..15410c5 100644 --- a/meals.py +++ b/meals.py @@ -23,6 +23,7 @@ from models import ( delete_household, count_household_users, ) +from i18n import t meals_bp = Blueprint("meals", __name__) @@ -60,7 +61,7 @@ def dashboard(): try: today = datetime.strptime(date_str, "%Y-%m-%d").date() except ValueError: - flash("Invalid date format. Use YYYY-MM-DD.", "error") + flash(t("Invalid date format. Use YYYY-MM-DD."), "error") if household_id is None: session.clear() @@ -87,27 +88,27 @@ def respond(): date_str = request.form.get("date", date.today().isoformat()) if meal_type not in ("lunch", "dinner"): - flash("Invalid meal type.", "error") + flash(t("Invalid meal type."), "error") return redirect(url_for("meals.dashboard")) if status not in ("yes", "no"): - flash("Invalid status.", "error") + flash(t("Invalid status."), "error") return redirect(url_for("meals.dashboard")) try: target_date = datetime.strptime(date_str, "%Y-%m-%d").date() except ValueError: - flash("Invalid date.", "error") + flash(t("Invalid date."), "error") return redirect(url_for("meals.dashboard")) if target_date < date.today(): - flash("Cannot change responses for past dates.", "error") + flash(t("Cannot change responses for past dates."), "error") return redirect(url_for("meals.dashboard")) periods = ensure_meal_periods(target_date) period = next((p for p in periods if p["meal_type"] == meal_type), None) if period is None: - flash("Meal period not found.", "error") + flash(t("Meal period not found."), "error") return redirect(url_for("meals.dashboard")) upsert_response(session["user_id"], period["id"], status) @@ -147,7 +148,7 @@ def delete_account(): user = get_user_by_id(session["user_id"]) if not check_password_hash(user["password_hash"], password): - flash("Incorrect password. Account not deleted.", "error") + flash(t("Incorrect password. Account not deleted."), "error") return redirect(url_for("meals.dashboard")) household_id = session["household_id"] @@ -157,10 +158,10 @@ def delete_account(): # If admin is the last user, delete the household too if is_admin and user_count == 1: delete_household(household_id) - flash("Your account and household have been deleted.", "success") + flash(t("Your account and household have been deleted."), "success") else: delete_user(session["user_id"]) - flash("Your account has been deleted.", "success") + flash(t("Your account has been deleted."), "success") # Clear auth keys but keep the session so flash survives the redirect for key in ("user_id", "username", "is_admin", "household_id", "household_name"): @@ -173,20 +174,20 @@ def delete_account(): def admin_remove_user(user_id): """Admin removes a user from the household.""" if not session["is_admin"]: - flash("Only admins can remove users.", "error") + flash(t("Only admins can remove users."), "error") return redirect(url_for("meals.dashboard")) user = get_user_by_id(user_id) if user is None or user["household_id"] != session["household_id"]: - flash("User not found in your household.", "error") + flash(t("User not found in your household."), "error") return redirect(url_for("meals.dashboard")) if user["is_admin"]: - flash("Cannot remove the admin. Transfer admin role first or delete the household.", "error") + flash(t("Cannot remove the admin. Transfer admin role first or delete the household."), "error") return redirect(url_for("meals.dashboard")) delete_user(user_id) - flash(f"User '{user['username']}' has been removed.", "success") + flash(t("User '{name}' has been removed.", name=user["username"]), "success") return redirect(url_for("meals.dashboard")) @@ -195,18 +196,18 @@ def admin_remove_user(user_id): def admin_delete_household(): """Admin deletes the entire household.""" if not session["is_admin"]: - flash("Only admins can delete the household.", "error") + flash(t("Only admins can delete the household."), "error") return redirect(url_for("meals.dashboard")) password = request.form.get("password", "") user = get_user_by_id(session["user_id"]) if not check_password_hash(user["password_hash"], password): - flash("Incorrect password. Household not deleted.", "error") + flash(t("Incorrect password. Household not deleted."), "error") return redirect(url_for("meals.dashboard")) delete_household(session["household_id"]) - flash("Household and all members have been deleted.", "success") + flash(t("Household and all members have been deleted."), "success") for key in ("user_id", "username", "is_admin", "household_id", "household_name"): session.pop(key, None) return redirect(url_for("auth.login")) diff --git a/static/style.css b/static/style.css index 243ea42..cb2711b 100644 --- a/static/style.css +++ b/static/style.css @@ -232,3 +232,25 @@ body { .manage-form h4 { margin-bottom: 8px; font-size: 0.9rem; } .manage-form .hint { margin-bottom: 8px; } .manage-form input[type="password"] { width: 100%; padding: 6px 10px; border: 1px solid var(--border); border-radius: 4px; margin-bottom: 8px; } + +/* ── Language footer ── */ +.lang-footer { + text-align: center; + padding: 16px; + margin-top: 32px; + border-top: 1px solid var(--border); +} +.lang-link { + display: inline-block; + padding: 4px 10px; + margin: 0 4px; + font-size: 0.8rem; + font-weight: 500; + color: var(--muted); + text-decoration: none; + border-radius: 12px; + transition: all 0.15s; +} +.lang-link:hover { background: #eee; color: var(--text); } +.lang-active { background: var(--primary); color: #fff; } +.lang-active:hover { background: #0d47a1; color: #fff; } diff --git a/templates/base.html b/templates/base.html index 0b77e98..7f35376 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,19 +1,19 @@ - + - {% block title %}Meal Tracker{% endblock %} + {% block title %}{{ t('Meal Tracker') }}{% endblock %} @@ -31,5 +31,13 @@ {% block content %}{% endblock %} + + diff --git a/templates/dashboard.html b/templates/dashboard.html index 99fb1b0..9e2b7b7 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -1,31 +1,31 @@ {% extends "base.html" %} -{% block title %}Dashboard — Meal Tracker{% endblock %} +{% block title %}{{ t('Dashboard — Meal Tracker') }}{% endblock %} {% block content %}

- {% if history_mode %}History — {{ viewing_date.isoformat() }}{% else %}Today's Meals{% endif %} + {% if history_mode %}{{ t('History — {date}', date=viewing_date.isoformat()) }}{% else %}{{ t("Today's Meals") }}{% endif %}

- ◀ Prev + {{ t('◀ Prev') }} {{ viewing_date.isoformat() }} - Next ▶ + {{ t('Next ▶') }} {% if viewing_date != today %} - Today + {{ t('Today') }} {% endif %}
{% if not dashboard.users %} -

No registered users yet. Ask household members to register!

+

{{ t('No registered users yet. Ask household members to register!') }}

{% else %}
- + {% for period in dashboard.periods %} - + {% endfor %} {% if is_admin %} @@ -38,8 +38,8 @@ {% for period in dashboard.periods %} {% set resp = user.responses[period.meal_type] %} @@ -51,25 +51,26 @@
+ >{{ t('Home') }} + >{{ t('Out') }}
{% if resp.changed_at %} -
↻ {{ resp.changed_at[11:16] }}
+
↻ {{ resp.changed_at[11:16] }}
{% endif %} {% else %} - {{ 'Home' if resp.status == 'yes' else ('Out' if resp.status == 'no' else '—') }} + {{ t('Home') if resp.status == 'yes' else (t('Out') if resp.status == 'no' else t('—')) }} {% endif %} {% endfor %} {% if is_admin and not is_me and not user.is_admin %} {% elif is_admin %} @@ -84,22 +85,26 @@
- ⚙ Account Settings + {{ t('⚙ Account Settings') }}
-

Delete My Account

- - + onsubmit="return confirm(this.dataset.confirmMsg);" + data-confirm-msg="{{ t('Delete YOUR account? This cannot be undone.') }}" + class="manage-form"> +

{{ t('Delete My Account') }}

+ + {% if is_admin %}
-

Delete Household

-

This will remove all users and all data in this household.

- - + onsubmit="return confirm(this.dataset.confirmMsg);" + data-confirm-msg="{{ t('Delete the ENTIRE household and ALL members? This cannot be undone.') }}" + class="manage-form"> +

{{ t('Delete Household') }}

+

{{ t('This will remove all users and all data in this household.') }}

+ + {% endif %}
diff --git a/templates/login.html b/templates/login.html index 71d3435..d5706ff 100644 --- a/templates/login.html +++ b/templates/login.html @@ -1,19 +1,19 @@ {% extends "base.html" %} -{% block title %}Login — Meal Tracker{% endblock %} +{% block title %}{{ t('Login — Meal Tracker') }}{% endblock %} {% block content %}
-

Log In

+

{{ t('Log In') }}

- + - + - +
{% endblock %} diff --git a/templates/register.html b/templates/register.html index 5d2c9e9..1f5c9e7 100644 --- a/templates/register.html +++ b/templates/register.html @@ -1,54 +1,54 @@ {% extends "base.html" %} -{% block title %}Register — Meal Tracker{% endblock %} +{% block title %}{{ t('Register — Meal Tracker') }}{% endblock %} {% block content %}
-

Create Account

+

{{ t('Create Account') }}

- + - + - +
- Household + {{ t('Household') }}
{% if not households %} -

No households yet. Create one below!

+

{{ t('No households yet. Create one below!') }}

{% endif %}
-

You'll be the admin of this household.

+

{{ t("You'll be the admin of this household.") }}

- +
User{{ t('User') }}{{ period.meal_type|capitalize }}{{ t(period.meal_type|capitalize) }}
{{ user.username }} - {% if user.is_admin %}admin{% endif %} - {% if is_me %}you{% endif %} + {% if user.is_admin %}{{ t('admin') }}{% endif %} + {% if is_me %}{{ t('you') }}{% endif %}
- + onsubmit="return confirm(this.dataset.confirmMsg);" + data-confirm-msg="{{ t('Remove {name} from this household?', name=user.username) }}"> +