Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2021-09-18 06:41:30 -07:00
commit 620d3ab804
23 changed files with 338 additions and 188 deletions

View file

@ -24,5 +24,5 @@ jobs:
pip install pylint
- name: Analysing the code with pylint
run: |
pylint bookwyrm/ --ignore=migrations,tests --disable=E1101,E1135,E1136,R0903,R0901,R0902,W0707,W0511,W0406,R0401,R0801
pylint bookwyrm/ --ignore=migrations,tests --disable=E1101,E1135,E1136,R0903,R0901,R0902,W0707,W0511,W0406,R0401,R0801,C0209

View file

@ -311,6 +311,12 @@ class EmailBlocklistForm(CustomForm):
fields = ["domain"]
class IPBlocklistForm(CustomForm):
class Meta:
model = models.IPBlocklist
fields = ["address"]
class ServerForm(CustomForm):
class Meta:
model = models.FederatedServer

View file

@ -0,0 +1,3 @@
""" look at all this nice middleware! """
from .timezone_middleware import TimezoneMiddleware
from .ip_middleware import IPBlocklistMiddleware

View file

@ -0,0 +1,16 @@
""" Block IP addresses """
from django.http import Http404
from bookwyrm import models
class IPBlocklistMiddleware:
"""check incoming traffic against an IP block-list"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
address = request.META.get("REMOTE_ADDR")
if models.IPBlocklist.objects.filter(address=address).exists():
raise Http404()
return self.get_response(request)

View file

@ -0,0 +1,38 @@
# Generated by Django 3.2.4 on 2021-09-17 18:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("bookwyrm", "0096_merge_20210912_0044"),
]
operations = [
migrations.CreateModel(
name="IPBlocklist",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_date", models.DateTimeField(auto_now_add=True)),
("address", models.CharField(max_length=255, unique=True)),
("is_active", models.BooleanField(default=True)),
],
options={
"ordering": ("-created_date",),
},
),
migrations.AddField(
model_name="emailblocklist",
name="is_active",
field=models.BooleanField(default=True),
),
]

View file

@ -26,8 +26,8 @@ from .import_job import ImportJob, ImportItem
from .site import SiteSettings, SiteInvite
from .site import PasswordReset, InviteRequest
from .site import EmailBlocklist
from .announcement import Announcement
from .antispam import EmailBlocklist, IPBlocklist
cls_members = inspect.getmembers(sys.modules[__name__], inspect.isclass)
activity_models = {

View file

@ -0,0 +1,35 @@
""" Lets try NOT to sell viagra """
from django.db import models
from .user import User
class EmailBlocklist(models.Model):
"""blocked email addresses"""
created_date = models.DateTimeField(auto_now_add=True)
domain = models.CharField(max_length=255, unique=True)
is_active = models.BooleanField(default=True)
class Meta:
"""default sorting"""
ordering = ("-created_date",)
@property
def users(self):
"""find the users associated with this address"""
return User.objects.filter(email__endswith=f"@{self.domain}")
class IPBlocklist(models.Model):
"""blocked ip addresses"""
created_date = models.DateTimeField(auto_now_add=True)
address = models.CharField(max_length=255, unique=True)
is_active = models.BooleanField(default=True)
class Meta:
"""default sorting"""
ordering = ("-created_date",)

View file

@ -124,23 +124,6 @@ class PasswordReset(models.Model):
return "https://{}/password-reset/{}".format(DOMAIN, self.code)
class EmailBlocklist(models.Model):
"""blocked email addresses"""
created_date = models.DateTimeField(auto_now_add=True)
domain = models.CharField(max_length=255, unique=True)
class Meta:
"""default sorting"""
ordering = ("-created_date",)
@property
def users(self):
"""find the users associated with this address"""
return User.objects.filter(email__endswith=f"@{self.domain}")
# pylint: disable=unused-argument
@receiver(models.signals.post_save, sender=SiteSettings)
def preview_image(instance, *args, **kwargs):

View file

@ -77,7 +77,8 @@ MIDDLEWARE = [
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"bookwyrm.timezone_middleware.TimezoneMiddleware",
"bookwyrm.middleware.TimezoneMiddleware",
"bookwyrm.middleware.IPBlocklistMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]

View file

@ -378,6 +378,13 @@ input[type=file]::file-selector-button:hover {
right: 1em;
}
/** Tooltips
******************************************************************************/
.tooltip {
width: 100%;
}
/** States
******************************************************************************/

View file

@ -3,7 +3,7 @@
{% trans "Help" as button_text %}
{% include 'snippets/toggle/open_button.html' with text=button_text class="ml-3 is-rounded is-small is-white p-0 pb-1" icon="question-circle is-size-6" controls_text=controls_text controls_uid=controls_uid %}
<aside class="notification is-hidden transition-y is-pulled-left mb-2" id="{{ controls_text }}{% if controls_uid %}-{{ controls_uid }}{% endif %}">
<aside class="tooltip notification is-hidden transition-y is-pulled-left mb-2" id="{{ controls_text }}{% if controls_uid %}-{{ controls_uid }}{% endif %}">
{% trans "Close" as button_text %}
{% include 'snippets/toggle/close_button.html' with label=button_text class="delete" nonbutton=True controls_text=controls_text controls_uid=controls_uid %}

View file

@ -8,7 +8,7 @@
{% block form %}
<form name="add-domain" method="post" action="{% url 'settings-email-blocks' %}">
{% csrf_token %}
<label class="label" for="id_event_date">{% trans "Domain:" %}</label>
<label class="label" for="id_domain">{% trans "Domain:" %}</label>
<div class="field has-addons">
<div class="control">
<div class="button is-disabled">@</div>

View file

@ -0,0 +1,36 @@
{% extends 'components/inline_form.html' %}
{% load i18n %}
{% block header %}
{% trans "Add IP address" %}
{% endblock %}
{% block form %}
<form name="add-address" method="post" action="{% url 'settings-ip-blocks' %}">
<div class="block">
{% trans "Use IP address blocks with caution, and consider using blocks only temporarily, as IP addresses are often shared or change hands. If you block your own IP, you will not be able to access this page." %}
</div>
{% csrf_token %}
<div class="field">
<label class="label" for="id_address">
{% trans "IP Address:" %}
</label>
</div>
<div class="field">
<input type="text" name="address" maxlength="255" class="input" required="" id="id_address" placeholder="190.0.2.0/24">
</div>
{% for error in form.address.errors %}
<p class="help is-danger">{{ error | escape }}</p>
{% endfor %}
<div class="field">
<div class="control">
<button type="submit" class="button is-primary">{% trans "Add" %}</button>
</div>
</div>
</form>
{% endblock %}

View file

@ -0,0 +1,47 @@
{% extends 'settings/layout.html' %}
{% load i18n %}
{% load humanize %}
{% block title %}{% trans "IP Address Blocklist" %}{% endblock %}
{% block header %}{% trans "IP Address Blocklist" %}{% endblock %}
{% block edit-button %}
{% trans "Add IP address" as button_text %}
{% include 'snippets/toggle/open_button.html' with controls_text="add_address" icon_with_text="plus" text=button_text focus="add_address_header" %}
{% endblock %}
{% block panel %}
{% include 'settings/ip_address_form.html' with controls_text="add_address" class="block" %}
<p class="notification block">
{% trans "Any traffic from this IP address will get a 404 response when trying to access any part of the application." %}
</p>
<table class="table is-striped is-fullwidth">
<tr>
<th>
{% trans "Address" %}
</th>
<th>
{% trans "Options" %}
</th>
</tr>
{% for address in addresses %}
<tr>
<td>{{ address.address }}</td>
<td>
<form name="remove-{{ address.id }}" action="{% url 'settings-ip-blocks-delete' address.id %}" method="post">
{% csrf_token %}
{% trans "Delete" as button_text %}
<button class="button" type="submit">
<span class="icon icon-x" title="{{ button_text }}" aria-hidden="true"></span>
<span class="is-hidden-mobile">{{ button_text }}</span>
</button>
</form>
</td>
</tr>
{% endfor %}
</table>
{% endblock %}

View file

@ -0,0 +1,8 @@
{% extends 'components/tooltip.html' %}
{% load i18n %}
{% block tooltip_content %}
{% trans "You can block IP ranges using CIDR syntax." %}
{% endblock %}

View file

@ -58,6 +58,10 @@
{% url 'settings-email-blocks' as url %}
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Email Blocklist" %}</a>
</li>
<li>
{% url 'settings-ip-blocks' as url %}
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "IP Address Blocklist" %}</a>
</li>
</ul>
{% endif %}
{% if perms.bookwyrm.edit_instance_settings %}

View file

@ -154,6 +154,16 @@ urlpatterns = [
views.EmailBlocklist.as_view(),
name="settings-email-blocks-delete",
),
re_path(
r"^settings/ip-blocklist/?$",
views.IPBlocklist.as_view(),
name="settings-ip-blocks",
),
re_path(
r"^settings/ip-blocks/(?P<block_id>\d+)/delete/?$",
views.IPBlocklist.as_view(),
name="settings-ip-blocks-delete",
),
# moderation
re_path(r"^settings/reports/?$", views.Reports.as_view(), name="settings-reports"),
re_path(

View file

@ -5,6 +5,7 @@ from .admin.federation import Federation, FederatedServer
from .admin.federation import AddFederatedServer, ImportServerBlocklist
from .admin.federation import block_server, unblock_server
from .admin.email_blocklist import EmailBlocklist
from .admin.ip_blocklist import IPBlocklist
from .admin.invite import ManageInvites, Invite, InviteRequest
from .admin.invite import ManageInviteRequests, ignore_invite_request
from .admin.reports import (

View file

@ -1,4 +1,4 @@
""" moderation via flagged posts and users """
""" Manage email blocklist"""
from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
@ -14,7 +14,7 @@ from bookwyrm import forms, models
name="dispatch",
)
class EmailBlocklist(View):
"""Block users by email address"""
"""Block registration by email address"""
def get(self, request):
"""view and compose blocks"""

View file

@ -0,0 +1,49 @@
""" Manage IP blocklist """
from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import get_object_or_404, redirect
from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator
from django.views import View
from bookwyrm import forms, models
# pylint: disable=no-self-use
@method_decorator(login_required, name="dispatch")
@method_decorator(
permission_required("bookwyrm.moderate_user", raise_exception=True),
name="dispatch",
)
class IPBlocklist(View):
"""Block registration by ip address"""
def get(self, request):
"""view and compose blocks"""
data = {
"addresses": models.IPBlocklist.objects.all(),
"form": forms.IPBlocklistForm(),
}
return TemplateResponse(request, "settings/ip_blocklist.html", data)
def post(self, request, block_id=None):
"""create a new ip address block"""
if block_id:
return self.delete(request, block_id)
form = forms.IPBlocklistForm(request.POST)
data = {
"addresses": models.IPBlocklist.objects.all(),
"form": form,
}
if not form.is_valid():
return TemplateResponse(request, "settings/ip_blocklist.html", data)
form.save()
data["form"] = forms.IPBlocklistForm()
return TemplateResponse(request, "settings/ip_blocklist.html", data)
# pylint: disable=unused-argument
def delete(self, request, domain_id):
"""remove a domain block"""
domain = get_object_or_404(models.IPBlocklist, id=domain_id)
domain.delete()
return redirect("settings-ip-blocks")

Binary file not shown.

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-09-12 17:16+0000\n"
"POT-Creation-Date: 2021-09-15 16:47+0000\n"
"PO-Revision-Date: 2021-03-19 11:49+0800\n"
"Last-Translator: Reese Porter <reesedporter@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -76,39 +76,31 @@ msgstr "Descendente"
#: bookwyrm/importers/importer.py:75
msgid "Error loading book"
msgstr ""
msgstr "Error en cargar libro"
#: bookwyrm/importers/importer.py:88
msgid "Could not find a match for book"
msgstr ""
msgstr "No se pudo encontrar el libro"
#: bookwyrm/models/base_model.py:13
#, fuzzy
#| msgid "Ascending"
msgid "Pending"
msgstr "Ascendente"
msgstr "Pendiente"
#: bookwyrm/models/base_model.py:14
msgid "Self deletion"
msgstr ""
msgstr "Auto-eliminación"
#: bookwyrm/models/base_model.py:15
#, fuzzy
#| msgid "Moderator Comments"
msgid "Moderator suspension"
msgstr "Comentarios de moderador"
msgstr "Suspensión de moderador"
#: bookwyrm/models/base_model.py:16
#, fuzzy
#| msgid "Duration"
msgid "Moderator deletion"
msgstr "Duración"
msgstr "Eliminación de moderador"
#: bookwyrm/models/base_model.py:17
#, fuzzy
#| msgid "Un-block"
msgid "Domain block"
msgstr "Desbloquear"
msgstr "Bloqueo de dominio"
#: bookwyrm/models/federated_server.py:11
#: bookwyrm/templates/settings/edit_server.html:40
@ -734,7 +726,7 @@ msgstr "Cerrar"
#: bookwyrm/templates/components/tooltip.html:3
msgid "Help"
msgstr ""
msgstr "Ayuda"
#: bookwyrm/templates/compose.html:5 bookwyrm/templates/compose.html:8
msgid "Compose status"
@ -1063,10 +1055,8 @@ msgid "Who to follow"
msgstr "A quién seguir"
#: bookwyrm/templates/feed/suggested_users.html:9
#, fuzzy
#| msgid "Show this account in suggested users:"
msgid "Don't show suggested users"
msgstr "Mostrar esta cuenta en los usuarios sugeridos:"
msgstr "No mostrar usuarios sugeridos"
#: bookwyrm/templates/feed/suggested_users.html:14
msgid "View directory"
@ -1349,7 +1339,7 @@ msgstr "Importado"
#: bookwyrm/templates/import/tooltip.html:6
msgid "You can download your GoodReads data from the <a href=\"https://www.goodreads.com/review/import\" target=\"_blank\" rel=\"noopener\">Import/Export page</a> of your GoodReads account."
msgstr ""
msgstr "Puedes descargar tus datos de GoodReads de la <a href=\"https://www.goodreads.com/review/import\" target=\"_blank\" rel=\"noopener\">Página de Exportación/Importación</a> de tu cuenta de GoodReads"
#: bookwyrm/templates/invite.html:4 bookwyrm/templates/invite.html:12
#: bookwyrm/templates/login.html:51
@ -1483,16 +1473,12 @@ msgid "Join"
msgstr "Unirse"
#: bookwyrm/templates/layout.html:214
#, fuzzy
#| msgid "Successfully imported"
msgid "Successfully posted status"
msgstr "Importado exitosamente"
msgstr "Status publicado exitosamente"
#: bookwyrm/templates/layout.html:215
#, fuzzy
#| msgid "Boost status"
msgid "Error posting status"
msgstr "Respaldar status"
msgstr "Error en publicar status"
#: bookwyrm/templates/layout.html:223
msgid "About this instance"
@ -1559,16 +1545,12 @@ msgid "Discard"
msgstr "Desechar"
#: bookwyrm/templates/lists/delete_list_modal.html:4
#, fuzzy
#| msgid "Delete this progress update"
msgid "Delete this list?"
msgstr "Eliminar esta actualización de progreso"
msgstr "¿Eliminar esta lista?"
#: bookwyrm/templates/lists/delete_list_modal.html:7
#, fuzzy
#| msgid "This field cannot be null."
msgid "This action cannot be un-done"
msgstr "Este campo no puede ser nulo."
msgstr "Esta acción no se puede deshacer"
#: bookwyrm/templates/lists/delete_list_modal.html:15
#: bookwyrm/templates/settings/announcement.html:20
@ -1615,10 +1597,8 @@ msgid "Anyone can add books to this list"
msgstr "Cualquer usuario puede agregar libros a esta lista"
#: bookwyrm/templates/lists/form.html:49
#, fuzzy
#| msgid "Delete status"
msgid "Delete list"
msgstr "Eliminar status"
msgstr "Eliminar lista"
#: bookwyrm/templates/lists/list.html:20
msgid "You successfully suggested a book for this list!"
@ -1989,16 +1969,12 @@ msgid "Edit Profile"
msgstr "Editar perfil"
#: bookwyrm/templates/preferences/edit_user.html:46
#, fuzzy
#| msgid "Show set reading goal prompt in feed:"
msgid "Show reading goal prompt in feed:"
msgstr "Mostrar meta de lectura en el feed:"
msgstr "Mostrar sugerencia de meta de lectura en el feed:"
#: bookwyrm/templates/preferences/edit_user.html:50
#, fuzzy
#| msgid "Show this account in suggested users:"
msgid "Show suggested users:"
msgstr "Mostrar esta cuenta en los usuarios sugeridos:"
msgstr "Mostrar usuarios sugeridos:"
#: bookwyrm/templates/preferences/edit_user.html:58
#, python-format
@ -2126,22 +2102,16 @@ msgid "Create Announcement"
msgstr "Crear anuncio"
#: bookwyrm/templates/settings/announcement_form.html:16
#, fuzzy
#| msgid "Preview"
msgid "Preview:"
msgstr "Vista preliminar"
msgstr "Vista preliminar:"
#: bookwyrm/templates/settings/announcement_form.html:23
#, fuzzy
#| msgid "Content"
msgid "Content:"
msgstr "Contenido"
msgstr "Contenido:"
#: bookwyrm/templates/settings/announcement_form.html:30
#, fuzzy
#| msgid "End date:"
msgid "Event date:"
msgstr "Fecha final:"
msgstr "Fecha de evento:"
#: bookwyrm/templates/settings/announcements.html:3
#: bookwyrm/templates/settings/announcements.html:5
@ -2184,106 +2154,84 @@ msgid "inactive"
msgstr "inactivo"
#: bookwyrm/templates/settings/announcements.html:54
#, fuzzy
#| msgid "Announcements"
msgid "No announcements found."
msgstr "Anuncios"
msgstr "No se encontró ningun anuncio."
#: bookwyrm/templates/settings/dashboard.html:6
#: bookwyrm/templates/settings/dashboard.html:8
#: bookwyrm/templates/settings/layout.html:26
msgid "Dashboard"
msgstr ""
msgstr "Tablero"
#: bookwyrm/templates/settings/dashboard.html:15
#, fuzzy
#| msgid "Local users"
msgid "Total users"
msgstr "Usuarios locales"
msgstr "Número de usuarios"
#: bookwyrm/templates/settings/dashboard.html:21
#: bookwyrm/templates/settings/dashboard_user_chart.html:12
msgid "Active this month"
msgstr ""
msgstr "Activos este mes"
#: bookwyrm/templates/settings/dashboard.html:27
#, fuzzy
#| msgid "Status"
msgid "Statuses"
msgstr "Status"
msgstr "Statuses"
#: bookwyrm/templates/settings/dashboard.html:33
msgid "Works"
msgstr ""
msgstr "Obras"
#: bookwyrm/templates/settings/dashboard.html:43
#, fuzzy, python-format
#| msgid "%(count)d uses"
msgid "%(display_count)s open report"
msgid_plural "%(display_count)s open reports"
msgstr[0] "%(count)d usos"
msgstr[1] "%(count)d usos"
msgstr[0] "%(display_count)s informe abierto"
msgstr[1] "%(display_count)s informes abiertos"
#: bookwyrm/templates/settings/dashboard.html:54
#, fuzzy, python-format
#| msgid "%(count)d uses"
msgid "%(display_count)s invite request"
msgid_plural "%(display_count)s invite requests"
msgstr[0] "%(count)d usos"
msgstr[1] "%(count)d usos"
msgstr[0] "%(display_count)s solicitación de invitado"
msgstr[1] "%(display_count)s solicitaciones de invitado"
#: bookwyrm/templates/settings/dashboard.html:65
#, fuzzy
#| msgid "User Activity"
msgid "Instance Activity"
msgstr "Actividad de usuario"
msgstr "Actividad de instancia"
#: bookwyrm/templates/settings/dashboard.html:83
#, fuzzy
#| msgid "Integer"
msgid "Interval:"
msgstr "Entero"
msgstr "Intervalo:"
#: bookwyrm/templates/settings/dashboard.html:86
msgid "Days"
msgstr ""
msgstr "Dias"
#: bookwyrm/templates/settings/dashboard.html:87
#, fuzzy
#| msgid "One Week"
msgid "Weeks"
msgstr "Una semana"
msgstr "Semanas"
#: bookwyrm/templates/settings/dashboard.html:100
#, fuzzy
#| msgid "User Activity"
msgid "User signup activity"
msgstr "Actividad de usuario"
msgstr "Actividad de inscripciones de usuarios"
#: bookwyrm/templates/settings/dashboard.html:106
#, fuzzy
#| msgid "User Activity"
msgid "Status activity"
msgstr "Actividad de usuario"
msgstr "Actividad de status"
#: bookwyrm/templates/settings/dashboard_status_chart.html:7
#, fuzzy
#| msgid "No statuses reported"
msgid "Statuses posted"
msgstr "Ningún estatus reportado"
msgstr "Statuses publicados"
#: bookwyrm/templates/settings/dashboard_user_chart.html:7
msgid "Total"
msgstr ""
msgstr "Suma"
#: bookwyrm/templates/settings/domain_form.html:5
#: bookwyrm/templates/settings/email_blocklist.html:10
msgid "Add domain"
msgstr ""
msgstr "Agregar dominio"
#: bookwyrm/templates/settings/domain_form.html:11
msgid "Domain:"
msgstr ""
msgstr "Dominio:"
#: bookwyrm/templates/settings/edit_server.html:3
#: bookwyrm/templates/settings/edit_server.html:6
@ -2334,32 +2282,26 @@ msgstr "Notas:"
#: bookwyrm/templates/settings/email_blocklist.html:5
#: bookwyrm/templates/settings/email_blocklist.html:7
#: bookwyrm/templates/settings/layout.html:59
#, fuzzy
#| msgid "Import Blocklist"
msgid "Email Blocklist"
msgstr "Importar lista de bloqueo"
msgstr "Lista de bloqueo de correos electrónicos"
#: bookwyrm/templates/settings/email_blocklist.html:18
msgid "When someone tries to register with an email from this domain, no account will be created. The registration process will appear to have worked."
msgstr ""
msgstr "Cuando alguien intenta registrarse con un correo electrónico de este dominio, ningun cuenta se creerá. El proceso de registración se parecerá a funcionado."
#: bookwyrm/templates/settings/email_blocklist.html:25
msgid "Domain"
msgstr ""
msgstr "Dominio"
#: bookwyrm/templates/settings/email_blocklist.html:29
#, fuzzy
#| msgid "Actions"
msgid "Options"
msgstr "Acciones"
msgstr "Opciones"
#: bookwyrm/templates/settings/email_blocklist.html:38
#, fuzzy, python-format
#| msgid "%(count)d uses"
msgid "%(display_count)s user"
msgid_plural "%(display_count)s users"
msgstr[0] "%(count)d usos"
msgstr[1] "%(count)d usos"
msgstr[0] "%(display_count)s usuario"
msgstr[1] "%(display_count)s usuarios"
#: bookwyrm/templates/settings/federated_server.html:19
msgid "Details"
@ -2452,10 +2394,8 @@ msgid "Manage Users"
msgstr "Administrar usuarios"
#: bookwyrm/templates/settings/layout.html:51
#, fuzzy
#| msgid "Duration"
msgid "Moderation"
msgstr "Duración"
msgstr "Moderación"
#: bookwyrm/templates/settings/layout.html:64
msgid "Instance Settings"
@ -2623,14 +2563,12 @@ msgid "Instance description:"
msgstr "Descripción de instancia:"
#: bookwyrm/templates/settings/site.html:27
#, fuzzy
#| msgid "Description:"
msgid "Short description:"
msgstr "Descripción:"
msgstr "Descripción corta:"
#: bookwyrm/templates/settings/site.html:28
msgid "Used when the instance is previewed on joinbookwyrm.com. Does not support html or markdown."
msgstr ""
msgstr "Utilizado cuando la instancia se ve de una vista previa en joinbookwyrm.com. No es compatible con html o markdown."
#: bookwyrm/templates/settings/site.html:32
msgid "Code of conduct:"
@ -2803,22 +2741,16 @@ msgid "An excerpt from '%(book_title)s'"
msgstr "Un extracto de '%(book_title)s'"
#: bookwyrm/templates/snippets/create_status/quotation.html:32
#, fuzzy
#| msgid "Description:"
msgid "Position:"
msgstr "Descripción:"
msgstr "Posición:"
#: bookwyrm/templates/snippets/create_status/quotation.html:45
#, fuzzy
#| msgid "pages"
msgid "On page:"
msgstr "páginas"
msgstr "En la página:"
#: bookwyrm/templates/snippets/create_status/quotation.html:51
#, fuzzy
#| msgid "percent"
msgid "At percent:"
msgstr "por ciento"
msgstr "Al por ciento:"
#: bookwyrm/templates/snippets/create_status/review.html:25
#, python-format
@ -2896,13 +2828,10 @@ msgid "No rating"
msgstr "No calificación"
#: bookwyrm/templates/snippets/form_rate_stars.html:28
#, fuzzy, python-format
#| msgid "%(rating)s star"
#| msgid_plural "%(rating)s stars"
msgid "%(half_rating)s star"
msgid_plural "%(half_rating)s stars"
msgstr[0] "%(rating)s estrella"
msgstr[1] "%(rating)s estrellas"
msgstr[0] "%(half_rating)s estrella"
msgstr[1] "%(half_rating)s estrellas"
#: bookwyrm/templates/snippets/form_rate_stars.html:64
#: bookwyrm/templates/snippets/stars.html:7
@ -3120,16 +3049,12 @@ msgid "Show less"
msgstr "Mostrar menos"
#: bookwyrm/templates/snippets/status/content_status.html:103
#, fuzzy, python-format
#| msgid "page %(page)s"
msgid "(Page %(page)s)"
msgstr "página %(page)s"
msgstr "(Página %(page)s)"
#: bookwyrm/templates/snippets/status/content_status.html:105
#, fuzzy, python-format
#| msgid "%(percent)s%% complete!"
msgid "(%(percent)s%%)"
msgstr "%(percent)s%% terminado!"
msgstr "(%(percent)s%%)"
#: bookwyrm/templates/snippets/status/content_status.html:127
msgid "Open image in new window"
@ -3312,7 +3237,8 @@ msgstr[1] ""
#: bookwyrm/templates/user/shelf/shelf.html:83
#, python-format
msgid "(showing %(start)s-%(end)s)"
msgstr ""
msgstr "(mostrando %(start)s-%(end)s)"
#: bookwyrm/templates/user/shelf/shelf.html:94
msgid "Edit shelf"
@ -3396,21 +3322,17 @@ msgstr "Ningún seguidor que tu sigues"
#: bookwyrm/templates/user_admin/delete_user_form.html:5
#: bookwyrm/templates/user_admin/user_moderation_actions.html:31
#, fuzzy
#| msgid "Permanently deleted"
msgid "Permanently delete user"
msgstr "Eliminado permanentemente"
msgstr "Eliminar usuario permanentemente"
#: bookwyrm/templates/user_admin/delete_user_form.html:12
#, python-format
msgid "Are you sure you want to delete <strong>%(username)s</strong>'s account? This action cannot be undone. To proceed, please enter your password to confirm deletion."
msgstr ""
msgstr "¿Estás seguro que quieres eliminar la cuenta de <strong>%(username)s</strong>'s? Esta acción no se puede deshacer. Para continuar, por favor, ingrese tu contraseña para confirmar eliminación."
#: bookwyrm/templates/user_admin/delete_user_form.html:17
#, fuzzy
#| msgid "Confirm password:"
msgid "Your password:"
msgstr "Confirmar contraseña:"
msgstr "Tu contraseña:"
#: bookwyrm/templates/user_admin/user.html:8
msgid "Back to users"
@ -3462,56 +3384,40 @@ msgid "Local"
msgstr "Local"
#: bookwyrm/templates/user_admin/user_info.html:38
#, fuzzy
#| msgid "Remove"
msgid "Remote"
msgstr "Quitar"
msgstr "Remoto"
#: bookwyrm/templates/user_admin/user_info.html:47
msgid "User details"
msgstr "Detalles"
#: bookwyrm/templates/user_admin/user_info.html:52
#, fuzzy
#| msgid "Email"
msgid "Email:"
msgstr "Correo electronico"
msgstr "Correo electronico:"
#: bookwyrm/templates/user_admin/user_info.html:64
#, fuzzy
#| msgid "View directory"
msgid "(View reports)"
msgstr "Ver directorio"
msgstr "(Ver informes)"
#: bookwyrm/templates/user_admin/user_info.html:72
#, fuzzy
#| msgid "Blocked by us:"
msgid "Blocked by count:"
msgstr "Bloqueado por nosotros:"
msgstr "Recuento de usuarios que han bloqueado este usuario:"
#: bookwyrm/templates/user_admin/user_info.html:77
#, fuzzy
#| msgid "last active"
msgid "Last active date:"
msgstr "actividad reciente"
msgstr "Fecha de actividad más reciente:"
#: bookwyrm/templates/user_admin/user_info.html:82
#, fuzzy
#| msgid "Manually approve followers:"
msgid "Manually approved followers:"
msgstr "Aprobar seguidores a mano:"
msgstr "Seguidores aprobados a mano:"
#: bookwyrm/templates/user_admin/user_info.html:87
#, fuzzy
#| msgid "Discover"
msgid "Discoverable:"
msgstr "Descubrir"
msgstr "Reconocible:"
#: bookwyrm/templates/user_admin/user_info.html:93
#, fuzzy
#| msgid "Deactivate user"
msgid "Deactivation reason:"
msgstr "Desactivar usuario"
msgstr "Razón de desactivación:"
#: bookwyrm/templates/user_admin/user_info.html:111
msgid "Instance details"