diff --git a/doc/traducciones.md b/doc/traducciones.md new file mode 100644 index 0000000..c32d1fc --- /dev/null +++ b/doc/traducciones.md @@ -0,0 +1,72 @@ +# Guía rápida sobre traducciones + +> [!IMPORTANT]\ +> La documentación oficial está disponible [aquí](https://docs.djangoproject.com/en/5.2/topics/i18n/translation/).\ +> Personalmente recomiendo leer al menos hasta la sección de `Lazy Translation` (incluida). + +> [!IMPORTANT]\ +> No queremos traducciones hechas por IA. +> Valoramos el trabajo humano y preferimos no disponer de las traducciones de un idioma antes que servir traducciones +> hechas por IA.\ +> **Por favor, abstente de realizar contribuciones que no sean de autoría humana.** + +## Procedimiento de traducción + +### Modificación del código o las plantillas para integrar las traducciones + +(Solo necesario la primera vez que se traduce un archivo) + +#### En Python + +Se introduce el texto a traducir en la llamada a la función `_()`, definida de forma distinta en función de qué archivo +se trate: + +- En archivos habituales,\ + `from django.utils.translation import gettext as _`.\ +- En archivos ejecutados en el arranque de Django (`admin.py`, `settings.py`, `models.py`, etc.) + https://docs.djangoproject.com/en/5.2/topics/i18n/translation/#lazy-translation \ + `from django.utils.translation import gettext_lazy as _` +- En casos en los que el texto a traducir requiera contexto (aparece en varias situaciones con traducciones distintas), + emplear las variantes:\ + https://docs.djangoproject.com/en/5.2/topics/i18n/translation/#contextual-markers \ + `from django.utils.translation import pgettext as _` y\ + `from django.utils.translation import pgettext_lazy as _`\ + como `_("", "")` +- En traducciones que requieran plurales, usar las variantes:\ + https://docs.djangoproject.com/en/5.2/topics/i18n/translation/#pluralization \ + `from django.utils.translation import ngettext` y\ + `from django.utils.translation import ngettext`. + Para más detalles de su uso ver la documentación oficial. + +Para aportar contexto adicional a los traductores, se debe añadir un comentario en la línea previa a la llamada con la +estructura siguiente: +`# Translators: ` + +Un ejemplo del uso está disponible en el archivo [settings.py](hackackathon/settings.py) en la definición de la variable `LANGUAGES`. + +#### En plantillas + +Se introduce el texto a traducir entre las comillas de `{% translate '' %}`. + +Para aportar contexto adicional a los traductores, se debe añadir un comentario en la línea previa a la llamada con la +estructura siguiente: +`{# Translators: #}` + +Un ejemplo del uso está disponible en el archivo [registro.html](templates/registro.html), en todos los textos presentes. + +### Creación del archivo de traducción + +`python manage.py makemessages --all` + +### Traducción de los archivos generados + +Se deberán traducir los archivos presentes en `locale/*/*.po`. + +### Compilación de las traducciones para su uso + +`python manage.py compilemessages` + +> Esto es solo un resumen rápido. +> Para información detallada *recomiendo muy encarecidamente* leer la documentación oficial. +> Hay muchos matices que no están recogidos en este documento, bien por desconocimiento, bien por no extenderse en +> exceso. diff --git a/gestion/views.py b/gestion/views.py index 83f06d5..1b4318a 100644 --- a/gestion/views.py +++ b/gestion/views.py @@ -23,8 +23,8 @@ ParticipanteForm, PaseForm, Registro, - RevisarParticipanteForm, RevisarMentorForm, + RevisarParticipanteForm, ) from gestion.models import ( Mentor, @@ -55,7 +55,7 @@ def registro(request: HttpRequest): logger.debug("Intento de acceso con el registro cerrado") return render(request, "registro_cerrado.html") - titulo = "Regístrate en" + titulo = _("Regístrate en") url = reverse("registro") if request.method == "GET": @@ -130,7 +130,7 @@ def registro_mentores(request: HttpRequest): logger.debug("Intento de acceso con el registro cerrado") return render(request, "registro_cerrado.html") - titulo = "Registro de mentores" + titulo = _("Registro de mentores") url = reverse("registro-mentores") if request.method == "GET": diff --git a/hackackathon/settings.py b/hackackathon/settings.py index 278a287..7a35826 100644 --- a/hackackathon/settings.py +++ b/hackackathon/settings.py @@ -5,6 +5,7 @@ from pathlib import Path from zoneinfo import ZoneInfo +from django.utils.translation import gettext_lazy as _ from dotenv import load_dotenv load_dotenv() @@ -236,7 +237,7 @@ # Internationalization # https://docs.djangoproject.com/en/5.1/topics/i18n/ -LANGUAGE_CODE = "es-es" +LANGUAGE_CODE = "es" TIME_ZONE = "Europe/Madrid" @@ -244,6 +245,18 @@ USE_TZ = True +# Translations +# https://docs.djangoproject.com/en/5.1/topics/i18n/translation/ +# ./manage.py makemessages --all +# ./manage.py compilemessages +LANGUAGES = [ + ("es", _("Castellano")), + ("gl", _("Gallego")), + ("en", _("Inglés")), +] +LANGUAGE_COOKIE_NAME = "lang" +LOCALE_PATHS = (BASE_DIR / "locale",) + # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/5.1/howto/static-files/ diff --git a/hackackathon/urls.py b/hackackathon/urls.py index c41ae22..9e82ff1 100644 --- a/hackackathon/urls.py +++ b/hackackathon/urls.py @@ -5,10 +5,13 @@ from django.contrib import admin from django.urls import include, path +from hackackathon import views + admin.site.site_header = "Hackackathon Admin" urlpatterns = [ path("admin/", admin.site.urls), + path("idioma/", views.idioma, name="idioma"), path("", include("gestion.urls")), ] diff --git a/hackackathon/views.py b/hackackathon/views.py new file mode 100644 index 0000000..c19e063 --- /dev/null +++ b/hackackathon/views.py @@ -0,0 +1,29 @@ +# Copyright (C) 2025-now p.fernandezf & iago.rivas + +from django.conf import settings +from django.contrib import messages +from django.contrib.auth.decorators import login_not_required +from django.http import HttpRequest +from django.shortcuts import redirect +from django.utils import translation +from django.utils.translation import gettext_lazy as _ + + +@login_not_required +def idioma(request: HttpRequest, codigo: str): + idiomas_validos = [codigo for codigo, nombre in settings.LANGUAGES] + + if codigo in idiomas_validos: + translation.activate(codigo) + else: + messages.error( + request, + _("Código de idioma no válido. Los posibles son") + + ": " + + ", ".join(idiomas_validos), + ) + + if siguiente := request.GET.get("next"): + return redirect(siguiente) + + return redirect("registro") diff --git a/locale/en/LC_MESSAGES/django.mo b/locale/en/LC_MESSAGES/django.mo new file mode 100644 index 0000000..9e540cb Binary files /dev/null and b/locale/en/LC_MESSAGES/django.mo differ diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po new file mode 100644 index 0000000..9694d47 --- /dev/null +++ b/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,93 @@ +# Hackackathon English translations +# Copyright (C) 2025-now p.fernandezf & iago.rivas +# This file is distributed under the same license as the Hackackathon package. +# p.fernandezf , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Hackackathon VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-28 16:16+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \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" + +#: gestion/admin.py:47 +#, python-format +msgid "" +"%d participante no tiene el correo verificado y no se ha podido aceptar." +msgid_plural "" +"%d participantes no tienen el correo verificado y no se han podido aceptar." +msgstr[0] "" +"%d participant's email wasn't verified and they couldn't be accepted." +msgstr[1] "" +"%d participants' emails weren't verified and they couldn't be accepted" + +#: gestion/admin.py:59 +#, python-format +msgid "%d participante ya estaba aceptado." +msgid_plural "%d participantes ya estaban aceptados." +msgstr[0] "%d participant was already accepted." +msgstr[1] "%d participants were already accepted." + +#: gestion/admin.py:70 +#, python-format +msgid "%d participante aceptado." +msgid_plural "%d participantes aceptados." +msgstr[0] "%d participant accepted." +msgstr[1] "%d participants accepted." + +#: hackackathon/settings.py:253 +msgid "Castellano" +msgstr "Spanish" + +#: hackackathon/settings.py:254 +msgid "Gallego" +msgstr "Galician" + +#: hackackathon/settings.py:255 +msgid "Inglés" +msgstr "English" + +#: hackackathon/views.py:21 +msgid "Código de idioma no válido. Los posibles son" +msgstr "Invalid language code. Valid ones are" + +#: templates/registro.html:10 +msgid "Regístrate en" +msgstr "Register at" + +#. Translators: Etiqueta para la casilla de términos y condiciones +#: templates/registro.html:22 +msgid "Acepto los" +msgstr "I accept the" + +#: templates/registro.html:23 +msgid "Términos y Condiciones" +msgstr "Terms and Conditions" + +#: templates/registro.html:24 +msgid "y el" +msgstr "and the" + +#: templates/registro.html:25 +msgid "Código de conducta" +msgstr "Code of conduct" + +#: templates/registro.html:31 +msgid "Apuntarme a HackUDC" +msgstr "Sign up for HackUDC" + +#: templates/registro.html:36 +msgid "Al enviar aceptas la" +msgstr "By signing up you accept the" + +#: templates/registro.html:36 +msgid "política de privacidad" +msgstr "privacy policy" diff --git a/locale/gl/LC_MESSAGES/django.mo b/locale/gl/LC_MESSAGES/django.mo new file mode 100644 index 0000000..349ed58 Binary files /dev/null and b/locale/gl/LC_MESSAGES/django.mo differ diff --git a/locale/gl/LC_MESSAGES/django.po b/locale/gl/LC_MESSAGES/django.po new file mode 100644 index 0000000..de3bdf7 --- /dev/null +++ b/locale/gl/LC_MESSAGES/django.po @@ -0,0 +1,92 @@ +# Traducións de Hackackathonao galego +# Copyright (C) 2025-now p.fernandezf & iago.rivas +# Este arquivo distriúese baixo a mesma licenza que o paquete Hackackathon. +# p.fernandezf , 2025. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Hackackathon VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2025-11-28 16:16+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \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" + +#: gestion/admin.py:47 +#, python-format +msgid "" +"%d participante no tiene el correo verificado y no se ha podido aceptar." +msgid_plural "" +"%d participantes no tienen el correo verificado y no se han podido aceptar." +msgstr[0] "%d participante non ten o correo verificado e non se puido aceptar." +msgstr[1] "" +"%d participantes non teñen o correo verificado e non se puideron aceptar." + +#: gestion/admin.py:59 +#, python-format +msgid "%d participante ya estaba aceptado." +msgid_plural "%d participantes ya estaban aceptados." +msgstr[0] "%d participante xa estaba aceptado." +msgstr[1] "%d participantes xa estaban aceptados." + +#: gestion/admin.py:70 +#, python-format +msgid "%d participante aceptado." +msgid_plural "%d participantes aceptados." +msgstr[0] "%d participante aceptado." +msgstr[1] "%d participantes aceptados." + +#: hackackathon/settings.py:253 +msgid "Castellano" +msgstr "Castelán" + +#: hackackathon/settings.py:254 +msgid "Gallego" +msgstr "Galego" + +#: hackackathon/settings.py:255 +msgid "Inglés" +msgstr "Inglés" + +#: hackackathon/views.py:21 +msgid "Código de idioma no válido. Los posibles son" +msgstr "Código de idioma non válido. Os posibles son" + +#: templates/registro.html:10 +msgid "Regístrate en" +msgstr "Rexístrate en" + +#. Translators: Etiqueta para la casilla de términos y condiciones +#: templates/registro.html:22 +msgid "Acepto los" +msgstr "Acepto os" + +#: templates/registro.html:23 +msgid "Términos y Condiciones" +msgstr "Termos e Condicións" + +#: templates/registro.html:24 +msgid "y el" +msgstr "e o" + +#: templates/registro.html:25 +msgid "Código de conducta" +msgstr "Código de conducta" + +#: templates/registro.html:31 +msgid "Apuntarme a HackUDC" +msgstr "Apuntarme a HackUDC" + +#: templates/registro.html:36 +msgid "Al enviar aceptas la" +msgstr "Ao enviar aceptas a" + +#: templates/registro.html:36 +msgid "política de privacidad" +msgstr "política de privacidade" diff --git a/staticfiles/css/style.css b/staticfiles/css/style.css index 78f9a2f..fb068cf 100644 --- a/staticfiles/css/style.css +++ b/staticfiles/css/style.css @@ -84,6 +84,44 @@ button.boton { .transparente { background-color: transparent;} +/* Footer */ +.nav-idioma { + display: flex; + justify-content: end; + + margin: 0 auto; + margin-top: 2em; + width: var(--max-width); + max-width: var(--max-width); +} + +#selector-idioma { + display: flex; + justify-items: center; + justify-content: center; + + gap: 0.5rem; + padding: 0.5em 2em; + + font-size: 0.8em; + color: var(--texto); +} + +#selector-idioma a { + font-weight: bolder; + color: var(--amarillo); + text-decoration: none; + padding: 0.25rem 1rem; + border: 2px solid var(--amarillo); + border-radius: .25rem; +} + +#selector-idioma a[aria-selected=true], #selector-idioma a.link-grey-hover-yellow:hover { + color: #000; + background-color: var(--amarillo); + text-decoration: underline; +} + /* GENERAL */ .lucide { diff --git a/templates/marco.html b/templates/marco.html index 4fb68b3..a54d753 100644 --- a/templates/marco.html +++ b/templates/marco.html @@ -1,7 +1,7 @@ - {% load static %} + {% load static i18n %} @@ -33,6 +33,20 @@ {% endif %} + +
{% for message in messages %}
{{ message }}
diff --git a/templates/registro.html b/templates/registro.html index ca0af38..f06f047 100644 --- a/templates/registro.html +++ b/templates/registro.html @@ -1,4 +1,5 @@ {% extends "marco.html" %} +{% load i18n %} {% block title %}Registro{% endblock %} @@ -15,17 +16,23 @@ {{ form }}
- +
-

Al enviar aceptas la política de privacidad

+

{% translate 'Al enviar aceptas la' %} {% translate 'política de privacidad' %}

{% if persona %}