Add i18n support for French and German with browser language detection
CI / test-and-package (push) Successful in 35s
CI / test-and-package (push) Successful in 35s
This commit is contained in:
+13
-5
@@ -1,19 +1,19 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="{{ lang }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}Meal Tracker{% endblock %}</title>
|
||||
<title>{% block title %}{{ t('Meal Tracker') }}{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar">
|
||||
<span class="brand">🍽 Meal Tracker</span>
|
||||
<span class="brand">🍽 {{ t('Meal Tracker') }}</span>
|
||||
{% if session.user_id %}
|
||||
<div class="nav-right">
|
||||
<span class="nav-household">{{ session.household_name }}</span>
|
||||
<span class="nav-user">{{ session.username }}{% if session.is_admin %} (admin){% endif %}</span>
|
||||
<a class="btn btn-sm" href="{{ url_for('auth.logout') }}">Logout</a>
|
||||
<span class="nav-user">{{ session.username }}{% if session.is_admin %} ({{ t('admin') }}){% endif %}</span>
|
||||
<a class="btn btn-sm" href="{{ url_for('auth.logout') }}">{{ t('Logout') }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</nav>
|
||||
@@ -31,5 +31,13 @@
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="lang-footer">
|
||||
{% for code, name in {'en': 'English', 'fr': 'Français', 'de': 'Deutsch'}.items() %}
|
||||
<a href="?lang={{ code }}"
|
||||
class="lang-link{% if lang == code %} lang-active{% endif %}"
|
||||
aria-label="{{ name }}">{{ code.upper() }}</a>
|
||||
{% endfor %}
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+31
-26
@@ -1,31 +1,31 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Dashboard — Meal Tracker{% endblock %}
|
||||
{% block title %}{{ t('Dashboard — Meal Tracker') }}{% endblock %}
|
||||
{% block content %}
|
||||
|
||||
<div class="dashboard-header">
|
||||
<h2>
|
||||
{% 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 %}
|
||||
</h2>
|
||||
<div class="date-nav">
|
||||
<a class="btn btn-sm" href="?date={{ prev_date.isoformat() }}">◀ Prev</a>
|
||||
<a class="btn btn-sm" href="?date={{ prev_date.isoformat() }}">{{ t('◀ Prev') }}</a>
|
||||
<span class="date-display">{{ viewing_date.isoformat() }}</span>
|
||||
<a class="btn btn-sm" href="?date={{ next_date.isoformat() }}">Next ▶</a>
|
||||
<a class="btn btn-sm" href="?date={{ next_date.isoformat() }}">{{ t('Next ▶') }}</a>
|
||||
{% if viewing_date != today %}
|
||||
<a class="btn btn-sm" href="{{ url_for('meals.dashboard') }}">Today</a>
|
||||
<a class="btn btn-sm" href="{{ url_for('meals.dashboard') }}">{{ t('Today') }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if not dashboard.users %}
|
||||
<p class="empty-state">No registered users yet. Ask household members to register!</p>
|
||||
<p class="empty-state">{{ t('No registered users yet. Ask household members to register!') }}</p>
|
||||
{% else %}
|
||||
<div class="table-wrapper">
|
||||
<table class="meal-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>{{ t('User') }}</th>
|
||||
{% for period in dashboard.periods %}
|
||||
<th class="meal-col">{{ period.meal_type|capitalize }}</th>
|
||||
<th class="meal-col">{{ t(period.meal_type|capitalize) }}</th>
|
||||
{% endfor %}
|
||||
{% if is_admin %}
|
||||
<th class="action-col"></th>
|
||||
@@ -38,8 +38,8 @@
|
||||
<tr>
|
||||
<td class="user-cell">
|
||||
{{ user.username }}
|
||||
{% if user.is_admin %}<span class="admin-tag">admin</span>{% endif %}
|
||||
{% if is_me %}<span class="you-tag">you</span>{% endif %}
|
||||
{% if user.is_admin %}<span class="admin-tag">{{ t('admin') }}</span>{% endif %}
|
||||
{% if is_me %}<span class="you-tag">{{ t('you') }}</span>{% endif %}
|
||||
</td>
|
||||
{% for period in dashboard.periods %}
|
||||
{% set resp = user.responses[period.meal_type] %}
|
||||
@@ -51,25 +51,26 @@
|
||||
<div class="btn-row">
|
||||
<button name="status" value="yes"
|
||||
class="chip chip-yes {% if resp.status == 'yes' %}active{% endif %}"
|
||||
>Home</button>
|
||||
>{{ t('Home') }}</button>
|
||||
<button name="status" value="no"
|
||||
class="chip chip-no {% if resp.status == 'no' %}active{% endif %}"
|
||||
>Out</button>
|
||||
>{{ t('Out') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% if resp.changed_at %}
|
||||
<div class="changed-badge" title="Changed mind at {{ resp.changed_at }}">↻ {{ resp.changed_at[11:16] }}</div>
|
||||
<div class="changed-badge" title="{{ t('Changed mind at {time}', time=resp.changed_at) }}">↻ {{ resp.changed_at[11:16] }}</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="badge badge-{{ resp.status }}">{{ 'Home' if resp.status == 'yes' else ('Out' if resp.status == 'no' else '—') }}</span>
|
||||
<span class="badge badge-{{ resp.status }}">{{ t('Home') if resp.status == 'yes' else (t('Out') if resp.status == 'no' else t('—')) }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
{% if is_admin and not is_me and not user.is_admin %}
|
||||
<td class="action-cell">
|
||||
<form method="POST" action="{{ url_for('meals.admin_remove_user', user_id=user.id) }}"
|
||||
onsubmit="return confirm('Remove {{ user.username }} from this household?');">
|
||||
<button class="btn btn-sm btn-danger">Remove</button>
|
||||
onsubmit="return confirm(this.dataset.confirmMsg);"
|
||||
data-confirm-msg="{{ t('Remove {name} from this household?', name=user.username) }}">
|
||||
<button class="btn btn-sm btn-danger">{{ t('Remove') }}</button>
|
||||
</form>
|
||||
</td>
|
||||
{% elif is_admin %}
|
||||
@@ -84,22 +85,26 @@
|
||||
|
||||
<div class="manage-section">
|
||||
<details class="manage-details">
|
||||
<summary>⚙ Account Settings</summary>
|
||||
<summary>{{ t('⚙ Account Settings') }}</summary>
|
||||
<div class="manage-forms">
|
||||
<form method="POST" action="{{ url_for('meals.delete_account') }}"
|
||||
onsubmit="return confirm('Delete YOUR account? This cannot be undone.');" class="manage-form">
|
||||
<h4>Delete My Account</h4>
|
||||
<input type="password" name="password" placeholder="Enter your password to confirm" required>
|
||||
<button class="btn btn-danger">Delete My Account</button>
|
||||
onsubmit="return confirm(this.dataset.confirmMsg);"
|
||||
data-confirm-msg="{{ t('Delete YOUR account? This cannot be undone.') }}"
|
||||
class="manage-form">
|
||||
<h4>{{ t('Delete My Account') }}</h4>
|
||||
<input type="password" name="password" placeholder="{{ t('Enter your password to confirm') }}" required>
|
||||
<button class="btn btn-danger">{{ t('Delete My Account') }}</button>
|
||||
</form>
|
||||
|
||||
{% if is_admin %}
|
||||
<form method="POST" action="{{ url_for('meals.admin_delete_household') }}"
|
||||
onsubmit="return confirm('Delete the ENTIRE household and ALL members? This cannot be undone.');" class="manage-form">
|
||||
<h4>Delete Household</h4>
|
||||
<p class="hint">This will remove all users and all data in this household.</p>
|
||||
<input type="password" name="password" placeholder="Enter your password to confirm" required>
|
||||
<button class="btn btn-danger">Delete Household</button>
|
||||
onsubmit="return confirm(this.dataset.confirmMsg);"
|
||||
data-confirm-msg="{{ t('Delete the ENTIRE household and ALL members? This cannot be undone.') }}"
|
||||
class="manage-form">
|
||||
<h4>{{ t('Delete Household') }}</h4>
|
||||
<p class="hint">{{ t('This will remove all users and all data in this household.') }}</p>
|
||||
<input type="password" name="password" placeholder="{{ t('Enter your password to confirm') }}" required>
|
||||
<button class="btn btn-danger">{{ t('Delete Household') }}</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Login — Meal Tracker{% endblock %}
|
||||
{% block title %}{{ t('Login — Meal Tracker') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="form-card">
|
||||
<h2>Log In</h2>
|
||||
<h2>{{ t('Log In') }}</h2>
|
||||
<form method="POST">
|
||||
<label for="username">Username</label>
|
||||
<label for="username">{{ t('Username') }}</label>
|
||||
<input type="text" id="username" name="username" required autofocus>
|
||||
|
||||
<label for="password">Password</label>
|
||||
<label for="password">{{ t('Password') }}</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Log In</button>
|
||||
<button type="submit" class="btn btn-primary">{{ t('Log In') }}</button>
|
||||
</form>
|
||||
<p class="form-footer">
|
||||
Don't have an account? <a href="{{ url_for('auth.register') }}">Register</a>
|
||||
{{ t("Don't have an account?") }} <a href="{{ url_for('auth.register') }}">{{ t('Register') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
+14
-14
@@ -1,54 +1,54 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}Register — Meal Tracker{% endblock %}
|
||||
{% block title %}{{ t('Register — Meal Tracker') }}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="form-card register-card">
|
||||
<h2>Create Account</h2>
|
||||
<h2>{{ t('Create Account') }}</h2>
|
||||
<form method="POST">
|
||||
<label for="username">Username</label>
|
||||
<label for="username">{{ t('Username') }}</label>
|
||||
<input type="text" id="username" name="username" required autofocus>
|
||||
|
||||
<label for="password">Password</label>
|
||||
<label for="password">{{ t('Password') }}</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
|
||||
<label for="confirm">Confirm Password</label>
|
||||
<label for="confirm">{{ t('Confirm Password') }}</label>
|
||||
<input type="password" id="confirm" name="confirm" required>
|
||||
|
||||
<fieldset class="household-section">
|
||||
<legend>Household</legend>
|
||||
<legend>{{ t('Household') }}</legend>
|
||||
|
||||
<label class="radio-label">
|
||||
<input type="radio" name="household_action" value="join" id="radio-join" checked>
|
||||
Join an existing household
|
||||
{{ t('Join an existing household') }}
|
||||
</label>
|
||||
<div id="join-block" class="household-block">
|
||||
<select name="household_id" class="select-input">
|
||||
<option value="">— Select —</option>
|
||||
<option value="">{{ t('— Select —') }}</option>
|
||||
{% for h in households %}
|
||||
<option value="{{ h.id }}">{{ h.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% if not households %}
|
||||
<p class="hint">No households yet. Create one below!</p>
|
||||
<p class="hint">{{ t('No households yet. Create one below!') }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<label class="radio-label">
|
||||
<input type="radio" name="household_action" value="create" id="radio-create"
|
||||
{% if not households %}checked{% endif %}>
|
||||
Create a new household
|
||||
{{ t('Create a new household') }}
|
||||
</label>
|
||||
<div id="create-block" class="household-block">
|
||||
<input type="text" name="new_household_name"
|
||||
placeholder="Household name (e.g. The Smiths)"
|
||||
placeholder="{{ t('Household name (e.g. The Smiths)') }}"
|
||||
class="text-input">
|
||||
<p class="hint">You'll be the admin of this household.</p>
|
||||
<p class="hint">{{ t("You'll be the admin of this household.") }}</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<button type="submit" class="btn btn-primary">Register</button>
|
||||
<button type="submit" class="btn btn-primary">{{ t('Register') }}</button>
|
||||
</form>
|
||||
<p class="form-footer">
|
||||
Already have an account? <a href="{{ url_for('auth.login') }}">Log in</a>
|
||||
{{ t('Already have an account?') }} <a href="{{ url_for('auth.login') }}">{{ t('Log in') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user