mirror of https://gitlab.com/bashrc2/epicyon
				
				
				
			Store book information from incoming bookwyrm notes
							parent
							
								
									db4f77e52d
								
							
						
					
					
						commit
						72b1ab9efe
					
				
							
								
								
									
										9
									
								
								inbox.py
								
								
								
								
							
							
						
						
									
										9
									
								
								inbox.py
								
								
								
								
							| 
						 | 
				
			
			@ -162,6 +162,7 @@ from maps import get_map_links_from_post_content
 | 
			
		|||
from maps import get_location_from_post
 | 
			
		||||
from maps import add_tag_map_links
 | 
			
		||||
from maps import geocoords_from_map_link
 | 
			
		||||
from reading import store_book_events
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def cache_svg_images(session, base_dir: str, http_prefix: str,
 | 
			
		||||
| 
						 | 
				
			
			@ -4759,6 +4760,14 @@ def _inbox_after_initial(server, inbox_start_time,
 | 
			
		|||
                        debug)
 | 
			
		||||
    inbox_start_time = time.time()
 | 
			
		||||
 | 
			
		||||
    # store any bookwyrm type notes
 | 
			
		||||
    store_book_events(base_dir,
 | 
			
		||||
                      message_json,
 | 
			
		||||
                      system_language,
 | 
			
		||||
                      languages_understood,
 | 
			
		||||
                      translate, debug,
 | 
			
		||||
                      1000)
 | 
			
		||||
 | 
			
		||||
    if _receive_announce(recent_posts_cache,
 | 
			
		||||
                         session, handle, is_group,
 | 
			
		||||
                         base_dir, http_prefix,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,294 @@
 | 
			
		|||
__filename__ = "reading.py"
 | 
			
		||||
__author__ = "Bob Mottram"
 | 
			
		||||
__license__ = "AGPL3+"
 | 
			
		||||
__version__ = "1.4.0"
 | 
			
		||||
__maintainer__ = "Bob Mottram"
 | 
			
		||||
__email__ = "bob@libreserver.org"
 | 
			
		||||
__status__ = "Production"
 | 
			
		||||
__module_group__ = "Core"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
from utils import get_content_from_post
 | 
			
		||||
from utils import has_object_dict
 | 
			
		||||
from utils import remove_id_ending
 | 
			
		||||
from utils import get_attributed_to
 | 
			
		||||
from utils import load_json
 | 
			
		||||
from utils import save_json
 | 
			
		||||
from utils import remove_html
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_book_link_from_content(content: str) -> str:
 | 
			
		||||
    """ Returns a book link from the given content
 | 
			
		||||
    """
 | 
			
		||||
    if '/book/' not in content or \
 | 
			
		||||
       '://' not in content or \
 | 
			
		||||
       '"' not in content:
 | 
			
		||||
        return None
 | 
			
		||||
    sections = content.split('/book/')
 | 
			
		||||
    if '"' not in sections[0] or '"' not in sections[1]:
 | 
			
		||||
        return None
 | 
			
		||||
    previous_str = sections[0].split('"')[-1]
 | 
			
		||||
    if '://' not in previous_str:
 | 
			
		||||
        return None
 | 
			
		||||
    next_str = sections[1].split('"')[0]
 | 
			
		||||
    book_url = previous_str + '/book/' + next_str
 | 
			
		||||
    return book_url
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_book_from_post(post_json_object: {}) -> {}:
 | 
			
		||||
    """ Returns a book details from the given post
 | 
			
		||||
    """
 | 
			
		||||
    if 'tag' not in post_json_object:
 | 
			
		||||
        return {}
 | 
			
		||||
    if not isinstance(post_json_object['tag'], list):
 | 
			
		||||
        return {}
 | 
			
		||||
    for tag_dict in post_json_object['tag']:
 | 
			
		||||
        if 'type' not in tag_dict:
 | 
			
		||||
            continue
 | 
			
		||||
        if not isinstance(tag_dict['type'], str):
 | 
			
		||||
            continue
 | 
			
		||||
        if tag_dict['type'] != 'Edition':
 | 
			
		||||
            continue
 | 
			
		||||
        if not tag_dict.get('href'):
 | 
			
		||||
            continue
 | 
			
		||||
        if not isinstance(tag_dict['href'], str):
 | 
			
		||||
            continue
 | 
			
		||||
        if not tag_dict.get('name'):
 | 
			
		||||
            continue
 | 
			
		||||
        if not isinstance(tag_dict['name'], str):
 | 
			
		||||
            continue
 | 
			
		||||
        tag_dict['name'] = tag_dict['name'].replace('@', '')
 | 
			
		||||
        return tag_dict
 | 
			
		||||
    return {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_reading_status(post_json_object: {},
 | 
			
		||||
                       system_language: str,
 | 
			
		||||
                       languages_understood: [],
 | 
			
		||||
                       translate: {}) -> {}:
 | 
			
		||||
    """Returns any reading status from the content of a post
 | 
			
		||||
    """
 | 
			
		||||
    post_obj = post_json_object
 | 
			
		||||
    if has_object_dict(post_json_object):
 | 
			
		||||
        post_obj = post_json_object['object']
 | 
			
		||||
 | 
			
		||||
    content = get_content_from_post(post_json_object, system_language,
 | 
			
		||||
                                    languages_understood,
 | 
			
		||||
                                    "content")
 | 
			
		||||
    if not content:
 | 
			
		||||
        return {}
 | 
			
		||||
    book_url = get_book_link_from_content(content)
 | 
			
		||||
    if not book_url:
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
    if not post_obj.get('id'):
 | 
			
		||||
        return {}
 | 
			
		||||
    if not isinstance(post_obj['id'], str):
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
    # get the published date
 | 
			
		||||
    if not post_obj.get('published'):
 | 
			
		||||
        return {}
 | 
			
		||||
    if not isinstance(post_obj['published'], str):
 | 
			
		||||
        return {}
 | 
			
		||||
    published = post_obj['published']
 | 
			
		||||
    if post_obj.get('updated'):
 | 
			
		||||
        if isinstance(post_obj['updated'], str):
 | 
			
		||||
            published = post_obj['updated']
 | 
			
		||||
 | 
			
		||||
    if not post_obj.get('attributedTo'):
 | 
			
		||||
        return {}
 | 
			
		||||
    actor = get_attributed_to(post_obj['attributedTo'])
 | 
			
		||||
    if not actor:
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
    # rating of a book
 | 
			
		||||
    if post_obj.get('rating'):
 | 
			
		||||
        rating = post_obj['rating']
 | 
			
		||||
        if isinstance(rating, (float, int)):
 | 
			
		||||
            translated_str = 'rated'
 | 
			
		||||
            if translate.get('rated'):
 | 
			
		||||
                translated_str = translate['rated']
 | 
			
		||||
            if translated_str in content or \
 | 
			
		||||
               'rated' in content:
 | 
			
		||||
                return {
 | 
			
		||||
                    'id': remove_id_ending(post_obj['id']),
 | 
			
		||||
                    'actor': actor,
 | 
			
		||||
                    'type': 'rated',
 | 
			
		||||
                    'href': book_url,
 | 
			
		||||
                    'rating': rating,
 | 
			
		||||
                    'published': published
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
    # get the book details from a post tag
 | 
			
		||||
    book_dict = get_book_from_post(post_json_object)
 | 
			
		||||
    if not book_dict:
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
    # want to read a book
 | 
			
		||||
    translated_str = 'wants to read'
 | 
			
		||||
    if translate.get('wants to read'):
 | 
			
		||||
        translated_str = translate['wants to read']
 | 
			
		||||
    if translated_str in content or \
 | 
			
		||||
       'wants to read' in content:
 | 
			
		||||
        book_dict['id'] = remove_id_ending(post_obj['id'])
 | 
			
		||||
        book_dict['actor'] = actor
 | 
			
		||||
        book_dict['type'] = 'want'
 | 
			
		||||
        book_dict['published'] = published
 | 
			
		||||
        return book_dict
 | 
			
		||||
 | 
			
		||||
    translated_str = 'finished reading'
 | 
			
		||||
    if translate.get('finished reading'):
 | 
			
		||||
        translated_str = translate['finished reading']
 | 
			
		||||
    if translated_str in content or \
 | 
			
		||||
       'finished reading' in content:
 | 
			
		||||
        book_dict['id'] = remove_id_ending(post_obj['id'])
 | 
			
		||||
        book_dict['actor'] = actor
 | 
			
		||||
        book_dict['type'] = 'finished'
 | 
			
		||||
        book_dict['published'] = published
 | 
			
		||||
        return book_dict
 | 
			
		||||
 | 
			
		||||
    return {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _add_book_to_reader(reader_books_json: {}, book_dict: {}) -> None:
 | 
			
		||||
    """Updates reader books
 | 
			
		||||
    """
 | 
			
		||||
    book_url = book_dict['href']
 | 
			
		||||
    book_event_type = book_dict['type']
 | 
			
		||||
    if not reader_books_json.get(book_url):
 | 
			
		||||
        reader_books_json[book_url] = {
 | 
			
		||||
            book_event_type: book_dict
 | 
			
		||||
        }
 | 
			
		||||
        return
 | 
			
		||||
    reader_books_json[book_url][book_event_type] = book_dict
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _add_reader_to_book(book_json: {}, book_dict: {}) -> None:
 | 
			
		||||
    """Updates book with a new reader
 | 
			
		||||
    """
 | 
			
		||||
    book_event_type = book_dict['type']
 | 
			
		||||
    actor = book_dict['actor']
 | 
			
		||||
    if not book_json.get(actor):
 | 
			
		||||
        book_json[actor] = {
 | 
			
		||||
            book_event_type: book_dict
 | 
			
		||||
        }
 | 
			
		||||
        if book_dict.get('name'):
 | 
			
		||||
            book_json['title'] = remove_html(book_dict['name'])
 | 
			
		||||
        return
 | 
			
		||||
    book_json[actor][book_event_type] = book_dict
 | 
			
		||||
    if book_dict.get('name'):
 | 
			
		||||
        book_json['title'] = remove_html(book_dict['name'])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def store_book_events(base_dir: str,
 | 
			
		||||
                      post_json_object: {},
 | 
			
		||||
                      system_language: str,
 | 
			
		||||
                      languages_understood: [],
 | 
			
		||||
                      translate: {},
 | 
			
		||||
                      debug: bool,
 | 
			
		||||
                      max_recent_books: int) -> bool:
 | 
			
		||||
    """Saves book events to file under accounts/reading/books
 | 
			
		||||
    and accounts/reading/readers
 | 
			
		||||
    """
 | 
			
		||||
    book_dict = get_reading_status(post_json_object,
 | 
			
		||||
                                   system_language,
 | 
			
		||||
                                   languages_understood,
 | 
			
		||||
                                   translate)
 | 
			
		||||
    if not book_dict:
 | 
			
		||||
        return False
 | 
			
		||||
    reading_path = base_dir + '/accounts/reading'
 | 
			
		||||
    if not os.path.isdir(reading_path):
 | 
			
		||||
        os.mkdir(reading_path)
 | 
			
		||||
    books_path = reading_path + '/books'
 | 
			
		||||
    if not os.path.isdir(books_path):
 | 
			
		||||
        os.mkdir(books_path)
 | 
			
		||||
    readers_path = reading_path + '/readers'
 | 
			
		||||
    if not os.path.isdir(readers_path):
 | 
			
		||||
        os.mkdir(readers_path)
 | 
			
		||||
 | 
			
		||||
    actor = book_dict['actor']
 | 
			
		||||
    book_url = remove_id_ending(book_dict['href'])
 | 
			
		||||
 | 
			
		||||
    reader_books_filename = \
 | 
			
		||||
        readers_path + '/' + actor.replace('/', '#') + '.json'
 | 
			
		||||
    reader_books_json = {}
 | 
			
		||||
    if os.path.isfile(reader_books_filename):
 | 
			
		||||
        reader_books_json = load_json(reader_books_filename)
 | 
			
		||||
    _add_book_to_reader(reader_books_json, book_dict)
 | 
			
		||||
    if not save_json(reader_books_json, reader_books_filename):
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    book_id = book_url.replace('/', '#')
 | 
			
		||||
    book_filename = books_path + '/' + book_id + '.json'
 | 
			
		||||
    book_json = {}
 | 
			
		||||
    if os.path.isfile(book_filename):
 | 
			
		||||
        book_json = load_json(book_filename)
 | 
			
		||||
    _add_reader_to_book(book_json, book_dict)
 | 
			
		||||
    if not save_json(book_json, book_filename):
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    # prepend to the recent books list
 | 
			
		||||
    recent_books_filename = base_dir + '/accounts/recent_books.txt'
 | 
			
		||||
    if os.path.isfile(recent_books_filename):
 | 
			
		||||
        try:
 | 
			
		||||
            with open(recent_books_filename, 'r+',
 | 
			
		||||
                      encoding='utf-8') as recent_file:
 | 
			
		||||
                content = recent_file.read()
 | 
			
		||||
                if book_id + '\n' not in content:
 | 
			
		||||
                    recent_file.seek(0, 0)
 | 
			
		||||
                    recent_file.write(book_id + '\n' + content)
 | 
			
		||||
                    if debug:
 | 
			
		||||
                        print('DEBUG: recent book added')
 | 
			
		||||
        except OSError as ex:
 | 
			
		||||
            print('WARN: Failed to write entry to recent books ' +
 | 
			
		||||
                  recent_books_filename + ' ' + str(ex))
 | 
			
		||||
    else:
 | 
			
		||||
        try:
 | 
			
		||||
            with open(recent_books_filename, 'w+',
 | 
			
		||||
                      encoding='utf-8') as recent_file:
 | 
			
		||||
                recent_file.write(book_id + '\n')
 | 
			
		||||
        except OSError:
 | 
			
		||||
            print('EX: unable to write recent books ' +
 | 
			
		||||
                  recent_books_filename)
 | 
			
		||||
 | 
			
		||||
    # deduplicate and limit the length of the recent books list
 | 
			
		||||
    if os.path.isfile(recent_books_filename):
 | 
			
		||||
        # load recent books as a list
 | 
			
		||||
        recent_lines = []
 | 
			
		||||
        try:
 | 
			
		||||
            with open(recent_books_filename, 'r',
 | 
			
		||||
                      encoding='utf-8') as recent_file:
 | 
			
		||||
                recent_lines = recent_file.read().split('\n')
 | 
			
		||||
        except OSError as ex:
 | 
			
		||||
            print('WARN: Failed to read recent books trim ' +
 | 
			
		||||
                  recent_books_filename + ' ' + str(ex))
 | 
			
		||||
 | 
			
		||||
        # deduplicate the list
 | 
			
		||||
        new_recent_lines = []
 | 
			
		||||
        for line in recent_lines:
 | 
			
		||||
            if line not in new_recent_lines:
 | 
			
		||||
                new_recent_lines.append(line)
 | 
			
		||||
        if len(new_recent_lines) < len(recent_lines):
 | 
			
		||||
            recent_lines = new_recent_lines
 | 
			
		||||
            try:
 | 
			
		||||
                with open(recent_books_filename, 'w+',
 | 
			
		||||
                          encoding='utf-8') as recent_file:
 | 
			
		||||
                    for line in recent_lines:
 | 
			
		||||
                        recent_file.write(line + '\n')
 | 
			
		||||
            except OSError:
 | 
			
		||||
                print('EX: unable to deduplicate recent books ' +
 | 
			
		||||
                      recent_books_filename)
 | 
			
		||||
 | 
			
		||||
        # remove excess lines from the list
 | 
			
		||||
        if len(recent_lines) > max_recent_books:
 | 
			
		||||
            try:
 | 
			
		||||
                with open(recent_books_filename, 'w+',
 | 
			
		||||
                          encoding='utf-8') as recent_file:
 | 
			
		||||
                    for ctr in range(max_recent_books):
 | 
			
		||||
                        recent_file.write(recent_lines[ctr] + '\n')
 | 
			
		||||
            except OSError:
 | 
			
		||||
                print('EX: unable to trim recent books ' +
 | 
			
		||||
                      recent_books_filename)
 | 
			
		||||
    return True
 | 
			
		||||
							
								
								
									
										152
									
								
								tests.py
								
								
								
								
							
							
						
						
									
										152
									
								
								tests.py
								
								
								
								
							| 
						 | 
				
			
			@ -214,6 +214,9 @@ from webapp_theme_designer import color_contrast
 | 
			
		|||
from maps import get_map_links_from_post_content
 | 
			
		||||
from maps import geocoords_from_map_link
 | 
			
		||||
from followerSync import get_followers_sync_hash
 | 
			
		||||
from reading import get_book_link_from_content
 | 
			
		||||
from reading import get_book_from_post
 | 
			
		||||
from reading import get_reading_status
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
TEST_SERVER_GROUP_RUNNING = False
 | 
			
		||||
| 
						 | 
				
			
			@ -8222,6 +8225,154 @@ def _test_dateformat():
 | 
			
		|||
    assert dtime.tzinfo
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _test_book_link():
 | 
			
		||||
    print('book_link')
 | 
			
		||||
    content = 'Not a link'
 | 
			
		||||
    result = get_book_link_from_content(content)
 | 
			
		||||
    assert result is None
 | 
			
		||||
 | 
			
		||||
    book_url = 'https://bookwyrm.instance/book/1234567'
 | 
			
		||||
    content = 'xyz wants to read <a ' + \
 | 
			
		||||
        'href="' + book_url + '"><i>Title</i></a>'
 | 
			
		||||
    result = get_book_link_from_content(content)
 | 
			
		||||
    assert result == book_url
 | 
			
		||||
 | 
			
		||||
    book_url = 'bookwyrm.instance/book/1234567'
 | 
			
		||||
    content = 'xyz wants to read <a ' + \
 | 
			
		||||
        'href="' + book_url + '"><i>Title</i></a>'
 | 
			
		||||
    result = get_book_link_from_content(content)
 | 
			
		||||
    assert result is None
 | 
			
		||||
 | 
			
		||||
    book_url = 'https://bookwyrm.instance/other/1234567'
 | 
			
		||||
    content = 'xyz wants to read <a ' + \
 | 
			
		||||
        'href="' + book_url + '"><i>Title</i></a>'
 | 
			
		||||
    result = get_book_link_from_content(content)
 | 
			
		||||
    assert result is None
 | 
			
		||||
 | 
			
		||||
    title = 'Tedious Tome'
 | 
			
		||||
    image_url = 'https://bookwyrm.instance/images/previews/covers/1234.jpg'
 | 
			
		||||
    book_url = 'https://bookwyrm.instance/book/56789'
 | 
			
		||||
    content = 'xyz wants to read <a href="' + book_url + \
 | 
			
		||||
        '"><i>' + title + '</i></a>'
 | 
			
		||||
    actor = 'https://bookwyrm.instance/user/xyz'
 | 
			
		||||
    id_str = actor + '/generatednote/63472854'
 | 
			
		||||
    published = '2024-01-01T10:30:00.2+00:00'
 | 
			
		||||
    post_json_object = {
 | 
			
		||||
        '@context': 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
        'attachment': [{'@context': 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
                        'name': title,
 | 
			
		||||
                        'type': 'Document',
 | 
			
		||||
                        'url': image_url}],
 | 
			
		||||
        'attributedTo': actor,
 | 
			
		||||
        'cc': [actor + '/followers'],
 | 
			
		||||
        'content': content,
 | 
			
		||||
        'id': id_str,
 | 
			
		||||
        'published': published,
 | 
			
		||||
        'sensitive': False,
 | 
			
		||||
        'tag': [{'href': book_url,
 | 
			
		||||
                 'name': title,
 | 
			
		||||
                 'type': 'Edition'}],
 | 
			
		||||
        'to': ['https://www.w3.org/ns/activitystreams#Public'],
 | 
			
		||||
        'type': 'Note'}
 | 
			
		||||
    languages_understood = []
 | 
			
		||||
    translate = {}
 | 
			
		||||
 | 
			
		||||
    book_dict = get_book_from_post(post_json_object)
 | 
			
		||||
    assert book_dict
 | 
			
		||||
    assert book_dict['name'] == title
 | 
			
		||||
    assert book_dict['href'] == book_url
 | 
			
		||||
 | 
			
		||||
    result = get_reading_status(post_json_object, 'en',
 | 
			
		||||
                                languages_understood,
 | 
			
		||||
                                translate)
 | 
			
		||||
    assert result.get('type')
 | 
			
		||||
    assert result['actor'] == actor
 | 
			
		||||
    assert result['published'] == published
 | 
			
		||||
    assert result['type'] == 'want'
 | 
			
		||||
    assert result['href'] == book_url
 | 
			
		||||
    assert result['name'] == title
 | 
			
		||||
    assert result['id'] == id_str
 | 
			
		||||
 | 
			
		||||
    title = 'The Rise of the Meritocracy'
 | 
			
		||||
    image_url = 'https://bookwyrm.instance/images/previews/covers/6735.jpg'
 | 
			
		||||
    book_url = 'https://bookwyrm.instance/book/7235'
 | 
			
		||||
    content = 'abc finished reading <a href="' + book_url + \
 | 
			
		||||
        '"><i>' + title + '</i></a>'
 | 
			
		||||
    actor = 'https://bookwyrm.instance/user/abc'
 | 
			
		||||
    id_str = actor + '/generatednote/366458384'
 | 
			
		||||
    published = '2024-01-02T11:30:00.2+00:00'
 | 
			
		||||
    post_json_object = {
 | 
			
		||||
        '@context': 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
        'attachment': [{'@context': 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
                        'name': title,
 | 
			
		||||
                        'type': 'Document',
 | 
			
		||||
                        'url': image_url}],
 | 
			
		||||
        'attributedTo': actor,
 | 
			
		||||
        'cc': [actor + '/followers'],
 | 
			
		||||
        'content': content,
 | 
			
		||||
        'id': id_str,
 | 
			
		||||
        'published': published,
 | 
			
		||||
        'sensitive': False,
 | 
			
		||||
        'tag': [{'href': book_url,
 | 
			
		||||
                 'name': title,
 | 
			
		||||
                 'type': 'Edition'}],
 | 
			
		||||
        'to': ['https://www.w3.org/ns/activitystreams#Public'],
 | 
			
		||||
        'type': 'Note'}
 | 
			
		||||
    book_dict = get_book_from_post(post_json_object)
 | 
			
		||||
    assert book_dict
 | 
			
		||||
    assert book_dict['name'] == title
 | 
			
		||||
    assert book_dict['href'] == book_url
 | 
			
		||||
 | 
			
		||||
    result = get_reading_status(post_json_object, 'en',
 | 
			
		||||
                                languages_understood,
 | 
			
		||||
                                translate)
 | 
			
		||||
    assert result.get('type')
 | 
			
		||||
    assert result['actor'] == actor
 | 
			
		||||
    assert result['published'] == published
 | 
			
		||||
    assert result['type'] == 'finished'
 | 
			
		||||
    assert result['href'] == book_url
 | 
			
		||||
    assert result['name'] == title
 | 
			
		||||
    assert result['id'] == id_str
 | 
			
		||||
 | 
			
		||||
    title = 'Pirate Enlightenment, or the Real Libertalia'
 | 
			
		||||
    image_url = 'https://bookwyrm.instance/images/previews/covers/5283.jpg'
 | 
			
		||||
    book_url = 'https://bookwyrm.instance/book/78252'
 | 
			
		||||
    content = 'rated <a href="' + book_url + \
 | 
			
		||||
        '"><i>' + title + '</i></a>'
 | 
			
		||||
    actor = 'https://bookwyrm.instance/user/ghi'
 | 
			
		||||
    rating = 3.5
 | 
			
		||||
    id_str = actor + '/generatednote/73467834576'
 | 
			
		||||
    published = '2024-01-03T12:30:00.2+00:00'
 | 
			
		||||
    post_json_object = {
 | 
			
		||||
        '@context': 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
        'attachment': [{'@context': 'https://www.w3.org/ns/activitystreams',
 | 
			
		||||
                        'name': title,
 | 
			
		||||
                        'type': 'Document',
 | 
			
		||||
                        'url': image_url}],
 | 
			
		||||
        'attributedTo': actor,
 | 
			
		||||
        'cc': [actor + '/followers'],
 | 
			
		||||
        'content': content,
 | 
			
		||||
        'rating': rating,
 | 
			
		||||
        'id': id_str,
 | 
			
		||||
        'published': published,
 | 
			
		||||
        'sensitive': False,
 | 
			
		||||
        'to': ['https://www.w3.org/ns/activitystreams#Public'],
 | 
			
		||||
        'type': 'Note'}
 | 
			
		||||
    book_dict = get_book_from_post(post_json_object)
 | 
			
		||||
    assert not book_dict
 | 
			
		||||
 | 
			
		||||
    result = get_reading_status(post_json_object, 'en',
 | 
			
		||||
                                languages_understood,
 | 
			
		||||
                                translate)
 | 
			
		||||
    assert result.get('type')
 | 
			
		||||
    assert result['actor'] == actor
 | 
			
		||||
    assert result['published'] == published
 | 
			
		||||
    assert result['type'] == 'rated'
 | 
			
		||||
    assert result['href'] == book_url
 | 
			
		||||
    assert result['rating'] == rating
 | 
			
		||||
    assert result['id'] == id_str
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def run_all_tests():
 | 
			
		||||
    base_dir = os.getcwd()
 | 
			
		||||
    print('Running tests...')
 | 
			
		||||
| 
						 | 
				
			
			@ -8239,6 +8390,7 @@ def run_all_tests():
 | 
			
		|||
    _test_checkbox_names()
 | 
			
		||||
    _test_thread_functions()
 | 
			
		||||
    _test_functions()
 | 
			
		||||
    _test_book_link()
 | 
			
		||||
    _test_dateformat()
 | 
			
		||||
    _test_is_right_to_left()
 | 
			
		||||
    _test_format_mixed_rtl()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "التعاضد",
 | 
			
		||||
    "Public replies default to unlisted scope": "الردود العامة افتراضية للنطاق غير المدرج",
 | 
			
		||||
    "About the author": "عن المؤلف",
 | 
			
		||||
    "Do not show follows on your profile": "لا تظهر المتابعات في ملفك الشخصي"
 | 
			
		||||
    "Do not show follows on your profile": "لا تظهر المتابعات في ملفك الشخصي",
 | 
			
		||||
    "rated": "تصنيف",
 | 
			
		||||
    "wants to read": "يريد أن يقرأ",
 | 
			
		||||
    "finished reading": "قراءة الانتهاء"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "পারস্পরিক",
 | 
			
		||||
    "Public replies default to unlisted scope": "অতালিকাভুক্ত সুযোগে সর্বজনীন উত্তর ডিফল্ট",
 | 
			
		||||
    "About the author": "লেখক সম্পর্কে",
 | 
			
		||||
    "Do not show follows on your profile": "আপনার প্রোফাইলে অনুসরণ দেখাবেন না"
 | 
			
		||||
    "Do not show follows on your profile": "আপনার প্রোফাইলে অনুসরণ দেখাবেন না",
 | 
			
		||||
    "rated": "রেট করা",
 | 
			
		||||
    "wants to read": "পড়তে চায়",
 | 
			
		||||
    "finished reading": "পড়া শেষ"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Mútues",
 | 
			
		||||
    "Public replies default to unlisted scope": "Les respostes públiques són per defecte a l'abast no llistat",
 | 
			
		||||
    "About the author": "Sobre l’autor",
 | 
			
		||||
    "Do not show follows on your profile": "No mostris els seguidors al teu perfil"
 | 
			
		||||
    "Do not show follows on your profile": "No mostris els seguidors al teu perfil",
 | 
			
		||||
    "rated": "valorat",
 | 
			
		||||
    "wants to read": "vol llegir",
 | 
			
		||||
    "finished reading": "acabat de llegir"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Cydfuddiol",
 | 
			
		||||
    "Public replies default to unlisted scope": "Ymatebion cyhoeddus rhagosodedig i gwmpas heb ei restru",
 | 
			
		||||
    "About the author": "Am yr awdur",
 | 
			
		||||
    "Do not show follows on your profile": "Peidiwch â dangos dilyniannau ar eich proffil"
 | 
			
		||||
    "Do not show follows on your profile": "Peidiwch â dangos dilyniannau ar eich proffil",
 | 
			
		||||
    "rated": "graddio",
 | 
			
		||||
    "wants to read": "eisiau darllen",
 | 
			
		||||
    "finished reading": "gorffen darllen"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Gegenseitigkeitsgesellschaften",
 | 
			
		||||
    "Public replies default to unlisted scope": "Öffentliche Antworten werden standardmäßig auf den nicht aufgeführten Bereich übertragen",
 | 
			
		||||
    "About the author": "Über den Autor",
 | 
			
		||||
    "Do not show follows on your profile": "Zeigen Sie keine Follower in Ihrem Profil an"
 | 
			
		||||
    "Do not show follows on your profile": "Zeigen Sie keine Follower in Ihrem Profil an",
 | 
			
		||||
    "rated": "bewertet",
 | 
			
		||||
    "wants to read": "will lesen",
 | 
			
		||||
    "finished reading": "fertig gelesen"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Αμοιβαία",
 | 
			
		||||
    "Public replies default to unlisted scope": "Οι δημόσιες απαντήσεις από προεπιλογή στο μη καταχωρισμένο εύρος",
 | 
			
		||||
    "About the author": "Σχετικά με τον Συγγραφέα",
 | 
			
		||||
    "Do not show follows on your profile": "Μην εμφανίζονται οι ακόλουθοι στο προφίλ σας"
 | 
			
		||||
    "Do not show follows on your profile": "Μην εμφανίζονται οι ακόλουθοι στο προφίλ σας",
 | 
			
		||||
    "rated": "Βαθμολογήθηκε",
 | 
			
		||||
    "wants to read": "θέλει να διαβάσει",
 | 
			
		||||
    "finished reading": "τελείωσε την ανάγνωση"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Mutuals",
 | 
			
		||||
    "Public replies default to unlisted scope": "Public replies default to unlisted scope",
 | 
			
		||||
    "About the author": "About the author",
 | 
			
		||||
    "Do not show follows on your profile": "Do not show follows on your profile"
 | 
			
		||||
    "Do not show follows on your profile": "Do not show follows on your profile",
 | 
			
		||||
    "rated": "rated",
 | 
			
		||||
    "wants to read": "wants to read",
 | 
			
		||||
    "finished reading": "finished reading"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Mutuales",
 | 
			
		||||
    "Public replies default to unlisted scope": "Las respuestas públicas tienen por defecto un alcance no listado",
 | 
			
		||||
    "About the author": "Sobre el Autor",
 | 
			
		||||
    "Do not show follows on your profile": "No mostrar seguidores en tu perfil"
 | 
			
		||||
    "Do not show follows on your profile": "No mostrar seguidores en tu perfil",
 | 
			
		||||
    "rated": "clasificado",
 | 
			
		||||
    "wants to read": "quiere leer",
 | 
			
		||||
    "finished reading": "lectura terminada"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "متقابل",
 | 
			
		||||
    "Public replies default to unlisted scope": "پاسخهای عمومی بهطور پیشفرض به محدوده فهرست نشده است",
 | 
			
		||||
    "About the author": "درباره نویسنده",
 | 
			
		||||
    "Do not show follows on your profile": "فالوورها را در نمایه خود نشان ندهید"
 | 
			
		||||
    "Do not show follows on your profile": "فالوورها را در نمایه خود نشان ندهید",
 | 
			
		||||
    "rated": "دارای رتبه",
 | 
			
		||||
    "wants to read": "می خواهد بخواند",
 | 
			
		||||
    "finished reading": "خواندن را تمام کرد"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Mutuelles",
 | 
			
		||||
    "Public replies default to unlisted scope": "Les réponses publiques ont par défaut une portée non répertoriée",
 | 
			
		||||
    "About the author": "A propos de l'auteur",
 | 
			
		||||
    "Do not show follows on your profile": "Ne pas afficher les suivis sur votre profil"
 | 
			
		||||
    "Do not show follows on your profile": "Ne pas afficher les suivis sur votre profil",
 | 
			
		||||
    "rated": "noté",
 | 
			
		||||
    "wants to read": "veut lire",
 | 
			
		||||
    "finished reading": "fini de lire"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Comhpháirteacha",
 | 
			
		||||
    "Public replies default to unlisted scope": "Freagraí poiblí réamhshocraithe ar scóip neamhliostaithe",
 | 
			
		||||
    "About the author": "Faoin tÚdar",
 | 
			
		||||
    "Do not show follows on your profile": "Ná taispeáin na nithe seo a leanas ar do phróifíl"
 | 
			
		||||
    "Do not show follows on your profile": "Ná taispeáin na nithe seo a leanas ar do phróifíl",
 | 
			
		||||
    "rated": "rátáil",
 | 
			
		||||
    "wants to read": "ag iarraidh a léamh",
 | 
			
		||||
    "finished reading": "léamh críochnaithe"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "הדדיות",
 | 
			
		||||
    "Public replies default to unlisted scope": "תשובות ציבוריות כברירת מחדל להיקף לא רשום",
 | 
			
		||||
    "About the author": "על הסופר",
 | 
			
		||||
    "Do not show follows on your profile": "אל תראה עוקבים בפרופיל שלך"
 | 
			
		||||
    "Do not show follows on your profile": "אל תראה עוקבים בפרופיל שלך",
 | 
			
		||||
    "rated": "מדורג",
 | 
			
		||||
    "wants to read": "רוצה לקרוא",
 | 
			
		||||
    "finished reading": "סיים לקרוא"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "पारस्परिक",
 | 
			
		||||
    "Public replies default to unlisted scope": "सार्वजनिक उत्तर डिफ़ॉल्ट रूप से असूचीबद्ध दायरे में आते हैं",
 | 
			
		||||
    "About the author": "लेखक के बारे में",
 | 
			
		||||
    "Do not show follows on your profile": "अपनी प्रोफ़ाइल पर फ़ॉलो न दिखाएं"
 | 
			
		||||
    "Do not show follows on your profile": "अपनी प्रोफ़ाइल पर फ़ॉलो न दिखाएं",
 | 
			
		||||
    "rated": "मूल्यांकन",
 | 
			
		||||
    "wants to read": "पढ़ना चाहता है",
 | 
			
		||||
    "finished reading": "पढ़ना समाप्त"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Mutui",
 | 
			
		||||
    "Public replies default to unlisted scope": "Per impostazione predefinita, le risposte pubbliche hanno un ambito non elencato",
 | 
			
		||||
    "About the author": "Circa l'autore",
 | 
			
		||||
    "Do not show follows on your profile": "Non mostrare follower sul tuo profilo"
 | 
			
		||||
    "Do not show follows on your profile": "Non mostrare follower sul tuo profilo",
 | 
			
		||||
    "rated": "valutato",
 | 
			
		||||
    "wants to read": "vuole leggere",
 | 
			
		||||
    "finished reading": "finito di leggere"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "相互作用",
 | 
			
		||||
    "Public replies default to unlisted scope": "パブリック返信はデフォルトで非公開スコープになります",
 | 
			
		||||
    "About the author": "著者について",
 | 
			
		||||
    "Do not show follows on your profile": "プロフィールにフォローを表示しない"
 | 
			
		||||
    "Do not show follows on your profile": "プロフィールにフォローを表示しない",
 | 
			
		||||
    "rated": "評価された",
 | 
			
		||||
    "wants to read": "読みたい",
 | 
			
		||||
    "finished reading": "読み終わった"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "상호",
 | 
			
		||||
    "Public replies default to unlisted scope": "공개 답글은 기본적으로 비공개 범위로 설정됩니다.",
 | 
			
		||||
    "About the author": "저자에 대해",
 | 
			
		||||
    "Do not show follows on your profile": "프로필에 팔로우를 표시하지 않습니다."
 | 
			
		||||
    "Do not show follows on your profile": "프로필에 팔로우를 표시하지 않습니다.",
 | 
			
		||||
    "rated": "평가됨",
 | 
			
		||||
    "wants to read": "읽고 싶어",
 | 
			
		||||
    "finished reading": "다 읽었다"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Mutuals",
 | 
			
		||||
    "Public replies default to unlisted scope": "Bersivên gelemperî ji bo çarçoveyek nelîstekirî xwerû dide",
 | 
			
		||||
    "About the author": "Di derbarê nivîskarê de",
 | 
			
		||||
    "Do not show follows on your profile": "Li ser profîla xwe şopandinê nîşan nedin"
 | 
			
		||||
    "Do not show follows on your profile": "Li ser profîla xwe şopandinê nîşan nedin",
 | 
			
		||||
    "rated": "nirxandin",
 | 
			
		||||
    "wants to read": "dixwaze bixwîne",
 | 
			
		||||
    "finished reading": "xwendina xwe qedand"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Mutualiteiten",
 | 
			
		||||
    "Public replies default to unlisted scope": "Openbare antwoorden hebben standaard een niet-vermeld bereik",
 | 
			
		||||
    "About the author": "Over de auteur",
 | 
			
		||||
    "Do not show follows on your profile": "Laat geen volgers zien op je profiel"
 | 
			
		||||
    "Do not show follows on your profile": "Laat geen volgers zien op je profiel",
 | 
			
		||||
    "rated": "beoordeeld",
 | 
			
		||||
    "wants to read": "wil lezen",
 | 
			
		||||
    "finished reading": "klaar met lezen"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -638,5 +638,8 @@
 | 
			
		|||
    "Mutuals": "Mutuals",
 | 
			
		||||
    "Public replies default to unlisted scope": "Public replies default to unlisted scope",
 | 
			
		||||
    "About the author": "About the author",
 | 
			
		||||
    "Do not show follows on your profile": "Do not show follows on your profile"
 | 
			
		||||
    "Do not show follows on your profile": "Do not show follows on your profile",
 | 
			
		||||
    "rated": "rated",
 | 
			
		||||
    "wants to read": "wants to read",
 | 
			
		||||
    "finished reading": "finished reading"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Wzajemne relacje",
 | 
			
		||||
    "Public replies default to unlisted scope": "Odpowiedzi publiczne domyślnie mają zakres niepubliczny",
 | 
			
		||||
    "About the author": "O autorze",
 | 
			
		||||
    "Do not show follows on your profile": "Nie pokazuj obserwujących w swoim profilu"
 | 
			
		||||
    "Do not show follows on your profile": "Nie pokazuj obserwujących w swoim profilu",
 | 
			
		||||
    "rated": "ocenione",
 | 
			
		||||
    "wants to read": "chce przeczytać",
 | 
			
		||||
    "finished reading": "skończyłem czytać"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Mútuas",
 | 
			
		||||
    "Public replies default to unlisted scope": "As respostas públicas são padronizadas para escopo não listado",
 | 
			
		||||
    "About the author": "Sobre o autor",
 | 
			
		||||
    "Do not show follows on your profile": "Não mostre seguidores em seu perfil"
 | 
			
		||||
    "Do not show follows on your profile": "Não mostre seguidores em seu perfil",
 | 
			
		||||
    "rated": "avaliada",
 | 
			
		||||
    "wants to read": "quer ler",
 | 
			
		||||
    "finished reading": "terminei de ler"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Взаимные отношения",
 | 
			
		||||
    "Public replies default to unlisted scope": "Публичные ответы по умолчанию имеют скрытую область действия.",
 | 
			
		||||
    "About the author": "Об авторе",
 | 
			
		||||
    "Do not show follows on your profile": "Не показывать подписчиков в своем профиле"
 | 
			
		||||
    "Do not show follows on your profile": "Не показывать подписчиков в своем профиле",
 | 
			
		||||
    "rated": "рейтинг",
 | 
			
		||||
    "wants to read": "хочет прочитать",
 | 
			
		||||
    "finished reading": "закончил читать"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Kuheshimiana",
 | 
			
		||||
    "Public replies default to unlisted scope": "Majibu ya umma kwa chaguomsingi kwa upeo ambao haujaorodheshwa",
 | 
			
		||||
    "About the author": "Kuhusu mwandishi",
 | 
			
		||||
    "Do not show follows on your profile": "Usionyeshe wafuasi kwenye wasifu wako"
 | 
			
		||||
    "Do not show follows on your profile": "Usionyeshe wafuasi kwenye wasifu wako",
 | 
			
		||||
    "rated": "imekadiriwa",
 | 
			
		||||
    "wants to read": "anataka kusoma",
 | 
			
		||||
    "finished reading": "kumaliza kusoma"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Karşılıklar",
 | 
			
		||||
    "Public replies default to unlisted scope": "Genel yanıtlar varsayılan olarak liste dışı kapsama alınır",
 | 
			
		||||
    "About the author": "Yazar hakkında",
 | 
			
		||||
    "Do not show follows on your profile": "Takip edilenleri profilinizde gösterme"
 | 
			
		||||
    "Do not show follows on your profile": "Takip edilenleri profilinizde gösterme",
 | 
			
		||||
    "rated": "oy",
 | 
			
		||||
    "wants to read": "okumak istiyor",
 | 
			
		||||
    "finished reading": "okumayı bitirdim"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "Мутуали",
 | 
			
		||||
    "Public replies default to unlisted scope": "Загальнодоступні відповіді за умовчанням мають приватний обсяг",
 | 
			
		||||
    "About the author": "Про автора",
 | 
			
		||||
    "Do not show follows on your profile": "Не показувати підписки у вашому профілі"
 | 
			
		||||
    "Do not show follows on your profile": "Не показувати підписки у вашому профілі",
 | 
			
		||||
    "rated": "оцінений",
 | 
			
		||||
    "wants to read": "хоче читати",
 | 
			
		||||
    "finished reading": "закінчив читати"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "קעגנצייַטיק",
 | 
			
		||||
    "Public replies default to unlisted scope": "ציבור ענטפֿערס פעליקייַט צו אַנליסטעד פאַרנעם",
 | 
			
		||||
    "About the author": "וועגן דעם מחבר",
 | 
			
		||||
    "Do not show follows on your profile": "דו זאלסט נישט ווייַזן די פאלגענדע אויף דיין פּראָפיל"
 | 
			
		||||
    "Do not show follows on your profile": "דו זאלסט נישט ווייַזן די פאלגענדע אויף דיין פּראָפיל",
 | 
			
		||||
    "rated": "רייטאַד",
 | 
			
		||||
    "wants to read": "וויל לייענען",
 | 
			
		||||
    "finished reading": "פאַרטיק לייענען"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -642,5 +642,8 @@
 | 
			
		|||
    "Mutuals": "互助基金",
 | 
			
		||||
    "Public replies default to unlisted scope": "公开回复默认为不公开范围",
 | 
			
		||||
    "About the author": "关于作者",
 | 
			
		||||
    "Do not show follows on your profile": "不要在您的个人资料上显示关注者"
 | 
			
		||||
    "Do not show follows on your profile": "不要在您的个人资料上显示关注者",
 | 
			
		||||
    "rated": "额定",
 | 
			
		||||
    "wants to read": "想读书",
 | 
			
		||||
    "finished reading": "读完"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue