Compare commits

...

4 commits

Author SHA1 Message Date
ijuanes
c23304052a Add corporative logo 2026-06-20 12:28:58 +01:00
ijuanes
49e4562d31 fix(i18n): define get_locale function before passing to Babel constructor
- Flask-Babel 4.0.0 requires locale_selector function to be defined first
- Move function definition before Babel initialization
- All tests still pass
2026-06-20 10:04:53 +01:00
ijuanes
6282bb9789 fix(i18n): use babel.locale_selector instead of babel.localeselector for Flask-Babel 4.0.0
- Flask-Babel 4.0.0 changed the API
- @babel.localeselector -> @babel.locale_selector
- Remove redundant request import in context processor
- All tests still pass
2026-06-20 10:01:38 +01:00
ijuanes
34dd3ca937 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
2026-06-20 09:58:13 +01:00
14 changed files with 1019 additions and 110 deletions

35
app.py
View file

@ -8,10 +8,45 @@ from flask import Flask, render_template, request, redirect, url_for, flash
import sqlite3 import sqlite3
import os import os
from datetime import datetime from datetime import datetime
from flask_babel import Babel, gettext, lazy_gettext
app = Flask(__name__) app = Flask(__name__)
app.secret_key = 'your_secret_key_here' 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 setup
DATABASE = 'tablets.db' DATABASE = 'tablets.db'

BIN
assets/Schamann.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

24
compile_translations.py Normal file
View file

@ -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()

226
extract_translations.py Normal file
View file

@ -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': '',
'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()

View file

@ -1,9 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="{{ language or 'en' }}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tablet Management System</title> <title>{{ _('Tablet Management System') }}</title>
<!-- Tailwind CSS via CDN --> <!-- Tailwind CSS via CDN -->
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
@ -48,16 +48,16 @@
extend: { extend: {
colors: { colors: {
primary: { primary: {
50: '#f0fdf4', 50: '#ecfdf5',
100: '#dcfce7', 100: '#d1fae5',
200: '#bbf7d0', 200: '#a7f3d0',
300: '#86efac', 300: '#6ee7b7',
400: '#4ade80', 400: '#34d399',
500: '#22c55e', 500: '#10b981',
600: '#16a34a', 600: '#059669',
700: '#15803d', 700: '#047857',
800: '#166534', 800: '#065f46',
900: '#14532d', 900: '#064e3b',
} }
} }
} }
@ -65,25 +65,68 @@
} }
</script> </script>
</head> </head>
<body class="bg-gray-50 min-h-screen"> <body class="bg-gray-50 text-gray-900">
<div class="max-w-7xl mx-auto p-4 sm:p-6 lg:p-8"> <!-- Skip to main content -->
<a href="#main-content" class="sr-only focus:not-sr-only">Skip to main content</a>
<!-- Container -->
<div class="min-h-screen">
<!-- Header --> <!-- Header -->
<header class="mb-8"> <header class="bg-primary-600 text-white shadow-lg">
<h1 class="text-2xl sm:text-3xl font-bold text-gray-800 text-center mb-6"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
Tablet Management System <div class="flex items-center justify-between h-16">
</h1> <!-- Logo / Title -->
<div class="flex items-center gap-3">
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path>
</svg>
<h1 class="text-xl font-bold">{{ _('Tablet Management System') }}</h1>
</div>
<!-- Language Switcher -->
<div class="flex items-center gap-2">
<span class="text-sm opacity-80">{{ _('Language') }}:</span>
{% for lang_code, lang_name in config['LANGUAGES'].items() %}
<a
href="?lang={{ lang_code }}"
class="px-2 py-1 text-sm rounded hover:bg-white/20 transition-colors
{% if language == lang_code %}bg-white/30 font-medium{% endif %}"
>
{{ lang_name }}
</a>
{% endfor %}
</div>
</div>
</div>
</header>
<!-- Main Content -->
<main id="main-content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Flash Messages --> <!-- Flash Messages -->
{% with messages = get_flashed_messages(with_categories=true) %} {% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %} {% if messages %}
<div class="mb-6 space-y-3"> <div class="mb-6 space-y-3">
{% for category, message in messages %} {% for category, message in messages %}
<div class="p-4 rounded-md <div class="p-4 rounded-lg
{% if category == 'success' %}bg-green-100 text-green-800 border border-green-200 {% if category == 'success' %}bg-green-100 text-green-800
{% elif category == 'error' %}bg-red-100 text-red-800 border border-red-200 {% elif category == 'error' %}bg-red-100 text-red-800
{% else %}bg-blue-100 text-blue-800 border border-blue-200 {% elif category == 'warning' %}bg-yellow-100 text-yellow-800
{% endif %}"> {% else %}bg-blue-100 text-blue-800{% endif %}">
{{ message }} <div class="flex items-center gap-2">
{% if category == 'success' %}
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M5 13l4 4L19 7"></path>
</svg>
{% elif category == 'error' %}
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
{% endif %}
<span>{{ _(message) }}</span>
</div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@ -91,50 +134,55 @@
{% endwith %} {% endwith %}
<!-- Navigation --> <!-- Navigation -->
<nav class="bg-green-600 rounded-lg shadow-md mb-6"> <nav class="bg-primary-600 rounded-lg shadow-md mb-6" aria-label="Main navigation">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-wrap justify-center sm:justify-start gap-1 sm:gap-2 py-3"> <div class="flex flex-wrap justify-center sm:justify-start gap-1 sm:gap-2 py-3">
<a href="/" <a href="/"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-green-700 transition-colors whitespace-nowrap"> class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-primary-700 transition-colors whitespace-nowrap">
Home {{ _('Home') }}
</a> </a>
<a href="/add_tablet" <a href="/add_tablet"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-green-700 transition-colors whitespace-nowrap"> class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-primary-700 transition-colors whitespace-nowrap">
Add Tablet {{ _('Add Tablet') }}
</a> </a>
<a href="/add_user" <a href="/add_user"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-green-700 transition-colors whitespace-nowrap"> class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-primary-700 transition-colors whitespace-nowrap">
Add User {{ _('Add User') }}
</a> </a>
<a href="/loan_tablet" <a href="/loan_tablet"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-green-700 transition-colors whitespace-nowrap"> class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-primary-700 transition-colors whitespace-nowrap">
Loan Tablet {{ _('Loan Tablet') }}
</a> </a>
<a href="/history" <a href="/history"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-green-700 transition-colors whitespace-nowrap"> class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-primary-700 transition-colors whitespace-nowrap">
Loan History {{ _('History') }}
</a> </a>
<a href="/user_loans" <a href="/user_loans"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-green-700 transition-colors whitespace-nowrap"> class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-primary-700 transition-colors whitespace-nowrap">
User Loans {{ _('User Loans') }}
</a> </a>
<a href="/non_loanable_devices" <a href="/non_loanable_devices"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-green-700 transition-colors whitespace-nowrap"> class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-primary-700 transition-colors whitespace-nowrap">
Non-Loanable Devices {{ _('Non-Loanable Devices') }}
</a> </a>
<a href="/project_management" <a href="/project_management"
class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-green-700 transition-colors whitespace-nowrap"> class="px-3 py-2 text-sm sm:text-base text-white rounded hover:bg-primary-700 transition-colors whitespace-nowrap">
Project Management {{ _('Project Management') }}
</a> </a>
</div> </div>
</div> </div>
</nav> </nav>
</header>
<!-- Page Content -->
<!-- Main Content -->
<main>
{% block content %}{% endblock %} {% block content %}{% endblock %}
</main> </main>
<!-- Footer -->
<footer class="mt-12 py-6 border-t border-gray-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center text-sm text-gray-500">
<p>{{ _('Tablet Management System - Internal Tool') }}</p>
</div>
</footer>
</div> </div>
</body> </body>
</html> </html>

View file

@ -7,19 +7,19 @@
<thead class="bg-green-600 text-white"> <thead class="bg-green-600 text-white">
<tr> <tr>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
User {{ _('User') }}
</th> </th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
Identification {{ _('Identification') }}
</th> </th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
Active Loans {{ _('Active Loans') }}
</th> </th>
<th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium uppercase tracking-wider">
Last Loan Date {{ _('Last Loan Date') }}
</th> </th>
<th class="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider"> <th class="px-6 py-3 text-right text-xs font-medium uppercase tracking-wider">
Actions {{ _('Actions') }}
</th> </th>
</tr> </tr>
</thead> </thead>
@ -30,7 +30,7 @@
<div class="font-medium text-gray-900">{{ user.name }}</div> <div class="font-medium text-gray-900">{{ user.name }}</div>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap text-gray-500"> <td class="px-6 py-4 whitespace-nowrap text-gray-500">
{{ user.identification or 'N/A' }} {{ user.identification or _('N/A') }}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
@ -41,12 +41,12 @@
</span> </span>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ user.last_loan_date or 'Never' }} {{ user.last_loan_date or _('Never') }}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm"> <td class="px-6 py-4 whitespace-nowrap text-right text-sm">
<a href="/user_loans/{{ user.id }}" <a href="/user_loans/{{ user.id }}"
class="text-green-600 hover:text-green-800 font-medium"> class="text-green-600 hover:text-green-800 font-medium">
View Details {{ _('View Details') }}
</a> </a>
</td> </td>
</tr> </tr>
@ -60,9 +60,9 @@
<nav class="flex items-center justify-between pt-4" aria-label="Table navigation"> <nav class="flex items-center justify-between pt-4" aria-label="Table navigation">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2">
<span class="text-sm text-gray-500"> <span class="text-sm text-gray-500">
Showing <span class="font-medium text-gray-900">{{ (page - 1) * per_page + 1 }}</span> {{ _('Showing') }} <span class="font-medium text-gray-900">{{ (page - 1) * per_page + 1 }}</span>
to <span class="font-medium text-gray-900">{{ min(page * per_page, total_users) }}</span> {{ _('to') }} <span class="font-medium text-gray-900">{{ min(page * per_page, total_users) }}</span>
of <span class="font-medium text-gray-900">{{ total_users }}</span> users {{ _('of') }} <span class="font-medium text-gray-900">{{ total_users }}</span> {{ _('users') }}
</span> </span>
</div> </div>
@ -77,11 +77,11 @@
hx-include="[name='search'], [name='status']" hx-include="[name='search'], [name='status']"
class="px-3 py-2 ml-0 leading-tight text-gray-500 bg-white border border-gray-300 rounded-l-lg hover:bg-gray-100 hover:text-gray-700" class="px-3 py-2 ml-0 leading-tight text-gray-500 bg-white border border-gray-300 rounded-l-lg hover:bg-gray-100 hover:text-gray-700"
> >
Previous {{ _('Previous') }}
</a> </a>
{%- else %} {%- else %}
<span class="px-3 py-2 ml-0 leading-tight text-gray-300 bg-white border border-gray-300 rounded-l-lg cursor-not-allowed"> <span class="px-3 py-2 ml-0 leading-tight text-gray-300 bg-white border border-gray-300 rounded-l-lg cursor-not-allowed">
Previous {{ _('Previous') }}
</span> </span>
{%- endif %} {%- endif %}
@ -168,11 +168,11 @@
hx-include="[name='search'], [name='status']" hx-include="[name='search'], [name='status']"
class="px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 rounded-r-lg hover:bg-gray-100 hover:text-gray-700" class="px-3 py-2 leading-tight text-gray-500 bg-white border border-gray-300 rounded-r-lg hover:bg-gray-100 hover:text-gray-700"
> >
Next {{ _('Next') }}
</a> </a>
{%- else %} {%- else %}
<span class="px-3 py-2 leading-tight text-gray-300 bg-white border border-gray-300 rounded-r-lg cursor-not-allowed"> <span class="px-3 py-2 leading-tight text-gray-300 bg-white border border-gray-300 rounded-r-lg cursor-not-allowed">
Next {{ _('Next') }}
</span> </span>
{%- endif %} {%- endif %}
</div> </div>
@ -186,21 +186,21 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path> d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg> </svg>
<h3 class="text-lg font-medium text-gray-900 mb-2">No users found</h3> <h3 class="text-lg font-medium text-gray-900 mb-2">{{ _('No users found') }}</h3>
<p class="text-gray-500 mb-4"> <p class="text-gray-500 mb-4">
{% if search %} {% if search %}
No users match your search for "{{ search }}" {{ _('No users match your search for') }} "{{ search }}"
{% elif status == 'with_loans' %} {% elif status == 'with_loans' %}
No users have active loans {{ _('No users have active loans') }}
{% elif status == 'no_loans' %} {% elif status == 'no_loans' %}
All users have at least one active loan {{ _('All users have at least one active loan') }}
{% else %} {% else %}
There are no users in the system yet {{ _('There are no users in the system yet') }}
{% endif %} {% endif %}
</p> </p>
<a href="/user_loans" <a href="/user_loans"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 transition-colors"> class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-green-600 hover:bg-green-700 transition-colors">
Clear Filters {{ _('Clear Filters') }}
</a> </a>
</div> </div>
{%- endif %} {%- endif %}

View file

@ -10,7 +10,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path> d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path>
</svg> </svg>
Available Tablets {{ _('Available Tablets') }}
<span class="ml-2 px-2 py-1 bg-green-100 text-green-800 text-xs font-medium rounded-full"> <span class="ml-2 px-2 py-1 bg-green-100 text-green-800 text-xs font-medium rounded-full">
{{ available_tablets|length }} {{ available_tablets|length }}
</span> </span>
@ -23,19 +23,19 @@
<thead class="bg-gray-50"> <thead class="bg-gray-50">
<tr> <tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Brand {{ _('Brand') }}
</th> </th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Model {{ _('Model') }}
</th> </th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Serial Number {{ _('Serial Number') }}
</th> </th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Notes {{ _('Notes') }}
</th> </th>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions {{ _('Actions') }}
</th> </th>
</tr> </tr>
</thead> </thead>
@ -61,7 +61,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path> d="M12 6v6m0 0v6m0-6h6m-6 0H6"></path>
</svg> </svg>
Loan {{ _('Loan') }}
</a> </a>
</td> </td>
</tr> </tr>
@ -75,10 +75,10 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path> d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4"></path>
</svg> </svg>
<p>No available tablets</p> <p>{{ _('No available tablets') }}</p>
<a href="/add_tablet" <a href="/add_tablet"
class="mt-4 inline-block px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700"> class="mt-4 inline-block px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700">
Add Tablet {{ _('Add Tablet') }}
</a> </a>
</div> </div>
{% endif %} {% endif %}
@ -92,7 +92,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path> d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg> </svg>
Active Loans {{ _('Active Loans') }}
<span class="ml-2 px-2 py-1 bg-green-100 text-green-800 text-xs font-medium rounded-full"> <span class="ml-2 px-2 py-1 bg-green-100 text-green-800 text-xs font-medium rounded-full">
{{ active_loans|length }} {{ active_loans|length }}
</span> </span>
@ -105,19 +105,19 @@
<thead class="bg-gray-50"> <thead class="bg-gray-50">
<tr> <tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tablet {{ _('Tablet') }}
</th> </th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Serial Number {{ _('Serial Number') }}
</th> </th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Borrower {{ _('Borrower') }}
</th> </th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Loan Date {{ _('Loan Date') }}
</th> </th>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions {{ _('Actions') }}
</th> </th>
</tr> </tr>
</thead> </thead>
@ -135,7 +135,7 @@
<div class="text-sm text-gray-500">{{ loan.identification }}</div> <div class="text-sm text-gray-500">{{ loan.identification }}</div>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ 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 '' }} {{ loan.loan_date[11:16] if loan.loan_date else '' }}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap text-right"> <td class="px-6 py-4 whitespace-nowrap text-right">
@ -145,7 +145,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 5l7 7-7 7"></path> d="M9 5l7 7-7 7"></path>
</svg> </svg>
Return {{ _('Return') }}
</a> </a>
</td> </td>
</tr> </tr>
@ -159,7 +159,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path> d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg> </svg>
<p>No active loans</p> <p>{{ _('No active loans') }}</p>
</div> </div>
{% endif %} {% endif %}
</div> </div>

View file

@ -4,9 +4,9 @@
<div class="space-y-6"> <div class="space-y-6">
<!-- Page Header --> <!-- Page Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"> <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"> <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>
</div> </div>
@ -24,7 +24,7 @@
<!-- Search Input --> <!-- Search Input -->
<div class="md:col-span-2"> <div class="md:col-span-2">
<label for="search" class="block text-sm font-medium text-gray-700 mb-1"> <label for="search" class="block text-sm font-medium text-gray-700 mb-1">
Search Users {{ _('Search Users') }}
</label> </label>
<div class="relative"> <div class="relative">
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" <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" id="search"
name="search" name="search"
value="{{ search or '' }}" 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" 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> </div>
@ -46,16 +46,16 @@
<!-- Status Filter --> <!-- Status Filter -->
<div> <div>
<label for="status" class="block text-sm font-medium text-gray-700 mb-1"> <label for="status" class="block text-sm font-medium text-gray-700 mb-1">
Loan Status {{ _('Loan Status') }}
</label> </label>
<select <select
id="status" id="status"
name="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" 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="" {% if not status %}selected{% endif %}>{{ _('All Users') }}</option>
<option value="with_loans" {% if status == 'with_loans' %}selected{% endif %}>With Active Loans</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="no_loans" {% if status == 'no_loans' %}selected{% endif %}>{{ _('No Active Loans') }}</option>
</select> </select>
</div> </div>
</div> </div>
@ -67,7 +67,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <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> d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
</svg> </svg>
Search {{ _('Search') }}
</button> </button>
</form> </form>
</div> </div>
@ -83,7 +83,6 @@
<!-- HTMX Script to handle URL updates --> <!-- HTMX Script to handle URL updates -->
<script> <script>
// Update browser URL when HTMX does a GET request
document.body.addEventListener('htmx:afterRequest', function(evt) { document.body.addEventListener('htmx:afterRequest', function(evt) {
if (evt.detail.requestConfig.method === 'get' && evt.detail.successful) { if (evt.detail.requestConfig.method === 'get' && evt.detail.successful) {
// Update URL without reload // 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() { document.addEventListener('DOMContentLoaded', function() {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const form = document.getElementById('search-form'); const form = document.getElementById('search-form');

View file

@ -10,7 +10,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 19l-7-7 7-7"></path> d="M15 19l-7-7 7-7"></path>
</svg> </svg>
Back to All Users {{ _('Back to All Users') }}
</a> </a>
</div> </div>
@ -25,16 +25,16 @@
<div class="flex-1"> <div class="flex-1">
<h1 class="text-xl font-bold text-gray-900">{{ user.name }}</h1> <h1 class="text-xl font-bold text-gray-900">{{ user.name }}</h1>
<p class="text-gray-500"> <p class="text-gray-500">
ID: {{ user.id }} | Identification: {{ user.identification or 'N/A' }} {{ _('ID') }}: {{ user.id }} | {{ _('Identification') }}: {{ user.identification or _('N/A') }}
</p> </p>
<div class="flex gap-4 mt-2"> <div class="flex gap-4 mt-2">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="w-3 h-3 bg-green-500 rounded-full"></span> <span class="w-3 h-3 bg-green-500 rounded-full"></span>
<span class="text-sm text-gray-600">{{ active_count }} Active Loans</span> <span class="text-sm text-gray-600">{{ active_count }} {{ _('Active Loans') }}</span>
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="w-3 h-3 bg-gray-400 rounded-full"></span> <span class="w-3 h-3 bg-gray-400 rounded-full"></span>
<span class="text-sm text-gray-600">{{ returned_count }} Returned</span> <span class="text-sm text-gray-600">{{ returned_count }} {{ _('Returned') }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -42,13 +42,13 @@
{% if user.email %} {% if user.email %}
<a href="mailto:{{ user.email }}" <a href="mailto:{{ user.email }}"
class="px-3 py-1 bg-blue-100 text-blue-700 rounded-lg text-sm hover:bg-blue-200"> class="px-3 py-1 bg-blue-100 text-blue-700 rounded-lg text-sm hover:bg-blue-200">
Email {{ _('Email') }}
</a> </a>
{% endif %} {% endif %}
{% if user.phone %} {% if user.phone %}
<a href="tel:{{ user.phone }}" <a href="tel:{{ user.phone }}"
class="px-3 py-1 bg-purple-100 text-purple-700 rounded-lg text-sm hover:bg-purple-200"> class="px-3 py-1 bg-purple-100 text-purple-700 rounded-lg text-sm hover:bg-purple-200">
Call {{ _('Call') }}
</a> </a>
{% endif %} {% endif %}
</div> </div>
@ -58,7 +58,7 @@
<!-- Loan History --> <!-- Loan History -->
<div class="bg-white rounded-lg shadow"> <div class="bg-white rounded-lg shadow">
<div class="p-6 border-b border-gray-200"> <div class="p-6 border-b border-gray-200">
<h2 class="text-lg font-semibold text-gray-800">Loan History</h2> <h2 class="text-lg font-semibold text-gray-800">{{ _('Loan History') }}</h2>
</div> </div>
{% if loans %} {% if loans %}
@ -67,16 +67,16 @@
<thead class="bg-gray-50"> <thead class="bg-gray-50">
<tr> <tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Tablet {{ _('Tablet') }}
</th> </th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Loan Date {{ _('Loan Date') }}
</th> </th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Return Date {{ _('Return Date') }}
</th> </th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Status {{ _('Status') }}
</th> </th>
</tr> </tr>
</thead> </thead>
@ -88,14 +88,14 @@
<div class="text-sm text-gray-500">{{ loan.serial_number }}</div> <div class="text-sm text-gray-500">{{ loan.serial_number }}</div>
</td> </td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{{ 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 '' }} {{ loan.loan_date[11:16] if loan.loan_date else '' }}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
{% if loan.return_date %} {% if loan.return_date %}
{{ loan.return_date[:10] }} {{ loan.return_date[11:16] }} {{ loan.return_date[:10] }} {{ loan.return_date[11:16] }}
{% else %} {% else %}
<span class="text-gray-400 italic">Not returned</span> <span class="text-gray-400 italic">{{ _('Not returned') }}</span>
{% endif %} {% endif %}
</td> </td>
<td class="px-6 py-4 whitespace-nowrap"> <td class="px-6 py-4 whitespace-nowrap">
@ -118,7 +118,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path> d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
</svg> </svg>
<p>No loan history for this user</p> <p>{{ _('No loan history for this user') }}</p>
</div> </div>
{% endif %} {% endif %}
</div> </div>

Binary file not shown.

View file

@ -0,0 +1,191 @@
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: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "Actions"
msgstr ""
msgid "Active Loans"
msgstr ""
msgid "Add Tablet"
msgstr ""
msgid "Add User"
msgstr ""
msgid "All Users"
msgstr ""
msgid "All users have at least one active loan"
msgstr ""
msgid "Available Tablets"
msgstr ""
msgid "Back to All Users"
msgstr ""
msgid "Borrower"
msgstr ""
msgid "Brand"
msgstr ""
msgid "Call"
msgstr ""
msgid "Clear Filters"
msgstr ""
msgid "Email"
msgstr ""
msgid "English"
msgstr ""
msgid "Español"
msgstr ""
msgid "History"
msgstr ""
msgid "Home"
msgstr ""
msgid "ID"
msgstr ""
msgid "Identification"
msgstr ""
msgid "Language"
msgstr ""
msgid "Last Loan Date"
msgstr ""
msgid "Loan"
msgstr ""
msgid "Loan Date"
msgstr ""
msgid "Loan History"
msgstr ""
msgid "Loan Status"
msgstr ""
msgid "Loan Tablet"
msgstr ""
msgid "Model"
msgstr ""
msgid "Next"
msgstr ""
msgid "No Active Loans"
msgstr ""
msgid "No active loans"
msgstr ""
msgid "No available tablets"
msgstr ""
msgid "No loan history for this user"
msgstr ""
msgid "No users found"
msgstr ""
msgid "No users have active loans"
msgstr ""
msgid "No users match your search for"
msgstr ""
msgid "Non-Loanable Devices"
msgstr ""
msgid "Notes"
msgstr ""
msgid "N/A"
msgstr ""
msgid "of"
msgstr ""
msgid "Previous"
msgstr ""
msgid "Project Management"
msgstr ""
msgid "Return"
msgstr ""
msgid "Return Date"
msgstr ""
msgid "Search"
msgstr ""
msgid "Search by name or identification..."
msgstr ""
msgid "Search Users"
msgstr ""
msgid "Serial Number"
msgstr ""
msgid "Showing"
msgstr ""
msgid "Status"
msgstr ""
msgid "Tablet"
msgstr ""
msgid "Tablet Management System"
msgstr ""
msgid "Tablet Management System - Internal Tool"
msgstr ""
msgid "There are no users in the system yet"
msgstr ""
msgid "to"
msgstr ""
msgid "User"
msgstr ""
msgid "User Loans"
msgstr ""
msgid "users"
msgstr ""
msgid "View"
msgstr ""
msgid "View Details"
msgstr ""
msgid "With Active Loans"
msgstr ""

Binary file not shown.

View file

@ -0,0 +1,192 @@
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"
msgid "Actions"
msgstr "Acciones"
msgid "Active Loans"
msgstr "Préstamos Activos"
msgid "Add Tablet"
msgstr "Añadir Tablet"
msgid "Add User"
msgstr "Añadir Usuario"
msgid "All Users"
msgstr "Todos los Usuarios"
msgid "All users have at least one active loan"
msgstr "Todos los usuarios tienen al menos un préstamo activo"
msgid "Available Tablets"
msgstr "Tablets Disponibles"
msgid "Back to All Users"
msgstr "Volver a Todos los Usuarios"
msgid "Borrower"
msgstr "Prestatario"
msgid "Brand"
msgstr "Marca"
msgid "Call"
msgstr "Llamar"
msgid "Clear Filters"
msgstr "Limpiar Filtros"
msgid "Email"
msgstr "Correo Electrónico"
msgid "English"
msgstr "Inglés"
msgid "Español"
msgstr "Español"
msgid "History"
msgstr "Historial"
msgid "Home"
msgstr "Inicio"
msgid "ID"
msgstr "ID"
msgid "Identification"
msgstr "Identificación"
msgid "Language"
msgstr "Idioma"
msgid "Last Loan Date"
msgstr "Fecha del Último Préstamo"
msgid "Loan"
msgstr "Prestar"
msgid "Loan Date"
msgstr "Fecha de Préstamo"
msgid "Loan History"
msgstr "Historial de Préstamos"
msgid "Loan Status"
msgstr "Estado de Préstamo"
msgid "Loan Tablet"
msgstr "Prestar Tablet"
msgid "Model"
msgstr "Modelo"
msgid "Next"
msgstr "Siguiente"
msgid "No Active Loans"
msgstr "Sin Préstamos Activos"
msgid "No active loans"
msgstr "No hay préstamos activos"
msgid "No available tablets"
msgstr "No hay tablets disponibles"
msgid "No loan history for this user"
msgstr "Este usuario no tiene historial de préstamos"
msgid "No users found"
msgstr "No se encontraron usuarios"
msgid "No users have active loans"
msgstr "No hay usuarios con préstamos activos"
msgid "No users match your search for"
msgstr "Ningún usuario coincide con tu búsqueda de"
msgid "Non-Loanable Devices"
msgstr "Dispositivos No Prestables"
msgid "Notes"
msgstr "Notas"
msgid "N/A"
msgstr "N/D"
msgid "of"
msgstr "de"
msgid "Previous"
msgstr "Anterior"
msgid "Project Management"
msgstr "Gestión de Proyectos"
msgid "Return"
msgstr "Devolver"
msgid "Return Date"
msgstr "Fecha de Devolución"
msgid "Search"
msgstr "Buscar"
msgid "Search by name or identification..."
msgstr "Buscar por nombre o identificación..."
msgid "Search Users"
msgstr "Buscar Usuarios"
msgid "Serial Number"
msgstr "Número de Serie"
msgid "Showing"
msgstr "Mostrando"
msgid "Status"
msgstr "Estado"
msgid "Tablet"
msgstr "Tablet"
msgid "Tablet Management System"
msgstr "Sistema de Gestión de Tablets"
msgid "Tablet Management System - Internal Tool"
msgstr "Sistema de Gestión de Tablets - Herramienta Interna"
msgid "There are no users in the system yet"
msgstr "Aún no hay usuarios en el sistema"
msgid "to"
msgstr "a"
msgid "User"
msgstr "Usuario"
msgid "User Loans"
msgstr "Préstamos por Usuario"
msgid "users"
msgstr "usuarios"
msgid "View"
msgstr "Ver"
msgid "View Details"
msgstr "Ver Detalles"
msgid "With Active Loans"
msgstr "Con Préstamos Activos"

195
translations/messages.pot Normal file
View file

@ -0,0 +1,195 @@
msgid ""
msgstr ""
"Project-Id-Version: Tablet Management System
"
"POT-Creation-Date: 2026-06-20
"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE
"
"Last-Translator:
"
"Language-Team:
"
"Language: en
"
"MIME-Version: 1.0
"
"Content-Type: text/plain; charset=UTF-8
"
"Content-Transfer-Encoding: 8bit
"
msgid "Actions"
msgstr ""
msgid "Active Loans"
msgstr ""
msgid "Add Tablet"
msgstr ""
msgid "Add User"
msgstr ""
msgid "All Users"
msgstr ""
msgid "All users have at least one active loan"
msgstr ""
msgid "Available Tablets"
msgstr ""
msgid "Back to All Users"
msgstr ""
msgid "Borrower"
msgstr ""
msgid "Brand"
msgstr ""
msgid "Call"
msgstr ""
msgid "Clear Filters"
msgstr ""
msgid "Email"
msgstr ""
msgid "History"
msgstr ""
msgid "Home"
msgstr ""
msgid "ID"
msgstr ""
msgid "Identification"
msgstr ""
msgid "Language"
msgstr ""
msgid "Last Loan Date"
msgstr ""
msgid "Loan"
msgstr ""
msgid "Loan Date"
msgstr ""
msgid "Loan History"
msgstr ""
msgid "Loan Status"
msgstr ""
msgid "Loan Tablet"
msgstr ""
msgid "Model"
msgstr ""
msgid "Next"
msgstr ""
msgid "No Active Loans"
msgstr ""
msgid "No active loans"
msgstr ""
msgid "No available tablets"
msgstr ""
msgid "No loan history for this user"
msgstr ""
msgid "No users found"
msgstr ""
msgid "No users have active loans"
msgstr ""
msgid "No users match your search for"
msgstr ""
msgid "Non-Loanable Devices"
msgstr ""
msgid "Not returned"
msgstr ""
msgid "Notes"
msgstr ""
msgid "Previous"
msgstr ""
msgid "Project Management"
msgstr ""
msgid "Return"
msgstr ""
msgid "Return Date"
msgstr ""
msgid "Returned"
msgstr ""
msgid "Search"
msgstr ""
msgid "Search Users"
msgstr ""
msgid "Search by name or identification..."
msgstr ""
msgid "Serial Number"
msgstr ""
msgid "Showing"
msgstr ""
msgid "Status"
msgstr ""
msgid "Tablet"
msgstr ""
msgid "Tablet Management System"
msgstr ""
msgid "Tablet Management System - Internal Tool"
msgstr ""
msgid "There are no users in the system yet"
msgstr ""
msgid "User"
msgstr ""
msgid "User Loans"
msgstr ""
msgid "View Details"
msgstr ""
msgid "With Active Loans"
msgstr ""
msgid "of"
msgstr ""
msgid "to"
msgstr ""
msgid "users"
msgstr ""