mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-22 09:31:08 +00:00
Merge branch 'main' into file-resubmit
This commit is contained in:
commit
1bedcdaebd
92 changed files with 1778 additions and 726 deletions
|
@ -13,14 +13,15 @@ User relationship interactions follow the standard ActivityPub spec.
|
|||
- `Block`: prevent users from seeing one another's statuses, and prevents the blocked user from viewing the actor's profile
|
||||
- `Update`: updates a user's profile and settings
|
||||
- `Delete`: deactivates a user
|
||||
- `Undo`: reverses a `Follow` or `Block`
|
||||
- `Undo`: reverses a `Block` or `Follow`
|
||||
|
||||
### Activities
|
||||
- `Create/Status`: saves a new status in the database.
|
||||
- `Delete/Status`: Removes a status
|
||||
- `Like/Status`: Creates a favorite on the status
|
||||
- `Announce/Status`: Boosts the status into the actor's timeline
|
||||
- `Undo/*`,: Reverses a `Like` or `Announce`
|
||||
- `Undo/*`,: Reverses an `Announce`, `Like`, or `Move`
|
||||
- `Move/User`: Moves a user from one ActivityPub id to another.
|
||||
|
||||
### Collections
|
||||
User's books and lists are represented by [`OrderedCollection`](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-orderedcollection)
|
||||
|
|
|
@ -23,6 +23,7 @@ from .verbs import Create, Delete, Undo, Update
|
|||
from .verbs import Follow, Accept, Reject, Block
|
||||
from .verbs import Add, Remove
|
||||
from .verbs import Announce, Like
|
||||
from .verbs import Move
|
||||
|
||||
# this creates a list of all the Activity types that we can serialize,
|
||||
# so when an Activity comes in from outside, we can check if it's known
|
||||
|
|
|
@ -22,8 +22,6 @@ class BookData(ActivityObject):
|
|||
aasin: Optional[str] = None
|
||||
isfdb: Optional[str] = None
|
||||
lastEditedBy: Optional[str] = None
|
||||
links: list[str] = field(default_factory=list)
|
||||
fileLinks: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
|
@ -45,6 +43,8 @@ class Book(BookData):
|
|||
firstPublishedDate: str = ""
|
||||
publishedDate: str = ""
|
||||
|
||||
fileLinks: list[str] = field(default_factory=list)
|
||||
|
||||
cover: Optional[Document] = None
|
||||
type: str = "Book"
|
||||
|
||||
|
|
|
@ -40,4 +40,6 @@ class Person(ActivityObject):
|
|||
manuallyApprovesFollowers: str = False
|
||||
discoverable: str = False
|
||||
hideFollows: str = False
|
||||
movedTo: str = None
|
||||
alsoKnownAs: dict[str] = None
|
||||
type: str = "Person"
|
||||
|
|
|
@ -231,3 +231,30 @@ class Announce(Verb):
|
|||
def action(self, allow_external_connections=True):
|
||||
"""boost"""
|
||||
self.to_model(allow_external_connections=allow_external_connections)
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class Move(Verb):
|
||||
"""a user moving an object"""
|
||||
|
||||
object: str
|
||||
type: str = "Move"
|
||||
origin: str = None
|
||||
target: str = None
|
||||
|
||||
def action(self, allow_external_connections=True):
|
||||
"""move"""
|
||||
|
||||
object_is_user = resolve_remote_id(remote_id=self.object, model="User")
|
||||
|
||||
if object_is_user:
|
||||
model = apps.get_model("bookwyrm.MoveUser")
|
||||
|
||||
self.to_model(
|
||||
model=model,
|
||||
save=True,
|
||||
allow_external_connections=allow_external_connections,
|
||||
)
|
||||
else:
|
||||
# we might do something with this to move other objects at some point
|
||||
pass
|
||||
|
|
|
@ -70,6 +70,22 @@ class DeleteUserForm(CustomForm):
|
|||
fields = ["password"]
|
||||
|
||||
|
||||
class MoveUserForm(CustomForm):
|
||||
target = forms.CharField(widget=forms.TextInput)
|
||||
|
||||
class Meta:
|
||||
model = models.User
|
||||
fields = ["password"]
|
||||
|
||||
|
||||
class AliasUserForm(CustomForm):
|
||||
username = forms.CharField(widget=forms.TextInput)
|
||||
|
||||
class Meta:
|
||||
model = models.User
|
||||
fields = ["password"]
|
||||
|
||||
|
||||
class ChangePasswordForm(CustomForm):
|
||||
current_password = forms.CharField(widget=forms.PasswordInput)
|
||||
confirm_password = forms.CharField(widget=forms.PasswordInput)
|
||||
|
|
|
@ -40,7 +40,12 @@ class IsbnHyphenator:
|
|||
self.__element_tree = ElementTree.parse(self.__range_file_path)
|
||||
|
||||
gs1_prefix = isbn_13[:3]
|
||||
reg_group = self.__find_reg_group(isbn_13, gs1_prefix)
|
||||
try:
|
||||
reg_group = self.__find_reg_group(isbn_13, gs1_prefix)
|
||||
except ValueError:
|
||||
# if the reg groups are invalid, just return the original isbn
|
||||
return isbn_13
|
||||
|
||||
if reg_group is None:
|
||||
return isbn_13 # failed to hyphenate
|
||||
|
||||
|
|
|
@ -45,5 +45,7 @@ class Migration(migrations.Migration):
|
|||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(populate_sort_title),
|
||||
migrations.RunPython(
|
||||
populate_sort_title, reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
||||
|
|
130
bookwyrm/migrations/0182_auto_20231027_1122.py
Normal file
130
bookwyrm/migrations/0182_auto_20231027_1122.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
# Generated by Django 3.2.20 on 2023-10-27 11:22
|
||||
|
||||
import bookwyrm.models.activitypub_mixin
|
||||
import bookwyrm.models.fields
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0181_merge_20230806_2302"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="also_known_as",
|
||||
field=bookwyrm.models.fields.ManyToManyField(to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="moved_to",
|
||||
field=bookwyrm.models.fields.RemoteIdField(
|
||||
max_length=255,
|
||||
null=True,
|
||||
validators=[bookwyrm.models.fields.validate_remote_id],
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="notification_type",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("FAVORITE", "Favorite"),
|
||||
("REPLY", "Reply"),
|
||||
("MENTION", "Mention"),
|
||||
("TAG", "Tag"),
|
||||
("FOLLOW", "Follow"),
|
||||
("FOLLOW_REQUEST", "Follow Request"),
|
||||
("BOOST", "Boost"),
|
||||
("IMPORT", "Import"),
|
||||
("ADD", "Add"),
|
||||
("REPORT", "Report"),
|
||||
("LINK_DOMAIN", "Link Domain"),
|
||||
("INVITE", "Invite"),
|
||||
("ACCEPT", "Accept"),
|
||||
("JOIN", "Join"),
|
||||
("LEAVE", "Leave"),
|
||||
("REMOVE", "Remove"),
|
||||
("GROUP_PRIVACY", "Group Privacy"),
|
||||
("GROUP_NAME", "Group Name"),
|
||||
("GROUP_DESCRIPTION", "Group Description"),
|
||||
("MOVE", "Move"),
|
||||
],
|
||||
max_length=255,
|
||||
),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="Move",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("created_date", models.DateTimeField(auto_now_add=True)),
|
||||
("updated_date", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"remote_id",
|
||||
bookwyrm.models.fields.RemoteIdField(
|
||||
max_length=255,
|
||||
null=True,
|
||||
validators=[bookwyrm.models.fields.validate_remote_id],
|
||||
),
|
||||
),
|
||||
("object", bookwyrm.models.fields.CharField(max_length=255)),
|
||||
(
|
||||
"origin",
|
||||
bookwyrm.models.fields.CharField(
|
||||
blank=True, default="", max_length=255, null=True
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
bookwyrm.models.fields.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=(bookwyrm.models.activitypub_mixin.ActivityMixin, models.Model),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="MoveUser",
|
||||
fields=[
|
||||
(
|
||||
"move_ptr",
|
||||
models.OneToOneField(
|
||||
auto_created=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
parent_link=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to="bookwyrm.move",
|
||||
),
|
||||
),
|
||||
(
|
||||
"target",
|
||||
bookwyrm.models.fields.ForeignKey(
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name="move_target",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"abstract": False,
|
||||
},
|
||||
bases=("bookwyrm.move",),
|
||||
),
|
||||
]
|
18
bookwyrm/migrations/0183_auto_20231105_1607.py
Normal file
18
bookwyrm/migrations/0183_auto_20231105_1607.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 3.2.20 on 2023-11-05 16:07
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0182_auto_20231027_1122"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="user",
|
||||
name="is_deleted",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
49
bookwyrm/migrations/0184_auto_20231106_0421.py
Normal file
49
bookwyrm/migrations/0184_auto_20231106_0421.py
Normal file
|
@ -0,0 +1,49 @@
|
|||
# Generated by Django 3.2.20 on 2023-11-06 04:21
|
||||
|
||||
from django.db import migrations
|
||||
from bookwyrm.models import User
|
||||
|
||||
|
||||
def update_deleted_users(apps, schema_editor):
|
||||
"""Find all the users who are deleted, not just inactive, and set deleted"""
|
||||
users = apps.get_model("bookwyrm", "User")
|
||||
db_alias = schema_editor.connection.alias
|
||||
users.objects.using(db_alias).filter(
|
||||
is_active=False,
|
||||
deactivation_reason__in=[
|
||||
"self_deletion",
|
||||
"moderator_deletion",
|
||||
],
|
||||
).update(is_deleted=True)
|
||||
|
||||
# differente rules for remote users
|
||||
users.objects.using(db_alias).filter(is_active=False, local=False,).exclude(
|
||||
deactivation_reason="moderator_deactivation",
|
||||
).update(is_deleted=True)
|
||||
|
||||
|
||||
def erase_deleted_user_data(apps, schema_editor):
|
||||
"""Retroactively clear user data"""
|
||||
for user in User.objects.filter(is_deleted=True):
|
||||
user.erase_user_data()
|
||||
user.save(
|
||||
broadcast=False,
|
||||
update_fields=["email", "avatar", "preview_image", "summary", "name"],
|
||||
)
|
||||
user.erase_user_statuses(broadcast=False)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0183_auto_20231105_1607"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
update_deleted_users, reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
migrations.RunPython(
|
||||
erase_deleted_user_data, reverse_code=migrations.RunPython.noop
|
||||
),
|
||||
]
|
|
@ -27,6 +27,8 @@ from .group import Group, GroupMember, GroupMemberInvitation
|
|||
|
||||
from .import_job import ImportJob, ImportItem
|
||||
|
||||
from .move import MoveUser
|
||||
|
||||
from .site import SiteSettings, Theme, SiteInvite
|
||||
from .site import PasswordReset, InviteRequest
|
||||
from .announcement import Announcement
|
||||
|
|
|
@ -366,9 +366,9 @@ class Edition(Book):
|
|||
|
||||
# normalize isbn format
|
||||
if self.isbn_10:
|
||||
self.isbn_10 = re.sub(r"[^0-9X]", "", self.isbn_10)
|
||||
self.isbn_10 = normalize_isbn(self.isbn_10)
|
||||
if self.isbn_13:
|
||||
self.isbn_13 = re.sub(r"[^0-9X]", "", self.isbn_13)
|
||||
self.isbn_13 = normalize_isbn(self.isbn_13)
|
||||
|
||||
# set rank
|
||||
self.edition_rank = self.get_rank()
|
||||
|
@ -463,6 +463,11 @@ def isbn_13_to_10(isbn_13):
|
|||
return converted + str(checkdigit)
|
||||
|
||||
|
||||
def normalize_isbn(isbn):
|
||||
"""Remove unexpected characters from ISBN 10 or 13"""
|
||||
return re.sub(r"[^0-9X]", "", isbn)
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
@receiver(models.signals.post_save, sender=Edition)
|
||||
def preview_image(instance, *args, **kwargs):
|
||||
|
|
|
@ -483,10 +483,12 @@ class ImageField(ActivitypubFieldMixin, models.ImageField):
|
|||
image_slug = value
|
||||
# when it's an inline image (User avatar/icon, Book cover), it's a json
|
||||
# blob, but when it's an attached image, it's just a url
|
||||
if hasattr(image_slug, "url"):
|
||||
url = image_slug.url
|
||||
elif isinstance(image_slug, str):
|
||||
if isinstance(image_slug, str):
|
||||
url = image_slug
|
||||
elif isinstance(image_slug, dict):
|
||||
url = image_slug.get("url")
|
||||
elif hasattr(image_slug, "url"): # Serialized to Image/Document object?
|
||||
url = image_slug.url
|
||||
else:
|
||||
return None
|
||||
|
||||
|
|
72
bookwyrm/models/move.py
Normal file
72
bookwyrm/models/move.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
""" move an object including migrating a user account """
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import models
|
||||
|
||||
from bookwyrm import activitypub
|
||||
from .activitypub_mixin import ActivityMixin
|
||||
from .base_model import BookWyrmModel
|
||||
from . import fields
|
||||
from .notification import Notification
|
||||
|
||||
|
||||
class Move(ActivityMixin, BookWyrmModel):
|
||||
"""migrating an activitypub user account"""
|
||||
|
||||
user = fields.ForeignKey(
|
||||
"User", on_delete=models.PROTECT, activitypub_field="actor"
|
||||
)
|
||||
|
||||
object = fields.CharField(
|
||||
max_length=255,
|
||||
blank=False,
|
||||
null=False,
|
||||
activitypub_field="object",
|
||||
)
|
||||
|
||||
origin = fields.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
null=True,
|
||||
default="",
|
||||
activitypub_field="origin",
|
||||
)
|
||||
|
||||
activity_serializer = activitypub.Move
|
||||
|
||||
|
||||
class MoveUser(Move):
|
||||
"""migrating an activitypub user account"""
|
||||
|
||||
target = fields.ForeignKey(
|
||||
"User",
|
||||
on_delete=models.PROTECT,
|
||||
related_name="move_target",
|
||||
activitypub_field="target",
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""update user info and broadcast it"""
|
||||
|
||||
# only allow if the source is listed in the target's alsoKnownAs
|
||||
if self.user in self.target.also_known_as.all():
|
||||
|
||||
self.user.also_known_as.add(self.target.id)
|
||||
self.user.update_active_date()
|
||||
self.user.moved_to = self.target.remote_id
|
||||
self.user.save(update_fields=["moved_to"])
|
||||
|
||||
if self.user.local:
|
||||
kwargs[
|
||||
"broadcast"
|
||||
] = True # Only broadcast if we are initiating the Move
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
for follower in self.user.followers.all():
|
||||
if follower.local:
|
||||
Notification.notify(
|
||||
follower, self.user, notification_type=Notification.MOVE
|
||||
)
|
||||
|
||||
else:
|
||||
raise PermissionDenied()
|
|
@ -40,11 +40,14 @@ class Notification(BookWyrmModel):
|
|||
GROUP_NAME = "GROUP_NAME"
|
||||
GROUP_DESCRIPTION = "GROUP_DESCRIPTION"
|
||||
|
||||
# Migrations
|
||||
MOVE = "MOVE"
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
NotificationType = models.TextChoices(
|
||||
# there has got be a better way to do this
|
||||
"NotificationType",
|
||||
f"{FAVORITE} {REPLY} {MENTION} {TAG} {FOLLOW} {FOLLOW_REQUEST} {BOOST} {IMPORT} {ADD} {REPORT} {LINK_DOMAIN} {INVITE} {ACCEPT} {JOIN} {LEAVE} {REMOVE} {GROUP_PRIVACY} {GROUP_NAME} {GROUP_DESCRIPTION}",
|
||||
f"{FAVORITE} {REPLY} {MENTION} {TAG} {FOLLOW} {FOLLOW_REQUEST} {BOOST} {IMPORT} {ADD} {REPORT} {LINK_DOMAIN} {INVITE} {ACCEPT} {JOIN} {LEAVE} {REMOVE} {GROUP_PRIVACY} {GROUP_NAME} {GROUP_DESCRIPTION} {MOVE}",
|
||||
)
|
||||
|
||||
user = models.ForeignKey("User", on_delete=models.CASCADE)
|
||||
|
|
|
@ -102,7 +102,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel):
|
|||
if hasattr(self, "quotation"):
|
||||
self.quotation = None # pylint: disable=attribute-defined-outside-init
|
||||
self.deleted_date = timezone.now()
|
||||
self.save()
|
||||
self.save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def recipients(self):
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
""" database schema for user data """
|
||||
import re
|
||||
from urllib.parse import urlparse
|
||||
from uuid import uuid4
|
||||
|
||||
from django.apps import apps
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.contrib.postgres.fields import ArrayField, CICharField
|
||||
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
|
||||
from django.dispatch import receiver
|
||||
from django.db import models, transaction
|
||||
from django.db import models, transaction, IntegrityError
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from model_utils import FieldTracker
|
||||
|
@ -53,6 +54,7 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
|
||||
username = fields.UsernameField()
|
||||
email = models.EmailField(unique=True, null=True)
|
||||
is_deleted = models.BooleanField(default=False)
|
||||
|
||||
key_pair = fields.OneToOneField(
|
||||
"KeyPair",
|
||||
|
@ -140,6 +142,19 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
theme = models.ForeignKey("Theme", null=True, blank=True, on_delete=models.SET_NULL)
|
||||
hide_follows = fields.BooleanField(default=False)
|
||||
|
||||
# migration fields
|
||||
|
||||
moved_to = fields.RemoteIdField(
|
||||
null=True, unique=False, activitypub_field="movedTo", deduplication_field=False
|
||||
)
|
||||
also_known_as = fields.ManyToManyField(
|
||||
"self",
|
||||
symmetrical=False,
|
||||
unique=False,
|
||||
activitypub_field="alsoKnownAs",
|
||||
deduplication_field=False,
|
||||
)
|
||||
|
||||
# options to turn features on and off
|
||||
show_goal = models.BooleanField(default=True)
|
||||
show_suggested_users = models.BooleanField(default=True)
|
||||
|
@ -314,6 +329,8 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
"schema": "http://schema.org#",
|
||||
"PropertyValue": "schema:PropertyValue",
|
||||
"value": "schema:value",
|
||||
"alsoKnownAs": {"@id": "as:alsoKnownAs", "@type": "@id"},
|
||||
"movedTo": {"@id": "as:movedTo", "@type": "@id"},
|
||||
},
|
||||
]
|
||||
return activity_object
|
||||
|
@ -379,9 +396,44 @@ class User(OrderedCollectionPageMixin, AbstractUser):
|
|||
"""We don't actually delete the database entry"""
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
self.is_active = False
|
||||
self.avatar = ""
|
||||
self.allow_reactivation = False
|
||||
self.is_deleted = True
|
||||
|
||||
self.erase_user_data()
|
||||
self.erase_user_statuses()
|
||||
|
||||
# skip the logic in this class's save()
|
||||
super().save(*args, **kwargs)
|
||||
super().save(
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def erase_user_data(self):
|
||||
"""Wipe a user's custom data"""
|
||||
if not self.is_deleted:
|
||||
raise IntegrityError(
|
||||
"Trying to erase user data on user that is not deleted"
|
||||
)
|
||||
|
||||
# mangle email address
|
||||
self.email = f"{uuid4()}@deleted.user"
|
||||
|
||||
# erase data fields
|
||||
self.avatar = ""
|
||||
self.preview_image = ""
|
||||
self.summary = None
|
||||
self.name = None
|
||||
self.favorites.set([])
|
||||
|
||||
def erase_user_statuses(self, broadcast=True):
|
||||
"""Wipe the data on all the user's statuses"""
|
||||
if not self.is_deleted:
|
||||
raise IntegrityError(
|
||||
"Trying to erase user data on user that is not deleted"
|
||||
)
|
||||
|
||||
for status in self.status_set.all():
|
||||
status.delete(broadcast=broadcast)
|
||||
|
||||
def deactivate(self):
|
||||
"""Disable the user but allow them to reactivate"""
|
||||
|
|
|
@ -375,9 +375,9 @@ if USE_S3:
|
|||
AWS_ACCESS_KEY_ID = env("AWS_ACCESS_KEY_ID")
|
||||
AWS_SECRET_ACCESS_KEY = env("AWS_SECRET_ACCESS_KEY")
|
||||
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")
|
||||
AWS_S3_CUSTOM_DOMAIN = env("AWS_S3_CUSTOM_DOMAIN")
|
||||
AWS_S3_CUSTOM_DOMAIN = env("AWS_S3_CUSTOM_DOMAIN", None)
|
||||
AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME", "")
|
||||
AWS_S3_ENDPOINT_URL = env("AWS_S3_ENDPOINT_URL")
|
||||
AWS_S3_ENDPOINT_URL = env("AWS_S3_ENDPOINT_URL", None)
|
||||
AWS_DEFAULT_ACL = "public-read"
|
||||
AWS_S3_OBJECT_PARAMETERS = {"CacheControl": "max-age=86400"}
|
||||
# S3 Static settings
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</div>
|
||||
<div class="column">
|
||||
<label class="label" for="id_cover_url">
|
||||
{% trans "Load cover from url:" %}
|
||||
{% trans "Load cover from URL:" %}
|
||||
</label>
|
||||
<input class="input" name="cover-url" id="id_cover_url">
|
||||
</div>
|
||||
|
|
|
@ -247,7 +247,7 @@
|
|||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="id_cover_url">
|
||||
{% trans "Load cover from url:" %}
|
||||
{% trans "Load cover from URL:" %}
|
||||
</label>
|
||||
<input class="input" name="cover-url" id="id_cover_url" type="url" value="{{ cover_url|default:'' }}" aria-describedby="desc_cover">
|
||||
</div>
|
||||
|
|
|
@ -99,7 +99,7 @@ homeTour.addSteps([
|
|||
],
|
||||
},
|
||||
{
|
||||
text: "{% trans 'Use the <strong>Feed</strong>, <strong>Lists</strong> and <strong>Discover</strong> links to discover the latest news from your feed, lists of books by topic, and the latest happenings on this Bookwyrm server!' %}",
|
||||
text: "{% trans 'Use the <strong>Lists</strong>, <strong>Discover</strong>, and <strong>Your Books</strong> links to discover reading suggestions and the latest happenings on this server, or to see your catalogued books!' %}",
|
||||
title: "{% trans 'Navigation Bar' %}",
|
||||
attachTo: {
|
||||
element: checkResponsiveState('#tour-navbar-start'),
|
||||
|
@ -197,7 +197,7 @@ homeTour.addSteps([
|
|||
],
|
||||
},
|
||||
{
|
||||
text: `{% trans "Your profile, books, direct messages, and settings can be accessed by clicking on your name in the menu here." %} <p class="notification is-warning is-light mt-3">{% trans "Try selecting <strong>Profile</strong> from the drop down menu to continue the tour." %}</p>`,
|
||||
text: `{% trans "Your profile, user directory, direct messages, and settings can be accessed by clicking on your name in the menu here." %} <p class="notification is-warning is-light mt-3">{% trans "Try selecting <strong>Profile</strong> from the drop down menu to continue the tour." %}</p>`,
|
||||
title: "{% trans 'Profile and settings menu' %}",
|
||||
attachTo: {
|
||||
element: checkResponsiveState('#navbar-dropdown'),
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
{% blocktrans trimmed count days=import_limit_reset with display_size=import_size_limit|intcomma %}
|
||||
Currently, you are allowed to import {{ display_size }} books every {{ import_limit_reset }} day.
|
||||
{% plural %}
|
||||
Currently, you are allowed to import {{ import_size_limit }} books every {{ import_limit_reset }} days.
|
||||
Currently, you are allowed to import {{ display_size }} books every {{ import_limit_reset }} days.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p>{% blocktrans with display_left=allowed_imports|intcomma %}You have {{ display_left }} left.{% endblocktrans %}</p>
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
<nav class="navbar" aria-label="main navigation">
|
||||
<div class="container">
|
||||
{% with notification_count=request.user.unread_notification_count has_unread_mentions=request.user.has_unread_mentions %}
|
||||
{% if not request.user.moved_to %}
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
<img class="image logo" src="{% if site.logo_small %}{% get_media_prefix %}{{ site.logo_small }}{% else %}{% static "images/logo-small.png" %}{% endif %}" alt="{% blocktrans with site_name=site.name %}{{ site_name }} home page{% endblocktrans %}" loading="lazy" decoding="async">
|
||||
|
@ -34,7 +35,7 @@
|
|||
<form class="navbar-item column is-align-items-start pt-5" action="{% url 'search' %}">
|
||||
<div class="field has-addons">
|
||||
<div class="control">
|
||||
{% if user.is_authenticated %}
|
||||
{% if request.user.is_authenticated %}
|
||||
{% trans "Search for a book, user, or list" as search_placeholder %}
|
||||
{% else %}
|
||||
{% trans "Search for a book" as search_placeholder %}
|
||||
|
@ -80,19 +81,18 @@
|
|||
</strong>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="navbar-menu" id="main_nav">
|
||||
<div class="navbar-start" id="tour-navbar-start">
|
||||
{% if request.user.is_authenticated %}
|
||||
<a href="/#feed" class="navbar-item mt-3 py-0">
|
||||
{% trans "Feed" %}
|
||||
</a>
|
||||
<a href="{% url 'lists' %}" class="navbar-item mt-3 py-0">
|
||||
{% trans "Lists" %}
|
||||
</a>
|
||||
<a href="{% url 'discover' %}" class="navbar-item mt-3 py-0">
|
||||
{% trans "Discover" %}
|
||||
</a>
|
||||
<a href="{% url 'user-shelves' request.user.localname %}" class="navbar-item mt-3 py-0">
|
||||
{% trans "Your Books" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
@ -157,6 +157,13 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
<img class="image logo" src="{% if site.logo_small %}{% get_media_prefix %}{{ site.logo_small }}{% else %}{% static "images/logo-small.png" %}{% endif %}" alt="{% blocktrans with site_name=site.name %}{{ site_name }} home page{% endblocktrans %}" loading="lazy" decoding="async">
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -173,11 +180,15 @@
|
|||
|
||||
<main class="section is-flex-grow-1">
|
||||
<div class="container">
|
||||
{# almost every view needs to know the user shelves #}
|
||||
{% with request.user.shelf_set.all as user_shelves %}
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
{% endwith %}
|
||||
{% if request.user.moved_to %}
|
||||
{% include "moved.html" %}
|
||||
{% else %}
|
||||
{# almost every view needs to know the user shelves #}
|
||||
{% with request.user.shelf_set.all as user_shelves %}
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
|
52
bookwyrm/templates/moved.html
Normal file
52
bookwyrm/templates/moved.html
Normal file
|
@ -0,0 +1,52 @@
|
|||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load utilities %}
|
||||
|
||||
<div class="container my-6">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img src="{% if request.user.avatar %}{% get_media_prefix %}{{ request.user.avatar }}{% else %}{% static "images/default_avi.jpg" %}{% endif %}"
|
||||
{% if ariaHide %}aria-hidden="true"{% endif %}
|
||||
alt="{{ request.user.alt_text }}"
|
||||
loading="lazy"
|
||||
decoding="async">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">{{ request.user.display_name }}</p>
|
||||
<p class="subtitle is-6"><s>{{request.user.username}}</s></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="notification is-warning">
|
||||
<p>
|
||||
{% id_to_username request.user.moved_to as username %}
|
||||
{% blocktrans trimmed with moved_to=user.moved_to %}
|
||||
<strong>You have moved your account</strong> to <a href="{{ moved_to }}">{{ username }}</a>
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
{% trans "You can undo the move to restore full functionality, but some followers may have already unfollowed this account." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="columns is-justify-content-center">
|
||||
<div class="column is-one-quarter">
|
||||
<div class="level">
|
||||
<form class="level-left" name="remove-alias" action="{% url 'prefs-unmove' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="remote_id" id="remote_id" value="{{user.moved_to}}">
|
||||
<button type="submit" class="button is-medium is-danger">{% trans "Undo move" %}</button>
|
||||
</form>
|
||||
<form class="level-right" name="logout" action="{% url 'logout' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="button is-medium is-primary">{% trans 'Log out' %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -10,7 +10,9 @@
|
|||
{% elif notification.notification_type == 'FOLLOW' %}
|
||||
{% include 'notifications/items/follow.html' %}
|
||||
{% elif notification.notification_type == 'FOLLOW_REQUEST' %}
|
||||
{% include 'notifications/items/follow_request.html' %}
|
||||
{% if notification.related_users.0.is_active %}
|
||||
{% include 'notifications/items/follow_request.html' %}
|
||||
{% endif %}
|
||||
{% elif notification.notification_type == 'IMPORT' %}
|
||||
{% include 'notifications/items/import.html' %}
|
||||
{% elif notification.notification_type == 'ADD' %}
|
||||
|
@ -35,4 +37,6 @@
|
|||
{% include 'notifications/items/update.html' %}
|
||||
{% elif notification.notification_type == 'GROUP_DESCRIPTION' %}
|
||||
{% include 'notifications/items/update.html' %}
|
||||
{% elif notification.notification_type == 'MOVE' %}
|
||||
{% include 'notifications/items/move_user.html' %}
|
||||
{% endif %}
|
||||
|
|
|
@ -39,6 +39,8 @@
|
|||
|
||||
{% with related_user=related_users.0.display_name %}
|
||||
{% with related_user_link=related_users.0.local_path %}
|
||||
{% with related_user_moved_to=related_users.0.moved_to %}
|
||||
{% with related_user_username=related_users.0.username %}
|
||||
{% with second_user=related_users.1.display_name %}
|
||||
{% with second_user_link=related_users.1.local_path %}
|
||||
{% with other_user_count=related_user_count|add:"-1" %}
|
||||
|
@ -50,6 +52,8 @@
|
|||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
{% if related_status %}
|
||||
|
|
29
bookwyrm/templates/notifications/items/move_user.html
Normal file
29
bookwyrm/templates/notifications/items/move_user.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
{% extends 'notifications/items/layout.html' %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load utilities %}
|
||||
{% load user_page_tags %}
|
||||
|
||||
{% block primary_link %}{% spaceless %}
|
||||
{{ notification.related_object.local_path }}
|
||||
{% endspaceless %}{% endblock %}
|
||||
|
||||
{% block icon %}
|
||||
<span class="icon icon-local"></span>
|
||||
{% endblock %}
|
||||
|
||||
{% block description %}
|
||||
{% if related_user_moved_to %}
|
||||
{% id_to_username request.user.moved_to as username %}
|
||||
{% blocktrans trimmed %}
|
||||
{{ related_user }} has moved to <a href="{{ related_user_moved_to }}">{{ username }}</a>
|
||||
{% endblocktrans %}
|
||||
<div class="row shrink my-2">
|
||||
{% include 'snippets/move_user_buttons.html' with group=notification.related_group %}
|
||||
</div>
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
{{ related_user }} has undone their move
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
59
bookwyrm/templates/preferences/alias_user.html
Normal file
59
bookwyrm/templates/preferences/alias_user.html
Normal file
|
@ -0,0 +1,59 @@
|
|||
{% extends 'preferences/layout.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Move Account" %}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
{% trans "Create Alias" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
<div class="block">
|
||||
<h2 class="title is-4">{% trans "Add another account as an alias" %}</h2>
|
||||
<div class="box">
|
||||
<div class="notification is-info is-light">
|
||||
<p>
|
||||
{% trans "Marking another account as an alias is required if you want to move that account to this one." %}
|
||||
</p>
|
||||
<p>
|
||||
{% trans "This is a reversable action and will not change the functionality of this account." %}
|
||||
</p>
|
||||
</div>
|
||||
<form name="alias-user" action="{% url 'prefs-alias' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<label class="label" for="id_target">{% trans "Enter the username for the account you want to add as an alias e.g. <em>user@example.com </em>:" %}</label>
|
||||
<input class="input {% if form.username.errors %}is-danger{% endif %}" type="text" name="username" id="id_username" required aria-describedby="desc_username">
|
||||
{% include 'snippets/form_errors.html' with errors_list=form.username.errors id="desc_username" %}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="id_password">{% trans "Confirm your password:" %}</label>
|
||||
<input class="input {% if form.password.errors %}is-danger{% endif %}" type="password" name="password" id="id_password" required aria-describedby="desc_password">
|
||||
{% include 'snippets/form_errors.html' with errors_list=form.password.errors id="desc_password" %}
|
||||
</div>
|
||||
<button type="submit" class="button is-success">{% trans "Create Alias" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
{% if user.also_known_as.all.0 %}
|
||||
<div class="box">
|
||||
<h2 class="title is-4">{% trans "Aliases" %}</h2>
|
||||
<div class="table-container block">
|
||||
<table class="table is-striped is-fullwidth">
|
||||
{% for alias in user.also_known_as.all %}
|
||||
<tr>
|
||||
<td>{{ alias.username }}</td>
|
||||
<td>
|
||||
<form name="remove-alias" action="{% url 'prefs-remove-alias' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="alias" id="alias" value="{{ alias.id }}">
|
||||
<button type="submit" class="button is-info">{% trans "Remove alias" %}</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -23,6 +23,14 @@
|
|||
{% url 'prefs-2fa' as url %}
|
||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Two Factor Authentication" %}</a>
|
||||
</li>
|
||||
<li>
|
||||
{% url 'prefs-alias' as url %}
|
||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Aliases" %}</a>
|
||||
</li>
|
||||
<li>
|
||||
{% url 'prefs-move' as url %}
|
||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Move Account" %}</a>
|
||||
</li>
|
||||
<li>
|
||||
{% url 'prefs-delete' as url %}
|
||||
<a href="{{ url }}"{% if url in request.path %} class="is-active" aria-selected="true"{% endif %}>{% trans "Delete Account" %}</a>
|
||||
|
|
43
bookwyrm/templates/preferences/move_user.html
Normal file
43
bookwyrm/templates/preferences/move_user.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
{% extends 'preferences/layout.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Move Account" %}{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
{% trans "Move Account" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
<div class="block">
|
||||
<h2 class="title is-4">{% trans "Migrate account to another server" %}</h2>
|
||||
<div class="box">
|
||||
<div class="notification is-danger is-light">
|
||||
<p>
|
||||
{% trans "Moving your account will notify all your followers and direct them to follow the new account." %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %}
|
||||
<strong>{{ user }}</strong> will be marked as moved and will not be discoverable or usable unless you undo the move.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="notification is-info is-light">
|
||||
<p>{% trans "Remember to add this user as an alias of the target account before you try to move." %}</p>
|
||||
</div>
|
||||
<form name="move-user" action="{% url 'prefs-move' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="field">
|
||||
<label class="label" for="id_target">{% trans "Enter the username for the account you want to move to e.g. <em>user@example.com </em>:" %}</label>
|
||||
<input class="input {% if form.target.errors %}is-danger{% endif %}" type="text" name="target" id="id_target" required aria-describedby="desc_target">
|
||||
{% include 'snippets/form_errors.html' with errors_list=form.target.errors id="desc_target" %}
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label" for="id_password">{% trans "Confirm your password:" %}</label>
|
||||
<input class="input {% if form.password.errors %}is-danger{% endif %}" type="password" name="password" id="id_password" required aria-describedby="desc_password">
|
||||
{% include 'snippets/form_errors.html' with errors_list=form.password.errors id="desc_password" %}
|
||||
</div>
|
||||
<button type="submit" class="button is-danger">{% trans "Move Account" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -29,7 +29,7 @@
|
|||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="notification">
|
||||
<p class="header">{% trans "Broadcasts" %}</p>
|
||||
<p class="header">{% trans "Broadcast" %}</p>
|
||||
<p class="title is-5">{{ queues.broadcast|intcomma }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -74,24 +74,7 @@
|
|||
<td>{{ user.created_date }}</td>
|
||||
<td>{{ user.last_active_date }}</td>
|
||||
<td>
|
||||
{% if user.is_active %}
|
||||
<span class="tag is-success" aria-hidden="true">
|
||||
<span class="icon icon-check"></span>
|
||||
</span>
|
||||
{% trans "Active" %}
|
||||
{% elif user.deactivation_reason == "moderator_deletion" or user.deactivation_reason == "self_deletion" %}
|
||||
<span class="tag is-danger" aria-hidden="true">
|
||||
<span class="icon icon-x"></span>
|
||||
</span>
|
||||
{% trans "Deleted" %}
|
||||
<span class="help">({{ user.get_deactivation_reason_display }})</span>
|
||||
{% else %}
|
||||
<span class="tag is-warning" aria-hidden="true">
|
||||
<span class="icon icon-x"></span>
|
||||
</span>
|
||||
{% trans "Inactive" %}
|
||||
<span class="help">({{ user.get_deactivation_reason_display }})</span>
|
||||
{% endif %}
|
||||
{% include "snippets/user_active_tag.html" with user=user %}
|
||||
</td>
|
||||
{% if status == "federated" %}
|
||||
<td>
|
||||
|
|
|
@ -23,18 +23,7 @@
|
|||
<div class="column is-flex is-flex-direction-column is-4">
|
||||
<h4 class="title is-4">{% trans "Status" %}</h4>
|
||||
<div class="box is-flex-grow-1 has-text-weight-bold">
|
||||
{% if user.is_active %}
|
||||
<p class="notification is-success">
|
||||
{% trans "Active" %}
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="notification is-warning">
|
||||
{% trans "Inactive" %}
|
||||
{% if user.deactivation_reason %}
|
||||
<span class="help">({% trans user.get_deactivation_reason_display %})</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% include "snippets/user_active_tag.html" with large=True %}
|
||||
<p class="notification">
|
||||
{% if user.local %}
|
||||
{% trans "Local" %}
|
||||
|
|
|
@ -18,7 +18,22 @@
|
|||
{% include 'user/books_header.html' %}
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
{% if user.moved_to %}
|
||||
<div class="container my-6">
|
||||
<div class="notification is-info has-text-centered">
|
||||
<p>
|
||||
{% trans "You have have moved to" %}
|
||||
<a href="{{user.moved_to}}">{% id_to_username user.moved_to %}</a>
|
||||
</p>
|
||||
<p> {% trans "You can undo this move to restore full functionality, but some followers may have already unfollowed this account." %}</p>
|
||||
<form name="remove-alias" action="{% url 'prefs-unmove' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="remote_id" id="remote_id" value="{{user.moved_to}}">
|
||||
<button type="submit" class="button is-small">{% trans "Undo move" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<nav class="breadcrumb subtitle" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="{% url 'user-feed' user|username %}">{% trans "User profile" %}</a></li>
|
||||
|
@ -215,6 +230,7 @@
|
|||
<div>
|
||||
{% include 'snippets/pagination.html' with page=books path=request.path %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
|
|
13
bookwyrm/templates/snippets/move_user_buttons.html
Normal file
13
bookwyrm/templates/snippets/move_user_buttons.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
{% load i18n %}
|
||||
{% load utilities %}
|
||||
|
||||
|
||||
{% if related_user_moved_to|user_from_remote_id not in request.user.following.all %}
|
||||
<div class="field is-grouped">
|
||||
<form action="{% url 'follow' %}" method="POST" class="interaction follow_{{ related_users.0.id }}" data-id="follow_{{ related_users.0.id }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="user" value="{% id_to_username related_user_moved_to %}">
|
||||
<button class="button is-link is-small" type="submit">{% trans "Follow at new account" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
17
bookwyrm/templates/snippets/user_active_tag.html
Normal file
17
bookwyrm/templates/snippets/user_active_tag.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
{% load i18n %}
|
||||
|
||||
{% if user.is_active %}
|
||||
{% if user.moved_to %}
|
||||
{% trans "Moved" as text %}
|
||||
{% include "snippets/user_active_tag_item.html" with icon="x" text=text level="info" %}
|
||||
{% else %}
|
||||
{% trans "Active" as text %}
|
||||
{% include "snippets/user_active_tag_item.html" with icon="check" text=text level="success" %}
|
||||
{% endif %}
|
||||
{% elif user.is_deleted %}
|
||||
{% trans "Deleted" as text %}
|
||||
{% include "snippets/user_active_tag_item.html" with icon="x" text=text level="danger" deactivation_reason=user.get_deactivation_reason_display %}
|
||||
{% else %}
|
||||
{% trans "Inactive" as text %}
|
||||
{% include "snippets/user_active_tag_item.html" with icon="x" text=text level="warning" deactivation_reason=user.get_deactivation_reason_display %}
|
||||
{% endif %}
|
19
bookwyrm/templates/snippets/user_active_tag_item.html
Normal file
19
bookwyrm/templates/snippets/user_active_tag_item.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
{% if large %}
|
||||
|
||||
<p class="notification is-{{ level }}">
|
||||
<span class="icon icon-{{ icon }}" aria-hidden="true"></span>
|
||||
{{ text }}
|
||||
{% if deactivation_reason %}
|
||||
<span class="help">({{ deactivation_reason }})</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% else %}
|
||||
|
||||
<span class="tag is-{{ level }}" aria-hidden="true">
|
||||
<span class="icon icon-{{ icon }}"></span>
|
||||
</span>
|
||||
{{ text }}
|
||||
|
||||
{% endif %}
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
{% load markdown %}
|
||||
{% load layout %}
|
||||
{% load group_tags %}
|
||||
{% load user_page_tags %}
|
||||
|
||||
{% block title %}{{ user.display_name }}{% endblock %}
|
||||
|
||||
|
@ -27,7 +28,11 @@
|
|||
<div class="block">
|
||||
<div class="columns">
|
||||
<div class="column is-two-fifths">
|
||||
{% include 'user/user_preview.html' with user=user %}
|
||||
{% if user.moved_to %}
|
||||
{% include 'user/moved.html' with user=user %}
|
||||
{% else %}
|
||||
{% include 'user/user_preview.html' with user=user %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if user.summary %}
|
||||
|
@ -38,70 +43,83 @@
|
|||
{% endspaceless %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if not is_self and request.user.is_authenticated %}
|
||||
{% include 'snippets/follow_button.html' with user=user %}
|
||||
{% endif %}
|
||||
{% if not is_self %}
|
||||
{% include 'ostatus/remote_follow_button.html' with user=user %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_self and user.active_follower_requests.all %}
|
||||
<div class="follow-requests">
|
||||
<h2>{% trans "Follow Requests" %}</h2>
|
||||
{% for requester in user.follower_requests.all %}
|
||||
<div class="row shrink">
|
||||
<p>
|
||||
<a href="{{ requester.local_path }}">{{ requester.display_name }}</a> ({{ requester.username }})
|
||||
</p>
|
||||
{% include 'snippets/follow_request_buttons.html' with user=requester %}
|
||||
<div>
|
||||
{% if user.moved_to %}
|
||||
<div class="container my-6">
|
||||
<div class="notification is-info has-text-centered">
|
||||
<p><em>{{ user.localname }}</em> {% trans "has moved to" %} <a href="{{user.moved_to}}">{% id_to_username user.moved_to %}</a></p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
{% if not is_self and request.user.is_authenticated %}
|
||||
{% include 'snippets/follow_button.html' with user=user %}
|
||||
{% endif %}
|
||||
{% if not is_self %}
|
||||
{% include 'ostatus/remote_follow_button.html' with user=user %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_self and user.active_follower_requests.all %}
|
||||
<div class="follow-requests">
|
||||
<h2>{% trans "Follow Requests" %}</h2>
|
||||
{% for requester in user.follower_requests.all %}
|
||||
<div class="row shrink">
|
||||
<p>
|
||||
<a href="{{ requester.local_path }}">{{ requester.display_name }}</a> ({{ requester.username }})
|
||||
</p>
|
||||
{% include 'snippets/follow_request_buttons.html' with user=requester %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% block tabs %}
|
||||
{% if not user.moved_to %}
|
||||
{% with user|username as username %}
|
||||
<nav class="tabs">
|
||||
<ul>
|
||||
{% url 'user-feed' user|username as url %}
|
||||
<li{% if url == request.path or url == request.path|add:'/' %} class="is-active"{% endif %}>
|
||||
<a href="{{ url }}">{% trans "Activity" %}</a>
|
||||
</li>
|
||||
{% url 'user-reviews-comments' user|username as url %}
|
||||
<li{% if url == request.path or url == request.path|add:'/' %} class="is-active"{% endif %}>
|
||||
<a href="{{ url }}">{% trans "Reviews and Comments" %}</a>
|
||||
</li>
|
||||
{% if is_self or user.goal.exists %}
|
||||
{% now 'Y' as year %}
|
||||
{% url 'user-goal' user|username year as url %}
|
||||
<li{% if url in request.path %} class="is-active"{% endif %} id="tour-reading-goal">
|
||||
<a href="{{ url }}">{% trans "Reading Goal" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_self or user|has_groups %}
|
||||
{% url 'user-groups' user|username as url %}
|
||||
<li{% if url in request.path %} class="is-active"{% endif %} id="tour-groups-tab">
|
||||
<a href="{{ url }}">{% trans "Groups" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_self or user.list_set.exists %}
|
||||
{% url 'user-lists' user|username as url %}
|
||||
<li{% if url in request.path %} class="is-active"{% endif %} id="tour-lists-tab">
|
||||
<a href="{{ url }}">{% trans "Lists" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user.shelf_set.exists %}
|
||||
{% url 'user-shelves' user|username as url %}
|
||||
<li{% if url in request.path %} class="is-active"{% endif %} id="tour-shelves-tab">
|
||||
<a href="{{ url }}">{% trans "Books" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
{% block tabs %}
|
||||
{% with user|username as username %}
|
||||
<nav class="tabs">
|
||||
<ul>
|
||||
{% url 'user-feed' user|username as url %}
|
||||
<li{% if url == request.path or url == request.path|add:'/' %} class="is-active"{% endif %}>
|
||||
<a href="{{ url }}">{% trans "Activity" %}</a>
|
||||
</li>
|
||||
{% url 'user-reviews-comments' user|username as url %}
|
||||
<li{% if url == request.path or url == request.path|add:'/' %} class="is-active"{% endif %}>
|
||||
<a href="{{ url }}">{% trans "Reviews and Comments" %}</a>
|
||||
</li>
|
||||
{% if is_self or user.goal.exists %}
|
||||
{% now 'Y' as year %}
|
||||
{% url 'user-goal' user|username year as url %}
|
||||
<li{% if url in request.path %} class="is-active"{% endif %} id="tour-reading-goal">
|
||||
<a href="{{ url }}">{% trans "Reading Goal" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_self or user|has_groups %}
|
||||
{% url 'user-groups' user|username as url %}
|
||||
<li{% if url in request.path %} class="is-active"{% endif %} id="tour-groups-tab">
|
||||
<a href="{{ url }}">{% trans "Groups" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_self or user.list_set.exists %}
|
||||
{% url 'user-lists' user|username as url %}
|
||||
<li{% if url in request.path %} class="is-active"{% endif %} id="tour-lists-tab">
|
||||
<a href="{{ url }}">{% trans "Lists" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if user.shelf_set.exists %}
|
||||
{% url 'user-shelves' user|username as url %}
|
||||
<li{% if url in request.path %} class="is-active"{% endif %} id="tour-shelves-tab">
|
||||
<a href="{{ url }}">{% trans "Books" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endwith %}
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}{% endblock %}
|
||||
|
||||
{% if not user.moved_to %}
|
||||
{% block panel %}{% endblock %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
27
bookwyrm/templates/user/moved.html
Normal file
27
bookwyrm/templates/user/moved.html
Normal file
|
@ -0,0 +1,27 @@
|
|||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
{% load utilities %}
|
||||
{% load markdown %}
|
||||
{% load layout %}
|
||||
{% load group_tags %}
|
||||
|
||||
|
||||
<div class="media block">
|
||||
<div class="media-left">
|
||||
<a href="{{ user.local_path }}">
|
||||
{% include 'snippets/avatar.html' with user=user large=True %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p>
|
||||
{% if user.name %}{{ user.name }}{% else %}{{ user.localname }}{% endif %}
|
||||
{% if user.manually_approves_followers %}
|
||||
<span class="icon icon-lock is-size-7" title="{% trans 'Locked account' %}">
|
||||
<span class="is-sr-only">{% trans "Locked account" %}</span>
|
||||
</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>{{ user.username }}</p>
|
||||
<p>{% blocktrans with date=user.created_date|naturaltime %}Joined {{ date }}{% endblocktrans %}</p>
|
||||
</div>
|
||||
</div>
|
|
@ -34,11 +34,6 @@
|
|||
{% trans "Directory" %}
|
||||
</a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a href="{% url 'user-shelves' request.user.localname %}" class="navbar-item">
|
||||
{% trans 'Your Books' %}
|
||||
</a>
|
||||
</li>
|
||||
<li role="menuitem">
|
||||
<a href="{% url 'direct-messages' %}" class="navbar-item">
|
||||
{% trans "Direct Messages" %}
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
import os
|
||||
import re
|
||||
from uuid import uuid4
|
||||
from urllib.parse import urlparse
|
||||
from django import template
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.templatetags.static import static
|
||||
|
||||
from bookwyrm.models import User
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
@ -29,6 +31,13 @@ def get_user_identifier(user):
|
|||
return user.localname if user.localname else user.username
|
||||
|
||||
|
||||
@register.filter(name="user_from_remote_id")
|
||||
def get_user_identifier_from_remote_id(remote_id):
|
||||
"""get the local user id from their remote id"""
|
||||
user = User.objects.get(remote_id=remote_id)
|
||||
return user if user else None
|
||||
|
||||
|
||||
@register.filter(name="book_title")
|
||||
def get_title(book, too_short=5):
|
||||
"""display the subtitle if the title is short"""
|
||||
|
@ -103,3 +112,16 @@ def get_isni(existing, author, autoescape=True):
|
|||
f'<input type="text" name="isni-for-{author.id}" value="{isni}" hidden>'
|
||||
)
|
||||
return ""
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=False)
|
||||
def id_to_username(user_id):
|
||||
"""given an arbitrary remote id, return the username"""
|
||||
if user_id:
|
||||
url = urlparse(user_id)
|
||||
domain = url.netloc
|
||||
parts = url.path.split("/")
|
||||
name = parts[-1]
|
||||
value = f"{name}@{domain}"
|
||||
|
||||
return value
|
||||
|
|
121
bookwyrm/tests/migrations/test_0184.py
Normal file
121
bookwyrm/tests/migrations/test_0184.py
Normal file
|
@ -0,0 +1,121 @@
|
|||
""" testing migrations """
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import TestCase
|
||||
from django.db.migrations.executor import MigrationExecutor
|
||||
from django.db import connection
|
||||
|
||||
from bookwyrm import models
|
||||
from bookwyrm.management.commands import initdb
|
||||
from bookwyrm.settings import DOMAIN
|
||||
|
||||
# pylint: disable=missing-class-docstring
|
||||
# pylint: disable=missing-function-docstring
|
||||
class EraseDeletedUserDataMigration(TestCase):
|
||||
|
||||
migrate_from = "0183_auto_20231105_1607"
|
||||
migrate_to = "0184_auto_20231106_0421"
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def setUp(self):
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||
), patch("bookwyrm.lists_stream.populate_lists_task.delay"):
|
||||
self.active_user = models.User.objects.create_user(
|
||||
f"activeuser@{DOMAIN}",
|
||||
"activeuser@activeuser.activeuser",
|
||||
"activeuserword",
|
||||
local=True,
|
||||
localname="active",
|
||||
name="a name",
|
||||
)
|
||||
self.inactive_user = models.User.objects.create_user(
|
||||
f"inactiveuser@{DOMAIN}",
|
||||
"inactiveuser@inactiveuser.inactiveuser",
|
||||
"inactiveuserword",
|
||||
local=True,
|
||||
localname="inactive",
|
||||
is_active=False,
|
||||
deactivation_reason="self_deactivation",
|
||||
name="name name",
|
||||
)
|
||||
self.deleted_user = models.User.objects.create_user(
|
||||
f"deleteduser@{DOMAIN}",
|
||||
"deleteduser@deleteduser.deleteduser",
|
||||
"deleteduserword",
|
||||
local=True,
|
||||
localname="deleted",
|
||||
is_active=False,
|
||||
deactivation_reason="self_deletion",
|
||||
name="cool name",
|
||||
)
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
), patch("bookwyrm.activitystreams.add_status_task.delay"):
|
||||
self.active_status = models.Status.objects.create(
|
||||
user=self.active_user, content="don't delete me"
|
||||
)
|
||||
self.inactive_status = models.Status.objects.create(
|
||||
user=self.inactive_user, content="also don't delete me"
|
||||
)
|
||||
self.deleted_status = models.Status.objects.create(
|
||||
user=self.deleted_user, content="yes, delete me"
|
||||
)
|
||||
|
||||
initdb.init_groups()
|
||||
initdb.init_permissions()
|
||||
|
||||
self.migrate_from = [("bookwyrm", self.migrate_from)]
|
||||
self.migrate_to = [("bookwyrm", self.migrate_to)]
|
||||
executor = MigrationExecutor(connection)
|
||||
old_apps = executor.loader.project_state(self.migrate_from).apps
|
||||
|
||||
# Reverse to the original migration
|
||||
executor.migrate(self.migrate_from)
|
||||
|
||||
self.setUpBeforeMigration(old_apps)
|
||||
|
||||
# Run the migration to test
|
||||
executor = MigrationExecutor(connection)
|
||||
executor.loader.build_graph() # reload.
|
||||
with patch("bookwyrm.activitystreams.remove_status_task.delay"):
|
||||
executor.migrate(self.migrate_to)
|
||||
|
||||
self.apps = executor.loader.project_state(self.migrate_to).apps
|
||||
|
||||
def setUpBeforeMigration(self, apps):
|
||||
pass
|
||||
|
||||
def test_user_data_deleted(self):
|
||||
"""Make sure that only the right data was deleted"""
|
||||
self.active_user.refresh_from_db()
|
||||
self.inactive_user.refresh_from_db()
|
||||
self.deleted_user.refresh_from_db()
|
||||
self.active_status.refresh_from_db()
|
||||
self.inactive_status.refresh_from_db()
|
||||
self.deleted_status.refresh_from_db()
|
||||
|
||||
self.assertTrue(self.active_user.is_active)
|
||||
self.assertFalse(self.active_user.is_deleted)
|
||||
self.assertEqual(self.active_user.name, "a name")
|
||||
self.assertNotEqual(self.deleted_user.email, "activeuser@activeuser.activeuser")
|
||||
self.assertFalse(self.active_status.deleted)
|
||||
self.assertEqual(self.active_status.content, "don't delete me")
|
||||
|
||||
self.assertFalse(self.inactive_user.is_active)
|
||||
self.assertFalse(self.inactive_user.is_deleted)
|
||||
self.assertEqual(self.inactive_user.name, "name name")
|
||||
self.assertNotEqual(
|
||||
self.deleted_user.email, "inactiveuser@inactiveuser.inactiveuser"
|
||||
)
|
||||
self.assertFalse(self.inactive_status.deleted)
|
||||
self.assertEqual(self.inactive_status.content, "also don't delete me")
|
||||
|
||||
self.assertFalse(self.deleted_user.is_active)
|
||||
self.assertTrue(self.deleted_user.is_deleted)
|
||||
self.assertIsNone(self.deleted_user.name)
|
||||
self.assertNotEqual(
|
||||
self.deleted_user.email, "deleteduser@deleteduser.deleteduser"
|
||||
)
|
||||
self.assertTrue(self.deleted_status.deleted)
|
||||
self.assertIsNone(self.deleted_status.content)
|
|
@ -119,6 +119,25 @@ class ActivitypubMixins(TestCase):
|
|||
result = models.Edition.find_existing({"openlibraryKey": "OL1234"})
|
||||
self.assertEqual(result, book)
|
||||
|
||||
def test_find_existing_with_id(self, *_):
|
||||
"""make sure that an "id" field won't produce a match"""
|
||||
book = models.Edition.objects.create(title="Test edition")
|
||||
|
||||
result = models.Edition.find_existing({"id": book.id})
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_find_existing_with_id_and_match(self, *_):
|
||||
"""make sure that an "id" field won't produce a match"""
|
||||
book = models.Edition.objects.create(title="Test edition")
|
||||
matching_book = models.Edition.objects.create(
|
||||
title="Another test edition", openlibrary_key="OL1234"
|
||||
)
|
||||
|
||||
result = models.Edition.find_existing(
|
||||
{"id": book.id, "openlibraryKey": "OL1234"}
|
||||
)
|
||||
self.assertEqual(result, matching_book)
|
||||
|
||||
def test_get_recipients_public_object(self, *_):
|
||||
"""determines the recipients for an object's broadcast"""
|
||||
MockSelf = namedtuple("Self", ("privacy"))
|
||||
|
|
|
@ -11,7 +11,7 @@ from django.test import TestCase
|
|||
from django.utils import timezone
|
||||
|
||||
from bookwyrm import models, settings
|
||||
from bookwyrm.models.book import isbn_10_to_13, isbn_13_to_10
|
||||
from bookwyrm.models.book import isbn_10_to_13, isbn_13_to_10, normalize_isbn
|
||||
from bookwyrm.settings import ENABLE_THUMBNAIL_GENERATION
|
||||
|
||||
|
||||
|
@ -72,6 +72,10 @@ class Book(TestCase):
|
|||
isbn_10 = isbn_13_to_10(isbn_13)
|
||||
self.assertEqual(isbn_10, "178816167X")
|
||||
|
||||
def test_normalize_isbn(self):
|
||||
"""Remove misc characters from ISBNs"""
|
||||
self.assertEqual(normalize_isbn("978-0-4633461-1-2"), "9780463346112")
|
||||
|
||||
def test_get_edition_info(self):
|
||||
"""text slug about an edition"""
|
||||
book = models.Edition.objects.create(title="Test Edition")
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
""" testing models """
|
||||
import json
|
||||
|
||||
from unittest.mock import patch
|
||||
from django.contrib.auth.models import Group
|
||||
from django.db import IntegrityError
|
||||
from django.test import TestCase
|
||||
import responses
|
||||
|
||||
|
@ -9,9 +11,11 @@ from bookwyrm import models
|
|||
from bookwyrm.management.commands import initdb
|
||||
from bookwyrm.settings import USE_HTTPS, DOMAIN
|
||||
|
||||
|
||||
# pylint: disable=missing-class-docstring
|
||||
# pylint: disable=missing-function-docstring
|
||||
class User(TestCase):
|
||||
|
||||
protocol = "https://" if USE_HTTPS else "http://"
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
|
@ -26,6 +30,7 @@ class User(TestCase):
|
|||
local=True,
|
||||
localname="mouse",
|
||||
name="hi",
|
||||
summary="a summary",
|
||||
bookwyrm_user=False,
|
||||
)
|
||||
self.another_user = models.User.objects.create_user(
|
||||
|
@ -88,9 +93,11 @@ class User(TestCase):
|
|||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"schema": "http://schema.org#",
|
||||
"PropertyValue": "schema:PropertyValue",
|
||||
"alsoKnownAs": {"@id": "as:alsoKnownAs", "@type": "@id"},
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"movedTo": {"@id": "as:movedTo", "@type": "@id"},
|
||||
"schema": "http://schema.org#",
|
||||
"value": "schema:value",
|
||||
},
|
||||
],
|
||||
|
@ -216,19 +223,71 @@ class User(TestCase):
|
|||
|
||||
@patch("bookwyrm.suggested_users.remove_user_task.delay")
|
||||
def test_delete_user(self, _):
|
||||
"""deactivate a user"""
|
||||
"""permanently delete a user"""
|
||||
self.assertTrue(self.user.is_active)
|
||||
self.assertEqual(self.user.name, "hi")
|
||||
self.assertEqual(self.user.summary, "a summary")
|
||||
self.assertEqual(self.user.email, "mouse@mouse.mouse")
|
||||
with patch(
|
||||
"bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"
|
||||
) as broadcast_mock:
|
||||
) as broadcast_mock, patch(
|
||||
"bookwyrm.models.user.User.erase_user_statuses"
|
||||
) as erase_statuses_mock:
|
||||
self.user.delete()
|
||||
|
||||
self.assertEqual(erase_statuses_mock.call_count, 1)
|
||||
|
||||
# make sure the deletion is broadcast
|
||||
self.assertEqual(broadcast_mock.call_count, 1)
|
||||
activity = json.loads(broadcast_mock.call_args[1]["args"][1])
|
||||
self.assertEqual(activity["type"], "Delete")
|
||||
self.assertEqual(activity["object"], self.user.remote_id)
|
||||
|
||||
self.user.refresh_from_db()
|
||||
|
||||
# the user's account data should be deleted
|
||||
self.assertIsNone(self.user.name)
|
||||
self.assertIsNone(self.user.summary)
|
||||
self.assertNotEqual(self.user.email, "mouse@mouse.mouse")
|
||||
self.assertFalse(self.user.is_active)
|
||||
|
||||
@patch("bookwyrm.suggested_users.remove_user_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
@patch("bookwyrm.activitystreams.remove_status_task.delay")
|
||||
def test_delete_user_erase_statuses(self, *_):
|
||||
"""erase user statuses when user is deleted"""
|
||||
status = models.Status.objects.create(user=self.user, content="hello")
|
||||
self.assertFalse(status.deleted)
|
||||
self.assertIsNotNone(status.content)
|
||||
self.assertIsNone(status.deleted_date)
|
||||
|
||||
self.user.delete()
|
||||
status.refresh_from_db()
|
||||
|
||||
self.assertTrue(status.deleted)
|
||||
self.assertIsNone(status.content)
|
||||
self.assertIsNotNone(status.deleted_date)
|
||||
|
||||
@patch("bookwyrm.suggested_users.remove_user_task.delay")
|
||||
@patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async")
|
||||
@patch("bookwyrm.activitystreams.add_status_task.delay")
|
||||
def test_delete_user_erase_statuses_invalid(self, *_):
|
||||
"""erase user statuses when user is deleted"""
|
||||
status = models.Status.objects.create(user=self.user, content="hello")
|
||||
self.assertFalse(status.deleted)
|
||||
self.assertIsNotNone(status.content)
|
||||
self.assertIsNone(status.deleted_date)
|
||||
|
||||
self.user.deactivate()
|
||||
with self.assertRaises(IntegrityError):
|
||||
self.user.erase_user_statuses()
|
||||
|
||||
status.refresh_from_db()
|
||||
self.assertFalse(status.deleted)
|
||||
self.assertIsNotNone(status.content)
|
||||
self.assertIsNone(status.deleted_date)
|
||||
|
||||
def test_admins_no_admins(self):
|
||||
"""list of admins"""
|
||||
result = models.User.admins()
|
||||
|
|
|
@ -29,3 +29,10 @@ class TestISBN(TestCase):
|
|||
self.assertEqual(hyphenator.hyphenate("9786769533251"), "9786769533251")
|
||||
# 979-8 (United States) 2300000-3499999 (unassigned)
|
||||
self.assertEqual(hyphenator.hyphenate("9798311111111"), "9798311111111")
|
||||
|
||||
def test_isbn_hyphenation_invalid_data(self):
|
||||
"""Make sure not to throw an error when a bad ISBN is found"""
|
||||
# no action taken
|
||||
self.assertEqual(hyphenator.hyphenate("978-0-4633461-1-2"), "978-0-4633461-1-2")
|
||||
self.assertEqual(hyphenator.hyphenate("9-0-4633461-1-2"), "9-0-4633461-1-2")
|
||||
self.assertEqual(hyphenator.hyphenate("90463346112"), "90463346112")
|
||||
|
|
|
@ -11,6 +11,7 @@ from bookwyrm import models, views
|
|||
class InboxActivities(TestCase):
|
||||
"""inbox tests"""
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def setUp(self):
|
||||
"""basic user and book data"""
|
||||
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||
|
@ -97,7 +98,8 @@ class InboxActivities(TestCase):
|
|||
self.assertEqual(models.Notification.objects.get(), notif)
|
||||
|
||||
@patch("bookwyrm.suggested_users.remove_user_task.delay")
|
||||
def test_delete_user(self, _):
|
||||
@patch("bookwyrm.activitystreams.remove_status_task.delay")
|
||||
def test_delete_user(self, *_):
|
||||
"""delete a user"""
|
||||
self.assertTrue(models.User.objects.get(username="rat@example.com").is_active)
|
||||
activity = {
|
||||
|
|
|
@ -420,21 +420,25 @@ http://www.fish.com/"""
|
|||
'okay\n\n<a href="http://www.fish.com/">www.fish.com/</a>',
|
||||
)
|
||||
|
||||
def test_format_links_parens(self, *_):
|
||||
"""find and format urls into a tags"""
|
||||
url = "http://www.fish.com/"
|
||||
self.assertEqual(
|
||||
views.status.format_links(f"({url})"),
|
||||
f'(<a href="{url}">www.fish.com/</a>)',
|
||||
)
|
||||
|
||||
def test_format_links_punctuation(self, *_):
|
||||
"""don’t take trailing punctuation into account pls"""
|
||||
url = "http://www.fish.com/"
|
||||
self.assertEqual(
|
||||
views.status.format_links(f"{url}."),
|
||||
f'<a href="{url}">www.fish.com/</a>.',
|
||||
)
|
||||
"""test many combinations of brackets, URLs, and punctuation"""
|
||||
url = "https://bookwyrm.social"
|
||||
html = f'<a href="{url}">bookwyrm.social</a>'
|
||||
test_table = [
|
||||
("punct", f"text and {url}.", f"text and {html}."),
|
||||
("multi_punct", f"text, then {url}?...", f"text, then {html}?..."),
|
||||
("bracket_punct", f"here ({url}).", f"here ({html})."),
|
||||
("punct_bracket", f"there [{url}?]", f"there [{html}?]"),
|
||||
("punct_bracket_punct", f"not here? ({url}!).", f"not here? ({html}!)."),
|
||||
(
|
||||
"multi_punct_bracket",
|
||||
f"not there ({url}...);",
|
||||
f"not there ({html}...);",
|
||||
),
|
||||
]
|
||||
for desc, text, output in test_table:
|
||||
with self.subTest(desc=desc):
|
||||
self.assertEqual(views.status.format_links(text), output)
|
||||
|
||||
def test_format_links_special_chars(self, *_):
|
||||
"""find and format urls into a tags"""
|
||||
|
@ -464,6 +468,13 @@ http://www.fish.com/"""
|
|||
views.status.format_links(url), f'<a href="{url}">{url[8:]}</a>'
|
||||
)
|
||||
|
||||
def test_format_links_ignore_non_urls(self, *_):
|
||||
"""formating links should leave plain text untouced"""
|
||||
text_elision = "> “The distinction is significant.” [...]" # bookwyrm#2993
|
||||
text_quoteparens = "some kind of gene-editing technology (?)" # bookwyrm#3049
|
||||
self.assertEqual(views.status.format_links(text_elision), text_elision)
|
||||
self.assertEqual(views.status.format_links(text_quoteparens), text_quoteparens)
|
||||
|
||||
def test_format_mentions_with_at_symbol_links(self, *_):
|
||||
"""A link with an @username shouldn't treat the username as a mention"""
|
||||
content = "a link to https://example.com/user/@mouse"
|
||||
|
|
|
@ -600,6 +600,12 @@ urlpatterns = [
|
|||
name="prompt-2fa",
|
||||
),
|
||||
re_path(r"^preferences/export/?$", views.Export.as_view(), name="prefs-export"),
|
||||
re_path(r"^preferences/move/?$", views.MoveUser.as_view(), name="prefs-move"),
|
||||
re_path(r"^preferences/alias/?$", views.AliasUser.as_view(), name="prefs-alias"),
|
||||
re_path(
|
||||
r"^preferences/remove-alias/?$", views.remove_alias, name="prefs-remove-alias"
|
||||
),
|
||||
re_path(r"^preferences/unmove/?$", views.unmove, name="prefs-unmove"),
|
||||
re_path(r"^preferences/delete/?$", views.DeleteUser.as_view(), name="prefs-delete"),
|
||||
re_path(
|
||||
r"^preferences/deactivate/?$",
|
||||
|
|
|
@ -37,6 +37,7 @@ from .admin.user_admin import UserAdmin, UserAdminList, ActivateUserAdmin
|
|||
from .preferences.change_password import ChangePassword
|
||||
from .preferences.edit_user import EditUser
|
||||
from .preferences.export import Export
|
||||
from .preferences.move_user import MoveUser, AliasUser, remove_alias, unmove
|
||||
from .preferences.delete_user import DeleteUser, DeactivateUser, ReactivateUser
|
||||
from .preferences.block import Block, unblock
|
||||
from .preferences.two_factor_auth import (
|
||||
|
|
|
@ -110,20 +110,20 @@ class ClearCeleryForm(forms.Form):
|
|||
queues = forms.MultipleChoiceField(
|
||||
label="Queues",
|
||||
choices=[
|
||||
(LOW, "Low prioirty"),
|
||||
(LOW, "Low priority"),
|
||||
(MEDIUM, "Medium priority"),
|
||||
(HIGH, "High priority"),
|
||||
(STREAMS, "Streams"),
|
||||
(IMAGES, "Images"),
|
||||
(SUGGESTED_USERS, "Suggested users"),
|
||||
(EMAIL, "Email"),
|
||||
(BROADCAST, "Broadcast"),
|
||||
(CONNECTORS, "Connectors"),
|
||||
(LISTS, "Lists"),
|
||||
(INBOX, "Inbox"),
|
||||
(EMAIL, "Email"),
|
||||
(IMAGES, "Images"),
|
||||
(IMPORTS, "Imports"),
|
||||
(IMPORT_TRIGGERED, "Import triggered"),
|
||||
(BROADCAST, "Broadcasts"),
|
||||
(INBOX, "Inbox"),
|
||||
(LISTS, "Lists"),
|
||||
(MISC, "Misc"),
|
||||
(STREAMS, "Streams"),
|
||||
(SUGGESTED_USERS, "Suggested users"),
|
||||
],
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
)
|
||||
|
|
111
bookwyrm/views/preferences/move_user.py
Normal file
111
bookwyrm/views/preferences/move_user.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
""" move your account somewhere else """
|
||||
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.contrib.auth.decorators import login_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 django.views.decorators.http import require_POST
|
||||
|
||||
from bookwyrm import forms, models
|
||||
from bookwyrm.views.helpers import handle_remote_webfinger
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class MoveUser(View):
|
||||
"""move user view"""
|
||||
|
||||
def get(self, request):
|
||||
"""move page for a user"""
|
||||
data = {
|
||||
"form": forms.MoveUserForm(),
|
||||
"user": request.user,
|
||||
}
|
||||
return TemplateResponse(request, "preferences/move_user.html", data)
|
||||
|
||||
def post(self, request):
|
||||
"""Packing your stuff and moving house"""
|
||||
form = forms.MoveUserForm(request.POST, instance=request.user)
|
||||
user = models.User.objects.get(id=request.user.id)
|
||||
|
||||
if form.is_valid() and user.check_password(form.cleaned_data["password"]):
|
||||
username = form.cleaned_data["target"]
|
||||
target = handle_remote_webfinger(username)
|
||||
|
||||
try:
|
||||
models.MoveUser.objects.create(
|
||||
user=request.user, object=request.user.remote_id, target=target
|
||||
)
|
||||
|
||||
return redirect("user-feed", username=request.user.username)
|
||||
|
||||
except PermissionDenied:
|
||||
form.errors["target"] = [
|
||||
"Set this user as an alias on the user you are moving to first"
|
||||
]
|
||||
data = {"form": form, "user": request.user}
|
||||
return TemplateResponse(request, "preferences/move_user.html", data)
|
||||
|
||||
form.errors["password"] = ["Invalid password"]
|
||||
data = {"form": form, "user": request.user}
|
||||
return TemplateResponse(request, "preferences/move_user.html", data)
|
||||
|
||||
|
||||
# pylint: disable=no-self-use
|
||||
@method_decorator(login_required, name="dispatch")
|
||||
class AliasUser(View):
|
||||
"""alias user view"""
|
||||
|
||||
def get(self, request):
|
||||
"""move page for a user"""
|
||||
data = {
|
||||
"form": forms.AliasUserForm(),
|
||||
"user": request.user,
|
||||
}
|
||||
return TemplateResponse(request, "preferences/alias_user.html", data)
|
||||
|
||||
def post(self, request):
|
||||
"""Creating a nom de plume"""
|
||||
form = forms.AliasUserForm(request.POST, instance=request.user)
|
||||
user = models.User.objects.get(id=request.user.id)
|
||||
|
||||
if form.is_valid() and user.check_password(form.cleaned_data["password"]):
|
||||
username = form.cleaned_data["username"]
|
||||
remote_user = handle_remote_webfinger(username)
|
||||
|
||||
if remote_user is None:
|
||||
form.errors["username"] = ["Username does not exist"]
|
||||
data = {"form": form, "user": request.user}
|
||||
return TemplateResponse(request, "preferences/alias_user.html", data)
|
||||
|
||||
user.also_known_as.add(remote_user.id)
|
||||
|
||||
return redirect("prefs-alias")
|
||||
|
||||
form.errors["password"] = ["Invalid password"]
|
||||
data = {"form": form, "user": request.user}
|
||||
return TemplateResponse(request, "preferences/alias_user.html", data)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_POST
|
||||
def remove_alias(request):
|
||||
"""remove an alias from the user profile"""
|
||||
|
||||
request.user.also_known_as.remove(request.POST["alias"])
|
||||
return redirect("prefs-alias")
|
||||
|
||||
|
||||
@require_POST
|
||||
@login_required
|
||||
def unmove(request):
|
||||
"""undo a user move"""
|
||||
target = get_object_or_404(models.User, remote_id=request.POST["remote_id"])
|
||||
move = get_object_or_404(models.MoveUser, target=target, user=request.user)
|
||||
move.delete()
|
||||
|
||||
request.user.moved_to = None
|
||||
request.user.save(update_fields=["moved_to"], broadcast=True)
|
||||
return redirect("prefs-alias")
|
|
@ -1,7 +1,6 @@
|
|||
""" what are we here for if not for posting """
|
||||
import re
|
||||
import logging
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.validators import URLValidator
|
||||
|
@ -297,65 +296,51 @@ def find_or_create_hashtags(content):
|
|||
|
||||
def format_links(content):
|
||||
"""detect and format links"""
|
||||
validator = URLValidator()
|
||||
formatted_content = ""
|
||||
validator = URLValidator(["http", "https"])
|
||||
schema_re = re.compile(r"\bhttps?://")
|
||||
split_content = re.split(r"(\s+)", content)
|
||||
|
||||
for potential_link in split_content:
|
||||
if not potential_link:
|
||||
for i, potential_link in enumerate(split_content):
|
||||
if not schema_re.search(potential_link):
|
||||
continue
|
||||
wrapped = _wrapped(potential_link)
|
||||
if wrapped:
|
||||
wrapper_close = potential_link[-1]
|
||||
formatted_content += potential_link[0]
|
||||
potential_link = potential_link[1:-1]
|
||||
|
||||
ends_with_punctuation = _ends_with_punctuation(potential_link)
|
||||
if ends_with_punctuation:
|
||||
punctuation_glyph = potential_link[-1]
|
||||
potential_link = potential_link[0:-1]
|
||||
|
||||
# Strip surrounding brackets and trailing punctuation.
|
||||
prefix, potential_link, suffix = _unwrap(potential_link)
|
||||
try:
|
||||
# raises an error on anything that's not a valid link
|
||||
validator(potential_link)
|
||||
|
||||
# use everything but the scheme in the presentation of the link
|
||||
url = urlparse(potential_link)
|
||||
link = url.netloc + url.path + url.params
|
||||
if url.query != "":
|
||||
link += "?" + url.query
|
||||
if url.fragment != "":
|
||||
link += "#" + url.fragment
|
||||
|
||||
formatted_content += f'<a href="{potential_link}">{link}</a>'
|
||||
link = schema_re.sub("", potential_link)
|
||||
split_content[i] = f'{prefix}<a href="{potential_link}">{link}</a>{suffix}'
|
||||
except (ValidationError, UnicodeError):
|
||||
formatted_content += potential_link
|
||||
pass
|
||||
|
||||
if wrapped:
|
||||
formatted_content += wrapper_close
|
||||
|
||||
if ends_with_punctuation:
|
||||
formatted_content += punctuation_glyph
|
||||
|
||||
return formatted_content
|
||||
return "".join(split_content)
|
||||
|
||||
|
||||
def _wrapped(text):
|
||||
"""check if a line of text is wrapped"""
|
||||
wrappers = [("(", ")"), ("[", "]"), ("{", "}")]
|
||||
for wrapper in wrappers:
|
||||
def _unwrap(text):
|
||||
"""split surrounding brackets and trailing punctuation from a string of text"""
|
||||
punct = re.compile(r'([.,;:!?"’”»]+)$')
|
||||
prefix = suffix = ""
|
||||
|
||||
if punct.search(text):
|
||||
# Move punctuation to suffix segment.
|
||||
text, suffix, _ = punct.split(text)
|
||||
|
||||
for wrapper in ("()", "[]", "{}"):
|
||||
if text[0] == wrapper[0] and text[-1] == wrapper[-1]:
|
||||
return True
|
||||
return False
|
||||
# Split out wrapping chars.
|
||||
suffix = text[-1] + suffix
|
||||
prefix, text = text[:1], text[1:-1]
|
||||
break # Nested wrappers not supported atm.
|
||||
|
||||
if punct.search(text):
|
||||
# Move inner punctuation to suffix segment.
|
||||
text, inner_punct, _ = punct.split(text)
|
||||
suffix = inner_punct + suffix
|
||||
|
||||
def _ends_with_punctuation(text):
|
||||
"""check if a line of text ends with a punctuation glyph"""
|
||||
glyphs = [".", ",", ";", ":", "!", "?", "”", "’", '"', "»"]
|
||||
for glyph in glyphs:
|
||||
if text[-1] == glyph:
|
||||
return True
|
||||
return False
|
||||
return prefix, text, suffix
|
||||
|
||||
|
||||
def to_markdown(content):
|
||||
|
|
|
@ -21,6 +21,7 @@ def webfinger(request):
|
|||
|
||||
username = resource.replace("acct:", "")
|
||||
user = get_object_or_404(models.User, username__iexact=username)
|
||||
href = user.moved_to if user.moved_to else user.remote_id
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
|
@ -29,7 +30,7 @@ def webfinger(request):
|
|||
{
|
||||
"rel": "self",
|
||||
"type": "application/activity+json",
|
||||
"href": user.remote_id,
|
||||
"href": href,
|
||||
},
|
||||
{
|
||||
"rel": "http://ostatus.org/schema/1.0/subscribe",
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 06:50\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-11 06:52\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Catalan\n"
|
||||
"Language: ca\n"
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "Edicions de %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Edicions de <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr "Edicions de <a href=\"%(work_path)s\"><i>\"%(work_title)s\"</i></a>"
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2805,15 +2805,10 @@ msgstr "Fitxer CSV no vàlid"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgstr[0] "\n"
|
||||
"Actualment, es permet la importació de %(display_size)s llibres cada %(import_limit_reset)s dies. "
|
||||
msgstr[1] ""
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] "Actualment, se't permet la importació de %(import_size_limit)s llibres cada %(import_limit_reset)s dies."
|
||||
|
||||
#: bookwyrm/templates/import/import.html:27
|
||||
#, python-format
|
||||
|
|
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 16:03\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-02 18:13\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: German\n"
|
||||
"Language: de\n"
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "Ausgaben von %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Ausgaben von <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2805,15 +2805,10 @@ msgstr "Keine gültige CSV-Datei"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] "\n"
|
||||
"Momentan darfst du alle %(import_limit_reset)s Tage %(import_size_limit)s Bücher importieren. "
|
||||
msgstr[1] ""
|
||||
|
||||
#: bookwyrm/templates/import/import.html:27
|
||||
#, python-format
|
||||
|
|
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 00:08\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-02 19:32\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Esperanto\n"
|
||||
"Language: eo\n"
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "Eldonoj de %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Eldonoj de <a href=\"%(work_path)s\">«%(work_title)s»</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr "Eldonoj de <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2805,14 +2805,10 @@ msgstr "La CSV-a dosiero ne validas"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] "Aktuale vi rajtas importi %(display_size)s librojn ĉiun %(import_limit_reset)s tagon."
|
||||
msgstr[1] "Aktuale vi rajtas importi %(import_size_limit)s librojn ĉiujn %(import_limit_reset)s tagojn."
|
||||
|
||||
#: bookwyrm/templates/import/import.html:27
|
||||
#, python-format
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 14:47\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-30 00:47\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Language: es\n"
|
||||
|
@ -1035,7 +1035,7 @@ msgstr "Tus citas"
|
|||
|
||||
#: bookwyrm/templates/book/book.html:360
|
||||
msgid "Subjects"
|
||||
msgstr "Sujetos"
|
||||
msgstr "Temas"
|
||||
|
||||
#: bookwyrm/templates/book/book.html:372
|
||||
msgid "Places"
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "Ediciones de %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Ediciones de <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr "Ediciones de <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2805,18 +2805,10 @@ msgstr "No es un archivo CSV válido"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgstr[0] "\n"
|
||||
" Actualmente, puedes importar %(display_size)s libros cada %(import_limit_reset)s días.\n"
|
||||
" "
|
||||
msgstr[1] "\n"
|
||||
" Actualmente, puedes importar %(import_size_limit)s libros cada %(import_limit_reset)s días.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] "Actualmente, puedes importar %(display_size)s libros cada %(import_limit_reset)s días."
|
||||
msgstr[1] "Actualmente, puedes importar %(import_size_limit)s libros cada %(import_limit_reset)s días."
|
||||
|
||||
#: bookwyrm/templates/import/import.html:27
|
||||
#, python-format
|
||||
|
@ -6162,7 +6154,7 @@ msgstr "Apoya a %(site_name)s en <a href=\"%(support_link)s\" target=\"_blank\"
|
|||
|
||||
#: bookwyrm/templates/snippets/footer.html:49
|
||||
msgid "BookWyrm's source code is freely available. You can contribute or report issues on <a href=\"https://github.com/bookwyrm-social/bookwyrm\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">GitHub</a>."
|
||||
msgstr "BookWyrm es software libre y de código abierto. Puedes contribuir o reportar problemas en <a href=\"https://github.com/bookwyrm-social/bookwyrm\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">GitHub</a>."
|
||||
msgstr "BookWyrm es software de código abierto. Puedes contribuir o reportar problemas en <a href=\"https://github.com/bookwyrm-social/bookwyrm\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">GitHub</a>."
|
||||
|
||||
#: bookwyrm/templates/snippets/form_rate_stars.html:20
|
||||
#: bookwyrm/templates/snippets/stars.html:23
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 00:08\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-02 18:13\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Basque\n"
|
||||
"Language: eu\n"
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "%(book_title)s(r)en edizioak"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "<a href=\"%(work_path)s\">\"%(work_title)s\"</a>-ren edizioak"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2805,12 +2805,8 @@ msgstr "CSV fitxategia ez da baliozkoa"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-29 23:37\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-02 18:13\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Finnish\n"
|
||||
"Language: fi\n"
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "Kirjan %(book_title)s laitokset"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Kirjan <a href=\"%(work_path)s\">\"%(work_title)s\"</a> laitokset"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2805,12 +2805,8 @@ msgstr "Epäkelpo CSV-tiedosto"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 00:08\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-31 20:26\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr\n"
|
||||
|
@ -310,47 +310,47 @@ msgstr "Commentaire"
|
|||
|
||||
#: bookwyrm/models/report.py:85
|
||||
msgid "Resolved report"
|
||||
msgstr ""
|
||||
msgstr "Signalement résolu"
|
||||
|
||||
#: bookwyrm/models/report.py:86
|
||||
msgid "Re-opened report"
|
||||
msgstr ""
|
||||
msgstr "Ouvrir le signalement de nouveau"
|
||||
|
||||
#: bookwyrm/models/report.py:87
|
||||
msgid "Messaged reporter"
|
||||
msgstr ""
|
||||
msgstr "Rapporteur contacté"
|
||||
|
||||
#: bookwyrm/models/report.py:88
|
||||
msgid "Messaged reported user"
|
||||
msgstr ""
|
||||
msgstr "Compte signalé contacté"
|
||||
|
||||
#: bookwyrm/models/report.py:89
|
||||
msgid "Suspended user"
|
||||
msgstr ""
|
||||
msgstr "Compte suspendu"
|
||||
|
||||
#: bookwyrm/models/report.py:90
|
||||
msgid "Un-suspended user"
|
||||
msgstr ""
|
||||
msgstr "Compte non‑suspendu"
|
||||
|
||||
#: bookwyrm/models/report.py:91
|
||||
msgid "Changed user permission level"
|
||||
msgstr ""
|
||||
msgstr "Niveau des permissions utilisateur modifié"
|
||||
|
||||
#: bookwyrm/models/report.py:92
|
||||
msgid "Deleted user account"
|
||||
msgstr ""
|
||||
msgstr "Compte supprimé"
|
||||
|
||||
#: bookwyrm/models/report.py:93
|
||||
msgid "Blocked domain"
|
||||
msgstr ""
|
||||
msgstr "Domaine bloqué"
|
||||
|
||||
#: bookwyrm/models/report.py:94
|
||||
msgid "Approved domain"
|
||||
msgstr ""
|
||||
msgstr "Domaine approuvé"
|
||||
|
||||
#: bookwyrm/models/report.py:95
|
||||
msgid "Deleted item"
|
||||
msgstr ""
|
||||
msgstr "Item supprimé"
|
||||
|
||||
#: bookwyrm/models/user.py:32 bookwyrm/templates/book/book.html:307
|
||||
msgid "Reviews"
|
||||
|
@ -378,7 +378,7 @@ msgstr "Accueil"
|
|||
|
||||
#: bookwyrm/settings.py:224
|
||||
msgid "Books Timeline"
|
||||
msgstr "Actualité de mes livres"
|
||||
msgstr "Mon fil d’actualité littéraire"
|
||||
|
||||
#: bookwyrm/settings.py:224
|
||||
#: bookwyrm/templates/guided_tour/user_profile.html:101
|
||||
|
@ -434,7 +434,7 @@ msgstr "Lietuvių (Lituanien)"
|
|||
|
||||
#: bookwyrm/settings.py:307
|
||||
msgid "Nederlands (Dutch)"
|
||||
msgstr ""
|
||||
msgstr "Pays‑Bas (Néerlandais)"
|
||||
|
||||
#: bookwyrm/settings.py:308
|
||||
msgid "Norsk (Norwegian)"
|
||||
|
@ -1076,11 +1076,11 @@ msgstr "ISBN :"
|
|||
#: bookwyrm/templates/book/book_identifiers.html:12
|
||||
#: bookwyrm/templates/book/book_identifiers.html:13
|
||||
msgid "Copy ISBN"
|
||||
msgstr ""
|
||||
msgstr "Copier l’ISBN"
|
||||
|
||||
#: bookwyrm/templates/book/book_identifiers.html:16
|
||||
msgid "Copied ISBN!"
|
||||
msgstr ""
|
||||
msgstr "ISBN copié !"
|
||||
|
||||
#: bookwyrm/templates/book/book_identifiers.html:23
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:352
|
||||
|
@ -1245,7 +1245,7 @@ msgstr "Titre :"
|
|||
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:35
|
||||
msgid "Sort Title:"
|
||||
msgstr ""
|
||||
msgstr "Titre de tri :"
|
||||
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:44
|
||||
msgid "Subtitle:"
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "Éditions de %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Éditions de <a href=\"%(work_path)s\">« %(work_title)s »</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr "Éditions de <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2268,7 +2268,7 @@ msgstr "Responsable"
|
|||
|
||||
#: bookwyrm/templates/groups/user_groups.html:35
|
||||
msgid "No groups found."
|
||||
msgstr ""
|
||||
msgstr "Aucun groupe trouvé."
|
||||
|
||||
#: bookwyrm/templates/guided_tour/book.html:10
|
||||
msgid "This is home page of a book. Let's see what you can do while you're here!"
|
||||
|
@ -2805,19 +2805,15 @@ msgstr "Fichier CSV non valide"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] "Vous êtes actuellement autorisé à importer %(display_size)s livres tous les %(import_limit_reset)s jours."
|
||||
msgstr[1] "Vous avez le droit d’importer %(display_size)s livres chaque %(import_limit_reset)s jours actuellement."
|
||||
|
||||
#: bookwyrm/templates/import/import.html:27
|
||||
#, python-format
|
||||
msgid "You have %(display_left)s left."
|
||||
msgstr ""
|
||||
msgstr "Encore %(display_left)s."
|
||||
|
||||
#: bookwyrm/templates/import/import.html:34
|
||||
#, python-format
|
||||
|
@ -3483,7 +3479,7 @@ msgstr "Sauvegardé"
|
|||
|
||||
#: bookwyrm/templates/lists/list_items.html:50
|
||||
msgid "No lists found."
|
||||
msgstr ""
|
||||
msgstr "Aucune liste trouvée."
|
||||
|
||||
#: bookwyrm/templates/lists/lists.html:14 bookwyrm/templates/user/lists.html:14
|
||||
msgid "Your Lists"
|
||||
|
@ -4575,7 +4571,7 @@ msgstr "Queues"
|
|||
|
||||
#: bookwyrm/templates/settings/celery.html:26
|
||||
msgid "Streams"
|
||||
msgstr ""
|
||||
msgstr "Flux"
|
||||
|
||||
#: bookwyrm/templates/settings/celery.html:32
|
||||
msgid "Broadcasts"
|
||||
|
@ -4583,15 +4579,15 @@ msgstr "Diffusion"
|
|||
|
||||
#: bookwyrm/templates/settings/celery.html:38
|
||||
msgid "Inbox"
|
||||
msgstr ""
|
||||
msgstr "Boîte de réception"
|
||||
|
||||
#: bookwyrm/templates/settings/celery.html:51
|
||||
msgid "Import triggered"
|
||||
msgstr ""
|
||||
msgstr "Import déclenché"
|
||||
|
||||
#: bookwyrm/templates/settings/celery.html:57
|
||||
msgid "Connectors"
|
||||
msgstr ""
|
||||
msgstr "Connecteurs"
|
||||
|
||||
#: bookwyrm/templates/settings/celery.html:64
|
||||
#: bookwyrm/templates/settings/site.html:91
|
||||
|
@ -4600,7 +4596,7 @@ msgstr "Images"
|
|||
|
||||
#: bookwyrm/templates/settings/celery.html:70
|
||||
msgid "Suggested Users"
|
||||
msgstr ""
|
||||
msgstr "Comptes suggérés"
|
||||
|
||||
#: bookwyrm/templates/settings/celery.html:83
|
||||
#: bookwyrm/templates/settings/invites/manage_invite_requests.html:43
|
||||
|
@ -4610,7 +4606,7 @@ msgstr "Email"
|
|||
|
||||
#: bookwyrm/templates/settings/celery.html:89
|
||||
msgid "Misc"
|
||||
msgstr ""
|
||||
msgstr "Divers"
|
||||
|
||||
#: bookwyrm/templates/settings/celery.html:96
|
||||
msgid "Low priority"
|
||||
|
@ -5424,22 +5420,22 @@ msgstr "Liens signalés"
|
|||
|
||||
#: bookwyrm/templates/settings/reports/report.html:66
|
||||
msgid "Moderation Activity"
|
||||
msgstr ""
|
||||
msgstr "Activité de la modération"
|
||||
|
||||
#: bookwyrm/templates/settings/reports/report.html:73
|
||||
#, python-format
|
||||
msgid "<a href=\"%(user_link)s\">%(user)s</a> opened this report"
|
||||
msgstr ""
|
||||
msgstr "<a href=\"%(user_link)s\">%(user)s</a> a ouvert ce signalement"
|
||||
|
||||
#: bookwyrm/templates/settings/reports/report.html:86
|
||||
#, python-format
|
||||
msgid "<a href=\"%(user_link)s\">%(user)s</a> commented on this report:"
|
||||
msgstr ""
|
||||
msgstr "<a href=\"%(user_link)s\">%(user)s</a> a commenté ce signalement :"
|
||||
|
||||
#: bookwyrm/templates/settings/reports/report.html:90
|
||||
#, python-format
|
||||
msgid "<a href=\"%(user_link)s\">%(user)s</a> took an action on this report:"
|
||||
msgstr ""
|
||||
msgstr "<a href=\"%(user_link)s\">%(user)s</a> a traité ce signalement :"
|
||||
|
||||
#: bookwyrm/templates/settings/reports/report_header.html:6
|
||||
#, python-format
|
||||
|
@ -5463,7 +5459,7 @@ msgstr "Signalement #%(report_id)s : compte @%(username)s"
|
|||
|
||||
#: bookwyrm/templates/settings/reports/report_links_table.html:19
|
||||
msgid "Approve domain"
|
||||
msgstr ""
|
||||
msgstr "Approuver le domaine"
|
||||
|
||||
#: bookwyrm/templates/settings/reports/report_links_table.html:26
|
||||
msgid "Block domain"
|
||||
|
@ -6053,7 +6049,7 @@ msgstr "Commentaire :"
|
|||
|
||||
#: bookwyrm/templates/snippets/create_status/post_options_block.html:19
|
||||
msgid "Update"
|
||||
msgstr ""
|
||||
msgstr "Mettre à jour"
|
||||
|
||||
#: bookwyrm/templates/snippets/create_status/post_options_block.html:21
|
||||
msgid "Post"
|
||||
|
@ -6711,8 +6707,8 @@ msgstr "A rejoint ce serveur %(date)s"
|
|||
#, python-format
|
||||
msgid "%(display_count)s follower"
|
||||
msgid_plural "%(display_count)s followers"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[0] "%(display_count)s abonné⋅e"
|
||||
msgstr[1] "%(display_count)s abonné⋅es"
|
||||
|
||||
#: bookwyrm/templates/user/user_preview.html:31
|
||||
#, python-format
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 04:25\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-20 13:05\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Galician\n"
|
||||
"Language: gl\n"
|
||||
|
@ -497,7 +497,7 @@ msgstr "Acerca de"
|
|||
#: bookwyrm/templates/get_started/layout.html:22
|
||||
#, python-format
|
||||
msgid "Welcome to %(site_name)s!"
|
||||
msgstr "Sexas ben vida a %(site_name)s!"
|
||||
msgstr "Recibe a benvida a %(site_name)s!"
|
||||
|
||||
#: bookwyrm/templates/about/about.html:25
|
||||
#, python-format
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "Edicións de %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Edicións de <a href=\"%(work_path)s\">%(work_title)s</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr "Edicións de <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2805,18 +2805,10 @@ msgstr "Non é un ficheiro CSV válido"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgstr[0] "\n"
|
||||
" Actualmente, tes permiso para importar %(display_size)s libros cada %(import_limit_reset)s día.\n"
|
||||
" "
|
||||
msgstr[1] "\n"
|
||||
" Actualmente, tes permiso para importar %(import_size_limit)s libros cada %(import_limit_reset)s días.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] "Actualmente podes importar %(display_size)s libros cada %(import_limit_reset)s día."
|
||||
msgstr[1] "Actualmente podes importar %(import_size_limit)s libros cada %(import_limit_reset)s días."
|
||||
|
||||
#: bookwyrm/templates/import/import.html:27
|
||||
#, python-format
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 09:30\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-02 19:32\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Italian\n"
|
||||
"Language: it\n"
|
||||
|
@ -310,15 +310,15 @@ msgstr "Commenta"
|
|||
|
||||
#: bookwyrm/models/report.py:85
|
||||
msgid "Resolved report"
|
||||
msgstr ""
|
||||
msgstr "Segnalazione risolta"
|
||||
|
||||
#: bookwyrm/models/report.py:86
|
||||
msgid "Re-opened report"
|
||||
msgstr ""
|
||||
msgstr "Segnalazione riaperta"
|
||||
|
||||
#: bookwyrm/models/report.py:87
|
||||
msgid "Messaged reporter"
|
||||
msgstr ""
|
||||
msgstr "Messaggio inviato al segnalatore"
|
||||
|
||||
#: bookwyrm/models/report.py:88
|
||||
msgid "Messaged reported user"
|
||||
|
@ -326,11 +326,11 @@ msgstr ""
|
|||
|
||||
#: bookwyrm/models/report.py:89
|
||||
msgid "Suspended user"
|
||||
msgstr ""
|
||||
msgstr "Utente sospeso"
|
||||
|
||||
#: bookwyrm/models/report.py:90
|
||||
msgid "Un-suspended user"
|
||||
msgstr ""
|
||||
msgstr "Utente riattivato"
|
||||
|
||||
#: bookwyrm/models/report.py:91
|
||||
msgid "Changed user permission level"
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "Edizioni di %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Edizioni di <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr "Edizioni di <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2805,14 +2805,10 @@ msgstr "Non è un file di csv valido"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[1] "Al momento puoi importare %(import_size_limit)s libri ogni %(import_limit_reset)s giorni."
|
||||
|
||||
#: bookwyrm/templates/import/import.html:27
|
||||
#, python-format
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 00:08\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-02 18:13\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Lithuanian\n"
|
||||
"Language: lt\n"
|
||||
|
@ -1384,8 +1384,8 @@ msgstr "Knygos %(book_title)s leidimai"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "<a href=\"%(work_path)s\">\"%(work_title)s\"</a> leidimai"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2825,12 +2825,8 @@ msgstr "Netinkamas CSV failas"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[2] ""
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 08:16\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-02 19:32\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Dutch\n"
|
||||
"Language: nl\n"
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "Edities van %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Edities van <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr "Edities van <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2805,16 +2805,10 @@ msgstr "Geen geldig CSV-bestand"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgstr[0] "\n"
|
||||
"Momenteel mag je elke %(import_limit_reset)s dag %(display_size)s boeken importeren. "
|
||||
msgstr[1] "\n"
|
||||
"Momenteel mag je elke %(import_limit_reset)s dagen %(import_size_limit)s boeken importeren. "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] "Momenteel mag je %(display_size)s boek importeren elke %(import_limit_reset)s dagen."
|
||||
msgstr[1] "Momenteel mag je %(import_size_limit)s boeken importeren elke %(import_limit_reset)s dagen."
|
||||
|
||||
#: bookwyrm/templates/import/import.html:27
|
||||
#, python-format
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 00:08\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-09 19:52\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Norwegian\n"
|
||||
"Language: no\n"
|
||||
|
@ -314,31 +314,31 @@ msgstr ""
|
|||
|
||||
#: bookwyrm/models/report.py:86
|
||||
msgid "Re-opened report"
|
||||
msgstr ""
|
||||
msgstr "Gjenåpnet rapport"
|
||||
|
||||
#: bookwyrm/models/report.py:87
|
||||
msgid "Messaged reporter"
|
||||
msgstr ""
|
||||
msgstr "Melding sendt til rapportør"
|
||||
|
||||
#: bookwyrm/models/report.py:88
|
||||
msgid "Messaged reported user"
|
||||
msgstr ""
|
||||
msgstr "Melding sendt til rapportert bruker"
|
||||
|
||||
#: bookwyrm/models/report.py:89
|
||||
msgid "Suspended user"
|
||||
msgstr ""
|
||||
msgstr "Deaktivert bruker"
|
||||
|
||||
#: bookwyrm/models/report.py:90
|
||||
msgid "Un-suspended user"
|
||||
msgstr ""
|
||||
msgstr "Reaktivert bruker"
|
||||
|
||||
#: bookwyrm/models/report.py:91
|
||||
msgid "Changed user permission level"
|
||||
msgstr ""
|
||||
msgstr "Endret brukerens rettighetsnivå"
|
||||
|
||||
#: bookwyrm/models/report.py:92
|
||||
msgid "Deleted user account"
|
||||
msgstr ""
|
||||
msgstr "Slettet brukerkonto"
|
||||
|
||||
#: bookwyrm/models/report.py:93
|
||||
msgid "Blocked domain"
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "Utgaver av %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Utgaver av <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2805,12 +2805,8 @@ msgstr "Ikke en gyldig CSV-fil"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 00:08\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-03 01:28\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Polish\n"
|
||||
"Language: pl\n"
|
||||
|
@ -350,7 +350,7 @@ msgstr ""
|
|||
|
||||
#: bookwyrm/models/report.py:95
|
||||
msgid "Deleted item"
|
||||
msgstr ""
|
||||
msgstr "Usunięty element"
|
||||
|
||||
#: bookwyrm/models/user.py:32 bookwyrm/templates/book/book.html:307
|
||||
msgid "Reviews"
|
||||
|
@ -434,7 +434,7 @@ msgstr "Lietuvių (Litewski)"
|
|||
|
||||
#: bookwyrm/settings.py:307
|
||||
msgid "Nederlands (Dutch)"
|
||||
msgstr ""
|
||||
msgstr "Holenderski"
|
||||
|
||||
#: bookwyrm/settings.py:308
|
||||
msgid "Norsk (Norwegian)"
|
||||
|
@ -502,7 +502,7 @@ msgstr "Witaj na %(site_name)s!"
|
|||
#: bookwyrm/templates/about/about.html:25
|
||||
#, python-format
|
||||
msgid "%(site_name)s is part of <em>BookWyrm</em>, a network of independent, self-directed communities for readers. While you can interact seamlessly with users anywhere in the <a href=\"https://joinbookwyrm.com/instances/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">BookWyrm network</a>, this community is unique."
|
||||
msgstr ""
|
||||
msgstr "%(site_name)s jest częścią <em>BookWyrm</em>, sieci niezależnych, samostanowiących społeczności czytelników. Możesz beproblemowo wchodzić w interakcje z użytkownikami gdziekolwiek w <a href=\"https://joinbookwyrm.com/instances/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"> sieci BookWyrm</a>, ta społeczność jest wyjątkowa."
|
||||
|
||||
#: bookwyrm/templates/about/about.html:45
|
||||
#, python-format
|
||||
|
@ -521,7 +521,7 @@ msgstr "<a href=\"%(book_path)s\"><em>%(title)s</em></a> ma najbardziej podzielo
|
|||
|
||||
#: bookwyrm/templates/about/about.html:94
|
||||
msgid "Track your reading, talk about books, write reviews, and discover what to read next. Always ad-free, anti-corporate, and community-oriented, BookWyrm is human-scale software, designed to stay small and personal. If you have feature requests, bug reports, or grand dreams, <a href=\"https://joinbookwyrm.com/get-involved\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">reach out</a> and make yourself heard."
|
||||
msgstr ""
|
||||
msgstr "Śledź swój postęp czytelniczy, rozmawiaj o książkach, pisz opinie i odkrywaj co czytać następne. Na zawsze bez reklam, antykorporacyjne i skierowane w stronę społeczności, BookWyrm jest programem dla ludzi, stworzonym, by pozostać małym i personalnym. Jeśli masz pomysł, zauważył_ś błąd, albo masz wielkie marzenie, <a href=\"https://joinbookwyrm.com/get-involved\" target=\"_blank\" rel=\"nofollow noopener noreferrer\"> złoś się </a> i pozwól się wysłuchać."
|
||||
|
||||
#: bookwyrm/templates/about/about.html:105
|
||||
msgid "Meet your admins"
|
||||
|
@ -672,7 +672,7 @@ msgstr "Przekłada się to na średnio %(pages)s stron na książkę."
|
|||
#, python-format
|
||||
msgid "(No page data was available for %(no_page_number)s book)"
|
||||
msgid_plural "(No page data was available for %(no_page_number)s books)"
|
||||
msgstr[0] ""
|
||||
msgstr[0] "(Nie mamy informacji o liczbie stron dla książki %(no_page_number)s)"
|
||||
msgstr[1] ""
|
||||
msgstr[2] ""
|
||||
msgstr[3] ""
|
||||
|
@ -778,7 +778,7 @@ msgstr "Zobacz wpis ISNI"
|
|||
#: bookwyrm/templates/author/author.html:95
|
||||
#: bookwyrm/templates/book/book.html:173
|
||||
msgid "View on ISFDB"
|
||||
msgstr ""
|
||||
msgstr "Zobacz na ISFDB"
|
||||
|
||||
#: bookwyrm/templates/author/author.html:100
|
||||
#: bookwyrm/templates/author/sync_modal.html:5
|
||||
|
@ -1116,7 +1116,7 @@ msgstr ""
|
|||
|
||||
#: bookwyrm/templates/book/book_identifiers.html:51
|
||||
msgid "Goodreads:"
|
||||
msgstr ""
|
||||
msgstr "Goodreads:"
|
||||
|
||||
#: bookwyrm/templates/book/cover_add_modal.html:5
|
||||
msgid "Add cover"
|
||||
|
@ -1257,7 +1257,7 @@ msgstr "Tytuł:"
|
|||
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:35
|
||||
msgid "Sort Title:"
|
||||
msgstr ""
|
||||
msgstr "Sortuj Według Tytułu:"
|
||||
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:44
|
||||
msgid "Subtitle:"
|
||||
|
@ -1384,8 +1384,8 @@ msgstr "Edycje %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Edycje <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr "Edycje <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -1567,7 +1567,7 @@ msgstr ""
|
|||
#: bookwyrm/templates/book/series.html:27
|
||||
#, python-format
|
||||
msgid "Book %(series_number)s"
|
||||
msgstr ""
|
||||
msgstr "Książka%(series_number)s"
|
||||
|
||||
#: bookwyrm/templates/book/series.html:27
|
||||
msgid "Unsorted Book"
|
||||
|
@ -2825,12 +2825,8 @@ msgstr "To nie jest prawidłowy plik CSV"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[2] ""
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 18:50\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-02 18:13\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Portuguese, Brazilian\n"
|
||||
"Language: pt\n"
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "Edições de %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Edições de <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2805,12 +2805,8 @@ msgstr ""
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 00:08\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-02 18:13\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Portuguese\n"
|
||||
"Language: pt\n"
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "Edições de %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Edições de <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2805,12 +2805,8 @@ msgstr "Não é um ficheiro CSV válido"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 00:08\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-02 18:13\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Romanian\n"
|
||||
"Language: ro\n"
|
||||
|
@ -1378,8 +1378,8 @@ msgstr "Ediții ale %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Ediții ale <a href=\"%(work_path)s\">%(work_title)s</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2815,12 +2815,8 @@ msgstr ""
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
msgstr[2] ""
|
||||
|
|
Binary file not shown.
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 09:30\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-02 18:13\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Swedish\n"
|
||||
"Language: sv\n"
|
||||
|
@ -1372,8 +1372,8 @@ msgstr "Utgåvor av %(book_title)s"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "Utgåvor av <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2805,12 +2805,8 @@ msgstr "Inte en giltig CSV-fil"
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 00:08\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-02 18:13\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"Language: zh\n"
|
||||
|
@ -1366,8 +1366,8 @@ msgstr "%(book_title)s 的各版本"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "<a href=\"%(work_path)s\">《%(work_title)s》</a> 的各版本"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2795,12 +2795,8 @@ msgstr ""
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
|
||||
#: bookwyrm/templates/import/import.html:27
|
||||
|
|
|
@ -2,8 +2,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: bookwyrm\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-09-27 01:11+0000\n"
|
||||
"PO-Revision-Date: 2023-09-28 00:08\n"
|
||||
"POT-Creation-Date: 2023-10-02 16:40+0000\n"
|
||||
"PO-Revision-Date: 2023-10-29 07:42\n"
|
||||
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
|
||||
"Language-Team: Chinese Traditional\n"
|
||||
"Language: zh\n"
|
||||
|
@ -173,7 +173,7 @@ msgstr ""
|
|||
|
||||
#: bookwyrm/models/book.py:283
|
||||
msgid "Audiobook"
|
||||
msgstr ""
|
||||
msgstr "有聲書"
|
||||
|
||||
#: bookwyrm/models/book.py:284
|
||||
msgid "eBook"
|
||||
|
@ -181,15 +181,15 @@ msgstr "電子書"
|
|||
|
||||
#: bookwyrm/models/book.py:285
|
||||
msgid "Graphic novel"
|
||||
msgstr ""
|
||||
msgstr "圖像小說"
|
||||
|
||||
#: bookwyrm/models/book.py:286
|
||||
msgid "Hardcover"
|
||||
msgstr ""
|
||||
msgstr "精裝書"
|
||||
|
||||
#: bookwyrm/models/book.py:287
|
||||
msgid "Paperback"
|
||||
msgstr ""
|
||||
msgstr "平裝書"
|
||||
|
||||
#: bookwyrm/models/federated_server.py:11
|
||||
#: bookwyrm/templates/settings/federation/edit_instance.html:55
|
||||
|
@ -267,15 +267,15 @@ msgstr "活躍"
|
|||
|
||||
#: bookwyrm/models/import_job.py:49 bookwyrm/templates/import/import.html:172
|
||||
msgid "Complete"
|
||||
msgstr ""
|
||||
msgstr "已完成"
|
||||
|
||||
#: bookwyrm/models/import_job.py:50
|
||||
msgid "Stopped"
|
||||
msgstr ""
|
||||
msgstr "已停止"
|
||||
|
||||
#: bookwyrm/models/import_job.py:83 bookwyrm/models/import_job.py:91
|
||||
msgid "Import stopped"
|
||||
msgstr ""
|
||||
msgstr "匯入已停止"
|
||||
|
||||
#: bookwyrm/models/import_job.py:363 bookwyrm/models/import_job.py:388
|
||||
msgid "Error loading book"
|
||||
|
@ -287,20 +287,20 @@ msgstr ""
|
|||
|
||||
#: bookwyrm/models/link.py:51
|
||||
msgid "Free"
|
||||
msgstr ""
|
||||
msgstr "免費"
|
||||
|
||||
#: bookwyrm/models/link.py:52
|
||||
msgid "Purchasable"
|
||||
msgstr ""
|
||||
msgstr "可購買"
|
||||
|
||||
#: bookwyrm/models/link.py:53
|
||||
msgid "Available for loan"
|
||||
msgstr ""
|
||||
msgstr "可借閱"
|
||||
|
||||
#: bookwyrm/models/link.py:70
|
||||
#: bookwyrm/templates/settings/link_domains/link_domains.html:23
|
||||
msgid "Approved"
|
||||
msgstr ""
|
||||
msgstr "已核准"
|
||||
|
||||
#: bookwyrm/models/report.py:84
|
||||
#: bookwyrm/templates/settings/reports/report.html:115
|
||||
|
@ -310,11 +310,11 @@ msgstr "評論"
|
|||
|
||||
#: bookwyrm/models/report.py:85
|
||||
msgid "Resolved report"
|
||||
msgstr ""
|
||||
msgstr "已處理的舉報"
|
||||
|
||||
#: bookwyrm/models/report.py:86
|
||||
msgid "Re-opened report"
|
||||
msgstr ""
|
||||
msgstr "已重新打開的舉報"
|
||||
|
||||
#: bookwyrm/models/report.py:87
|
||||
msgid "Messaged reporter"
|
||||
|
@ -358,15 +358,15 @@ msgstr "書評"
|
|||
|
||||
#: bookwyrm/models/user.py:33
|
||||
msgid "Comments"
|
||||
msgstr ""
|
||||
msgstr "評論"
|
||||
|
||||
#: bookwyrm/models/user.py:34
|
||||
msgid "Quotations"
|
||||
msgstr ""
|
||||
msgstr "引用"
|
||||
|
||||
#: bookwyrm/models/user.py:35
|
||||
msgid "Everything else"
|
||||
msgstr ""
|
||||
msgstr "所有其他內容"
|
||||
|
||||
#: bookwyrm/settings.py:223
|
||||
msgid "Home Timeline"
|
||||
|
@ -378,7 +378,7 @@ msgstr "主頁"
|
|||
|
||||
#: bookwyrm/settings.py:224
|
||||
msgid "Books Timeline"
|
||||
msgstr ""
|
||||
msgstr "書目時間線"
|
||||
|
||||
#: bookwyrm/settings.py:224
|
||||
#: bookwyrm/templates/guided_tour/user_profile.html:101
|
||||
|
@ -394,7 +394,7 @@ msgstr "English(英語)"
|
|||
|
||||
#: bookwyrm/settings.py:297
|
||||
msgid "Català (Catalan)"
|
||||
msgstr ""
|
||||
msgstr "Català (加泰羅尼亞語)"
|
||||
|
||||
#: bookwyrm/settings.py:298
|
||||
msgid "Deutsch (German)"
|
||||
|
@ -402,7 +402,7 @@ msgstr "Deutsch(德語)"
|
|||
|
||||
#: bookwyrm/settings.py:299
|
||||
msgid "Esperanto (Esperanto)"
|
||||
msgstr ""
|
||||
msgstr "Esperanto (世界語)"
|
||||
|
||||
#: bookwyrm/settings.py:300
|
||||
msgid "Español (Spanish)"
|
||||
|
@ -410,19 +410,19 @@ msgstr "Español(西班牙語)"
|
|||
|
||||
#: bookwyrm/settings.py:301
|
||||
msgid "Euskara (Basque)"
|
||||
msgstr ""
|
||||
msgstr "Euskara (巴斯克語)"
|
||||
|
||||
#: bookwyrm/settings.py:302
|
||||
msgid "Galego (Galician)"
|
||||
msgstr ""
|
||||
msgstr "Galego (加利西亞語)"
|
||||
|
||||
#: bookwyrm/settings.py:303
|
||||
msgid "Italiano (Italian)"
|
||||
msgstr ""
|
||||
msgstr "Italiano (意大利語)"
|
||||
|
||||
#: bookwyrm/settings.py:304
|
||||
msgid "Suomi (Finnish)"
|
||||
msgstr ""
|
||||
msgstr "Suomi (芬蘭語)"
|
||||
|
||||
#: bookwyrm/settings.py:305
|
||||
msgid "Français (French)"
|
||||
|
@ -430,35 +430,35 @@ msgstr "Français(法語)"
|
|||
|
||||
#: bookwyrm/settings.py:306
|
||||
msgid "Lietuvių (Lithuanian)"
|
||||
msgstr ""
|
||||
msgstr "Lietuvių (立陶宛語)"
|
||||
|
||||
#: bookwyrm/settings.py:307
|
||||
msgid "Nederlands (Dutch)"
|
||||
msgstr ""
|
||||
msgstr "Nederlands (荷蘭語)"
|
||||
|
||||
#: bookwyrm/settings.py:308
|
||||
msgid "Norsk (Norwegian)"
|
||||
msgstr ""
|
||||
msgstr "Norsk (挪威語)"
|
||||
|
||||
#: bookwyrm/settings.py:309
|
||||
msgid "Polski (Polish)"
|
||||
msgstr ""
|
||||
msgstr "Polski (波蘭語)"
|
||||
|
||||
#: bookwyrm/settings.py:310
|
||||
msgid "Português do Brasil (Brazilian Portuguese)"
|
||||
msgstr ""
|
||||
msgstr "Português do Brasil (巴西葡萄牙語)"
|
||||
|
||||
#: bookwyrm/settings.py:311
|
||||
msgid "Português Europeu (European Portuguese)"
|
||||
msgstr ""
|
||||
msgstr "Português Europeu (歐洲葡萄牙語)"
|
||||
|
||||
#: bookwyrm/settings.py:312
|
||||
msgid "Română (Romanian)"
|
||||
msgstr ""
|
||||
msgstr "Română (羅馬尼亞語)"
|
||||
|
||||
#: bookwyrm/settings.py:313
|
||||
msgid "Svenska (Swedish)"
|
||||
msgstr ""
|
||||
msgstr "Svenska (瑞典語)"
|
||||
|
||||
#: bookwyrm/settings.py:314
|
||||
msgid "简体中文 (Simplified Chinese)"
|
||||
|
@ -491,7 +491,7 @@ msgstr "某些東西出錯了!抱歉。"
|
|||
#: bookwyrm/templates/about/about.html:9
|
||||
#: bookwyrm/templates/about/layout.html:35
|
||||
msgid "About"
|
||||
msgstr ""
|
||||
msgstr "關於"
|
||||
|
||||
#: bookwyrm/templates/about/about.html:21
|
||||
#: bookwyrm/templates/get_started/layout.html:22
|
||||
|
@ -502,12 +502,12 @@ msgstr "歡迎來到 %(site_name)s!"
|
|||
#: bookwyrm/templates/about/about.html:25
|
||||
#, python-format
|
||||
msgid "%(site_name)s is part of <em>BookWyrm</em>, a network of independent, self-directed communities for readers. While you can interact seamlessly with users anywhere in the <a href=\"https://joinbookwyrm.com/instances/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">BookWyrm network</a>, this community is unique."
|
||||
msgstr ""
|
||||
msgstr "%(site_name)s 是 <em>BookWyrm</em> 的一部分,這是一個為讀者建立的獨立、自我導向的社區網絡。雖然您可以在 <a href=\"https://joinbookwyrm.com/instances/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">BookWyrm 網絡</a>中任何地方的用戶無縫互動,但這個社區是獨一無二的。"
|
||||
|
||||
#: bookwyrm/templates/about/about.html:45
|
||||
#, python-format
|
||||
msgid "<a href=\"%(book_path)s\"><em>%(title)s</em></a> is %(site_name)s's most beloved book, with an average rating of %(rating)s out of 5."
|
||||
msgstr ""
|
||||
msgstr "<a href=\"%(book_path)s\"><em>%(title)s</em></a> 是 %(site_name)s 最受歡迎的書,平均得分為 %(rating)s(滿分五分)。"
|
||||
|
||||
#: bookwyrm/templates/about/about.html:64
|
||||
#, python-format
|
||||
|
@ -703,13 +703,13 @@ msgstr[0] ""
|
|||
|
||||
#: bookwyrm/templates/annual_summary/layout.html:211
|
||||
msgid "Way to go!"
|
||||
msgstr ""
|
||||
msgstr "還不錯!"
|
||||
|
||||
#: bookwyrm/templates/annual_summary/layout.html:226
|
||||
#, python-format
|
||||
msgid "%(display_name)s left %(ratings_total)s rating, <br />their average rating is %(rating_average)s"
|
||||
msgid_plural "%(display_name)s left %(ratings_total)s ratings, <br />their average rating is %(rating_average)s"
|
||||
msgstr[0] ""
|
||||
msgstr[0] "%(display_name)s 留下了 %(ratings_total)s 條評分,<br />他的平均評分是 %(rating_average)s"
|
||||
|
||||
#: bookwyrm/templates/annual_summary/layout.html:240
|
||||
msgid "Their best rated review"
|
||||
|
@ -732,7 +732,7 @@ msgstr "編輯作者"
|
|||
|
||||
#: bookwyrm/templates/author/author.html:36
|
||||
msgid "Author details"
|
||||
msgstr ""
|
||||
msgstr "作者詳情"
|
||||
|
||||
#: bookwyrm/templates/author/author.html:40
|
||||
#: bookwyrm/templates/author/edit_author.html:42
|
||||
|
@ -749,7 +749,7 @@ msgstr "逝世:"
|
|||
|
||||
#: bookwyrm/templates/author/author.html:66
|
||||
msgid "External links"
|
||||
msgstr ""
|
||||
msgstr "外部連結"
|
||||
|
||||
#: bookwyrm/templates/author/author.html:71
|
||||
msgid "Wikipedia"
|
||||
|
@ -757,23 +757,23 @@ msgstr "維基百科"
|
|||
|
||||
#: bookwyrm/templates/author/author.html:79
|
||||
msgid "Website"
|
||||
msgstr ""
|
||||
msgstr "網站"
|
||||
|
||||
#: bookwyrm/templates/author/author.html:87
|
||||
msgid "View ISNI record"
|
||||
msgstr ""
|
||||
msgstr "查看 ISNI 記錄"
|
||||
|
||||
#: bookwyrm/templates/author/author.html:95
|
||||
#: bookwyrm/templates/book/book.html:173
|
||||
msgid "View on ISFDB"
|
||||
msgstr ""
|
||||
msgstr "在 ISFDB 查看"
|
||||
|
||||
#: bookwyrm/templates/author/author.html:100
|
||||
#: bookwyrm/templates/author/sync_modal.html:5
|
||||
#: bookwyrm/templates/book/book.html:140
|
||||
#: bookwyrm/templates/book/sync_modal.html:5
|
||||
msgid "Load data"
|
||||
msgstr ""
|
||||
msgstr "載入資料"
|
||||
|
||||
#: bookwyrm/templates/author/author.html:104
|
||||
#: bookwyrm/templates/book/book.html:144
|
||||
|
@ -787,15 +787,15 @@ msgstr "在 Inventaire 檢視"
|
|||
|
||||
#: bookwyrm/templates/author/author.html:135
|
||||
msgid "View on LibraryThing"
|
||||
msgstr ""
|
||||
msgstr "在 LibraryThing 查看"
|
||||
|
||||
#: bookwyrm/templates/author/author.html:143
|
||||
msgid "View on Goodreads"
|
||||
msgstr ""
|
||||
msgstr "在 Goodreads 查看"
|
||||
|
||||
#: bookwyrm/templates/author/author.html:151
|
||||
msgid "View ISFDB entry"
|
||||
msgstr ""
|
||||
msgstr "查看 ISFDB 條目"
|
||||
|
||||
#: bookwyrm/templates/author/author.html:166
|
||||
#, python-format
|
||||
|
@ -849,7 +849,7 @@ msgstr "維基百科連結:"
|
|||
|
||||
#: bookwyrm/templates/author/edit_author.html:60
|
||||
msgid "Website:"
|
||||
msgstr ""
|
||||
msgstr "網站:"
|
||||
|
||||
#: bookwyrm/templates/author/edit_author.html:65
|
||||
msgid "Birth date:"
|
||||
|
@ -883,11 +883,11 @@ msgstr "Goodreads key:"
|
|||
|
||||
#: bookwyrm/templates/author/edit_author.html:109
|
||||
msgid "ISFDB:"
|
||||
msgstr ""
|
||||
msgstr "ISFDB:"
|
||||
|
||||
#: bookwyrm/templates/author/edit_author.html:116
|
||||
msgid "ISNI:"
|
||||
msgstr ""
|
||||
msgstr "ISNI:"
|
||||
|
||||
#: bookwyrm/templates/author/edit_author.html:126
|
||||
#: bookwyrm/templates/book/book.html:220
|
||||
|
@ -953,7 +953,7 @@ msgstr "確認"
|
|||
|
||||
#: bookwyrm/templates/book/book.html:20
|
||||
msgid "Unable to connect to remote source."
|
||||
msgstr ""
|
||||
msgstr "無法連接到遠程數據源。"
|
||||
|
||||
#: bookwyrm/templates/book/book.html:71 bookwyrm/templates/book/book.html:72
|
||||
msgid "Edit Book"
|
||||
|
@ -961,7 +961,7 @@ msgstr "編輯書目"
|
|||
|
||||
#: bookwyrm/templates/book/book.html:97 bookwyrm/templates/book/book.html:100
|
||||
msgid "Click to add cover"
|
||||
msgstr ""
|
||||
msgstr "點擊添加封面"
|
||||
|
||||
#: bookwyrm/templates/book/book.html:106
|
||||
msgid "Failed to load cover"
|
||||
|
@ -969,7 +969,7 @@ msgstr "載入封面失敗"
|
|||
|
||||
#: bookwyrm/templates/book/book.html:117
|
||||
msgid "Click to enlarge"
|
||||
msgstr ""
|
||||
msgstr "點擊放大"
|
||||
|
||||
#: bookwyrm/templates/book/book.html:196
|
||||
#, python-format
|
||||
|
@ -991,7 +991,7 @@ msgstr "描述:"
|
|||
#, python-format
|
||||
msgid "%(count)s edition"
|
||||
msgid_plural "%(count)s editions"
|
||||
msgstr[0] ""
|
||||
msgstr[0] "%(count)s 版次"
|
||||
|
||||
#: bookwyrm/templates/book/book.html:246
|
||||
msgid "You have shelved this edition in:"
|
||||
|
@ -1070,11 +1070,11 @@ msgstr "ISBN:"
|
|||
#: bookwyrm/templates/book/book_identifiers.html:12
|
||||
#: bookwyrm/templates/book/book_identifiers.html:13
|
||||
msgid "Copy ISBN"
|
||||
msgstr ""
|
||||
msgstr "複製ISBN"
|
||||
|
||||
#: bookwyrm/templates/book/book_identifiers.html:16
|
||||
msgid "Copied ISBN!"
|
||||
msgstr ""
|
||||
msgstr "已複製ISBN!"
|
||||
|
||||
#: bookwyrm/templates/book/book_identifiers.html:23
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:352
|
||||
|
@ -1089,16 +1089,16 @@ msgstr "ASIN:"
|
|||
#: bookwyrm/templates/book/book_identifiers.html:37
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:370
|
||||
msgid "Audible ASIN:"
|
||||
msgstr ""
|
||||
msgstr "Audible ASIN:"
|
||||
|
||||
#: bookwyrm/templates/book/book_identifiers.html:44
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:379
|
||||
msgid "ISFDB ID:"
|
||||
msgstr ""
|
||||
msgstr "ISFDB ID:"
|
||||
|
||||
#: bookwyrm/templates/book/book_identifiers.html:51
|
||||
msgid "Goodreads:"
|
||||
msgstr ""
|
||||
msgstr "Goodreads:"
|
||||
|
||||
#: bookwyrm/templates/book/cover_add_modal.html:5
|
||||
msgid "Add cover"
|
||||
|
@ -1116,7 +1116,7 @@ msgstr "從網址載入封面:"
|
|||
|
||||
#: bookwyrm/templates/book/cover_show_modal.html:6
|
||||
msgid "Book cover preview"
|
||||
msgstr ""
|
||||
msgstr "書籍封面預覽"
|
||||
|
||||
#: bookwyrm/templates/book/cover_show_modal.html:11
|
||||
#: bookwyrm/templates/components/inline_form.html:8
|
||||
|
@ -1310,16 +1310,16 @@ msgstr "新增作者:"
|
|||
#: bookwyrm/templates/book/edit/edit_book_form.html:211
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:214
|
||||
msgid "Add Author"
|
||||
msgstr ""
|
||||
msgstr "新增作者"
|
||||
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:212
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:215
|
||||
msgid "Jane Doe"
|
||||
msgstr ""
|
||||
msgstr "陳大文"
|
||||
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:221
|
||||
msgid "Add Another Author"
|
||||
msgstr ""
|
||||
msgstr "新增其他作者"
|
||||
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:231
|
||||
#: bookwyrm/templates/shelf/shelf.html:147
|
||||
|
@ -1337,7 +1337,7 @@ msgstr "格式:"
|
|||
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:280
|
||||
msgid "Format details:"
|
||||
msgstr ""
|
||||
msgstr "裝訂詳情:"
|
||||
|
||||
#: bookwyrm/templates/book/edit/edit_book_form.html:291
|
||||
msgid "Pages:"
|
||||
|
@ -1366,8 +1366,8 @@ msgstr "%(book_title)s 的各版本"
|
|||
|
||||
#: bookwyrm/templates/book/editions/editions.html:8
|
||||
#, python-format
|
||||
msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
|
||||
msgstr "<a href=\"%(work_path)s\">\"%(work_title)s\"</a> 的各版本"
|
||||
msgid "Editions of <a href=\"%(work_path)s\"><i>%(work_title)s</i></a>"
|
||||
msgstr ""
|
||||
|
||||
#: bookwyrm/templates/book/editions/editions.html:55
|
||||
msgid "Can't find the edition you're looking for?"
|
||||
|
@ -2795,12 +2795,8 @@ msgstr ""
|
|||
|
||||
#: bookwyrm/templates/import/import.html:21
|
||||
#, python-format
|
||||
msgid "\n"
|
||||
" Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day.\n"
|
||||
" "
|
||||
msgid_plural "\n"
|
||||
" Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days.\n"
|
||||
" "
|
||||
msgid "Currently, you are allowed to import %(display_size)s books every %(import_limit_reset)s day."
|
||||
msgid_plural "Currently, you are allowed to import %(import_size_limit)s books every %(import_limit_reset)s days."
|
||||
msgstr[0] ""
|
||||
|
||||
#: bookwyrm/templates/import/import.html:27
|
||||
|
|
|
@ -2,7 +2,7 @@ aiohttp==3.8.5
|
|||
bleach==5.0.1
|
||||
celery==5.2.7
|
||||
colorthief==0.2.1
|
||||
Django==3.2.20
|
||||
Django==3.2.23
|
||||
django-celery-beat==2.4.0
|
||||
bw-file-resubmit==0.6.0rc2
|
||||
django-compressor==4.3.1
|
||||
|
|
Loading…
Reference in a new issue