runique 3.0.1

A Django-inspired web framework for Rust with ORM, templates, and comprehensive security middleware
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
{
  "forms": {
    "upload_dir_error": "Error al crear el directorio de subida",
    "file_create_error": "Error al crear el archivo",
    "multipart_stream_error": "Error en el stream multipart",
    "file_write_error": "Error al escribir el archivo",
    "required": "Este campo es obligatorio",
    "too_short": "Demasiado corto (mín {})",
    "too_long": "Demasiado largo (máx {})",
    "email_invalid": "Formato de dirección de correo electrónico no válido",
    "url_invalid": "Por favor introduce una URL válida",
    "phone_invalid": "Número de teléfono inválido (dígitos, +, espacios, guiones, 7-15 dígitos)",
    "hash_error": "Error de hash: {}",
    "password_empty": "La contraseña no puede estar vacía",
    "filter_invalid_value": "El filtro static/media recibió un valor no válido ({}) en lugar de una ruta de archivo.",
    "number_invalid": "Número no válido",
    "number_required": "Requerido",
    "integer_required": "Debe ser un número entero",
    "decimal_required": "Debe estar en formato decimal",
    "min_value": "Mín {} requerido",
    "max_value": "Máx {} superado",
    "precision_max": "Precisión máxima {} dígitos después de la coma",
    "precision_min": "Precisión mínima {} dígitos después de la coma",
    "color_invalid": "Color no válido (formato: #RRGGBB o #RGB)",
    "color_no_hash": "El color debe comenzar con #",
    "color_bad_hex": "Contiene caracteres no hexadecimales",
    "slug_invalid": "Slug no válido",
    "slug_no_dash": "El slug no debe comenzar ni terminar con un guion",
    "uuid_invalid": "Formato UUID no válido (esperado: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)",
    "json_invalid": "JSON no válido: {}",
    "ipv4_only": "Solo se aceptan direcciones IPv4",
    "ipv6_only": "Solo se aceptan direcciones IPv6",
    "ip_invalid": "Dirección IP no válida",
    "date_invalid": "Formato de fecha no válido",
    "date_too_old": "Demasiado antiguo (mín: {})",
    "date_too_far": "Demasiado lejano (máx: {})",
    "time_invalid": "Formato de hora no válido",
    "file_required": "Por favor selecciona al menos un archivo",
    "data_invalid": "Valor no válido para «{}»",
    "data_required": "«{}» es obligatorio",
    "data_password": "No se pudo generar el hash de la contraseña de «{}»; no se guardó nada",
    "file_extension_blocked": "Archivo '{}' no permitido. Extensiones: {}",
    "finalize_error": "Error al finalizar el campo '{}': {}",
    "template_missing": "Plantilla faltante: {}",
    "render_js_error": "Error al renderizar JS: {}",
    "tera_not_configured": "Tera no configurado",
    "config_not_found": "Configuración no encontrada en las extensiones",
    "hint_slug": "Formato: letras-números-guiones",
    "hint_uuid": "Formato: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "hint_json": "Se requiere un formato JSON válido",
    "hint_ip_v4": "Formato IPv4: 192.168.1.1",
    "hint_ip_v6": "Formato IPv6: 2001:0db8:85a3::8a2e:0370:7334",
    "hint_ip": "Formato IPv4 o IPv6",
    "validation_overflow": "Stack overflow detectado: recursión infinita en la validación",
    "choice_invalid": "Opción no válida",
    "choice_invalid_value": "Opción no válida: {}",
    "select_required": "Por favor selecciona una opción",
    "checkbox_required": "Por favor selecciona al menos una opción",
    "multipart_error": "Error multipart",
    "body_error": "Error al leer el body",
    "aegis_query_error": "Error al analizar la query string: {}",
    "aegis_urlencoded_error": "Error al analizar el body urlencoded: {}",
    "aegis_json_error": "Error al analizar el body JSON: {}",
    "auth_required": "Autenticación requerida",
    "role_insufficient": "Rol insuficiente",
    "unknown_group": "GuardRules: grupo(s) inexistente(s) en roles([...]): {}",
    "time_too_old": "Demasiado temprano (mín: {})",
    "time_too_far": "Demasiado tarde (máx: {})",
    "datetime_too_old": "Demasiado temprano (mín: {})",
    "datetime_too_far": "Demasiado tarde (máx: {})",
    "duration_invalid": "Duración no válida (se espera número de segundos)",
    "duration_too_short": "Duración mínima: {} segundos",
    "duration_too_long": "Duración máxima: {} segundos",
    "file_max_count": "Máximo {} archivos permitidos",
    "file_too_large": "Archivo '{}' demasiado grande ({}MB > {}MB máx)",
    "file_invalid_image": "El archivo '{}' no es una imagen válida",
    "image_too_wide": "La imagen '{}' es demasiado ancha ({}px > {}px)",
    "image_too_tall": "La imagen '{}' es demasiado alta ({}px > {}px)",
    "boolean_required": "Debes aceptar este campo",
    "slug_unicode_invalid": "Solo se permiten letras, números, guiones y guiones bajos",
    "unique_field_taken": "Este {} ya está en uso.",
    "unique_value_taken": "El valor del campo '{}' ya está en uso.",
    "unique_constraint_violated": "Se ha violado una restricción de unicidad.",
    "db_error": "Error de base de datos: {}",
    "text_field_too_large": "Campo de texto demasiado grande (max 1 MB)",
    "upload_too_large": "Archivo demasiado grande (max {}MB)"
  },
  "csrf": {
    "missing": "Token CSRF faltante",
    "invalid": "Token CSRF no válido",
    "invalid_or_missing": "Token CSRF no válido o faltante",
    "forbidden": "Token CSRF no válido",
    "session_write_error": "Error de escritura de sesión"
  },
  "error": {
    "source": "Fuente: {}",
    "AppError": "Error de aplicación",
    "build": "Error de compilación: {}",
    "internal": "Error interno",
    "forbidden": "Acceso prohibido",
    "not_found": "Recurso no encontrado",
    "validation": "Error de validación: {}",
    "database": "Error de base de datos: {}",
    "io": "Error IO: {}",
    "template": "Error de plantilla: {}",
    "custom": "Error personalizado: {}",
    "title": {
      "bad_request": "Solicitud incorrecta",
      "not_found": "Página no encontrada",
      "forbidden": "Acceso prohibido",
      "validation": "Error de validación",
      "database": "Error de base de datos",
      "template": "Error de renderizado de plantilla",
      "internal": "Error interno del servidor"
    },
    "invalid_query": "Parámetros de consulta no válidos: {}",
    "path_not_found": "La ruta solicitada '{}' no fue encontrada",
    "internal_occurred": "Ocurrió un error interno",
    "context_occurred": "Error AppError ocurrido"
  },
  "title": {
    "template": "Error de plantilla en '{}'"
  },
  "build": {
    "subscriber_already_installed": "Ya hay un subscriber de tracing instalado; la configuración de log de Runique está inactiva (RUST_LOG / with_log no tienen efecto)",
    "external_outputs_ignored": "`with_log(|l| l.external())` está definido; las salidas de log configuradas se ignoran (la aplicación posee el subscriber)",
    "validation_failed": "La validación del build falló: {}",
    "check_failed": "El build falló con {} error(es) de verificación",
    "template_failed": "La carga de la plantilla falló: {}",
    "driver_not_enabled": "Driver {} no activado: Runique se compiló sin la feature '{}'.\n\nEn el Cargo.toml de su proyecto:\nrunique = { version = \"{}\", features = [\"{}\"] }\n\nPara la CLI de runique:\ncargo install runique --features {} --locked --force\n\nPara admitir todos los motores, use la feature 'all-databases'.",
    "db_missing": "Se requiere conexión a la base de datos cuando la feature 'orm' está activada",
    "component_not_ready": "El componente '{}' no está listo",
    "check": {
      "db_required": "Se requiere una conexión o configuración de base de datos (feature `orm` activada)",
      "db_required_hint": "Añada .with_database(db) o .with_database_config(config) a su cadena de construcción",
      "admin_prefix_empty": "El prefijo de ruta del admin no puede estar vacío",
      "admin_prefix_empty_hint": "Use .prefix(\"/admin\") o deje el valor por defecto",
      "extra_route_unknown": "La ruta adicional `{}` designa el recurso `{}`, que no está registrado",
      "extra_route_unknown_hint": "Use la clave de un recurso declarado en admin!{}",
      "cache_control_invalid": "Valor de cabecera Cache-Control no válido: '{}'",
      "cache_control_invalid_hint": "Use solo caracteres ASCII visibles (sin caracteres de control ni saltos de línea).",
      "media_root_unusable": "No se puede crear ni acceder al directorio MEDIA_ROOT '{}': {}",
      "media_root_unusable_hint": "Defina MEDIA_ROOT con una ruta absoluta con permiso de escritura en su .env (p. ej. MEDIA_ROOT=/var/www/miapp/media) y asegúrese de que el proceso pueda escribir en ella.",
      "secret_key_weak": "SECRET_KEY falta, está vacía, tiene el valor por defecto o tiene menos de 32 caracteres",
      "secret_key_weak_hint": "Defina SECRET_KEY con una cadena aleatoria de al menos 32 caracteres en su .env (la CLI `runique` puede generar una)",
      "public_url_missing": "Sin URL pública: los enlaces de restablecimiento se construirían a partir de la cabecera Host de la petición",
      "public_url_missing_hint": "Defínala en el builder: .with_public_url(\"https://misitio.es\")",
      "acme_domain_missing": "ACME_ENABLED=true pero ACME_DOMAIN no está definido",
      "acme_domain_missing_hint": "Defina ACME_DOMAIN con su dominio de producción en su .env",
      "acme_email_missing": "ACME_ENABLED=true pero ACME_EMAIL no está definido",
      "acme_email_missing_hint": "Defina ACME_EMAIL con su email de contacto de Let's Encrypt en su .env",
      "suggestion": "Sugerencia",
      "context": "Contexto"
    }
  },
  "server": {
    "operational": "Runique está operativo",
    "launched": "Servidor iniciado en {}",
    "connected_db": "Conectado a la base de datos {} -> {}",
    "stop_hint": "ctrl + c para detener",
    "shutting_down": "Deteniendo el servidor Runique...",
    "acme_not_compiled": "ACME_ENABLED=true pero la feature `acme` no está compilada.",
    "acme_add_feature": "Añade la feature `acme` a tu Cargo.toml.",
    "test_utils_feature_enabled": "La función `test-utils` está compilada en este build — asegúrate de que esté en [dev-dependencies], nunca en [dependencies], antes de desplegar a producción."
  },
  "middleware": {
    "bad_request": "Solicitud incorrecta",
    "slot_collision": "Colisión de slot de middleware Runique: '{}' y '{}' reclaman ambos el slot {}. Registre menos middlewares personalizados o asigne a este un slot explícito."
  },
  "html": {
    "400_title": "400",
    "400_text": "Solicitud incorrecta: los parámetros de la dirección no son válidos.",
    "404_title": "404",
    "404_text": "Página no encontrada",
    "500_title": "500",
    "500_text": "Error interno del servidor",
    "500_notice": "Nuestro equipo ha sido notificado y está trabajando en el problema.",
    "429_title": "429",
    "429_text": "Demasiadas solicitudes. Por favor espere un momento antes de intentarlo de nuevo.",
    "503_title": "503",
    "503_text": "Servicio temporalmente no disponible por sobrecarga. Por favor inténtelo de nuevo en breve.",
    "back_home": "Volver al inicio",
    "critical_error_title": "Error crítico",
    "critical_error_text": "El sistema de gestión de errores ha encontrado un error.",
    "critical_error_contact": "Esta situación no debería ocurrir nunca. Por favor contacta al administrador del sistema."
  },
  "debug": {
    "stack_trace_title": "Traza de error detallada",
    "stack_trace_tip": "El error más alto (nivel 0) suele ser la causa raíz. Los errores siguientes muestran cómo se propagó el error en el código."
  },
  "flash": {
    "welcome": "Bienvenido {}, ¡tu cuenta ha sido creada!",
    "fix_errors": "Por favor corrige los errores",
    "success_demo": "Este es un mensaje de éxito.",
    "info_demo": "Este es un mensaje informativo.",
    "warning_demo": "Este es un mensaje de advertencia.",
    "error_demo": "Este es un mensaje de error."
  },
  "cli": {
    "creating_project": "Creando el proyecto '{}'...",
    "admin_detected": "Admin detectado → generando código admin",
    "add_admin_hint": "Añade .with_admin(...) en tu builder para activar el panel de administración.",
    "daemon_error": "[Daemon] Error: {}",
    "cargo_run_failed": "El servidor de aplicaciones no se inició correctamente (cargo run)",
    "file_not_found": "Archivo no encontrado: {}\nAsegúrate de estar en la raíz de tu proyecto Runique.",
    "admin_not_found": "Archivo admin no encontrado: {}\nCrea src/admin.rs con la macro admin!{}.",
    "name_empty": "El nombre del proyecto no puede estar vacío",
    "name_invalid": "El nombre del proyecto solo debe contener letras, números, _ o -",
    "name_dash": "El nombre del proyecto no puede comenzar con -",
    "folder_exists": "La carpeta '{}' ya existe",
    "cargo_run_expect": "Error al lanzar cargo run",
    "project_created": "¡Proyecto '{}' creado con éxito!",
    "getting_started": "Para comenzar:",
    "cd_hint": "cd {}",
    "cargo_run_hint": "cargo run",
    "fmt_failed": "cargo fmt falló — formateo omitido: {}"
  },
  "daemon": {
    "unable_read": "No se puede leer: {}",
    "parse_error": "Error de análisis: {}",
    "no_resource": "No hay recursos en admin!{} — nada que generar",
    "operational": "Daemon operativo → src/admins/admin.rs",
    "generation_error": "Error de generación: {}",
    "generated_header": "GENERADO AUTOMÁTICAMENTE — NO EDITAR MANUALMENTE",
    "generated_notice": "Producido por `runique start` desde src/admin.rs. Cualquier cambio será sobrescrito."
  },
  "macro": {
    "named_fields_only": "Solo structs con campos nombrados",
    "structs_only": "Solo funciona en structs",
    "forms_field_required": "La struct '{}' debe tener un campo 'Forms'",
    "field_no_name": "Campo sin nombre",
    "model_form_named_only": "DeriveModelForm: solo structs con campos nombrados",
    "model_form_structs_only": "DeriveModelForm: solo structs"
  },
  "parser": {
    "multiple_admin_blocks": "admin!: solo se permite un bloque admin! por archivo (primer bloque en la línea {}, segundo en la línea {}) — fusiónelos",
    "title_required": "admin!: 'title' es obligatorio",
    "list_display_exclude_exclusive": "admin!: 'list_display' y 'list_exclude' son mutuamente excluyentes"
  },
  "TemplateMessage": {
    "debug_info_title": "Información de depuración",
    "debug_field_title": "Título",
    "debug_field_count": "Cantidad de campos",
    "debug_error_count": "Errores",
    "debug_main_message": "Mensaje principal",
    "debug_root_cause": "Causa raíz",
    "debug_details": "Detalles adicionales",
    "debug_request_info": "Información de la solicitud",
    "debug_http_method": "Método HTTP",
    "debug_path": "Ruta",
    "debug_query_params": "Parámetros de consulta",
    "debug_http_headers": "Encabezados HTTP",
    "debug_template_info": "Información del template",
    "debug_template_name": "Nombre del template",
    "debug_error_line": "Línea del error",
    "debug_template_source": "Código fuente del template",
    "debug_available_templates": "Templates disponibles",
    "debug_stack_trace_title": "Traza de error detallada",
    "debug_stack_trace_tip": "El error superior (nivel 0) suele ser la causa raíz. Los errores siguientes muestran cómo se propagó el error en el código.",
    "debug_environment": "Entorno",
    "debug_debug_mode": "Modo debug",
    "debug_enabled": "Activado",
    "debug_disabled": "Desactivado",
    "debug_rust_version": "Versión de Rust",
    "debug_app_version": "Versión de la app",
    "debug_status_code": "Código de estado",
    "debug_header_name": "Nombre",
    "debug_header_value": "Valor"
  },
  "admin": {
    "group": "Grupo",
    "groups": "Grupos",
    "resource": "Recurso",
    "resources": "Recursos",
    "can_create": "Crear",
    "can_read": "Leer",
    "can_update": "Modificar",
    "can_delete": "Eliminar",
    "can_update_own": "Modificar",
    "can_delete_own": "Eliminar",
    "name": "Nombre",
    "username": "Nombre de usuario",
    "email": "Email",
    "staff": "Staff",
    "superuser": "Superusuario",
    "active": "Activo",
    "user": {
      "username_too_short": "El nombre de usuario es demasiado corto (min 3 car.)",
      "invalid_email": "Formato de dirección de correo electrónico no válido",
      "not_activated": "Esta cuenta aún no ha sido activada por su usuario: lo será cuando haya elegido su contraseña mediante el correo recibido."
    },
    "login": {
      "title": "Inicio de sesión",
      "subtitle": "Inicie sesión para acceder al panel de administración",
      "label_username": "Nombre de usuario",
      "label_password": "Contraseña",
      "btn_submit": "Iniciar sesión",
      "error_session": "Error al abrir la sesión.",
      "error_credentials": "Credenciales incorrectas o permisos insuficientes.",
      "error_locked": "Demasiados intentos. Cuenta bloqueada temporalmente. Inténtelo de nuevo en {} segundos."
    },
    "logout": {
      "success": "Sesión cerrada correctamente."
    },
    "access": {
      "insufficient_rights": "Permisos insuficientes para acceder a la administración"
    },
    "dashboard": {
      "title": "Panel de control",
      "subtitle": "Resumen de la aplicación",
      "card_resources": "Recursos",
      "th_resource": "Recurso",
      "th_key": "Clave",
      "th_permissions": "Permisos",
      "th_actions": "Acciones",
      "btn_list": "Lista",
      "btn_create": "Crear",
      "see_list": "Ver la lista →",
      "empty_title": "No hay recursos registrados",
      "empty_desc": "Declare sus recursos en src/admin.rs con la macro admin!{{}} y luego ejecute runique start",
      "kpi_resources": "Recursos",
      "kpi_entries": "Entradas totales",
      "kpi_largest": "Mayor",
      "th_count": "Entradas"
    },
    "list": {
      "breadcrumb_admin": "Administración",
      "entries_count_one": "entrada",
      "entries_count_many": "entradas",
      "btn_create": "Crear",
      "th_id": "id",
      "th_actions": "Acciones",
      "bool_true": "Sí",
      "bool_false": "No",
      "btn_detail": "Detalle",
      "btn_edit": "Editar",
      "btn_delete": "Eliminar",
      "confirm_delete": "¿Realmente eliminar?",
      "empty_title": "No hay entradas",
      "empty_desc": "Comience creando un elemento.",
      "btn_create_first": "Crear el primero",
      "search_placeholder": "Buscar..."
    },
    "create": {
      "title": "Crear",
      "breadcrumb": "Crear",
      "card_info": "Información",
      "no_fields": "No hay campos disponibles por el momento.",
      "btn_cancel": "Cancelar",
      "btn_submit": "Crear",
      "success": "¡Entrada creada con éxito!"
    },
    "edit": {
      "title": "Editar",
      "breadcrumb": "Editar",
      "card_info": "Información",
      "no_fields": "No hay campos disponibles por el momento.",
      "btn_cancel": "Cancelar",
      "btn_submit": "Guardar",
      "success": "¡Entrada actualizada con éxito!"
    },
    "detail": {
      "title": "Detalle",
      "breadcrumb": "Detalle",
      "entry_label": "Entrada",
      "btn_list": "← Lista",
      "btn_edit": "Editar",
      "btn_delete": "Eliminar",
      "confirm_delete": "¿Eliminar esta entrada?",
      "btn_reset_password": "Restablecer contraseña",
      "btn_reset_password_short": "Restablecer"
    },
    "reset_password": {
      "btn": "Restablecer contraseña",
      "success_email": "Reset link sent to {}.",
      "success_link": "Reset link (mailer not configured): {}",
      "error_no_email": "No email field found for this user.",
      "error_send": "Error sending email: {}",
      "email_subject": "Restablece tu contraseña",
      "email_title": "Restablecimiento de contraseña",
      "email_greeting": "Hola,",
      "email_body": "Haz clic en el enlace para restablecer tu contraseña (válido 1 hora):",
      "email_btn": "Restablecer mi contraseña",
      "email_ignore": "Si no solicitaste este restablecimiento, ignora este mensaje."
    },
    "user_created": {
      "email_subject": "Bienvenido — define tu contraseña",
      "email_sent": "Correo de bienvenida enviado a {}.",
      "email_greeting": "Bienvenido,",
      "email_body": "Se ha creado una cuenta para ti. Haz clic en el enlace para definir tu contraseña:",
      "email_btn": "Definir mi contraseña",
      "email_validity": "Este enlace es válido por 1 hora. Si no esperabas este mensaje, ignóralo."
    },
    "delete": {
      "title": "Eliminar",
      "breadcrumb": "Eliminar",
      "heading": "Confirmar eliminación",
      "btn_cancel": "← Cancelar",
      "btn_confirm": "Sí, eliminar permanentemente",
      "warning": {
        "title": "¡Advertencia!",
        "desc": "Está a punto de eliminar la entrada",
        "of": "del recurso",
        "irreversible": "Esta acción es irreversible."
      },
      "success": "¡Entrada eliminada con éxito!",
      "not_found": "Recurso no encontrado."
    },
    "bulk": {
      "no_selection": "Ningún elemento seleccionado.",
      "no_field_selected": "Ningún campo modificado.",
      "delete_success": "elemento(s) eliminado(s).",
      "update_success": "elemento(s) actualizado(s)."
    },
    "base": {
      "title": "Administración",
      "breadcrumb": "Admin",
      "toggle": "Contraer menú",
      "logout_title": "Cerrar sesión",
      "theme_toggle": "Cambiar tema",
      "back_to_site": "← Sitio"
    },
    "superuser_wizard": {
      "title": "=== Crear superusuario ===",
      "algo_prompt": "[1/5] Algoritmo de hash",
      "algo_argon2": "Argon2 (recomendado)",
      "algo_bcrypt": "Bcrypt",
      "algo_scrypt": "Scrypt",
      "algo_custom": "Proveedor personalizado",
      "provider_path": "Ruta del proveedor",
      "invalid_path": "Ruta inválida.",
      "username_prompt": "[2/5] Nombre de usuario",
      "username_empty": "El nombre de usuario no puede estar vacío.",
      "username_taken": "Ya existe un usuario con este nombre.",
      "email_prompt": "[3/5] Correo electrónico",
      "email_invalid": "Correo electrónico inválido.",
      "email_taken": "Ya existe un usuario con este correo.",
      "password_prompt": "[4/5] Contraseña",
      "password_too_short": "La contraseña debe tener al menos 12 caracteres.",
      "password_weak": "La contraseña debe contener al menos una minúscula, una mayúscula, un dígito y un carácter especial.",
      "confirm_prompt": "[5/5] Confirmar contraseña",
      "password_mismatch": "Las contraseñas no coinciden. Por favor, inténtelo de nuevo.",
      "review_algo": "  Algoritmo  : {}",
      "review_username": "  Usuario    : {}",
      "review_email": "  Email      : {}",
      "review_password": "  Contraseña : ••••••••",
      "review_action": "Acción",
      "confirm_create": "Confirmar y crear",
      "change_algo": "Cambiar algoritmo",
      "modify_password": "Modificar contraseña",
      "success": "✓ Superusuario creado con éxito!",
      "id_line": "  ID         : {}",
      "username_line": "  Usuario    : {}",
      "email_line": "  Email      : {}"
    },
    "builtin": {
      "invalid_id": "Identificador inválido.",
      "droit_resource_required": "Por favor, seleccione al menos un recurso.",
      "droit_exists": "El permiso \"{}\" ya existe.",
      "groupe_exists": "Ya existe un grupo llamado \"{}\".",
      "user_username_exists": "El nombre de usuario \"{}\" ya está en uso.",
      "user_email_exists": "La dirección de correo \"{}\" ya está registrada."
    }
  },
  "makemigrations": {
    "destructive_detected": "Cambios destructivos detectados:",
    "destructive_require_force": "Use --force para aplicar estos cambios destructivos.",
    "long_identifier_detected": "Los identificadores SQL generados son demasiado largos (límite de MariaDB/MySQL: 64 caracteres):",
    "long_identifier_rename_required": "Acorte los nombres de tabla/columna involucrados — Runique no trunca automáticamente los identificadores.",
    "files_ready": "{} archivo(s) listo(s) para migrar.",
    "snapshots_upgraded": "{} snapshot(s) actualizado(s) para registrar la longitud de las columnas (sin migración)."
  },
  "migrate": {
    "applying": "Aplicando migraciones desde '{}'...",
    "complete_up": "Migración completada.",
    "crate_missing": "No se encontró el crate de migraciones: '{}' no contiene Cargo.toml."
  },
  "reset": {
    "forgot_title": "Contraseña olvidada",
    "reset_title": "Nueva contraseña",
    "success_title": "Contraseña restablecida",
    "email_label": "Dirección de correo electrónico",
    "new_password_label": "Nueva contraseña",
    "confirm_label": "Confirmar contraseña",
    "email_mismatch": "La dirección de correo no coincide con el enlace.",
    "invalid_link": "Enlace inválido.",
    "invalid_or_expired": "El enlace de restablecimiento es inválido o ha expirado.",
    "password_min_length": "La contraseña debe tener al menos {} caracteres.",
    "password_weak": "La contraseña debe contener al menos una mayúscula, una minúscula, un número y un carácter especial (!@#$%...).",
    "password_mismatch": "Las contraseñas no coinciden.",
    "check_inbox": "Si esta dirección está asociada a una cuenta, recibirá un correo con los pasos a seguir.",
    "blocked_subject": "Su cuenta está bloqueada",
    "blocked_body": "<p>Hola {},</p><p>Se solicitó un restablecimiento de contraseña para su cuenta, pero esta cuenta está bloqueada. Contacte con el administrador del sitio para desbloquearla.</p><p>Si usted no hizo esta solicitud, ignore este correo.</p>",
    "email_subject": "Restablecer contraseña",
    "email_body": "<p>Hola {},</p><p>Haga clic en el siguiente enlace para restablecer su contraseña (válido durante 1 hora):</p><p><a href=\"{}\">{}</a></p>"
  },
  "permission": {
    "col": {
      "id": "ID",
      "can_create": "Crear",
      "can_delete": "Eliminar",
      "can_delete_own": "Eliminar (propio)",
      "can_read": "Leer",
      "can_update": "Modificar",
      "can_update_own": "Modificar (propio)",
      "groupes": "Grupos",
      "resource_key": "Recurso",
      "actions": "Acciones"
    }
  },
  "runique_test": {
    "connect_failed": "no se puede conectar: {}",
    "begin_failed": "no se puede abrir la transacción de prueba: {}",
    "env_read_failed": "no se puede leer {}: {}",
    "env_parse_failed": "no se puede analizar {}: {}",
    "env_line_malformed": "no se puede analizar {}: una línea está mal formada en el carácter {} (la línea no se muestra, puede contener una contraseña)",
    "no_panic_message": "sin mensaje",
    "query_failed": "fallida",
    "panicked": "pánico: {}",
    "rollback_failed": "rollback: {}",
    "test_failed": "{} falló, consulta la salida de arriba",
    "clone_outlived": "un clon de la conexión de prueba sobrevivió al handler; la transacción se revertirá cuando ese clon se libere",
    "not_a_transaction": "la prueba no se ejecutó dentro de una transacción, así que no se pudo revertir nada",
    "nested": "runique_test no puede ejecutarse dentro de otro runique_test: esperaría indefinidamente a su propio llamador",
    "timed_out": "el handler no terminó en {} s: ¿un interbloqueo, o una fila bloqueada por otra conexión? RUNIQUE_TEST_TIMEOUT en el archivo env cambia el límite",
    "bad_timeout": "{} debe ser un número entero de segundos mayor que 0, se recibió \"{}\"",
    "transaction_ended": "la transacción del test se terminó desde dentro del propio test (un COMMIT, o DDL en MariaDB/MySQL): todo lo escrito antes ya está en la base de datos",
    "swallowed_failures": "consultas fallidas: {}, y aun así el handler devolvió Ok: un error se tragó en algún sitio (envuelve los fallos esperados en expect_db_error)",
    "rollback_timed_out": "el rollback no terminó en {} s",
    "expected": "esperado",
    "not_refused": "la base de datos aceptó lo que el test esperaba que rechazara",
    "no_db_config": "el archivo env no define ni DATABASE_URL ni DB_ENGINE: la base de datos de prueba debe indicarse en él, no se toma nada del shell",
    "in_memory": "en memoria: cada test empieza con una base de datos vacía"
  },
  "test_runner": {
    "no_cargo_toml": "No hay Cargo.toml aquí: ejecuta `runique test` desde la raíz de tu proyecto.",
    "test_utils_missing": "La feature `test-utils` no está activada para runique en Cargo.toml.\nAñádela como dev-dependency para que nunca acabe en una build de release:\n    cargo add --dev runique --features test-utils",
    "test_utils_shipped": "Cargo.toml activa `test-utils` fuera de [dev-dependencies], así que el builder de pruebas acabaría en tu build de release.\nQuítala de [dependencies] (y de cualquier entrada de [features]) y añádela solo como dev-dependency:\n    cargo add --dev runique --features test-utils",
    "no_mod_file": "No existe src/runique_test/mod.rs: créalo y declara en él cada archivo de pruebas (`mod user;`).",
    "module_not_declared": "src/main.rs no declara el módulo de pruebas. Añade:\n    #[cfg(test)]\n    mod runique_test;",
    "module_not_gated": "El módulo runique_test no está detrás de `cfg(test)`, así que acabaría en todas las builds.\nPon `#[cfg(test)]` justo encima de `mod runique_test;` en src/main.rs.",
    "file_not_declared_warning": "aviso: src/runique_test/{}.rs no está declarado en mod.rs, así que sus pruebas no se ejecutarán. Añade `mod {};` a mod.rs.",
    "no_test_file": "No existe src/runique_test/{}.rs. Archivos de pruebas: {}",
    "file_not_declared": "src/runique_test/{}.rs no está declarado en mod.rs: añade `mod {};`.",
    "cargo_spawn_failed": "No se pudo ejecutar cargo.",
    "output_failed": "No se pudo mostrar la salida de cargo.",
    "cargo_wait_failed": "cargo test no terminó correctamente.",
    "tests_failed": "cargo test falló, consulta la salida de arriba.",
    "build_failed": "cargo no pudo compilar las pruebas, consulta los errores de arriba.",
    "no_such_test": "No hay ninguna prueba \"{}\" en {}.rs. Pruebas disponibles: {}",
    "dir_read_failed": "No se puede leer {}"
  },
  "auth": {
    "login_cannot_sign_in": "Esta cuenta no puede iniciar sesión: está inactiva o nunca se activó.",
    "login_session_failed": "No se pudo guardar la sesión: {}"
  }
}