Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2021-03-07 09:00:20 -08:00
commit c770b369d2
61 changed files with 2609 additions and 302 deletions

View file

@ -31,7 +31,7 @@ Code contributions are gladly welcomed! If you're not sure where to start, take
If you have questions about the project or contributing, you can set up a video call during BookWyrm ["office hours"](https://calendly.com/mouse-reeve/30min).
### Translation
Do you speak a language besides English? BookWyrm needs localization! If you're comfortable using git and want to get into the code, there are [instructions](#workin-with-translations-and-locale-files) on how to create and edit localization files. If you feel more comfortable working in a regular text editor and would prefer not to run the application, get in touch directly and we can figure out a system, like emailing a text file, that works best.
Do you speak a language besides English? BookWyrm needs localization! If you're comfortable using git and want to get into the code, there are [instructions](#working-with-translations-and-locale-files) on how to create and edit localization files. If you feel more comfortable working in a regular text editor and would prefer not to run the application, get in touch directly and we can figure out a system, like emailing a text file, that works best.
### Financial Support
BookWyrm is an ad-free passion project with no intentions of seeking out venture funding or corporate financial relationships. If you want to help keep the project going, you can donate to the [Patreon](https://www.patreon.com/bookwyrm), or make a one time gift via [PayPal](https://paypal.me/oulipo).
@ -118,7 +118,7 @@ If you edit the CSS or JavaScript, you will need to run Django's `collectstatic`
./bw-dev collectstatic
```
### Workin with translations and locale files
### Working with translations and locale files
Text in the html files are wrapped in translation tags (`{% trans %}` and `{% blocktrans %}`), and Django generates locale files for all the strings in which you can add translations for the text. You can find existing translations in the `locale/` directory.
The application's language is set by a request header sent by your browser to the application, so to change the language of the application, you can change the default language requested by your browser.
@ -132,7 +132,10 @@ To start translation into a language which is currently supported, run the djang
#### Editing a locale
When you have a locale file, open the `django.po` in the directory for the language (for example, if you were adding German, `locale/de/LC_MESSAGES/django.po`. All the the text in the application will be shown in paired strings, with `msgid` as the original text, and `msgstr` as the translation (by default, this is set to an empty string, and will display the original text).
Add you translations to the `msgstr` strings, and when you're ready, compile the locale by running:
Add your translations to the `msgstr` strings. As the messages in the application are updated, `gettext` will sometimes add best-guess fuzzy matched options for those translations. When a message is marked as fuzzy, it will not be used in the application, so be sure to remove it when you translate that line.
When you're done, compile the locale by running:
``` bash
./bw-dev compilemessages
```

View file

@ -26,7 +26,7 @@ class Book(ActivityObject):
librarythingKey: str = ''
goodreadsKey: str = ''
cover: Image = field(default_factory=lambda: {})
cover: Image = None
type: str = 'Book'

View file

@ -26,6 +26,7 @@ class AbstractMinimalConnector(ABC):
'books_url',
'covers_url',
'search_url',
'isbn_search_url',
'max_query_count',
'name',
'identifier',
@ -61,6 +62,30 @@ class AbstractMinimalConnector(ABC):
results.append(self.format_search_result(doc))
return results
def isbn_search(self, query):
''' isbn search '''
params = {}
resp = requests.get(
'%s%s' % (self.isbn_search_url, query),
params=params,
headers={
'Accept': 'application/json; charset=utf-8',
'User-Agent': settings.USER_AGENT,
},
)
if not resp.ok:
resp.raise_for_status()
try:
data = resp.json()
except ValueError as e:
logger.exception(e)
raise ConnectorException('Unable to parse json response', e)
results = []
for doc in self.parse_isbn_search_data(data):
results.append(self.format_isbn_search_result(doc))
return results
@abstractmethod
def get_or_create_book(self, remote_id):
''' pull up a book record by whatever means possible '''
@ -73,6 +98,14 @@ class AbstractMinimalConnector(ABC):
def format_search_result(self, search_result):
''' create a SearchResult obj from json '''
@abstractmethod
def parse_isbn_search_data(self, data):
''' turn the result json from a search into a list '''
@abstractmethod
def format_isbn_search_result(self, search_result):
''' create a SearchResult obj from json '''
class AbstractConnector(AbstractMinimalConnector):
''' generic book data connector '''

View file

@ -19,3 +19,11 @@ class Connector(AbstractMinimalConnector):
def format_search_result(self, search_result):
search_result['connector'] = self
return SearchResult(**search_result)
def parse_isbn_search_data(self, data):
return data
def format_isbn_search_result(self, search_result):
search_result['connector'] = self
return SearchResult(**search_result)

View file

@ -1,5 +1,6 @@
''' interface with whatever connectors the app has '''
import importlib
import re
from urllib.parse import urlparse
from requests import HTTPError
@ -15,9 +16,27 @@ class ConnectorException(HTTPError):
def search(query, min_confidence=0.1):
''' find books based on arbitary keywords '''
results = []
# Have we got a ISBN ?
isbn = re.sub('[\W_]', '', query)
maybe_isbn = len(isbn) in [10, 13] # ISBN10 or ISBN13
dedup_slug = lambda r: '%s/%s/%s' % (r.title, r.author, r.year)
result_index = set()
for connector in get_connectors():
result_set = None
if maybe_isbn:
# Search on ISBN
if not connector.isbn_search_url or connector.isbn_search_url == '':
result_set = []
else:
try:
result_set = connector.isbn_search(isbn)
except (HTTPError, ConnectorException):
pass
# if no isbn search or results, we fallback to generic search
if result_set == None or result_set == []:
try:
result_set = connector.search(query, min_confidence=min_confidence)
except (HTTPError, ConnectorException):
@ -41,6 +60,12 @@ def local_search(query, min_confidence=0.1, raw=False):
return connector.search(query, min_confidence=min_confidence, raw=raw)
def isbn_local_search(query, raw=False):
''' only look at local search results '''
connector = load_connector(models.Connector.objects.get(local=True))
return connector.isbn_search(query, raw=raw)
def first_search_result(query, min_confidence=0.1):
''' search until you find a result that fits '''
for connector in get_connectors():

View file

@ -129,6 +129,22 @@ class Connector(AbstractConnector):
)
def parse_isbn_search_data(self, data):
return list(data.values())
def format_isbn_search_result(self, search_result):
# build the remote id from the openlibrary key
key = self.books_url + search_result['key']
authors = search_result.get('authors') or [{'name': 'Unknown'}]
author_names = [ author.get('name') for author in authors]
return SearchResult(
title=search_result.get('title'),
key=key,
author=', '.join(author_names),
connector=self,
year=search_result.get('publish_date'),
)
def load_edition_data(self, olkey):
''' query openlibrary for editions of a work '''
url = '%s/works/%s/editions' % (self.books_url, olkey)

View file

@ -33,6 +33,31 @@ class Connector(AbstractConnector):
search_results.sort(key=lambda r: r.confidence, reverse=True)
return search_results
def isbn_search(self, query, raw=False):
''' search your local database '''
if not query:
return []
filters = [{f: query} for f in ['isbn_10', 'isbn_13']]
results = models.Edition.objects.filter(
reduce(operator.or_, (Q(**f) for f in filters))
).distinct()
# when there are multiple editions of the same work, pick the default.
# it would be odd for this to happen.
results = results.filter(parent_work__default_edition__id=F('id')) \
or results
search_results = []
for result in results:
if raw:
search_results.append(result)
else:
search_results.append(self.format_search_result(result))
if len(search_results) >= 10:
break
return search_results
def format_search_result(self, search_result):
return SearchResult(
@ -47,6 +72,19 @@ class Connector(AbstractConnector):
)
def format_isbn_search_result(self, search_result):
return SearchResult(
title=search_result.title,
key=search_result.remote_id,
author=search_result.author_text,
year=search_result.published_date.year if \
search_result.published_date else None,
connector=self,
confidence=search_result.rank if \
hasattr(search_result, 'rank') else 1,
)
def is_work_data(self, data):
pass
@ -59,6 +97,10 @@ class Connector(AbstractConnector):
def get_authors_from_data(self, data):
return None
def parse_isbn_search_data(self, data):
''' it's already in the right format, don't even worry about it '''
return data
def parse_search_data(self, data):
''' it's already in the right format, don't even worry about it '''
return data

View file

@ -66,6 +66,7 @@ def init_connectors():
books_url='https://%s/book' % DOMAIN,
covers_url='https://%s/images/covers' % DOMAIN,
search_url='https://%s/search?q=' % DOMAIN,
isbn_search_url='https://%s/isbn/' % DOMAIN,
priority=1,
)
@ -77,6 +78,7 @@ def init_connectors():
books_url='https://bookwyrm.social/book',
covers_url='https://bookwyrm.social/images/covers',
search_url='https://bookwyrm.social/search?q=',
isbn_search_url='https://bookwyrm.social/isbn/',
priority=2,
)
@ -88,6 +90,7 @@ def init_connectors():
books_url='https://openlibrary.org',
covers_url='https://covers.openlibrary.org',
search_url='https://openlibrary.org/search?q=',
isbn_search_url='https://openlibrary.org/api/books?jscmd=data&format=json&bibkeys=ISBN:',
priority=3,
)

View file

@ -0,0 +1,18 @@
# Generated by Django 3.0.7 on 2021-02-28 16:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bookwyrm', '0046_sitesettings_privacy_policy'),
]
operations = [
migrations.AddField(
model_name='connector',
name='isbn_search_url',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View file

@ -91,7 +91,7 @@ class Book(BookDataModel):
@property
def alt_text(self):
''' image alt test '''
text = '%s cover' % self.title
text = '%s' % self.title
if self.edition_info:
text += ' (%s)' % self.edition_info
return text

View file

@ -22,6 +22,7 @@ class Connector(BookWyrmModel):
books_url = models.CharField(max_length=255)
covers_url = models.CharField(max_length=255)
search_url = models.CharField(max_length=255, null=True, blank=True)
isbn_search_url = models.CharField(max_length=255, null=True, blank=True)
politeness_delay = models.IntegerField(null=True, blank=True) #seconds
max_query_count = models.IntegerField(null=True, blank=True)

View file

@ -3,7 +3,7 @@ import re
from urllib.parse import urlparse
from django.apps import apps
from django.contrib.auth.models import AbstractUser
from django.contrib.auth.models import AbstractUser, Group
from django.core.validators import MinValueValidator
from django.db import models
from django.utils import timezone
@ -208,6 +208,13 @@ class User(OrderedCollectionPageMixin, AbstractUser):
# an id needs to be set before we can proceed with related models
super().save(*args, **kwargs)
# make users editors by default
try:
self.groups.add(Group.objects.get(name='editor'))
except Group.DoesNotExist:
# this should only happen in tests
pass
# create keys and shelves for new local users
self.key_pair = KeyPair.objects.create(
remote_id='%s/#main-key' % self.remote_id)

View file

@ -141,6 +141,7 @@ LANGUAGE_CODE = 'en-us'
LANGUAGES = [
('en-us', _('English')),
('de-de', _('German')),
('es', _('Spanish')),
('fr-fr', _('French')),
('zh-cn', _('Simplified Chinese')),
]

View file

@ -1,3 +1,8 @@
html {
scroll-behavior: smooth;
scroll-padding-top: 20%;
}
/* --- --- */
.image {
overflow: hidden;

View file

@ -8,9 +8,12 @@ window.onload = function() {
Array.from(document.getElementsByClassName('interaction'))
.forEach(t => t.onsubmit = interact);
// select all
Array.from(document.getElementsByClassName('select-all'))
.forEach(t => t.onclick = selectAll);
// Toggle all checkboxes.
document
.querySelectorAll('[data-action="toggle-all"]')
.forEach(input => {
input.addEventListener('change', toggleAllCheckboxes);
});
// tab groups
Array.from(document.getElementsByClassName('tab-group'))
@ -136,9 +139,20 @@ function interact(e) {
.forEach(t => addRemoveClass(t, 'hidden', t.className.indexOf('hidden') == -1));
}
function selectAll(e) {
e.target.parentElement.parentElement.querySelectorAll('[type="checkbox"]')
.forEach(t => t.checked=true);
/**
* Toggle all descendant checkboxes of a target.
*
* Use `data-target="ID_OF_TARGET"` on the node being listened to.
*
* @param {Event} event - change Event
* @return {undefined}
*/
function toggleAllCheckboxes(event) {
const mainCheckbox = event.target;
document
.querySelectorAll(`#${mainCheckbox.dataset.target} [type="checkbox"]`)
.forEach(checkbox => {checkbox.checked = mainCheckbox.checked;});
}
function toggleMenu(e) {

View file

@ -35,7 +35,7 @@
</div>
<div class="columns">
<div class="column is-narrow">
<div class="column is-one-fifth">
{% include 'snippets/book_cover.html' with book=book size=large %}
{% include 'snippets/rate_action.html' with user=request.user book=book %}
{% include 'snippets/shelve_button/shelve_button.html' %}
@ -93,7 +93,7 @@
</section>
</div>
<div class="column">
<div class="column is-three-fifths">
<div class="block">
<h3 class="field is-grouped">
{% include 'snippets/stars.html' with rating=rating %}
@ -201,7 +201,7 @@
</div>
</div>
<div class="column is-narrow">
<div class="column is-one-fifth">
{% if book.subjects %}
<section class="content block">
<h2 class="title is-5">{% trans "Subjects" %}</h2>
@ -217,7 +217,7 @@
<section class="content block">
<h2 class="title is-5">{% trans "Places" %}</h2>
<ul>
{% for place in book.subject_placess %}
{% for place in book.subject_places %}
<li>{{ place }}</li>
{% endfor %}
</ul>
@ -254,8 +254,8 @@
<div>
{% include 'snippets/username.html' with user=rating.user %}
</div>
<div class="field is-grouped mb-0">
<div>{% trans "rated it" %}</div>
<div class="is-flex">
<p class="mr-1">{% trans "rated it" %}</p>
{% include 'snippets/stars.html' with rating=rating.rating %}
</div>
<div>

View file

@ -5,7 +5,7 @@
{% block dropdown-trigger %}{% endblock %}
</button>
<div class="dropdown-menu">
<ul class="dropdown-content" role="menu" id="menu-options-{{ book.id }}">
<ul class="dropdown-content" role="menu" id="menu-options-{{ uuid }}">
{% block dropdown-list %}{% endblock %}
</ul>
</div>

View file

@ -1,8 +1,15 @@
<div class="modal hidden" id="{{ controls_text }}-{{ controls_uid }}">
<div
role="dialog"
class="modal hidden"
id="{{ controls_text }}-{{ controls_uid }}"
aria-labelledby="modal-card-title-{{ controls_text }}-{{ controls_uid }}"
aria-modal="true"
>
{# @todo Implement focus traps to prevent tabbing out of the modal. #}
<div class="modal-background"></div>
<div class="modal-card">
<header class="modal-card-head" tabindex="0" id="modal-title-{{ controls_text }}-{{ controls_uid }}">
<h2 class="modal-card-title">
<h2 class="modal-card-title" id="modal-card-title-{{ controls_text }}-{{ controls_uid }}">
{% block modal-title %}{% endblock %}
</h2>
{% include 'snippets/toggle/toggle_button.html' with label="close" class="delete" nonbutton=True %}
@ -18,7 +25,6 @@
</footer>
{% block modal-form-close %}{% endblock %}
</div>
<label class="modal-close is-large" for="{{ controls_text }}-{{ controls_uid }}" aria-label="close"></label>
{% include 'snippets/toggle/toggle_button.html' with label="close" class="modal-close is-large" nonbutton=True %}
</div>

View file

@ -54,11 +54,11 @@
<div class="tile is-child box has-background-white-bis">
<h2 class="title is-4">{% trans "Your Account" %}</h2>
{% include 'user/user_preview.html' with user=request.user %}
<div class="box content">
{% if request.user.summary %}
<div class="box content">
{{ request.user.summary | to_markdown | safe }}
{% endif %}
</div>
{% endif %}
</div>
{% endif %}
</div>

View file

@ -1,4 +1,5 @@
{% load bookwyrm_tags %}
{% load i18n %}
{% if book %}
<div class="columns">
<div class="column is-narrow">
@ -8,7 +9,7 @@
<div class="column">
<h3 class="title is-5"><a href="/book/{{ book.id }}">{{ book.title }}</a></h3>
{% if book.authors %}
<p class="subtitle is-5">by {% include 'snippets/authors.html' with book=book %}</p>
<p class="subtitle is-5">{% trans "by" %} {% include 'snippets/authors.html' with book=book %}</p>
{% endif %}
{% if book|book_description %}
<blockquote class="content">{{ book|book_description|to_markdown|safe|truncatewords_html:50 }}</blockquote>

View file

@ -1,11 +1,12 @@
{% load bookwyrm_tags %}
{% load i18n %}
{% if book %}
<a href="{{ book.local_path }}">{% include 'snippets/book_cover.html' with book=book %}</a>
{% include 'snippets/stars.html' with rating=book|rating:request.user %}
<h3 class="title is-6"><a href="/book/{{ book.id }}">{{ book.title }}</a></h3>
{% if book.authors %}
<p class="subtitle is-6">by {% include 'snippets/authors.html' with book=book %}</p>
<p class="subtitle is-6">{% trans "by" %} {% include 'snippets/authors.html' with book=book %}</p>
{% endif %}
{% endif %}

View file

@ -6,13 +6,13 @@
<h1 class="title">{% blocktrans %}{{ tab_title }} Timeline{% endblocktrans %}</h1>
<div class="tabs">
<ul>
<li class="{% if tab == 'home' %}is-active{% endif %}">
<li class="{% if tab == 'home' %}is-active{% endif %}"{% if tab == 'home' %} aria-current="page"{% endif %}>
<a href="/#feed">{% trans "Home" %}</a>
</li>
<li class="{% if tab == 'local' %}is-active{% endif %}">
<li class="{% if tab == 'local' %}is-active{% endif %}"{% if tab == 'local' %} aria-current="page"{% endif %}>
<a href="/local#feed">{% trans "Local" %}</a>
</li>
<li class="{% if tab == 'federated' %}is-active{% endif %}">
<li class="{% if tab == 'federated' %}is-active{% endif %}"{% if tab == 'federated' %} aria-current="page"{% endif %}>
<a href="/federated#feed">{% trans "Federated" %}</a>
</li>
</ul>

View file

@ -4,7 +4,7 @@
{% block panel %}
<header class="block">
<a href="/#feed" class="button" data-back>
<span class="icon icon-arrow-left" aira-hidden="true"></span>
<span class="icon icon-arrow-left" aria-hidden="true"></span>
<span>{% trans "Back" %}</span>
</a>
</header>

View file

@ -5,7 +5,7 @@
{% block title %}{% trans "Import Status" %}{% endblock %}
{% block content %}
{% block content %}{% spaceless %}
<div class="block">
<h1 class="title">{% trans "Import Status" %}</h1>
@ -36,8 +36,19 @@
{% if not job.retry %}
<form name="retry" action="/import/{{ job.id }}" method="post">
{% csrf_token %}
{% with failed_count=failed_items|length %}
{% if failed_count > 10 %}
<p class="block">
<a href="#select-all-failed-imports">
{% blocktrans %}Jump to the bottom of the list to select the {{ failed_count }} items which failed to import.{% endblocktrans %}
</a>
</p>
{% endif %}
{% endwith %}
<fieldset id="failed-imports">
<ul>
<fieldset>
{% for item in failed_items %}
<li class="pb-1">
<input class="checkbox" type="checkbox" name="import_item" value="{{ item.id }}" id="import-item-{{ item.id }}">
@ -51,15 +62,28 @@
</p>
</li>
{% endfor %}
</fieldset>
</ul>
<div class="block pt-1 select-all">
<label class="label">
<input type="checkbox" class="checkbox">
</fieldset>
<fieldset class="mt-3">
<a name="select-all-failed-imports"></a>
<label class="label is-inline">
<input
id="toggle-all-checkboxes-failed-imports"
class="checkbox"
type="checkbox"
data-action="toggle-all"
data-target="failed-imports"
/>
{% trans "Select all" %}
</label>
</div>
<button class="button" type="submit">{% trans "Retry items" %}</button>
<button class="button is-block mt-3" type="submit">{% trans "Retry items" %}</button>
</fieldset>
<hr>
{% else %}
<ul>
{% for item in failed_items %}
@ -123,4 +147,4 @@
</table>
</div>
</div>
{% endblock %}
{% endspaceless %}{% endblock %}

View file

@ -0,0 +1,33 @@
{% extends 'layout.html' %}
{% load i18n %}
{% block title %}{% trans "Search Results" %}{% endblock %}
{% block content %}
{% with book_results|first as local_results %}
<div class="block">
<h1 class="title">{% blocktrans %}Search Results for "{{ query }}"{% endblocktrans %}</h1>
</div>
<div class="block columns">
<div class="column">
<h2 class="title">{% trans "Matching Books" %}</h2>
<section class="block">
{% if not results %}
<p>{% blocktrans %}No books found for "{{ query }}"{% endblocktrans %}</p>
{% else %}
<ul>
{% for result in results %}
<li class="pd-4">
<a href="{{ result.key }}">{% include 'snippets/search_result_text.html' with result=result link=True %}</a>
</li>
{% endfor %}
</ul>
{% endif %}
</section>
<div class="column">
</div>
</div>
{% endwith %}
{% endblock %}

View file

@ -22,7 +22,7 @@
<meta name="twitter:image:alt" content="BookWyrm Logo">
</head>
<body>
<nav class="navbar container" role="navigation" aria-label="main navigation">
<nav class="navbar container" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="/">
<img class="image logo" src="{% if site.logo_small %}/images/{{ site.logo_small }}{% else %}/static/images/logo-small.png{% endif %}" alt="Home page">
@ -69,10 +69,18 @@
<div class="navbar-end">
{% if request.user.is_authenticated %}
<div class="navbar-item has-dropdown is-hoverable">
<div class="navbar-link pulldown-menu" role="button" aria-expanded="false" tabindex="0" aria-haspopup="true" aria-controls="navbar-dropdown"><p>
<a
href="{{ user.local_path }}"
class="navbar-link pulldown-menu"
role="button"
aria-expanded="false"
tabindex="0"
aria-haspopup="true"
aria-controls="navbar-dropdown"
>
{% include 'snippets/avatar.html' with user=request.user %}
{% include 'snippets/username.html' with user=request.user %}
</p></div>
<span class="ml-2">{% include 'snippets/username.html' with user=request.user anchor=false %}</span>
</a>
<ul class="navbar-dropdown" id="navbar-dropdown">
<li>
<a href="/direct-messages" class="navbar-item">
@ -95,7 +103,7 @@
</a>
</li>
{% if perms.bookwyrm.create_invites or perms.bookwyrm.edit_instance_settings%}
<hr class="navbar-divider">
<li class="navbar-divider" role="presentation"></li>
{% endif %}
{% if perms.bookwyrm.create_invites %}
<li>
@ -111,7 +119,7 @@
</a>
</li>
{% endif %}
<hr class="navbar-divider">
<li class="navbar-divider" role="presentation"></li>
<li>
<a href="/logout" class="navbar-item">
{% trans 'Log out' %}
@ -141,12 +149,12 @@
<div class="columns is-variable is-1">
<div class="column">
<label class="is-sr-only" for="id_localname">{% trans "Username:" %}</label>
<input type="text" name="localname" maxlength="150" class="input" required="" id="id_localname" placeholder="username">
<input type="text" name="localname" maxlength="150" class="input" required="" id="id_localname" placeholder="{% trans 'username' %}">
</div>
<div class="column">
<label class="is-sr-only" for="id_password">{% trans "Username:" %}</label>
<input type="password" name="password" maxlength="128" class="input" required="" id="id_password" placeholder="password">
<p class="help"><a href="/password-reset">Forgot your password?</a></p>
<input type="password" name="password" maxlength="128" class="input" required="" id="id_password" placeholder="{% trans 'password' %}">
<p class="help"><a href="/password-reset">{% trans "Forgot your password?" %}</a></p>
</div>
<div class="column is-narrow">
<button class="button is-primary" type="submit">{% trans "Log in" %}</button>
@ -157,7 +165,7 @@
{% if site.allow_registration and request.path != '' and request.path != '/' %}
<div class="column is-narrow">
<a href="/" class="button is-link">
Join
{% trans "Join" %}
</a>
</div>
{% endif %}
@ -191,7 +199,7 @@
{% if site.support_link %}
<div class="column">
<span class="icon icon-heart"></span>
Support {{ site.name }} on <a href="{{ site.support_link }}" target="_blank">{{ site.support_title }}</a>
{% blocktrans %}Support {{ site.name }} on <a href="{{ site.support_link }}" target="_blank">{{ site.support_title }}</a>{% endblocktrans %}
</div>
{% endif %}
<div class="column">

View file

@ -6,6 +6,6 @@
{% block content %}
<div class="block">
<h1 class="title">{% trans "Not Found" %}</h1>
<p>{% trans "The page your requested doesn't seem to exist!" %}</p>
<p>{% trans "The page you requested doesn't seem to exist!" %}</p>
</div>
{% endblock %}

View file

@ -87,11 +87,11 @@
</div>
{% elif notification.notification_type == 'BOOST' %}
{% if related_status.status_type == 'Review' %}
{% blocktrans with related_path=related_status.local_path book_title=related_status.book.title %}boosted your <a href="{{ related_path }}">review of <em>{{ book.title }}</em></a>{% endblocktrans %}
{% blocktrans with related_path=related_status.local_path book_title=related_status.book.title %}boosted your <a href="{{ related_path }}">review of <em>{{ book_title }}</em></a>{% endblocktrans %}
{% elif related_status.status_type == 'Comment' %}
{% blocktrans with related_path=related_status.local_path book_title=related_status.book.title %}boosted your <a href="{{ related_path }}">comment on<em>{{ book.title }}</em></a>{% endblocktrans %}
{% blocktrans with related_path=related_status.local_path book_title=related_status.book.title %}boosted your <a href="{{ related_path }}">comment on<em>{{ book_title }}</em></a>{% endblocktrans %}
{% elif related_status.status_type == 'Quotation' %}
{% blocktrans with related_path=related_status.local_path book_title=related_status.book.title %}boosted your <a href="{{ related_path }}">quote from <em>{{ book.title }}</em></a>{% endblocktrans %}
{% blocktrans with related_path=related_status.local_path book_title=related_status.book.title %}boosted your <a href="{{ related_path }}">quote from <em>{{ book_title }}</em></a>{% endblocktrans %}
{% else %}
{% blocktrans with related_path=related_status.local_path %}boosted your <a href="{{ related_path }}">status</a>{% endblocktrans %}
{% endif %}
@ -114,7 +114,9 @@
<div class="columns">
<div class="column">
{% if related_status.content %}
<a href="{{ related_status.local_path }}">{{ related_status.content | safe | truncatewords_html:10 }}</a>
<a href="{{ related_status.local_path }}">
{{ related_status.content | safe | truncatewords_html:10 }}{% if related_status.mention_books %} <em>{{ related_status.mention_books.first.title }}</em>{% endif %}
</a>
{% elif related_status.quote %}
<a href="{{ related_status.local_path }}">{{ related_status.quote | safe | truncatewords_html:10 }}</a>
{% elif related_status.rating %}

View file

@ -1,3 +1,3 @@
{% load bookwyrm_tags %}
<img class="avatar image {% if large %}is-96x96{% else %}is-32x32{% endif %}" src="{% if user.avatar %}/images/{{ user.avatar }}{% else %}/static/images/default_avi.jpg{% endif %}" alt="{{ user.alt_text }}">
<img class="avatar image {% if large %}is-96x96{% else %}is-32x32{% endif %}" src="{% if user.avatar %}/images/{{ user.avatar }}{% else %}/static/images/default_avi.jpg{% endif %}" {% if ariaHide %}aria-hidden="true"{% endif %} alt="{{ user.alt_text }}">

View file

@ -6,8 +6,7 @@
<div class="no-cover book-cover">
<img class="book-cover" src="/static/images/no_cover.jpg" alt="No cover">
<div>
<p>{{ book.title }}</p>
<p>({{ book.edition_info }})</p>
<p>{{ book.alt_text }}</p>
</div>
</div>
{% endif %}

View file

@ -1,5 +1,5 @@
{% load i18n %}
<nav class="pagination" role="navigation" aria-label="pagination">
<nav class="pagination" aria-label="pagination">
{% if page.has_previous %}
<p class="pagination-previous">
<a href="{{ path }}?page={{ page.previous_page_number }}{{ anchor }}">

View file

@ -67,4 +67,4 @@
</div>
</form>
</div>
{% include 'snippets/delete_readthrough_modal.html' with controls_text="delete-readthrough" controls_uid=readthrough.id %}
{% include 'snippets/delete_readthrough_modal.html' with controls_text="delete-readthrough" controls_uid=readthrough.id no_body=True %}

View file

@ -18,7 +18,7 @@
</li>
{% endif %}
{% endfor %}
<hr class="navbar-divider">
<li class="navbar-divider" role="presentation"></li>
<li>
<form class="dropdown-item pt-0 pb-0" name="shelve" action="/unshelve/" method="post">
{% csrf_token %}

View file

@ -3,10 +3,12 @@
<div class="block">
{% if status.status_type == 'Review' %}
<div>
<h3 class="title is-5 has-subtitle">
{% if status.name %}<span dir="auto">{{ status.name }}</span><br>{% endif %}
{% if status.name %}
<h3 class="title is-5 has-subtitle" dir="auto">
{{ status.name|escape }}
</h3>
<p class="subtitle">{% include 'snippets/stars.html' with rating=status.rating %}</p>
{% endif %}
{% include 'snippets/stars.html' with rating=status.rating %}
</div>
{% endif %}
@ -35,7 +37,7 @@
{% if status.content and status.status_type != 'GeneratedNote' and status.status_type != 'Announce' %}
{% include 'snippets/trimmed_text.html' with full=status.content|safe %}
{% endif %}
{% if status.attachments %}
{% if status.attachments.exists %}
<div class="block">
<div class="columns">
{% for attachment in status.attachments.all %}

View file

@ -1,7 +1,9 @@
{% load bookwyrm_tags %}
{% load i18n %}
{% include 'snippets/avatar.html' with user=status.user %}
<a href="{{ status.user.local_path }}">
{% include 'snippets/avatar.html' with user=status.user ariaHide="true" %}
{% include 'snippets/username.html' with user=status.user %}
</a>
{% if status.status_type == 'GeneratedNote' %}
{{ status.content | safe }}

View file

@ -8,19 +8,29 @@
{% with full|to_markdown|safe|truncatewords_html:60 as trimmed %}
{% if trimmed != full %}
<div id="hide-full-{{ uuid }}">
<div class="content" id="trimmed-{{ uuid }}"><span dir="auto">{{ trimmed }}</span>
<div class="content" id="trimmed-{{ uuid }}">
<p dir="auto">{{ trimmed }}</p>
<div>
{% trans "Show more" as button_text %}
{% include 'snippets/toggle/open_button.html' with text=button_text controls_text="full" controls_uid=uuid class="is-small" %}
</div>
</div>
</div>
<div id="full-{{ uuid }}" class="hidden">
<div class="content"><span dir="auto">{{ full }}</span>
<div class="content">
<div dir="auto">{{ full }}</div>
<div>
{% trans "Show less" as button_text %}
{% include 'snippets/toggle/close_button.html' with text=button_text controls_text="full" controls_uid=uuid class="is-small" %}
</div>
</div>
</div>
{% else %}
<div class="content"><span dir="auto">{{ full }}</span></div>
<div class="content">
<div dir="auto">{{ full }}</div>
</div>
{% endif %}
{% endwith %}

View file

@ -1,2 +1,13 @@
{% spaceless %}
{% load bookwyrm_tags %}
<a href="{{ user.local_path }}" class="user">{% if user.name %}{{ user.name }}{% else %}{{ user | username }}{% endif %}</a>{% if possessive %}'s{% endif %}{% if show_full and user.name or show_full and user.localname %} ({{ user.username }}){% endif %}
<{% if anchor %}a href="{{ user.local_path }}"{% else %}span{% endif %}>
{{ user.display_name }}
</a>
{% if possessive %}'s{% endif %}
{% if show_full and user.name or show_full and user.localname %}
({{ user.username }})
{% endif %}
{% endspaceless %}

View file

@ -1,7 +1,7 @@
{% extends 'user/user_layout.html' %}
{% load i18n %}
{% block title %}{{ user.name }}{% endblock %}
{% block title %}{{ user.display_name }}{% endblock %}
{% block header %}
<div class="columns is-mobile">
@ -67,7 +67,7 @@
</div>
</div>
{% for activity in activities %}
<div class="block" id="feed">
<div class="block" id="feed-{{ activity.id }}">
{% include 'snippets/status/status.html' with status=activity %}
</div>
{% endfor %}

View file

@ -17,11 +17,11 @@
{% include 'user/user_preview.html' with user=user %}
</div>
<div class="column box has-background-white-bis content">
{% if user.summary %}
<div class="column box has-background-white-bis content">
{{ user.summary | to_markdown | safe }}
{% endif %}
</div>
{% endif %}
</div>
{% if not is_self and request.user.is_authenticated %}
{% include 'snippets/follow_button.html' with user=user %}
@ -56,7 +56,7 @@
<a href="{{ url }}">{% trans "Reading Goal" %}</a>
</li>
{% endif %}
{% if is_self or user.lists.exists %}
{% 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 %}>
<a href="{{ url }}">{% trans "Lists" %}</a>

View file

@ -1 +0,0 @@
from . import *

View file

@ -42,6 +42,10 @@ class AbstractConnector(TestCase):
return search_result
def parse_search_data(self, data):
return data
def format_isbn_search_result(self, search_result):
return search_result
def parse_isbn_search_data(self, data):
return data
def is_work_data(self, data):
return data['type'] == 'work'
def get_edition_from_work_data(self, data):

View file

@ -18,6 +18,7 @@ class AbstractConnector(TestCase):
books_url='https://example.com/books',
covers_url='https://example.com/covers',
search_url='https://example.com/search?q=',
isbn_search_url='https://example.com/isbn',
)
class TestConnector(abstract_connector.AbstractMinimalConnector):
@ -28,6 +29,10 @@ class AbstractConnector(TestCase):
pass
def parse_search_data(self, data):
return data
def format_isbn_search_result(self, search_result):
return search_result
def parse_isbn_search_data(self, data):
return data
self.test_connector = TestConnector('example.com')
@ -39,6 +44,7 @@ class AbstractConnector(TestCase):
self.assertEqual(connector.books_url, 'https://example.com/books')
self.assertEqual(connector.covers_url, 'https://example.com/covers')
self.assertEqual(connector.search_url, 'https://example.com/search?q=')
self.assertEqual(connector.isbn_search_url, 'https://example.com/isbn')
self.assertIsNone(connector.name)
self.assertEqual(connector.identifier, 'example.com')
self.assertIsNone(connector.max_query_count)

View file

@ -27,6 +27,7 @@ class Openlibrary(TestCase):
books_url='https://openlibrary.org',
covers_url='https://covers.openlibrary.org',
search_url='https://openlibrary.org/search?q=',
isbn_search_url='https://openlibrary.org/isbn',
)
self.connector = Connector('openlibrary.org')
@ -149,6 +150,34 @@ class Openlibrary(TestCase):
self.assertEqual(result.connector, self.connector)
def test_parse_isbn_search_result(self):
''' extract the results from the search json response '''
datafile = pathlib.Path(__file__).parent.joinpath(
'../data/ol_isbn_search.json')
search_data = json.loads(datafile.read_bytes())
result = self.connector.parse_isbn_search_data(search_data)
self.assertIsInstance(result, list)
self.assertEqual(len(result), 1)
def test_format_isbn_search_result(self):
''' translate json from openlibrary into SearchResult '''
datafile = pathlib.Path(__file__).parent.joinpath(
'../data/ol_isbn_search.json')
search_data = json.loads(datafile.read_bytes())
results = self.connector.parse_isbn_search_data(search_data)
self.assertIsInstance(results, list)
result = self.connector.format_isbn_search_result(results[0])
self.assertIsInstance(result, SearchResult)
self.assertEqual(result.title, 'Les ombres errantes')
self.assertEqual(
result.key, 'https://openlibrary.org/books/OL16262504M')
self.assertEqual(result.author, 'Pascal Quignard')
self.assertEqual(result.year, '2002')
self.assertEqual(result.connector, self.connector)
@responses.activate
def test_load_edition_data(self):
''' format url from key and make request '''

View file

@ -0,0 +1,45 @@
{
"ISBN:9782070427796": {
"url": "https://openlibrary.org/books/OL16262504M/Les_ombres_errantes",
"key": "/books/OL16262504M",
"title": "Les ombres errantes",
"authors": [
{
"url": "https://openlibrary.org/authors/OL269675A/Pascal_Quignard",
"name": "Pascal Quignard"
}
],
"by_statement": "Pascal Quignard.",
"identifiers": {
"goodreads": [
"1835483"
],
"librarything": [
"983474"
],
"isbn_10": [
"207042779X"
],
"openlibrary": [
"OL16262504M"
]
},
"classifications": {
"dewey_decimal_class": [
"848/.91403"
]
},
"publishers": [
{
"name": "Gallimard"
}
],
"publish_places": [
{
"name": "Paris"
}
],
"publish_date": "2002",
"notes": "Hardback published Grasset, 2002."
}
}

View file

@ -81,7 +81,7 @@ class Book(TestCase):
book.save()
self.assertEqual(book.edition_info, 'worm, Glorbish language, 2020')
self.assertEqual(
book.alt_text, 'Test Edition cover (worm, Glorbish language, 2020)')
book.alt_text, 'Test Edition (worm, Glorbish language, 2020)')
def test_get_rank(self):

View file

@ -150,7 +150,7 @@ class Status(TestCase):
self.assertEqual(activity['attachment'][0].url, 'https://%s%s' % \
(settings.DOMAIN, self.book.cover.url))
self.assertEqual(
activity['attachment'][0].name, 'Test Edition cover')
activity['attachment'][0].name, 'Test Edition')
def test_comment_to_activity(self, _):
''' subclass of the base model version with a "pure" serializer '''
@ -177,7 +177,7 @@ class Status(TestCase):
self.assertEqual(activity['attachment'][0].url, 'https://%s%s' % \
(settings.DOMAIN, self.book.cover.url))
self.assertEqual(
activity['attachment'][0].name, 'Test Edition cover')
activity['attachment'][0].name, 'Test Edition')
def test_quotation_to_activity(self, _):
''' subclass of the base model version with a "pure" serializer '''
@ -207,7 +207,7 @@ class Status(TestCase):
self.assertEqual(activity['attachment'][0].url, 'https://%s%s' % \
(settings.DOMAIN, self.book.cover.url))
self.assertEqual(
activity['attachment'][0].name, 'Test Edition cover')
activity['attachment'][0].name, 'Test Edition')
def test_review_to_activity(self, _):
''' subclass of the base model version with a "pure" serializer '''
@ -238,7 +238,7 @@ class Status(TestCase):
self.assertEqual(activity['attachment'][0].url, 'https://%s%s' % \
(settings.DOMAIN, self.book.cover.url))
self.assertEqual(
activity['attachment'][0].name, 'Test Edition cover')
activity['attachment'][0].name, 'Test Edition')
def test_favorite(self, _):
''' fav a status '''

View file

@ -0,0 +1,54 @@
''' test for app action functionality '''
import json
from unittest.mock import patch
from django.http import JsonResponse
from django.template.response import TemplateResponse
from django.test import TestCase
from django.test.client import RequestFactory
from bookwyrm import models, views
from bookwyrm.connectors import abstract_connector
from bookwyrm.settings import DOMAIN
class IsbnViews(TestCase):
''' tag views'''
def setUp(self):
''' we need basic test data and mocks '''
self.factory = RequestFactory()
self.local_user = models.User.objects.create_user(
'mouse@local.com', 'mouse@mouse.com', 'mouseword',
local=True, localname='mouse',
remote_id='https://example.com/users/mouse',
)
self.work = models.Work.objects.create(title='Test Work')
self.book = models.Edition.objects.create(
title='Test Book',
isbn_13='1234567890123',
remote_id='https://example.com/book/1',
parent_work=self.work
)
models.Connector.objects.create(
identifier='self',
connector_file='self_connector',
local=True
)
models.SiteSettings.objects.create()
def test_isbn_json_response(self):
''' searches local data only and returns book data in json format '''
view = views.Isbn.as_view()
request = self.factory.get('')
with patch('bookwyrm.views.isbn.is_api_request') as is_api:
is_api.return_value = True
response = view(request, isbn='1234567890123')
self.assertIsInstance(response, JsonResponse)
data = json.loads(response.content)
self.assertEqual(len(data), 1)
self.assertEqual(data[0]['title'], 'Test Book')
self.assertEqual(
data[0]['key'], 'https://%s/book/%d' % (DOMAIN, self.book.id))

View file

@ -64,6 +64,10 @@ class ShelfViews(TestCase):
pass
def parse_search_data(self, data):
pass
def format_isbn_search_result(self, search_result):
return search_result
def parse_isbn_search_data(self, data):
return data
models.Connector.objects.create(
identifier='example.com',
connector_file='openlibrary',

View file

@ -135,6 +135,9 @@ urlpatterns = [
re_path(r'^resolve-book/?$', views.resolve_book),
re_path(r'^switch-edition/?$', views.switch_edition),
# isbn
re_path(r'^isbn/(?P<isbn>\d+)(.json)?/?$', views.Isbn.as_view()),
# author
re_path(r'^author/(?P<author_id>\d+)(.json)?/?$', views.Author.as_view()),
re_path(r'^author/(?P<author_id>\d+)/edit/?$', views.EditAuthor.as_view()),

View file

@ -31,3 +31,4 @@ from .site import Site
from .status import CreateStatus, DeleteStatus
from .updates import Updates
from .user import User, EditUser, Followers, Following
from .isbn import Isbn

View file

@ -89,7 +89,7 @@ class Book(View):
'rating': reviews.aggregate(Avg('rating'))['rating__avg'],
'tags': models.UserTag.objects.filter(book=book),
'lists': privacy_filter(
request.user, book.list_set.all()
request.user, book.list_set.filter(listitem__approved=True)
),
'user_tags': user_tags,
'user_shelves': user_shelves,

29
bookwyrm/views/isbn.py Normal file
View file

@ -0,0 +1,29 @@
''' isbn search view '''
from django.http import HttpResponseNotFound
from django.http import JsonResponse
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.connectors import connector_manager
from .helpers import is_api_request
# pylint: disable= no-self-use
class Isbn(View):
''' search a book by isbn '''
def get(self, request, isbn):
''' info about a book '''
book_results = connector_manager.isbn_local_search(isbn)
if is_api_request(request):
return JsonResponse([r.json() for r in book_results], safe=False)
data = {
'title': 'ISBN Search Results',
'results': book_results,
'query': isbn,
}
return TemplateResponse(request, 'isbn_search_results.html', data)

Binary file not shown.

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-03-02 21:36+0000\n"
"POT-Creation-Date: 2021-03-04 22:24+0000\n"
"PO-Revision-Date: 2021-03-02 17:19-0800\n"
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
"Language-Team: English <LL@li.org>\n"
@ -55,7 +55,7 @@ msgstr "%(value)s ist keine gültige remote_id"
msgid "%(value)s is not a valid username"
msgstr "%(value)s ist kein gültiger Username"
#: bookwyrm/models/fields.py:164
#: bookwyrm/models/fields.py:164 bookwyrm/templates/layout.html:152
#, fuzzy
#| msgid "Server name"
msgid "username"
@ -74,10 +74,14 @@ msgid "German"
msgstr "Deutsch"
#: bookwyrm/settings.py:144
msgid "Spanish"
msgstr ""
#: bookwyrm/settings.py:145
msgid "French"
msgstr "Französisch"
#: bookwyrm/settings.py:145
#: bookwyrm/settings.py:146
msgid "Simplified Chinese"
msgstr "Vereinfachtes Chinesisch"
@ -96,6 +100,8 @@ msgid "Books by %(name)s"
msgstr "Bücher von %(name)s"
#: bookwyrm/templates/book.html:21
#: bookwyrm/templates/discover/large-book.html:12
#: bookwyrm/templates/discover/small-book.html:9
msgid "by"
msgstr "von"
@ -428,7 +434,7 @@ msgid "Direct Messages with <a href=\"%(path)s\">%(username)s</a>"
msgstr "Direktnachrichten mit <a href=\"%(path)s\">%(username)s</a>"
#: bookwyrm/templates/feed/direct_messages.html:10
#: bookwyrm/templates/layout.html:79
#: bookwyrm/templates/layout.html:87
msgid "Direct Messages"
msgstr "Direktnachrichten"
@ -538,7 +544,7 @@ msgid "%(username)s's %(year)s Books"
msgstr "%(username)ss %(year)s Bücher"
#: bookwyrm/templates/import.html:5 bookwyrm/templates/import.html:9
#: bookwyrm/templates/layout.html:94
#: bookwyrm/templates/layout.html:102
msgid "Import Books"
msgstr "Bücher importieren"
@ -661,55 +667,72 @@ msgstr "Deine Regale"
msgid "Feed"
msgstr ""
#: bookwyrm/templates/layout.html:84
#: bookwyrm/templates/layout.html:92
#: bookwyrm/templates/preferences/preferences_layout.html:14
msgid "Profile"
msgstr ""
#: bookwyrm/templates/layout.html:89
#: bookwyrm/templates/layout.html:97
msgid "Settings"
msgstr "Einstellungen"
#: bookwyrm/templates/layout.html:103
#: bookwyrm/templates/layout.html:111
#: bookwyrm/templates/settings/admin_layout.html:19
#: bookwyrm/templates/settings/manage_invites.html:3
msgid "Invites"
msgstr "Einladungen"
#: bookwyrm/templates/layout.html:110
#: bookwyrm/templates/layout.html:118
msgid "Site Configuration"
msgstr "Seiteneinstellungen"
#: bookwyrm/templates/layout.html:117
#: bookwyrm/templates/layout.html:125
msgid "Log out"
msgstr "Abmelden"
#: bookwyrm/templates/layout.html:125 bookwyrm/templates/layout.html:126
#: bookwyrm/templates/layout.html:133 bookwyrm/templates/layout.html:134
#: bookwyrm/templates/notifications.html:6
#: bookwyrm/templates/notifications.html:10
msgid "Notifications"
msgstr "Benachrichtigungen"
#: bookwyrm/templates/layout.html:143 bookwyrm/templates/layout.html:147
#: bookwyrm/templates/layout.html:151 bookwyrm/templates/layout.html:155
#: bookwyrm/templates/login.html:17
#: bookwyrm/templates/snippets/register_form.html:4
msgid "Username:"
msgstr ""
#: bookwyrm/templates/layout.html:152 bookwyrm/templates/login.html:10
#: bookwyrm/templates/layout.html:156
msgid "password"
msgstr "Passwort"
#: bookwyrm/templates/layout.html:157 bookwyrm/templates/login.html:36
msgid "Forgot your password?"
msgstr "Passwort vergessen?"
#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:10
#: bookwyrm/templates/login.html:33
msgid "Log in"
msgstr "Anmelden"
#: bookwyrm/templates/layout.html:183
#: bookwyrm/templates/layout.html:168
msgid "Join"
msgstr ""
#: bookwyrm/templates/layout.html:191
msgid "About this server"
msgstr "Über diesen Server"
#: bookwyrm/templates/layout.html:187
#: bookwyrm/templates/layout.html:195
msgid "Contact site admin"
msgstr "Admin kontaktieren"
#: bookwyrm/templates/layout.html:198
#: bookwyrm/templates/layout.html:202
#, python-format
msgid "Support %(site.name)s on <a href=\"%(site.support_link)s\" target=\"_blank\">%(site.support_title)s</a>"
msgstr ""
#: bookwyrm/templates/layout.html:206
msgid "BookWyrm is open source software. You can contribute or report issues on <a href=\"https://github.com/mouse-reeve/bookwyrm\">GitHub</a>."
msgstr "BookWyrm ist open source Software. Du kannst dich auf <a href=\"https://github.com/mouse-reeve/bookwyrm\">GitHub</a> beteiligen oder etwas melden."
@ -855,10 +878,6 @@ msgstr ""
msgid "Password:"
msgstr "Passwort:"
#: bookwyrm/templates/login.html:36
msgid "Forgot your password?"
msgstr "Passwort vergessen?"
#: bookwyrm/templates/login.html:49
msgid "Contact an administrator to get an invite"
msgstr "Kontaktiere für eine Einladung eine*n Admin"
@ -872,7 +891,7 @@ msgid "Not Found"
msgstr "Nicht gefunden"
#: bookwyrm/templates/notfound.html:9
msgid "The page your requested doesn't seem to exist!"
msgid "The page you requested doesn't seem to exist!"
msgstr "Die Seite die du angefordert hast scheint nicht zu existieren!"
#: bookwyrm/templates/notifications.html:14
@ -949,18 +968,18 @@ msgstr "hat dir eine Folgeanfrage geschickt"
#: bookwyrm/templates/notifications.html:90
#, python-format
msgid "boosted your <a href=\"%(related_path)s\">review of <em>%(book.title)s</em></a>"
msgstr "hat deine <a href=\"%(related_path)s\">Bewertung von <em>%(book.title)s</em></a> geteilt"
msgid "boosted your <a href=\"%(related_path)s\">review of <em>%(book_title)s</em></a>"
msgstr "hat deine <a href=\"%(related_path)s\">Bewertung von <em>%(book_title)s</em></a> geteilt"
#: bookwyrm/templates/notifications.html:92
#, python-format
msgid "boosted your <a href=\"%(related_path)s\">comment on<em>%(book.title)s</em></a>"
msgstr "hat deinen <a href=\"%(related_path)s\">Kommentar zu<em>%(book.title)s</em></a> geteilt"
msgid "boosted your <a href=\"%(related_path)s\">comment on<em>%(book_title)s</em></a>"
msgstr "hat deinen <a href=\"%(related_path)s\">Kommentar zu<em>%(book_title)s</em></a> geteilt"
#: bookwyrm/templates/notifications.html:94
#, python-format
msgid "boosted your <a href=\"%(related_path)s\">quote from <em>%(book.title)s</em></a>"
msgstr "hat dein <a href=\"%(related_path)s\">Zitat aus <em>%(book.title)s</em></a> geteilt"
msgid "boosted your <a href=\"%(related_path)s\">quote from <em>%(book_title)s</em></a>"
msgstr "hat dein <a href=\"%(related_path)s\">Zitat aus <em>%(book_title)s</em></a> geteilt"
#: bookwyrm/templates/notifications.html:96
#, python-format
@ -1648,17 +1667,17 @@ msgstr "geteilt"
msgid "Reply"
msgstr "Antwort"
#: bookwyrm/templates/snippets/status/status_content.html:16
#: bookwyrm/templates/snippets/trimmed_text.html:12
#: bookwyrm/templates/snippets/status/status_content.html:18
#: bookwyrm/templates/snippets/trimmed_text.html:15
msgid "Show more"
msgstr "Mehr anzeigen"
#: bookwyrm/templates/snippets/status/status_content.html:23
#: bookwyrm/templates/snippets/trimmed_text.html:18
#: bookwyrm/templates/snippets/status/status_content.html:25
#: bookwyrm/templates/snippets/trimmed_text.html:25
msgid "Show less"
msgstr "Weniger anzeigen"
#: bookwyrm/templates/snippets/status/status_content.html:44
#: bookwyrm/templates/snippets/status/status_content.html:46
msgid "Open image in new window"
msgstr "Bild in neuem Fenster öffnen"
@ -1668,8 +1687,6 @@ msgid "More options"
msgstr "Mehr Optionen"
#: bookwyrm/templates/snippets/status/status_options.html:17
#, fuzzy
#| msgid "Delete post"
msgid "Delete status"
msgstr "Post löschen"
@ -1693,8 +1710,6 @@ msgstr "Mit \"%(tag.name)s\" markierte Bücher"
#: bookwyrm/templates/user/create_shelf_form.html:5
#: bookwyrm/templates/user/create_shelf_form.html:22
#, fuzzy
#| msgid "Create shelf"
msgid "Create Shelf"
msgstr "Regal erstellen"

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-03-02 21:36+0000\n"
"POT-Creation-Date: 2021-03-04 22:24+0000\n"
"PO-Revision-Date: 2021-02-28 17:19-0800\n"
"Last-Translator: Mouse Reeve <mousereeve@riseup.net>\n"
"Language-Team: English <LL@li.org>\n"
@ -53,7 +53,7 @@ msgstr ""
msgid "%(value)s is not a valid username"
msgstr ""
#: bookwyrm/models/fields.py:164
#: bookwyrm/models/fields.py:164 bookwyrm/templates/layout.html:152
msgid "username"
msgstr ""
@ -70,10 +70,14 @@ msgid "German"
msgstr ""
#: bookwyrm/settings.py:144
msgid "French"
msgid "Spanish"
msgstr ""
#: bookwyrm/settings.py:145
msgid "French"
msgstr ""
#: bookwyrm/settings.py:146
msgid "Simplified Chinese"
msgstr ""
@ -92,6 +96,8 @@ msgid "Books by %(name)s"
msgstr ""
#: bookwyrm/templates/book.html:21
#: bookwyrm/templates/discover/large-book.html:12
#: bookwyrm/templates/discover/small-book.html:9
msgid "by"
msgstr ""
@ -419,7 +425,7 @@ msgid "Direct Messages with <a href=\"%(path)s\">%(username)s</a>"
msgstr ""
#: bookwyrm/templates/feed/direct_messages.html:10
#: bookwyrm/templates/layout.html:79
#: bookwyrm/templates/layout.html:87
msgid "Direct Messages"
msgstr ""
@ -525,7 +531,7 @@ msgid "%(username)s's %(year)s Books"
msgstr ""
#: bookwyrm/templates/import.html:5 bookwyrm/templates/import.html:9
#: bookwyrm/templates/layout.html:94
#: bookwyrm/templates/layout.html:102
msgid "Import Books"
msgstr ""
@ -648,55 +654,72 @@ msgstr ""
msgid "Feed"
msgstr ""
#: bookwyrm/templates/layout.html:84
#: bookwyrm/templates/layout.html:92
#: bookwyrm/templates/preferences/preferences_layout.html:14
msgid "Profile"
msgstr ""
#: bookwyrm/templates/layout.html:89
#: bookwyrm/templates/layout.html:97
msgid "Settings"
msgstr ""
#: bookwyrm/templates/layout.html:103
#: bookwyrm/templates/layout.html:111
#: bookwyrm/templates/settings/admin_layout.html:19
#: bookwyrm/templates/settings/manage_invites.html:3
msgid "Invites"
msgstr ""
#: bookwyrm/templates/layout.html:110
#: bookwyrm/templates/layout.html:118
msgid "Site Configuration"
msgstr ""
#: bookwyrm/templates/layout.html:117
#: bookwyrm/templates/layout.html:125
msgid "Log out"
msgstr ""
#: bookwyrm/templates/layout.html:125 bookwyrm/templates/layout.html:126
#: bookwyrm/templates/layout.html:133 bookwyrm/templates/layout.html:134
#: bookwyrm/templates/notifications.html:6
#: bookwyrm/templates/notifications.html:10
msgid "Notifications"
msgstr ""
#: bookwyrm/templates/layout.html:143 bookwyrm/templates/layout.html:147
#: bookwyrm/templates/layout.html:151 bookwyrm/templates/layout.html:155
#: bookwyrm/templates/login.html:17
#: bookwyrm/templates/snippets/register_form.html:4
msgid "Username:"
msgstr ""
#: bookwyrm/templates/layout.html:152 bookwyrm/templates/login.html:10
#: bookwyrm/templates/layout.html:156
msgid "password"
msgstr ""
#: bookwyrm/templates/layout.html:157 bookwyrm/templates/login.html:36
msgid "Forgot your password?"
msgstr ""
#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:10
#: bookwyrm/templates/login.html:33
msgid "Log in"
msgstr ""
#: bookwyrm/templates/layout.html:183
#: bookwyrm/templates/layout.html:168
msgid "Join"
msgstr ""
#: bookwyrm/templates/layout.html:191
msgid "About this server"
msgstr ""
#: bookwyrm/templates/layout.html:187
#: bookwyrm/templates/layout.html:195
msgid "Contact site admin"
msgstr ""
#: bookwyrm/templates/layout.html:198
#: bookwyrm/templates/layout.html:202
#, python-format
msgid "Support %(site.name)s on <a href=\"%(site.support_link)s\" target=\"_blank\">%(site.support_title)s</a>"
msgstr ""
#: bookwyrm/templates/layout.html:206
msgid "BookWyrm is open source software. You can contribute or report issues on <a href=\"https://github.com/mouse-reeve/bookwyrm\">GitHub</a>."
msgstr ""
@ -840,10 +863,6 @@ msgstr ""
msgid "Password:"
msgstr ""
#: bookwyrm/templates/login.html:36
msgid "Forgot your password?"
msgstr ""
#: bookwyrm/templates/login.html:49
msgid "Contact an administrator to get an invite"
msgstr ""
@ -857,7 +876,7 @@ msgid "Not Found"
msgstr ""
#: bookwyrm/templates/notfound.html:9
msgid "The page your requested doesn't seem to exist!"
msgid "The page you requested doesn't seem to exist!"
msgstr ""
#: bookwyrm/templates/notifications.html:14
@ -934,17 +953,17 @@ msgstr ""
#: bookwyrm/templates/notifications.html:90
#, python-format
msgid "boosted your <a href=\"%(related_path)s\">review of <em>%(book.title)s</em></a>"
msgid "boosted your <a href=\"%(related_path)s\">review of <em>%(book_title)s</em></a>"
msgstr ""
#: bookwyrm/templates/notifications.html:92
#, python-format
msgid "boosted your <a href=\"%(related_path)s\">comment on<em>%(book.title)s</em></a>"
msgid "boosted your <a href=\"%(related_path)s\">comment on<em>%(book_title)s</em></a>"
msgstr ""
#: bookwyrm/templates/notifications.html:94
#, python-format
msgid "boosted your <a href=\"%(related_path)s\">quote from <em>%(book.title)s</em></a>"
msgid "boosted your <a href=\"%(related_path)s\">quote from <em>%(book_title)s</em></a>"
msgstr ""
#: bookwyrm/templates/notifications.html:96
@ -1633,17 +1652,17 @@ msgstr ""
msgid "Reply"
msgstr ""
#: bookwyrm/templates/snippets/status/status_content.html:16
#: bookwyrm/templates/snippets/trimmed_text.html:12
#: bookwyrm/templates/snippets/status/status_content.html:18
#: bookwyrm/templates/snippets/trimmed_text.html:15
msgid "Show more"
msgstr ""
#: bookwyrm/templates/snippets/status/status_content.html:23
#: bookwyrm/templates/snippets/trimmed_text.html:18
#: bookwyrm/templates/snippets/status/status_content.html:25
#: bookwyrm/templates/snippets/trimmed_text.html:25
msgid "Show less"
msgstr ""
#: bookwyrm/templates/snippets/status/status_content.html:44
#: bookwyrm/templates/snippets/status/status_content.html:46
msgid "Open image in new window"
msgstr ""

Binary file not shown.

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.1.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-03-02 21:36+0000\n"
"POT-Creation-Date: 2021-03-05 14:10+0000\n"
"PO-Revision-Date: 2021-03-02 12:37+0100\n"
"Last-Translator: Fabien Basmaison <contact@arkhi.org>\n"
"Language-Team: Mouse Reeve <LL@li.org>\n"
@ -55,7 +55,7 @@ msgstr ""
msgid "%(value)s is not a valid username"
msgstr ""
#: bookwyrm/models/fields.py:164
#: bookwyrm/models/fields.py:164 bookwyrm/templates/layout.html:152
#, fuzzy
#| msgid "Username:"
msgid "username"
@ -74,10 +74,14 @@ msgid "German"
msgstr ""
#: bookwyrm/settings.py:144
msgid "French"
msgid "Spanish"
msgstr ""
#: bookwyrm/settings.py:145
msgid "French"
msgstr ""
#: bookwyrm/settings.py:146
msgid "Simplified Chinese"
msgstr ""
@ -96,6 +100,8 @@ msgid "Books by %(name)s"
msgstr "Livres par %(name)s"
#: bookwyrm/templates/book.html:21
#: bookwyrm/templates/discover/large-book.html:12
#: bookwyrm/templates/discover/small-book.html:9
msgid "by"
msgstr ""
@ -431,15 +437,12 @@ msgid "Something went wrong! Sorry about that."
msgstr "Une erreur sest produite; désolé!"
#: bookwyrm/templates/feed/direct_messages.html:8
#, fuzzy, python-format
#| msgid "favorited your <a href=\"%(related_path)s\">%(preview_name)s</a>"
#, python-format
msgid "Direct Messages with <a href=\"%(path)s\">%(username)s</a>"
msgstr "Messages directs avec <a href=\"%(path)s\">%(username)s</a>"
#: bookwyrm/templates/feed/direct_messages.html:10
#: bookwyrm/templates/layout.html:79
#, fuzzy
#| msgid "Send direct message"
#: bookwyrm/templates/layout.html:87
msgid "Direct Messages"
msgstr "Messages directs"
@ -555,7 +558,7 @@ msgid "%(username)s's %(year)s Books"
msgstr "Livres de %(username)s en %(year)s"
#: bookwyrm/templates/import.html:5 bookwyrm/templates/import.html:9
#: bookwyrm/templates/layout.html:94
#: bookwyrm/templates/layout.html:102
msgid "Import Books"
msgstr "Importer des livres"
@ -610,37 +613,42 @@ msgstr "(Rechargez la page pour mettre à jour!"
#: bookwyrm/templates/import_status.html:35
msgid "Failed to load"
msgstr "Le chargement a échoué"
msgstr "Items non importés"
#: bookwyrm/templates/import_status.html:59
#: bookwyrm/templates/import_status.html:42
#, python-format
msgid "Jump to the bottom of the list to select the %(failed_count)s items which failed to import."
msgstr "Sauter en bas de liste pour sélectionner les %(failed_count)s items nayant pu être importés."
#: bookwyrm/templates/import_status.html:74
msgid "Select all"
msgstr "Tout sélectionner"
#: bookwyrm/templates/import_status.html:62
#: bookwyrm/templates/import_status.html:79
msgid "Retry items"
msgstr "Essayer dimporter les objets sélectionnés de nouveau"
msgstr "Essayer dimporter les items sélectionnés de nouveau"
#: bookwyrm/templates/import_status.html:84
#: bookwyrm/templates/import_status.html:101
msgid "Successfully imported"
msgstr "Importation réussie"
#: bookwyrm/templates/import_status.html:88
#: bookwyrm/templates/import_status.html:105
#: bookwyrm/templates/lists/curate.html:14
msgid "Book"
msgstr "Livre"
#: bookwyrm/templates/import_status.html:91
#: bookwyrm/templates/import_status.html:108
#: bookwyrm/templates/snippets/create_status_form.html:10
#: bookwyrm/templates/snippets/shelf.html:10
msgid "Title"
msgstr "Titre"
#: bookwyrm/templates/import_status.html:94
#: bookwyrm/templates/import_status.html:111
#: bookwyrm/templates/snippets/shelf.html:11
msgid "Author"
msgstr "Auteur ou autrice"
#: bookwyrm/templates/import_status.html:117
#: bookwyrm/templates/import_status.html:134
msgid "Imported"
msgstr "Importé"
@ -658,8 +666,6 @@ msgid "Sorry! This invite code is no longer valid."
msgstr "Cette invitation nest plus valide; désolé!"
#: bookwyrm/templates/layout.html:33
#, fuzzy
#| msgid "Search for a book"
msgid "Search for a book or user"
msgstr "Chercher un livre ou un compte"
@ -680,59 +686,76 @@ msgstr "Vos étagères"
msgid "Feed"
msgstr "Fil dactualité"
#: bookwyrm/templates/layout.html:84
#: bookwyrm/templates/layout.html:92
#: bookwyrm/templates/preferences/preferences_layout.html:14
msgid "Profile"
msgstr "Profil"
#: bookwyrm/templates/layout.html:89
#: bookwyrm/templates/layout.html:97
#, fuzzy
#| msgid "Instance Settings"
msgid "Settings"
msgstr "Paramètres de linstance"
#: bookwyrm/templates/layout.html:103
#: bookwyrm/templates/layout.html:111
#: bookwyrm/templates/settings/admin_layout.html:19
#: bookwyrm/templates/settings/manage_invites.html:3
msgid "Invites"
msgstr "Invitations"
#: bookwyrm/templates/layout.html:110
#: bookwyrm/templates/layout.html:118
msgid "Site Configuration"
msgstr "Configuration du site"
#: bookwyrm/templates/layout.html:117
#, fuzzy
#| msgid "Log in"
#: bookwyrm/templates/layout.html:125
msgid "Log out"
msgstr "Se déconnecter"
#: bookwyrm/templates/layout.html:125 bookwyrm/templates/layout.html:126
#: bookwyrm/templates/layout.html:133 bookwyrm/templates/layout.html:134
#: bookwyrm/templates/notifications.html:6
#: bookwyrm/templates/notifications.html:10
msgid "Notifications"
msgstr "Notifications"
#: bookwyrm/templates/layout.html:143 bookwyrm/templates/layout.html:147
#: bookwyrm/templates/layout.html:151 bookwyrm/templates/layout.html:155
#: bookwyrm/templates/login.html:17
#: bookwyrm/templates/snippets/register_form.html:4
msgid "Username:"
msgstr "Nom dutilisateur:"
#: bookwyrm/templates/layout.html:152 bookwyrm/templates/login.html:10
#: bookwyrm/templates/layout.html:156
#, fuzzy
#| msgid "Password:"
msgid "password"
msgstr "Mot de passe:"
#: bookwyrm/templates/layout.html:157 bookwyrm/templates/login.html:36
msgid "Forgot your password?"
msgstr "Mot de passe oublié?"
#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:10
#: bookwyrm/templates/login.html:33
msgid "Log in"
msgstr "Se connecter"
#: bookwyrm/templates/layout.html:183
#: bookwyrm/templates/layout.html:168
msgid "Join"
msgstr ""
#: bookwyrm/templates/layout.html:191
msgid "About this server"
msgstr "À propos de ce serveur"
#: bookwyrm/templates/layout.html:187
#: bookwyrm/templates/layout.html:195
msgid "Contact site admin"
msgstr "Contacter ladministrateur du site"
#: bookwyrm/templates/layout.html:198
#: bookwyrm/templates/layout.html:202
#, python-format
msgid "Support %(site.name)s on <a href=\"%(site.support_link)s\" target=\"_blank\">%(site.support_title)s</a>"
msgstr ""
#: bookwyrm/templates/layout.html:206
msgid "BookWyrm is open source software. You can contribute or report issues on <a href=\"https://github.com/mouse-reeve/bookwyrm\">GitHub</a>."
msgstr "Bookwyrm est un logiciel libre. Vous pouvez contribuer ou faire des rapports de bogues via <a href=\"https://github.com/mouse-reeve/bookwyrm\">GitHub</a>."
@ -878,10 +901,6 @@ msgstr "Connexion"
msgid "Password:"
msgstr "Mot de passe:"
#: bookwyrm/templates/login.html:36
msgid "Forgot your password?"
msgstr "Mot de passe oublié?"
#: bookwyrm/templates/login.html:49
msgid "Contact an administrator to get an invite"
msgstr "Contacter un administrateur pour obtenir une invitation"
@ -895,7 +914,7 @@ msgid "Not Found"
msgstr "Introuvable"
#: bookwyrm/templates/notfound.html:9
msgid "The page your requested doesn't seem to exist!"
msgid "The page you requested doesn't seem to exist!"
msgstr "Il semblerait que la page que vous avez demandée nexiste pas!"
#: bookwyrm/templates/notifications.html:14
@ -903,74 +922,62 @@ msgid "Delete notifications"
msgstr "Supprimer les notifications"
#: bookwyrm/templates/notifications.html:49
#, fuzzy, python-format
#| msgid "favorited your <a href=\"%(related_path)s\">%(preview_name)s</a>"
#, python-format
msgid "favorited your <a href=\"%(related_path)s\">review of <em>%(book_title)s</em></a>"
msgstr "a ajouté votre <a href=\"%(related_path)s\">critique de <em>%(book_title)s</em></a> à ses favoris"
#: bookwyrm/templates/notifications.html:51
#, fuzzy, python-format
#| msgid "favorited your <a href=\"%(related_path)s\">%(preview_name)s</a>"
#, python-format
msgid "favorited your <a href=\"%(related_path)s\">comment on <em>%(book_title)s</em></a>"
msgstr "a ajouté votre <a href=\"%(related_path)s\">commentaire sur <em>%(book_title)s</em></a> à ses favoris"
#: bookwyrm/templates/notifications.html:53
#, fuzzy, python-format
#| msgid "favorited your <a href=\"%(related_path)s\">%(preview_name)s</a>"
#, python-format
msgid "favorited your <a href=\"%(related_path)s\">quote from <em>%(book_title)s</em></a>"
msgstr "a ajouté votre <a href=\"%(related_path)s\">citation de <em>%(book_title)s</em></a> à ses favoris"
#: bookwyrm/templates/notifications.html:55
#, fuzzy, python-format
#| msgid "favorited your <a href=\"%(related_path)s\">%(preview_name)s</a>"
#, python-format
msgid "favorited your <a href=\"%(related_path)s\">status</a>"
msgstr "a ajouté votre <a href=\"%(related_path)s\">statut</a> à ses favoris"
#: bookwyrm/templates/notifications.html:60
#, fuzzy, python-format
#| msgid "mentioned you in a <a href=\"%(related_path)s\">%(preview_name)s</a>"
#, python-format
msgid "mentioned you in a <a href=\"%(related_path)s\">review of <em>%(book_title)s</em></a>"
msgstr "vous a mentionné dans sa <a href=\"%(related_path)s\">critique de <em>%(book_title)s</em></a>"
#: bookwyrm/templates/notifications.html:62
#, fuzzy, python-format
#| msgid "mentioned you in a <a href=\"%(related_path)s\">%(preview_name)s</a>"
#, python-format
msgid "mentioned you in a <a href=\"%(related_path)s\">comment on <em>%(book_title)s</em></a>"
msgstr "vous a mentionné dans son <a href=\"%(related_path)s\">commentaire sur <em>%(book_title)s</em></a>"
#: bookwyrm/templates/notifications.html:64
#, fuzzy, python-format
#| msgid "mentioned you in a <a href=\"%(related_path)s\">%(preview_name)s</a>"
#, python-format
msgid "mentioned you in a <a href=\"%(related_path)s\">quote from <em>%(book_title)s</em></a>"
msgstr "vous a mentionné dans sa <a href=\"%(related_path)s\">citation de <em>%(book_title)s</em></a>"
#: bookwyrm/templates/notifications.html:66
#, fuzzy, python-format
#| msgid "mentioned you in a <a href=\"%(related_path)s\">%(preview_name)s</a>"
#, python-format
msgid "mentioned you in a <a href=\"%(related_path)s\">status</a>"
msgstr "vous a mentionné dans son <a href=\"%(related_path)s\">statut</a>"
#: bookwyrm/templates/notifications.html:71
#, fuzzy, python-format
#| msgid "<a href=\"%(related_path)s\">replied</a> to your <a href=\"%(parent_path)s\">%(preview_name)s</a>"
#, python-format
msgid "<a href=\"%(related_path)s\">replied</a> to your <a href=\"%(parent_path)s\">review of <em>%(book_title)s</em></a>"
msgstr "<a href=\"%(related_path)s\">a répondu</a> à votre <a href=\"%(parent_path)s\">critique de <em>%(book_title)s</em></a>"
#: bookwyrm/templates/notifications.html:73
#, fuzzy, python-format
#| msgid "<a href=\"%(related_path)s\">replied</a> to your <a href=\"%(parent_path)s\">%(preview_name)s</a>"
#, python-format
msgid "<a href=\"%(related_path)s\">replied</a> to your <a href=\"%(parent_path)s\">comment on <em>%(book_title)s</em></a>"
msgstr "<a href=\"%(related_path)s\">a répondu</a> à votre <a href=\"%(parent_path)s\">commentaire sur <em>%(book_title)s</em></a>"
#: bookwyrm/templates/notifications.html:75
#, fuzzy, python-format
#| msgid "<a href=\"%(related_path)s\">replied</a> to your <a href=\"%(parent_path)s\">%(preview_name)s</a>"
#, python-format
msgid "<a href=\"%(related_path)s\">replied</a> to your <a href=\"%(parent_path)s\">quote from <em>%(book_title)s</em></a>"
msgstr "<a href=\"%(related_path)s\">a répondu</a> à votre <a href=\"%(parent_path)s\">citation de <em>%(book_title)s</em></a>"
#: bookwyrm/templates/notifications.html:77
#, fuzzy, python-format
#| msgid "<a href=\"%(related_path)s\">replied</a> to your <a href=\"%(parent_path)s\">%(preview_name)s</a>"
#, python-format
msgid "<a href=\"%(related_path)s\">replied</a> to your <a href=\"%(parent_path)s\">status</a>"
msgstr "<a href=\"%(related_path)s\">a répondu</a> à votre <a href=\"%(parent_path)s\">statut</a>"
@ -983,38 +990,32 @@ msgid "sent you a follow request"
msgstr "vous a envoyé une demande dabonnement"
#: bookwyrm/templates/notifications.html:90
#, fuzzy, python-format
#| msgid "boosted your <a href=\"%(related_path)s\">%(preview_name)s</a>"
msgid "boosted your <a href=\"%(related_path)s\">review of <em>%(book.title)s</em></a>"
#, python-format
msgid "boosted your <a href=\"%(related_path)s\">review of <em>%(book_title)s</em></a>"
msgstr "a partagé votre <a href=\"%(related_path)s\">critique de <em>%(book_title)s</em></a>"
#: bookwyrm/templates/notifications.html:92
#, fuzzy, python-format
#| msgid "boosted your <a href=\"%(related_path)s\">%(preview_name)s</a>"
msgid "boosted your <a href=\"%(related_path)s\">comment on<em>%(book.title)s</em></a>"
#, python-format
msgid "boosted your <a href=\"%(related_path)s\">comment on<em>%(book_title)s</em></a>"
msgstr "a partagé votre <a href=\"%(related_path)s\">commentaire sur <em>%(book_title)s</em></a>"
#: bookwyrm/templates/notifications.html:94
#, fuzzy, python-format
#| msgid "boosted your <a href=\"%(related_path)s\">%(preview_name)s</a>"
msgid "boosted your <a href=\"%(related_path)s\">quote from <em>%(book.title)s</em></a>"
#, python-format
msgid "boosted your <a href=\"%(related_path)s\">quote from <em>%(book_title)s</em></a>"
msgstr "a partagé votre <a href=\"%(related_path)s\">citation de <em>%(book_title)s</em></a>"
#: bookwyrm/templates/notifications.html:96
#, fuzzy, python-format
#| msgid "boosted your <a href=\"%(related_path)s\">%(preview_name)s</a>"
#, python-format
msgid "boosted your <a href=\"%(related_path)s\">status</a>"
msgstr "a partagé votre <a href=\"%(related_path)s\">statut</a>"
#: bookwyrm/templates/notifications.html:100
#, fuzzy, python-format
#| msgid "<a href=\"%(related_path)s\">replied</a> to your <a href=\"%(parent_path)s\">%(preview_name)s</a>"
#, python-format
msgid " added <em><a href=\"%(book_path)s\">%(book_title)s</a></em> to your list \"<a href=\"%(list_path)s\">%(list_name)s</a>\""
msgstr " a ajouté <em><a href=\"%(book_path)s\">%(book_title)s</a></em> à votre liste « <a href=\"%(list_path)s\">%(list_name)s</a> »"
#: bookwyrm/templates/notifications.html:102
#, fuzzy, python-format
#| msgid "<a href=\"%(related_path)s\">replied</a> to your <a href=\"%(parent_path)s\">%(preview_name)s</a>"
#, python-format
msgid " suggested adding <em><a href=\"%(book_path)s\">%(book_title)s</a></em> to your list \"<a href=\"%(list_path)s/curate\">%(list_name)s</a>\""
msgstr " a suggégré lajout de <em><a href=\"%(book_path)s\">%(book_title)s</a></em> à votre liste « <a href=\"%(list_path)s/curate\">%(list_name)s</a> »"
@ -1108,8 +1109,6 @@ msgid "Relationships"
msgstr "Relations"
#: bookwyrm/templates/search_results.html:4
#, fuzzy
#| msgid "Search Results for \"%(query)s\""
msgid "Search Results"
msgstr "Résultats de recherche"
@ -1158,8 +1157,6 @@ msgid "No lists found for \"%(query)s\""
msgstr "Aucune liste trouvée pour « %(query)s»"
#: bookwyrm/templates/settings/admin_layout.html:4
#, fuzzy
#| msgid "Registration"
msgid "Administration"
msgstr "Administration"
@ -1313,8 +1310,7 @@ msgid "Un-block"
msgstr "Débloquer"
#: bookwyrm/templates/snippets/book_titleby.html:3
#, fuzzy, python-format
#| msgid "Editions of <a href=\"%(work_path)s\">\"%(work_title)s\"</a>"
#, python-format
msgid "<a href=\"%(path)s\">%(title)s</a> by "
msgstr "<a href=\"%(path)s\">%(title)s</a> par "
@ -1363,8 +1359,6 @@ msgid "No rating"
msgstr "Aucune note"
#: bookwyrm/templates/snippets/create_status_form.html:54
#, fuzzy
#| msgid "Spoiler alert:"
msgid "Include spoiler alert"
msgstr "Afficher une alerte spoiler"
@ -1704,17 +1698,17 @@ msgstr "partagé"
msgid "Reply"
msgstr "Répondre"
#: bookwyrm/templates/snippets/status/status_content.html:16
#: bookwyrm/templates/snippets/trimmed_text.html:12
#: bookwyrm/templates/snippets/status/status_content.html:18
#: bookwyrm/templates/snippets/trimmed_text.html:15
msgid "Show more"
msgstr "Déplier"
#: bookwyrm/templates/snippets/status/status_content.html:23
#: bookwyrm/templates/snippets/trimmed_text.html:18
#: bookwyrm/templates/snippets/status/status_content.html:25
#: bookwyrm/templates/snippets/trimmed_text.html:25
msgid "Show less"
msgstr "Replier"
#: bookwyrm/templates/snippets/status/status_content.html:44
#: bookwyrm/templates/snippets/status/status_content.html:46
msgid "Open image in new window"
msgstr "Ouvrir limage dans une nouvelle fenêtre"
@ -1724,8 +1718,6 @@ msgid "More options"
msgstr "Plus doptions"
#: bookwyrm/templates/snippets/status/status_options.html:17
#, fuzzy
#| msgid "Delete post"
msgid "Delete status"
msgstr "Supprimer le statut"
@ -1749,8 +1741,6 @@ msgstr "Livres tagués « %(tag.name)s»"
#: bookwyrm/templates/user/create_shelf_form.html:5
#: bookwyrm/templates/user/create_shelf_form.html:22
#, fuzzy
#| msgid "Create shelf"
msgid "Create Shelf"
msgstr "Créer létagère"
@ -1764,8 +1754,6 @@ msgstr "Mettre létagère à jour"
#: bookwyrm/templates/user/followers.html:7
#: bookwyrm/templates/user/following.html:7 bookwyrm/templates/user/user.html:9
#, fuzzy
#| msgid "User profile"
msgid "User Profile"
msgstr "Profil"
@ -1784,8 +1772,6 @@ msgid "%(username)s isn't following any users"
msgstr "%(username)s nest abonné(e) à personne"
#: bookwyrm/templates/user/lists.html:9
#, fuzzy
#| msgid "Your lists"
msgid "Your Lists"
msgstr "Vos listes"
@ -1800,14 +1786,11 @@ msgid "Create list"
msgstr "Créer une liste"
#: bookwyrm/templates/user/shelf.html:9
#, fuzzy
#| msgid "Your shelves"
msgid "Your Shelves"
msgstr "Vos étagères"
#: bookwyrm/templates/user/shelf.html:11
#, fuzzy, python-format
#| msgid "%(username)s has no followers"
#, python-format
msgid "%(username)s: Shelves"
msgstr "%(username)s: Étagères"
@ -1816,8 +1799,6 @@ msgid "Create shelf"
msgstr "Créer létagère"
#: bookwyrm/templates/user/shelf.html:54
#, fuzzy
#| msgid "Edit Shelf"
msgid "Edit shelf"
msgstr "Modifier létagère"

Binary file not shown.

View file

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.1.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-03-02 21:36+0000\n"
"POT-Creation-Date: 2021-03-04 22:24+0000\n"
"PO-Revision-Date: 2021-03-02 10:35+0000\n"
"Last-Translator: Kana <gudzpoz@live.com>\n"
"Language-Team: Mouse Reeve <LL@li.org>\n"
@ -55,11 +55,9 @@ msgstr ""
msgid "%(value)s is not a valid username"
msgstr ""
#: bookwyrm/models/fields.py:164
#, fuzzy
#| msgid "Username:"
#: bookwyrm/models/fields.py:164 bookwyrm/templates/layout.html:152
msgid "username"
msgstr "用户名:"
msgstr "用户名"
#: bookwyrm/models/fields.py:169
msgid "A user with that username already exists."
@ -74,10 +72,14 @@ msgid "German"
msgstr ""
#: bookwyrm/settings.py:144
msgid "French"
msgid "Spanish"
msgstr ""
#: bookwyrm/settings.py:145
msgid "French"
msgstr ""
#: bookwyrm/settings.py:146
msgid "Simplified Chinese"
msgstr ""
@ -96,6 +98,8 @@ msgid "Books by %(name)s"
msgstr "%(name)s 所著的书"
#: bookwyrm/templates/book.html:21
#: bookwyrm/templates/discover/large-book.html:12
#: bookwyrm/templates/discover/small-book.html:9
msgid "by"
msgstr ""
@ -427,7 +431,7 @@ msgid "Direct Messages with <a href=\"%(path)s\">%(username)s</a>"
msgstr "与 <a href=\"%(path)s\">%(username)s</a> 私信"
#: bookwyrm/templates/feed/direct_messages.html:10
#: bookwyrm/templates/layout.html:79
#: bookwyrm/templates/layout.html:87
msgid "Direct Messages"
msgstr "私信"
@ -537,7 +541,7 @@ msgid "%(username)s's %(year)s Books"
msgstr "%(username)s 在 %(year)s 的书目"
#: bookwyrm/templates/import.html:5 bookwyrm/templates/import.html:9
#: bookwyrm/templates/layout.html:94
#: bookwyrm/templates/layout.html:102
msgid "Import Books"
msgstr "导入书目"
@ -660,55 +664,72 @@ msgstr "你的书架"
msgid "Feed"
msgstr "动态"
#: bookwyrm/templates/layout.html:84
#: bookwyrm/templates/layout.html:92
#: bookwyrm/templates/preferences/preferences_layout.html:14
msgid "Profile"
msgstr "个人资料"
#: bookwyrm/templates/layout.html:89
#: bookwyrm/templates/layout.html:97
msgid "Settings"
msgstr "设置"
#: bookwyrm/templates/layout.html:103
#: bookwyrm/templates/layout.html:111
#: bookwyrm/templates/settings/admin_layout.html:19
#: bookwyrm/templates/settings/manage_invites.html:3
msgid "Invites"
msgstr "邀请"
#: bookwyrm/templates/layout.html:110
#: bookwyrm/templates/layout.html:118
msgid "Site Configuration"
msgstr "站点配置"
#: bookwyrm/templates/layout.html:117
#: bookwyrm/templates/layout.html:125
msgid "Log out"
msgstr "登出"
#: bookwyrm/templates/layout.html:125 bookwyrm/templates/layout.html:126
#: bookwyrm/templates/layout.html:133 bookwyrm/templates/layout.html:134
#: bookwyrm/templates/notifications.html:6
#: bookwyrm/templates/notifications.html:10
msgid "Notifications"
msgstr "通知"
#: bookwyrm/templates/layout.html:143 bookwyrm/templates/layout.html:147
#: bookwyrm/templates/layout.html:151 bookwyrm/templates/layout.html:155
#: bookwyrm/templates/login.html:17
#: bookwyrm/templates/snippets/register_form.html:4
msgid "Username:"
msgstr "用户名:"
#: bookwyrm/templates/layout.html:152 bookwyrm/templates/login.html:10
#: bookwyrm/templates/layout.html:156
msgid "password"
msgstr "密码"
#: bookwyrm/templates/layout.html:157 bookwyrm/templates/login.html:36
msgid "Forgot your password?"
msgstr "忘记了密码?"
#: bookwyrm/templates/layout.html:160 bookwyrm/templates/login.html:10
#: bookwyrm/templates/login.html:33
msgid "Log in"
msgstr "登录"
#: bookwyrm/templates/layout.html:183
#: bookwyrm/templates/layout.html:168
msgid "Join"
msgstr ""
#: bookwyrm/templates/layout.html:191
msgid "About this server"
msgstr "关于本服务器"
#: bookwyrm/templates/layout.html:187
#: bookwyrm/templates/layout.html:195
msgid "Contact site admin"
msgstr "联系站点管理员"
#: bookwyrm/templates/layout.html:198
#: bookwyrm/templates/layout.html:202
#, python-format
msgid "Support %(site.name)s on <a href=\"%(site.support_link)s\" target=\"_blank\">%(site.support_title)s</a>"
msgstr ""
#: bookwyrm/templates/layout.html:206
msgid "BookWyrm is open source software. You can contribute or report issues on <a href=\"https://github.com/mouse-reeve/bookwyrm\">GitHub</a>."
msgstr "BookWyrm 是开源软件。你可以在<a href=\"https://github.com/mouse-reeve/bookwyrm\">GitHub</a> 贡献或报告问题。"
@ -854,10 +875,6 @@ msgstr "登录"
msgid "Password:"
msgstr "密码:"
#: bookwyrm/templates/login.html:36
msgid "Forgot your password?"
msgstr "忘记了密码?"
#: bookwyrm/templates/login.html:49
msgid "Contact an administrator to get an invite"
msgstr "联系管理员以取得邀请"
@ -871,7 +888,7 @@ msgid "Not Found"
msgstr "未找到"
#: bookwyrm/templates/notfound.html:9
msgid "The page your requested doesn't seem to exist!"
msgid "The page you requested doesn't seem to exist!"
msgstr "你请求的页面似乎并不存在!"
#: bookwyrm/templates/notifications.html:14
@ -948,18 +965,18 @@ msgstr "向你发送了关注请求"
#: bookwyrm/templates/notifications.html:90
#, python-format
msgid "boosted your <a href=\"%(related_path)s\">review of <em>%(book.title)s</em></a>"
msgstr "转发了你的 <a href=\"%(related_path)s\">对 <em>%(book.title)s</em> 的书评</a>"
msgid "boosted your <a href=\"%(related_path)s\">review of <em>%(book_title)s</em></a>"
msgstr "转发了你的 <a href=\"%(related_path)s\">对 <em>%(book_title)s</em> 的书评</a>"
#: bookwyrm/templates/notifications.html:92
#, python-format
msgid "boosted your <a href=\"%(related_path)s\">comment on<em>%(book.title)s</em></a>"
msgstr "转发了你的 <a href=\"%(related_path)s\">对 <em>%(book.title)s</em> 的评论</a>"
msgid "boosted your <a href=\"%(related_path)s\">comment on<em>%(book_title)s</em></a>"
msgstr "转发了你的 <a href=\"%(related_path)s\">对 <em>%(book_title)s</em> 的评论</a>"
#: bookwyrm/templates/notifications.html:94
#, python-format
msgid "boosted your <a href=\"%(related_path)s\">quote from <em>%(book.title)s</em></a>"
msgstr "转发了你的 <a href=\"%(related_path)s\">对 <em>%(book.title)s</em> 的引用</a>"
msgid "boosted your <a href=\"%(related_path)s\">quote from <em>%(book_title)s</em></a>"
msgstr "转发了你的 <a href=\"%(related_path)s\">对 <em>%(book_title)s</em> 的引用</a>"
#: bookwyrm/templates/notifications.html:96
#, python-format
@ -989,7 +1006,6 @@ msgstr "你什么也没错过!"
#: bookwyrm/templates/password_reset.html:10
#: bookwyrm/templates/password_reset_request.html:4
#: bookwyrm/templates/password_reset_request.html:10
#, fuzzy
msgid "Reset Password"
msgstr "重设密码"
@ -1647,17 +1663,17 @@ msgstr "转发了"
msgid "Reply"
msgstr "回复"
#: bookwyrm/templates/snippets/status/status_content.html:16
#: bookwyrm/templates/snippets/trimmed_text.html:12
#: bookwyrm/templates/snippets/status/status_content.html:18
#: bookwyrm/templates/snippets/trimmed_text.html:15
msgid "Show more"
msgstr "显示更多"
#: bookwyrm/templates/snippets/status/status_content.html:23
#: bookwyrm/templates/snippets/trimmed_text.html:18
#: bookwyrm/templates/snippets/status/status_content.html:25
#: bookwyrm/templates/snippets/trimmed_text.html:25
msgid "Show less"
msgstr "显示更少"
#: bookwyrm/templates/snippets/status/status_content.html:44
#: bookwyrm/templates/snippets/status/status_content.html:46
msgid "Open image in new window"
msgstr "在新窗口中打开图像"
@ -1667,8 +1683,6 @@ msgid "More options"
msgstr "更多选项"
#: bookwyrm/templates/snippets/status/status_options.html:17
#, fuzzy
#| msgid "Delete post"
msgid "Delete status"
msgstr "删除发文"
@ -1692,8 +1706,6 @@ msgstr "标有 \"%(tag.name)s\" 标签的书"
#: bookwyrm/templates/user/create_shelf_form.html:5
#: bookwyrm/templates/user/create_shelf_form.html:22
#, fuzzy
#| msgid "Create shelf"
msgid "Create Shelf"
msgstr "创建书架"