feat(i18n): add Spanish localization support with Flask-Babel

- Add Flask-Babel for internationalization
- Configure app for Spanish (default) and English
- Add language switcher in navigation
- Wrap all UI text in templates with gettext()
- Create translation directory structure
- Add complete Spanish translations (58 strings)
- Add English translations (template)
- Compile .po to .mo files
- Add extract_translations.py script for future updates
- Add compile_translations.py script

Spanish is now the default language, with English available via ?lang=en

Translated strings include:
- Navigation (Home, Add Tablet, Add User, etc.)
- User Loans interface (Search, Filter, Status, etc.)
- Table headers (User, Tablet, Serial Number, etc.)
- Buttons (Loan, Return, Search, etc.)
- Messages (No users found, No active loans, etc.)
- All UI text across all templates
This commit is contained in:
ijuanes 2026-06-20 09:58:13 +01:00
parent db7e9591e1
commit 34dd3ca937
13 changed files with 1020 additions and 110 deletions

View file

@ -4,9 +4,9 @@
<div class="space-y-6">
<!-- Page Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<h2 class="text-xl font-semibold text-gray-800">User Loans</h2>
<h2 class="text-xl font-semibold text-gray-800">{{ _('User Loans') }}</h2>
<div class="text-sm text-gray-500">
<span id="result-count" hx-swap-oob="true">{{ total_users }} users</span>
<span id="result-count" hx-swap-oob="true">{{ total_users }} {{ _('users') }}</span>
</div>
</div>
@ -24,7 +24,7 @@
<!-- Search Input -->
<div class="md:col-span-2">
<label for="search" class="block text-sm font-medium text-gray-700 mb-1">
Search Users
{{ _('Search Users') }}
</label>
<div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"
@ -37,7 +37,7 @@
id="search"
name="search"
value="{{ search or '' }}"
placeholder="Search by name or identification..."
placeholder="{{ _('Search by name or identification...') }}"
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-green-500 focus:border-green-500"
>
</div>
@ -46,16 +46,16 @@
<!-- Status Filter -->
<div>
<label for="status" class="block text-sm font-medium text-gray-700 mb-1">
Loan Status
{{ _('Loan Status') }}
</label>
<select
id="status"
name="status"
class="w-full p-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-green-500 focus:border-green-500"
>
<option value="" {% if not status %}selected{% endif %}>All Users</option>
<option value="with_loans" {% if status == 'with_loans' %}selected{% endif %}>With Active Loans</option>
<option value="no_loans" {% if status == 'no_loans' %}selected{% endif %}>No Active Loans</option>
<option value="" {% if not status %}selected{% endif %}>{{ _('All Users') }}</option>
<option value="with_loans" {% if status == 'with_loans' %}selected{% endif %}>{{ _('With Active Loans') }}</option>
<option value="no_loans" {% if status == 'no_loans' %}selected{% endif %}>{{ _('No Active Loans') }}</option>
</select>
</div>
</div>
@ -67,7 +67,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg>
Search
{{ _('Search') }}
</button>
</form>
</div>
@ -83,7 +83,6 @@
<!-- HTMX Script to handle URL updates -->
<script>
// Update browser URL when HTMX does a GET request
document.body.addEventListener('htmx:afterRequest', function(evt) {
if (evt.detail.requestConfig.method === 'get' && evt.detail.successful) {
// Update URL without reload
@ -103,7 +102,6 @@ document.body.addEventListener('htmx:afterRequest', function(evt) {
}
});
// Restore form state from URL on page load
document.addEventListener('DOMContentLoaded', function() {
const urlParams = new URLSearchParams(window.location.search);
const form = document.getElementById('search-form');