fix: improved language selector
CI / test (push) Successful in 1m0s
CI / build (push) Successful in 28s

This commit is contained in:
2026-07-20 12:38:33 +02:00
parent 73109a268b
commit 55186a0891
7 changed files with 127 additions and 86 deletions
+30 -28
View File
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: crochet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-20 09:18+0000\n"
"POT-Creation-Date: 2026-07-20 10:32+0000\n"
"Language: en\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -47,35 +47,33 @@ msgstr "Create pattern"
msgid "¿Seguro que quieres eliminar este patrón? No podrás deshacerlo."
msgstr "Are you sure you want to delete this pattern? This cannot be undone."
#: crochet/templates/crochet/account_home.html:34
#: crochet/templates/crochet/account_home.html:37
msgid "Patrón sin título"
msgstr "Untitled pattern"
#: crochet/templates/crochet/account_home.html:37
#: crochet/templates/crochet/account_home.html:41
#, python-format
msgid "Actualizado el %(date)s"
msgstr "Updated on %(date)s"
#: crochet/templates/crochet/account_home.html:40
#: crochet/templates/crochet/pattern.html:33
msgid "Ver patrón"
msgstr "View pattern"
#: crochet/templates/crochet/account_home.html:44
#: crochet/templates/crochet/pattern.html:143
#: crochet/templates/crochet/pattern.html:189
msgid "Vista previa"
msgstr "Preview"
#: crochet/templates/crochet/account_home.html:41
msgid "Editar"
msgstr "Edit"
#: crochet/templates/crochet/account_home.html:45
#: crochet/templates/crochet/account_home.html:48
#: crochet/templates/crochet/pattern.html:38
msgid "Eliminar"
msgstr "Delete"
#: crochet/templates/crochet/account_home.html:54
#: crochet/templates/crochet/account_home.html:57
msgid "Todavía no tienes ningún patrón."
msgstr "You don't have any patterns yet."
#: crochet/templates/crochet/account_settings.html:4
#: crochet/templates/crochet/account_settings.html:18
#: crochet/templates/crochet/base.html:50
#: crochet/templates/crochet/base.html:52
msgid "Ajustes de la cuenta"
msgstr "Account settings"
@@ -84,21 +82,21 @@ msgid "Volver a mis patrones"
msgstr "Back to my patterns"
#: crochet/templates/crochet/base.html:39
msgid "Idioma"
msgstr "Language"
msgid "Cambiar idioma"
msgstr "Change language"
#: crochet/templates/crochet/base.html:54
#: crochet/templates/crochet/base.html:56
msgid "Cerrar sesión"
msgstr "Log out"
#: crochet/templates/crochet/base.html:60
#: crochet/templates/crochet/base.html:62
#: crochet/templates/crochet/home.html:24
#: crochet/templates/registration/login.html:4
#: crochet/templates/registration/login.html:14
msgid "Iniciar sesión"
msgstr "Log in"
#: crochet/templates/crochet/base.html:61
#: crochet/templates/crochet/base.html:63
#: crochet/templates/registration/register.html:29
msgid "Registrarme"
msgstr "Sign up"
@@ -172,14 +170,14 @@ msgstr "Couldn't save. Please try again."
msgid "Guardar"
msgstr "Save"
#: crochet/templates/crochet/pattern.html:33
msgid "Ver patrón"
msgstr "View pattern"
#: crochet/templates/crochet/pattern.html:34
msgid "Exportar a PDF"
msgstr "Export to PDF"
#: crochet/templates/crochet/pattern.html:38
msgid "Eliminar patrón"
msgstr "Delete pattern"
#: crochet/templates/crochet/pattern.html:45
msgid "Guardado"
msgstr "Saved"
@@ -291,11 +289,6 @@ msgstr "Landscape"
msgid "Secciones"
msgstr "Sections"
#: crochet/templates/crochet/pattern.html:143
#: crochet/templates/crochet/pattern.html:189
msgid "Vista previa"
msgstr "Preview"
#: crochet/templates/crochet/pattern.html:152
msgid "Colapsar todo"
msgstr "Collapse all"
@@ -491,3 +484,12 @@ msgstr ""
#: crochet/views.py:24
msgid "Este patrón todavía no tiene contenido."
msgstr "This pattern doesn't have any content yet."
#~ msgid "Idioma"
#~ msgstr "Language"
#~ msgid "Editar"
#~ msgstr "Edit"
#~ msgid "Eliminar patrón"
#~ msgstr "Delete pattern"
+19 -26
View File
@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: crochet\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-20 09:18+0000\n"
"POT-Creation-Date: 2026-07-20 10:32+0000\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -47,35 +47,33 @@ msgstr ""
msgid "¿Seguro que quieres eliminar este patrón? No podrás deshacerlo."
msgstr ""
#: crochet/templates/crochet/account_home.html:34
#: crochet/templates/crochet/account_home.html:37
msgid "Patrón sin título"
msgstr ""
#: crochet/templates/crochet/account_home.html:37
#: crochet/templates/crochet/account_home.html:41
#, python-format
msgid "Actualizado el %(date)s"
msgstr ""
#: crochet/templates/crochet/account_home.html:40
#: crochet/templates/crochet/pattern.html:33
msgid "Ver patrón"
#: crochet/templates/crochet/account_home.html:44
#: crochet/templates/crochet/pattern.html:143
#: crochet/templates/crochet/pattern.html:189
msgid "Vista previa"
msgstr ""
#: crochet/templates/crochet/account_home.html:41
msgid "Editar"
msgstr ""
#: crochet/templates/crochet/account_home.html:45
#: crochet/templates/crochet/account_home.html:48
#: crochet/templates/crochet/pattern.html:38
msgid "Eliminar"
msgstr ""
#: crochet/templates/crochet/account_home.html:54
#: crochet/templates/crochet/account_home.html:57
msgid "Todavía no tienes ningún patrón."
msgstr ""
#: crochet/templates/crochet/account_settings.html:4
#: crochet/templates/crochet/account_settings.html:18
#: crochet/templates/crochet/base.html:50
#: crochet/templates/crochet/base.html:52
msgid "Ajustes de la cuenta"
msgstr ""
@@ -84,21 +82,21 @@ msgid "Volver a mis patrones"
msgstr ""
#: crochet/templates/crochet/base.html:39
msgid "Idioma"
msgid "Cambiar idioma"
msgstr ""
#: crochet/templates/crochet/base.html:54
#: crochet/templates/crochet/base.html:56
msgid "Cerrar sesión"
msgstr ""
#: crochet/templates/crochet/base.html:60
#: crochet/templates/crochet/base.html:62
#: crochet/templates/crochet/home.html:24
#: crochet/templates/registration/login.html:4
#: crochet/templates/registration/login.html:14
msgid "Iniciar sesión"
msgstr ""
#: crochet/templates/crochet/base.html:61
#: crochet/templates/crochet/base.html:63
#: crochet/templates/registration/register.html:29
msgid "Registrarme"
msgstr ""
@@ -163,12 +161,12 @@ msgstr ""
msgid "Guardar"
msgstr ""
#: crochet/templates/crochet/pattern.html:34
msgid "Exportar a PDF"
#: crochet/templates/crochet/pattern.html:33
msgid "Ver patrón"
msgstr ""
#: crochet/templates/crochet/pattern.html:38
msgid "Eliminar patrón"
#: crochet/templates/crochet/pattern.html:34
msgid "Exportar a PDF"
msgstr ""
#: crochet/templates/crochet/pattern.html:45
@@ -282,11 +280,6 @@ msgstr ""
msgid "Secciones"
msgstr ""
#: crochet/templates/crochet/pattern.html:143
#: crochet/templates/crochet/pattern.html:189
msgid "Vista previa"
msgstr ""
#: crochet/templates/crochet/pattern.html:152
msgid "Colapsar todo"
msgstr ""
+5 -2
View File
@@ -22,6 +22,7 @@
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{% for pattern in patterns %}
<div class="card bg-base-100 shadow border border-base-300">
<a href="{% url 'crochet:pattern_edit' pattern.uuid %}">
<figure class="h-40 bg-base-200">
{% if pattern.cover_image_thumbnail %}
<img src="{{ pattern.cover_image_thumbnail.url }}" alt="" class="w-full h-full object-cover">
@@ -29,16 +30,18 @@
<span class="text-4xl">🧶</span>
{% endif %}
</figure>
</a>
<div class="card-body">
<a href="{% url 'crochet:pattern_edit' pattern.uuid %}">
<h2 class="card-title text-lg">
{% if pattern.page_settings.title %}{{ pattern.page_settings.title }}{% else %}{% trans 'Patrón sin título' %}{% endif %}
</h2>
</a>
<p class="text-sm text-base-content/60">
{% blocktrans with date=pattern.updated_at|date:'d/m/Y' %}Actualizado el {{ date }}{% endblocktrans %}
</p>
<div class="card-actions justify-end mt-2">
<a href="{% url 'crochet:pattern_detail' pattern.uuid %}" class="btn btn-sm btn-outline">{% trans 'Ver patrón' %}</a>
<a href="{% url 'crochet:pattern_edit' pattern.uuid %}" class="btn btn-sm btn-primary">{% trans 'Editar' %}</a>
<a href="{% url 'crochet:pattern_detail' pattern.uuid %}" class="btn btn-sm btn-outline">{% trans 'Vista previa' %}</a>
<form method="post" action="{% url 'crochet:pattern_delete' pattern.uuid %}"
onsubmit="return confirm('{{ delete_confirm_message }}')">
{% csrf_token %}
+12 -10
View File
@@ -27,20 +27,22 @@
<a href="{% url 'crochet:home' %}" class="btn btn-ghost text-xl px-2">🧶 Crochet</a>
</div>
<div class="flex-none flex items-center gap-2">
<!-- Cambia de idioma sin perder la página en la que estás: cada
ruta traducible (ver crochet/urls.py) cambia entera de un
idioma a otro, no solo el prefijo (/es/, /en/), así que no
basta con sustituirlo a mano en request.path (ver
crochet_i18n.translate_url). onchange en vez de un <form> normal
porque cada <option> lleva ya la URL completa de destino (no
hay un único action al que enviar un valor). -->
{% get_current_language as CURRENT_LANGUAGE %}
{% get_available_languages as AVAILABLE_LANGUAGES %}
<select class="select select-sm select-bordered" onchange="window.location.href=this.value" aria-label="{% trans 'Idioma' %}">
<!-- El idioma es una preferencia de la página, no de la cuenta: va
siempre en el mismo sitio del navbar, tanto si has iniciado
sesión como si no, en vez de vivir dentro del dropdown de
usuario (que no existe para anónimos). Cada opción es un <a>
normal (no un <select>), así que navegar con teclado entre
ellas no dispara nada hasta que se pulsa/activa una. -->
<div class="dropdown dropdown-end">
<button type="button" tabindex="0" class="btn btn-sm btn-ghost" aria-label="{% trans 'Cambiar idioma' %}">{{ CURRENT_LANGUAGE|language_flag }} {{ CURRENT_LANGUAGE|upper }}</button>
<ul tabindex="0" class="dropdown-content menu menu-sm bg-base-100 border border-base-300 rounded-box z-10 w-40 p-2 shadow">
{% for lang_code, lang_name in AVAILABLE_LANGUAGES %}
<option value="{% translate_url request.path lang_code %}"{% if lang_code == CURRENT_LANGUAGE %} selected{% endif %}>{{ lang_name }}</option>
<li><a href="{% translate_url request.path lang_code %}"{% if lang_code == CURRENT_LANGUAGE %} class="menu-active"{% endif %}>{{ lang_code|language_flag }} {{ lang_name }}</a></li>
{% endfor %}
</select>
</ul>
</div>
{% if user.is_authenticated %}
<!-- Dropdown CSS puro de daisyUI (sin JS propio): se abre con
:focus-within al pulsar/tabular al botón disparador. -->
+1 -1
View File
@@ -35,7 +35,7 @@
<form method="post" action="{% url 'crochet:pattern_delete' pattern.uuid %}"
onsubmit="return confirm('{{ delete_confirm_message }}')">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline btn-error">{% trans 'Eliminar patrón' %}</button>
<button type="submit" class="btn btn-sm btn-outline btn-error">{% trans 'Eliminar' %}</button>
</form>
</div>
</header>
+11
View File
@@ -3,6 +3,12 @@ from django.urls import translate_url as django_translate_url
register = template.Library()
# Mismos idiomas que LANGUAGES en config/settings/base.py.
LANGUAGE_FLAGS = {
'es': '🇪🇸',
'en': '🇬🇧',
}
@register.simple_tag
def translate_url(path, lang_code):
@@ -13,3 +19,8 @@ def translate_url(path, lang_code):
# prefijo (p.ej. /es/patron/<uuid>/editar/ frente a
# /en/pattern/<uuid>/edit/).
return django_translate_url(path, lang_code)
@register.filter
def language_flag(lang_code):
return LANGUAGE_FLAGS.get(lang_code, '')
+37 -7
View File
@@ -56,7 +56,7 @@ class HomeViewTests(TestCase):
class LanguageSwitcherTests(TestCase):
def test_home_page_switcher_offers_the_same_page_in_english(self):
response = self.client.get('/es/')
self.assertContains(response, '<option value="/en/"')
self.assertContains(response, 'href="/en/"')
def test_translated_route_switcher_preserves_the_current_view(self):
# No solo cambia el prefijo (/es/ -> /en/): toda la ruta traducible
@@ -71,12 +71,28 @@ class LanguageSwitcherTests(TestCase):
response = self.client.get(es_url)
self.assertContains(response, f'value="{en_url}"')
self.assertContains(response, f'href="{en_url}"')
def test_current_language_is_preselected(self):
def test_current_language_is_shown_on_the_trigger_button(self):
response = self.client.get('/es/')
self.assertContains(response, '<option value="/es/" selected>')
self.assertContains(response, '<option value="/en/">')
self.assertContains(response, '🇪🇸 ES<')
def test_menu_options_show_a_flag_per_language(self):
response = self.client.get('/es/')
self.assertContains(response, '🇪🇸 Español')
self.assertContains(response, '🇬🇧 Inglés')
def test_switcher_is_shown_regardless_of_login_state(self):
# El idioma es una preferencia de la página, no de la cuenta: debe
# verse tanto si hay sesión iniciada como si no, no solo dentro del
# dropdown de usuario (que no existe para anónimos).
user = User.objects.create_user(username='crocheter', password='a-very-uncommon-pw-1')
self.client.force_login(user)
response = self.client.get('/es/')
self.assertContains(response, '🇪🇸 ES<')
class NavbarAccountDropdownTests(TestCase):
@@ -94,10 +110,24 @@ class NavbarAccountDropdownTests(TestCase):
self.assertContains(response, f'href="{settings_url}"')
self.assertContains(response, f'action="{logout_url}"')
def test_anonymous_user_does_not_see_the_dropdown(self):
def test_anonymous_user_does_not_see_the_account_dropdown(self):
with translation.override('es'):
settings_url = reverse('crochet:account_settings')
response = self.client.get('/es/')
self.assertNotContains(response, 'dropdown dropdown-end')
# El dropdown de idioma sí está presente para anónimos; lo que no
# debe verse es el de la cuenta (ajustes/cerrar sesión).
self.assertNotContains(response, f'href="{settings_url}"')
self.assertEqual(response.content.decode().count('class="dropdown dropdown-end"'), 1)
def test_language_dropdown_is_separate_from_the_account_dropdown(self):
user = User.objects.create_user(username='crocheter', password='a-very-uncommon-pw-1')
self.client.force_login(user)
response = self.client.get('/es/')
self.assertEqual(response.content.decode().count('class="dropdown dropdown-end"'), 2)
class PatternEditViewTests(TestCase):