diff --git a/app.py b/app.py index 0f32968..807be11 100644 --- a/app.py +++ b/app.py @@ -8,10 +8,45 @@ from flask import Flask, render_template, request, redirect, url_for, flash import sqlite3 import os from datetime import datetime +from flask_babel import Babel, gettext, lazy_gettext app = Flask(__name__) app.secret_key = 'your_secret_key_here' +# Configure supported languages +app.config['BABEL_DEFAULT_LOCALE'] = 'es' +app.config['LANGUAGES'] = { + 'en': 'English', + 'es': 'Español' +} + +# Configure translation directory +app.config['BABEL_TRANSLATION_DIRECTORIES'] = 'translations' + +# Locale selector function +def get_locale(): + # Try to get language from URL parameter + lang = request.args.get('lang') + if lang and lang in app.config['LANGUAGES']: + return lang + # Try to get from session + if hasattr(request, 'session') and 'language' in request.session: + return request.session['language'] + # Try to get from browser preferences + return request.accept_languages.best_match(app.config['LANGUAGES'].keys()) + +# Initialize Babel with locale selector +babel = Babel(app, locale_selector=get_locale) + +# Context processor to make language and config available in templates +@app.context_processor +def inject_global_variables(): + lang = get_locale() + return { + 'language': lang, + 'config': app.config + } + # Database setup DATABASE = 'tablets.db' diff --git a/assets/Schamann.png b/assets/Schamann.png new file mode 100644 index 0000000..8292d12 Binary files /dev/null and b/assets/Schamann.png differ diff --git a/compile_translations.py b/compile_translations.py new file mode 100644 index 0000000..4f165ba --- /dev/null +++ b/compile_translations.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +""" +Compile .po files to .mo files for Flask-Babel +""" +import os +from pathlib import Path + +def compile_translations(): + translations_dir = Path('translations') + + for lang_dir in translations_dir.iterdir(): + if lang_dir.is_dir(): + lc_messages_dir = lang_dir / 'LC_MESSAGES' + if lc_messages_dir.exists(): + for po_file in lc_messages_dir.glob('*.po'): + mo_file = lc_messages_dir / f"{po_file.stem}.mo" + print(f"Compiling {po_file} -> {mo_file}") + # Use msgfmt to compile + os.system(f"msgfmt -o {mo_file} {po_file}") + + print("✅ All translations compiled!") + +if __name__ == '__main__': + compile_translations() diff --git a/extract_translations.py b/extract_translations.py new file mode 100644 index 0000000..6367e78 --- /dev/null +++ b/extract_translations.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +""" +Extract strings for translation and create Spanish .po files +""" +import os +import re +from pathlib import Path + +# Directories +TEMPLATES_DIR = Path('templates') +TRANSLATIONS_DIR = Path('translations') + +# Find all template files +def find_template_files(): + template_files = [] + for root, dirs, files in os.walk(TEMPLATES_DIR): + for file in files: + if file.endswith('.html'): + template_files.append(Path(root) / file) + return template_files + +# Extract strings from templates +def extract_strings_from_file(filepath): + strings = set() + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + + # Find all _('...') and gettext('...') calls + pattern = r"[\"'](?:_|gettext)\([\"']([^\"']+)[\"']\)" + matches = re.findall(r"\{\{\s*_\('([^']+)'\)\s*\}\}", content) + matches += re.findall(r"\{\{\s*gettext\('([^']+)'\)\s*\}\}", content) + + return matches + +# Create .pot file +def create_pot_file(strings): + pot_content = """msgid "" +msgstr "" +"Project-Id-Version: Tablet Management System\n" +"POT-Creation-Date: 2026-06-20\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: en\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +""" + + for string in sorted(strings): + pot_content += f'msgid "{string}"\n' + pot_content += 'msgstr ""\n\n' + + return pot_content + +# Create Spanish .po file +def create_es_po_file(strings): + # Spanish translations + translations = { + # Navigation + 'Tablet Management System': 'Sistema de Gestión de Tablets', + 'Home': 'Inicio', + 'Add Tablet': 'Añadir Tablet', + 'Add User': 'Añadir Usuario', + 'Loan Tablet': 'Prestar Tablet', + 'History': 'Historial', + 'User Loans': 'Préstamos por Usuario', + 'Non-Loanable Devices': 'Dispositivos No Prestables', + 'Project Management': 'Gestión de Proyectos', + + # Language + 'Language': 'Idioma', + 'English': 'Inglés', + 'Español': 'Español', + + # Common + 'Search': 'Buscar', + 'Filter': 'Filtrar', + 'Status': 'Estado', + 'Actions': 'Acciones', + 'Details': 'Detalles', + 'View': 'Ver', + 'All': 'Todos', + 'Active': 'Activo', + 'Inactive': 'Inactivo', + 'Available': 'Disponible', + 'Returned': 'Devuelto', + 'Never': 'Nunca', + 'N/A': 'N/D', + 'Yes': 'Sí', + 'No': 'No', + 'Showing': 'Mostrando', + 'to': 'a', + 'of': 'de', + 'Previous': 'Anterior', + 'Next': 'Siguiente', + 'Page': 'Página', + + # User Loans + 'User Loans': 'Préstamos por Usuario', + 'Search Users': 'Buscar Usuarios', + 'Loan Status': 'Estado de Préstamo', + 'All Users': 'Todos los Usuarios', + 'With Active Loans': 'Con Préstamos Activos', + 'No Active Loans': 'Sin Préstamos Activos', + 'Search by name or identification...': 'Buscar por nombre o identificación...', + 'users': 'usuarios', + 'No users found': 'No se encontraron usuarios', + 'No users match your search for': 'Ningún usuario coincide con tu búsqueda de', + 'Clear Filters': 'Limpiar Filtros', + + # Table Headers + 'User': 'Usuario', + 'Identification': 'Identificación', + 'Active Loans': 'Préstamos Activos', + 'Last Loan Date': 'Fecha del Último Préstamo', + 'Tablet': 'Tablet', + 'Model': 'Modelo', + 'Serial Number': 'Número de Serie', + 'Notes': 'Notas', + 'Borrower': 'Prestatario', + 'Loan Date': 'Fecha de Préstamo', + 'Return Date': 'Fecha de Devolución', + 'Not returned': 'No devuelto', + + # Buttons + 'Loan': 'Prestar', + 'Return': 'Devolver', + 'Add': 'Añadir', + 'Save': 'Guardar', + 'Cancel': 'Cancelar', + 'Delete': 'Eliminar', + 'Edit': 'Editar', + 'Update': 'Actualizar', + + # Messages + 'Tablet Management System - Internal Tool': 'Sistema de Gestión de Tablets - Herramienta Interna', + 'No available tablets': 'No hay tablets disponibles', + 'No active loans': 'No hay préstamos activos', + 'No loan history for this user': 'Este usuario no tiene historial de préstamos', + + # Forms + 'Brand': 'Marca', + 'Model': 'Modelo', + 'Serial Number': 'Número de Serie', + 'Name': 'Nombre', + 'Email': 'Correo Electrónico', + 'Phone': 'Teléfono', + 'Identification': 'Identificación', + 'Date': 'Fecha', + 'Notes': 'Notas', + 'Required field': 'Campo obligatorio', + + # Status + 'Active Loans': 'Préstamos Activos', + 'Returned': 'Devueltos', + } + + po_content = """msgid "" +msgstr "" +"Project-Id-Version: Tablet Management System\n" +"POT-Creation-Date: 2026-06-20\n" +"PO-Revision-Date: 2026-06-20\n" +"Last-Translator: Auto-generated\n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +""" + + for string in sorted(strings): + msgid = string + msgstr = translations.get(string, '') + po_content += f'msgid "{msgid}"\n' + po_content += f'msgstr "{msgstr}"\n\n' + + return po_content + +# Main +def main(): + print("Extracting strings for translation...") + + # Find all template files + template_files = find_template_files() + print(f"Found {len(template_files)} template files") + + # Extract all strings + all_strings = set() + for filepath in template_files: + strings = extract_strings_from_file(filepath) + all_strings.update(strings) + + print(f"Found {len(all_strings)} unique strings") + + # Create .pot file + pot_content = create_pot_file(all_strings) + pot_path = TRANSLATIONS_DIR / 'messages.pot' + with open(pot_path, 'w', encoding='utf-8') as f: + f.write(pot_content) + print(f"Created {pot_path}") + + # Create Spanish .po file + es_po_content = create_es_po_file(all_strings) + es_dir = TRANSLATIONS_DIR / 'es' / 'LC_MESSAGES' + es_dir.mkdir(parents=True, exist_ok=True) + es_po_path = es_dir / 'messages.po' + with open(es_po_path, 'w', encoding='utf-8') as f: + f.write(es_po_content) + print(f"Created {es_po_path}") + + # Create English .po file + en_dir = TRANSLATIONS_DIR / 'en' / 'LC_MESSAGES' + en_dir.mkdir(parents=True, exist_ok=True) + en_po_path = en_dir / 'messages.po' + with open(en_po_path, 'w', encoding='utf-8') as f: + f.write(create_pot_file(all_strings)) + print(f"Created {en_po_path}") + + print("\n✅ Translation files created!") + +if __name__ == '__main__': + main() diff --git a/templates/base.html b/templates/base.html index a35f0b3..d62041d 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,9 +1,9 @@ - + - Tablet Management System + {{ _('Tablet Management System') }} @@ -48,16 +48,16 @@ extend: { colors: { primary: { - 50: '#f0fdf4', - 100: '#dcfce7', - 200: '#bbf7d0', - 300: '#86efac', - 400: '#4ade80', - 500: '#22c55e', - 600: '#16a34a', - 700: '#15803d', - 800: '#166534', - 900: '#14532d', + 50: '#ecfdf5', + 100: '#d1fae5', + 200: '#a7f3d0', + 300: '#6ee7b7', + 400: '#34d399', + 500: '#10b981', + 600: '#059669', + 700: '#047857', + 800: '#065f46', + 900: '#064e3b', } } } @@ -65,25 +65,68 @@ } - -
+ + + Skip to main content + + +
-
-

- Tablet Management System -

- +
+
+
+ +
+ + + +

{{ _('Tablet Management System') }}

+
+ + +
+ {{ _('Language') }}: + {% for lang_code, lang_name in config['LANGUAGES'].items() %} + + {{ lang_name }} + + {% endfor %} +
+
+
+
+ + +
{% with messages = get_flashed_messages(with_categories=true) %} {% if messages %}
- - -
+ + {% block content %}{% endblock %}
+ + +
+
+

{{ _('Tablet Management System - Internal Tool') }}

+
+
diff --git a/templates/components/user_loans_results.html b/templates/components/user_loans_results.html index ffb87a3..fbdc7ce 100644 --- a/templates/components/user_loans_results.html +++ b/templates/components/user_loans_results.html @@ -7,19 +7,19 @@ - User + {{ _('User') }} - Identification + {{ _('Identification') }} - Active Loans + {{ _('Active Loans') }} - Last Loan Date + {{ _('Last Loan Date') }} - Actions + {{ _('Actions') }} @@ -30,7 +30,7 @@
{{ user.name }}
- {{ user.identification or 'N/A' }} + {{ user.identification or _('N/A') }} - {{ user.last_loan_date or 'Never' }} + {{ user.last_loan_date or _('Never') }} - View Details + {{ _('View Details') }} @@ -60,9 +60,9 @@
@@ -186,21 +186,21 @@ -

No users found

+

{{ _('No users found') }}

{% if search %} - No users match your search for "{{ search }}" + {{ _('No users match your search for') }} "{{ search }}" {% elif status == 'with_loans' %} - No users have active loans + {{ _('No users have active loans') }} {% elif status == 'no_loans' %} - All users have at least one active loan + {{ _('All users have at least one active loan') }} {% else %} - There are no users in the system yet + {{ _('There are no users in the system yet') }} {% endif %}

- Clear Filters + {{ _('Clear Filters') }} {%- endif %} diff --git a/templates/index.html b/templates/index.html index fd904ef..5f0cc43 100644 --- a/templates/index.html +++ b/templates/index.html @@ -10,7 +10,7 @@ - Available Tablets + {{ _('Available Tablets') }} {{ available_tablets|length }} @@ -23,19 +23,19 @@ - Brand + {{ _('Brand') }} - Model + {{ _('Model') }} - Serial Number + {{ _('Serial Number') }} - Notes + {{ _('Notes') }} - Actions + {{ _('Actions') }} @@ -61,7 +61,7 @@ - Loan + {{ _('Loan') }} @@ -75,10 +75,10 @@ -

No available tablets

+

{{ _('No available tablets') }}

- Add Tablet + {{ _('Add Tablet') }} {% endif %} @@ -92,7 +92,7 @@ - Active Loans + {{ _('Active Loans') }} {{ active_loans|length }} @@ -105,19 +105,19 @@ - Tablet + {{ _('Tablet') }} - Serial Number + {{ _('Serial Number') }} - Borrower + {{ _('Borrower') }} - Loan Date + {{ _('Loan Date') }} - Actions + {{ _('Actions') }} @@ -135,7 +135,7 @@
{{ loan.identification }}
- {{ loan.loan_date[:10] if loan.loan_date else 'N/A' }} + {{ loan.loan_date[:10] if loan.loan_date else _('N/A') }} {{ loan.loan_date[11:16] if loan.loan_date else '' }} @@ -145,7 +145,7 @@ - Return + {{ _('Return') }} @@ -159,7 +159,7 @@ -

No active loans

+

{{ _('No active loans') }}

{% endif %} diff --git a/templates/user_loans.html b/templates/user_loans.html index 325eb39..a4aad1d 100644 --- a/templates/user_loans.html +++ b/templates/user_loans.html @@ -4,9 +4,9 @@
-

User Loans

+

{{ _('User Loans') }}

- {{ total_users }} users + {{ total_users }} {{ _('users') }}
@@ -24,7 +24,7 @@
@@ -46,16 +46,16 @@
@@ -67,7 +67,7 @@ - Search + {{ _('Search') }}
@@ -83,7 +83,6 @@