From 86b779f39b3725ecfdb9b8b6e837a219baab26e9 Mon Sep 17 00:00:00 2001 From: xlivevil Date: Fri, 25 Feb 2022 01:01:12 +0800 Subject: [PATCH 01/13] Add douban metadate provider --- cps/metadata_provider/douban.py | 175 ++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 cps/metadata_provider/douban.py diff --git a/cps/metadata_provider/douban.py b/cps/metadata_provider/douban.py new file mode 100644 index 00000000..5eda21ec --- /dev/null +++ b/cps/metadata_provider/douban.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- + +# This file is part of the Calibre-Web (https://github.com/janeczku/calibre-web) +# Copyright (C) 2022 xlivevil +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +import re +from concurrent import futures +from typing import List, Optional + +import requests +from html2text import HTML2Text +from lxml import etree + +from cps import logger +from cps.services.Metadata import Metadata, MetaRecord, MetaSourceInfo + +log = logger.create() + + +def html2text(html: str) -> str: + + h2t = HTML2Text() + h2t.body_width = 0 + h2t.single_line_break = True + h2t.emphasis_mark = "*" + return h2t.handle(html) + + +class Douban(Metadata): + __name__ = "豆瓣" + __id__ = "douban" + DESCRIPTION = "豆瓣" + META_URL = "https://book.douban.com/" + SEARCH_URL = "https://www.douban.com/j/search" + + ID_PATTERN = re.compile(r"sid: (?P\d+),") + AUTHORS_PATTERN = re.compile(r"作者|译者") + PUBLISHER_PATTERN = re.compile(r"出版社") + SUBTITLE_PATTERN = re.compile(r"副标题") + PUBLISHED_DATE_PATTERN = re.compile(r"出版年") + SERIES_PATTERN = re.compile(r"丛书") + IDENTIFIERS_PATTERN = re.compile(r"ISBN|统一书号") + + TITTLE_XPATH = "//span[@property='v:itemreviewed']" + COVER_XPATH = "//a[@class='nbg']" + INFO_XPATH = "//*[@id='info']//span[@class='pl']" + TAGS_XPATH = "//a[contains(@class, 'tag')]" + DESCRIPTION_XPATH = "//div[@id='link-report']//div[@class='intro']" + RATING_XPATH = "//div[@class='rating_self clearfix']/strong" + + session = requests.Session() + session.headers = { + 'user-agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36 Edg/98.0.1108.56', + } + + def search( + self, query: str, generic_cover: str = "", locale: str = "en" + ) -> Optional[List[MetaRecord]]: + if self.active: + log.debug(f"starting search {query} on douban") + if title_tokens := list( + self.get_title_tokens(query, strip_joiners=False) + ): + query = "+".join(title_tokens) + + try: + r = self.session.get( + self.SEARCH_URL, params={"cat": 1001, "q": query} + ) + r.raise_for_status() + + except Exception as e: + log.warning(e) + return None + + results = r.json() + if results["total"] == 0: + return val + + book_id_list = [ + self.ID_PATTERN.search(item).group("id") + for item in results["items"][:10] if self.ID_PATTERN.search(item) + ] + + with futures.ThreadPoolExecutor(max_workers=5) as executor: + + fut = [ + executor.submit(self._parse_single_book, book_id, generic_cover) + for book_id in book_id_list + ] + + val = [ + future.result() + for future in futures.as_completed(fut) if future.result() + ] + + return val + + def _parse_single_book( + self, id: str, generic_cover: str = "" + ) -> Optional[MetaRecord]: + url = f"https://book.douban.com/subject/{id}/" + + try: + r = self.session.get(url) + r.raise_for_status() + except Exception as e: + log.warning(e) + return None + + match = MetaRecord( + id=id, + title="", + authors=[], + url=url, + source=MetaSourceInfo( + id=self.__id__, + description=self.DESCRIPTION, + link=self.META_URL, + ), + ) + + html = etree.HTML(r.content.decode("utf8")) + + match.title = html.xpath(self.TITTLE_XPATH)[0].text + match.cover = html.xpath(self.COVER_XPATH)[0].attrib["href"] or generic_cover + try: + rating_num = float(html.xpath(self.RATING_XPATH)[0].text.strip()) + except ValueError: + rating_num = 0 + match.rating = int(-1 * rating_num // 2 * -1) if rating_num else 0 + + tag_elements = html.xpath(self.TAGS_XPATH) + if len(tag_elements): + match.tags = [tag_element.text for tag_element in tag_elements] + + description_element = html.xpath(self.DESCRIPTION_XPATH) + if len(description_element): + match.description = html2text(etree.tostring( + description_element[-1], encoding="utf8").decode("utf8")) + + info = html.xpath(self.INFO_XPATH) + + for element in info: + text = element.text + if self.AUTHORS_PATTERN.search(text): + next = element.getnext() + while next is not None and next.tag != "br": + match.authors.append(next.text) + next = next.getnext() + elif self.PUBLISHER_PATTERN.search(text): + match.publisher = element.tail.strip() + elif self.SUBTITLE_PATTERN.search(text): + match.title = f'{match.title}:' + element.tail.strip() + elif self.PUBLISHED_DATE_PATTERN.search(text): + match.publishedDate = element.tail.strip() + elif self.SUBTITLE_PATTERN.search(text): + match.series = element.getnext().text + elif i_type := self.IDENTIFIERS_PATTERN.search(text): + match.identifiers[i_type.group()] = element.tail.strip() + + return match From 695ce836813969aaffb3dac288536cdf7ff3edc9 Mon Sep 17 00:00:00 2001 From: xlivevil Date: Fri, 25 Feb 2022 01:12:22 +0800 Subject: [PATCH 02/13] Fix Uncaught RangeError --- cps/static/js/edit_books.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/static/js/edit_books.js b/cps/static/js/edit_books.js index 0bfe078c..c1eb319d 100644 --- a/cps/static/js/edit_books.js +++ b/cps/static/js/edit_books.js @@ -33,7 +33,7 @@ $(".datepicker").datepicker({ if (results) { pubDate = new Date(results[1], parseInt(results[2], 10) - 1, results[3]) || new Date(this.value); $(this).next('input') - .val(pubDate.toLocaleDateString(language)) + .val(pubDate.toLocaleDateString(language.replaceAll("_","-"))) .removeClass("hidden"); } }).trigger("change"); From 97cf20764bcc5ca37a7d58791d350e962c4edbc8 Mon Sep 17 00:00:00 2001 From: xlivevil Date: Fri, 25 Feb 2022 12:18:07 +0800 Subject: [PATCH 03/13] Add exception handling and logger in metadata provider --- cps/metadata_provider/amazon.py | 33 +++++++++++++++++++-------- cps/metadata_provider/comicvine.py | 16 +++++++++---- cps/metadata_provider/google.py | 10 +++++++- cps/metadata_provider/lubimyczytac.py | 17 ++++++++++++-- cps/metadata_provider/scholar.py | 10 +++++++- cps/search_metadata.py | 2 +- cps/static/js/get_meta.js | 19 +++++++++------ 7 files changed, 81 insertions(+), 26 deletions(-) diff --git a/cps/metadata_provider/amazon.py b/cps/metadata_provider/amazon.py index 558edebc..5c74cf71 100644 --- a/cps/metadata_provider/amazon.py +++ b/cps/metadata_provider/amazon.py @@ -19,15 +19,20 @@ import concurrent.futures import requests from bs4 import BeautifulSoup as BS # requirement +from typing import List, Optional try: import cchardet #optional for better speed except ImportError: pass +from cps import logger from cps.services.Metadata import MetaRecord, MetaSourceInfo, Metadata #from time import time from operator import itemgetter +log = logger.create() + + class Amazon(Metadata): __name__ = "Amazon" __id__ = "amazon" @@ -46,12 +51,16 @@ class Amazon(Metadata): def search( self, query: str, generic_cover: str = "", locale: str = "en" - ): + ) -> Optional[List[MetaRecord]]: #timer=time() - def inner(link,index)->[dict,int]: - with self.session as session: - r = session.get(f"https://www.amazon.com/{link}") - r.raise_for_status() + def inner(link,index) -> tuple[dict,int]: + with self.session as session: + try: + r = session.get(f"https://www.amazon.com/{link}") + r.raise_for_status() + except Exception as e: + log.warning(e) + return long_soup = BS(r.text, "lxml") #~4sec :/ soup2 = long_soup.find("div", attrs={"cel_widget_id": "dpx-books-ppd_csm_instrumentation_wrapper"}) if soup2 is None: @@ -107,11 +116,15 @@ class Amazon(Metadata): val = list() if self.active: - results = self.session.get( - f"https://www.amazon.com/s?k={query.replace(' ', '+')}&i=digital-text&sprefix={query.replace(' ', '+')}" - f"%2Cdigital-text&ref=nb_sb_noss", - headers=self.headers) - results.raise_for_status() + try: + results = self.session.get( + f"https://www.amazon.com/s?k={query.replace(' ', '+')}&i=digital-text&sprefix={query.replace(' ', '+')}" + f"%2Cdigital-text&ref=nb_sb_noss", + headers=self.headers) + results.raise_for_status() + except Exception as e: + log.warning(e) + return None soup = BS(results.text, 'html.parser') links_list = [next(filter(lambda i: "digital-text" in i["href"], x.findAll("a")))["href"] for x in soup.findAll("div", attrs={"data-component-type": "s-search-result"})] diff --git a/cps/metadata_provider/comicvine.py b/cps/metadata_provider/comicvine.py index 56618d4b..b4d8d34c 100644 --- a/cps/metadata_provider/comicvine.py +++ b/cps/metadata_provider/comicvine.py @@ -21,8 +21,11 @@ from typing import Dict, List, Optional from urllib.parse import quote import requests +from cps import logger from cps.services.Metadata import MetaRecord, MetaSourceInfo, Metadata +log = logger.create() + class ComicVine(Metadata): __name__ = "ComicVine" @@ -46,10 +49,15 @@ class ComicVine(Metadata): if title_tokens: tokens = [quote(t.encode("utf-8")) for t in title_tokens] query = "%20".join(tokens) - result = requests.get( - f"{ComicVine.BASE_URL}{query}{ComicVine.QUERY_PARAMS}", - headers=ComicVine.HEADERS, - ) + try: + result = requests.get( + f"{ComicVine.BASE_URL}{query}{ComicVine.QUERY_PARAMS}", + headers=ComicVine.HEADERS, + ) + result.raise_for_status() + except Exception as e: + log.warning(e) + return None for result in result.json()["results"]: match = self._parse_search_result( result=result, generic_cover=generic_cover, locale=locale diff --git a/cps/metadata_provider/google.py b/cps/metadata_provider/google.py index fbb68965..98fadd37 100644 --- a/cps/metadata_provider/google.py +++ b/cps/metadata_provider/google.py @@ -22,9 +22,12 @@ from urllib.parse import quote import requests +from cps import logger from cps.isoLanguages import get_lang3, get_language_name from cps.services.Metadata import MetaRecord, MetaSourceInfo, Metadata +log = logger.create() + class Google(Metadata): __name__ = "Google" @@ -45,7 +48,12 @@ class Google(Metadata): if title_tokens: tokens = [quote(t.encode("utf-8")) for t in title_tokens] query = "+".join(tokens) - results = requests.get(Google.SEARCH_URL + query) + try: + results = requests.get(Google.SEARCH_URL + query) + results.raise_for_status() + except Exception as e: + log.warning(e) + return None for result in results.json().get("items", []): val.append( self._parse_search_result( diff --git a/cps/metadata_provider/lubimyczytac.py b/cps/metadata_provider/lubimyczytac.py index 814a785e..e4abe9db 100644 --- a/cps/metadata_provider/lubimyczytac.py +++ b/cps/metadata_provider/lubimyczytac.py @@ -27,9 +27,12 @@ from html2text import HTML2Text from lxml.html import HtmlElement, fromstring, tostring from markdown2 import Markdown +from cps import logger from cps.isoLanguages import get_language_name from cps.services.Metadata import MetaRecord, MetaSourceInfo, Metadata +log = logger.create() + SYMBOLS_TO_TRANSLATE = ( "öÖüÜóÓőŐúÚéÉáÁűŰíÍąĄćĆęĘłŁńŃóÓśŚźŹżŻ", "oOuUoOoOuUeEaAuUiIaAcCeElLnNoOsSzZzZ", @@ -112,7 +115,12 @@ class LubimyCzytac(Metadata): self, query: str, generic_cover: str = "", locale: str = "en" ) -> Optional[List[MetaRecord]]: if self.active: - result = requests.get(self._prepare_query(title=query)) + try: + result = requests.get(self._prepare_query(title=query)) + result.raise_for_status() + except Exception as e: + log.warning(e) + return None root = fromstring(result.text) lc_parser = LubimyCzytacParser(root=root, metadata=self) matches = lc_parser.parse_search_results() @@ -200,7 +208,12 @@ class LubimyCzytacParser: def parse_single_book( self, match: MetaRecord, generic_cover: str, locale: str ) -> MetaRecord: - response = requests.get(match.url) + try: + response = requests.get(match.url) + response.raise_for_status() + except Exception as e: + log.warning(e) + return None self.root = fromstring(response.text) match.cover = self._parse_cover(generic_cover=generic_cover) match.description = self._parse_description() diff --git a/cps/metadata_provider/scholar.py b/cps/metadata_provider/scholar.py index b0c10b66..7feb0ee9 100644 --- a/cps/metadata_provider/scholar.py +++ b/cps/metadata_provider/scholar.py @@ -28,8 +28,12 @@ try: except FakeUserAgentError: raise ImportError("No module named 'scholarly'") +from cps import logger from cps.services.Metadata import MetaRecord, MetaSourceInfo, Metadata +log = logger.create() + + class scholar(Metadata): __name__ = "Google Scholar" __id__ = "googlescholar" @@ -44,7 +48,11 @@ class scholar(Metadata): if title_tokens: tokens = [quote(t.encode("utf-8")) for t in title_tokens] query = " ".join(tokens) - scholar_gen = itertools.islice(scholarly.search_pubs(query), 10) + try: + scholar_gen = itertools.islice(scholarly.search_pubs(query), 10) + except Exception as e: + log.warning(e) + return None for result in scholar_gen: match = self._parse_search_result( result=result, generic_cover="", locale=locale diff --git a/cps/search_metadata.py b/cps/search_metadata.py index d72273f6..d02667d5 100644 --- a/cps/search_metadata.py +++ b/cps/search_metadata.py @@ -130,6 +130,6 @@ def metadata_search(): if active.get(c.__id__, True) } for future in concurrent.futures.as_completed(meta): - data.extend([asdict(x) for x in future.result()]) + data.extend([asdict(x) for x in future.result() if x]) # log.info({'Time elapsed {}'.format(current_milli_time()-start)}) return Response(json.dumps(data), mimetype="application/json") diff --git a/cps/static/js/get_meta.js b/cps/static/js/get_meta.js index 6db1a261..43a40fa6 100644 --- a/cps/static/js/get_meta.js +++ b/cps/static/js/get_meta.js @@ -92,14 +92,19 @@ $(function () { data: {"query": keyword}, dataType: "json", success: function success(data) { - $("#meta-info").html("
    "); - data.forEach(function(book) { - var $book = $(templates.bookResult(book)); - $book.find("img").on("click", function () { - populateForm(book); + if (data.length) { + $("#meta-info").html("
      "); + data.forEach(function(book) { + var $book = $(templates.bookResult(book)); + $book.find("img").on("click", function () { + populateForm(book); + }); + $("#book-list").append($book); }); - $("#book-list").append($book); - }); + } + else { + $("#meta-info").html("

      " + msg.no_result + "!

      " + $("#meta-info")[0].innerHTML) + } }, error: function error() { $("#meta-info").html("

      " + msg.search_error + "!

      " + $("#meta-info")[0].innerHTML); From b54a170a00f3b5ecf4d4e2888db7f092522ba552 Mon Sep 17 00:00:00 2001 From: xlivevil Date: Sat, 12 Mar 2022 13:54:37 +0800 Subject: [PATCH 04/13] Add clean_date method in douban metadata_provider --- cps/metadata_provider/douban.py | 37 ++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/cps/metadata_provider/douban.py b/cps/metadata_provider/douban.py index 5eda21ec..ee21f587 100644 --- a/cps/metadata_provider/douban.py +++ b/cps/metadata_provider/douban.py @@ -88,7 +88,7 @@ class Douban(Metadata): results = r.json() if results["total"] == 0: - return val + return [] book_id_list = [ self.ID_PATTERN.search(item).group("id") @@ -139,7 +139,7 @@ class Douban(Metadata): match.cover = html.xpath(self.COVER_XPATH)[0].attrib["href"] or generic_cover try: rating_num = float(html.xpath(self.RATING_XPATH)[0].text.strip()) - except ValueError: + except Exception: rating_num = 0 match.rating = int(-1 * rating_num // 2 * -1) if rating_num else 0 @@ -166,10 +166,41 @@ class Douban(Metadata): elif self.SUBTITLE_PATTERN.search(text): match.title = f'{match.title}:' + element.tail.strip() elif self.PUBLISHED_DATE_PATTERN.search(text): - match.publishedDate = element.tail.strip() + match.publishedDate = self._clean_date(element.tail.strip()) elif self.SUBTITLE_PATTERN.search(text): match.series = element.getnext().text elif i_type := self.IDENTIFIERS_PATTERN.search(text): match.identifiers[i_type.group()] = element.tail.strip() return match + + + def _clean_date(self, date: str) -> str: + """ + Clean up the date string to be in the format YYYY-MM-DD + + Examples of possible patterns: + '2014-7-16', '1988年4月', '1995-04', '2021-8', '2020-12-1', '1996年', + '1972', '2004/11/01', '1959年3月北京第1版第1印' + """ + year = date[:4] + moon = "01" + day = "01" + + if len(date) > 5: + digit = [] + ls = [] + for i in range(5, len(date)): + if date[i].isdigit(): + digit.append(date[i]) + elif digit: + ls.append("".join(digit) if len(digit)==2 else f"0{digit[0]}") + digit = [] + if digit: + ls.append("".join(digit) if len(digit)==2 else f"0{digit[0]}") + + moon = ls[0] + if len(ls)>1: + day = ls[1] + + return f"{year}-{moon}-{day}" From 2d0af0ab496b84c97e1564d97eb5415f9ec6236b Mon Sep 17 00:00:00 2001 From: Wulf Rajek <40003252+AnonTester@users.noreply.github.com> Date: Tue, 5 Apr 2022 01:26:35 +0100 Subject: [PATCH 05/13] Add pubdate, publisher and identifiers metadata #2163 --- cps/comic.py | 8 +++++-- cps/constants.py | 2 +- cps/editbooks.py | 17 ++++++++++++++- cps/epub.py | 22 ++++++++++++++++++-- cps/fb2.py | 4 +++- cps/uploader.py | 54 +++++++----------------------------------------- 6 files changed, 53 insertions(+), 54 deletions(-) diff --git a/cps/comic.py b/cps/comic.py index 2549579e..8f3a6f61 100644 --- a/cps/comic.py +++ b/cps/comic.py @@ -130,7 +130,9 @@ def get_comic_info(tmp_file_path, original_file_name, original_file_extension, r series=loaded_metadata.series or "", series_id=loaded_metadata.issue or "", languages=loaded_metadata.language, - publisher="") + publisher="", + pubdate="", + identifiers=[]) return BookMeta( file_path=tmp_file_path, @@ -143,4 +145,6 @@ def get_comic_info(tmp_file_path, original_file_name, original_file_extension, r series="", series_id="", languages="", - publisher="") + publisher="", + pubdate="", + identifiers=[]) diff --git a/cps/constants.py b/cps/constants.py index f40d16b0..762336dd 100644 --- a/cps/constants.py +++ b/cps/constants.py @@ -152,7 +152,7 @@ def selected_roles(dictionary): # :rtype: BookMeta BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, ' - 'series_id, languages, publisher') + 'series_id, languages, publisher, pubdate, identifiers') STABLE_VERSION = {'version': '0.6.19 Beta'} diff --git a/cps/editbooks.py b/cps/editbooks.py index c07e5d24..db87d891 100755 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -983,8 +983,13 @@ def create_book_on_upload(modify_date, meta): # combine path and normalize path from Windows systems path = os.path.join(author_dir, title_dir).replace('\\', '/') + if meta.pubdate != "": + pubdate = datetime.strptime(meta.pubdate[:10], "%Y-%m-%d") + else: + pubdate = datetime(101, 1, 1) + # Calibre adds books with utc as timezone - db_book = db.Books(title, "", sort_authors, datetime.utcnow(), datetime(101, 1, 1), + db_book = db.Books(title, "", sort_authors, datetime.utcnow(), pubdate, '1', datetime.utcnow(), path, meta.cover, db_author, [], "") modify_date |= modify_database_object(input_authors, db_book.authors, db.Authors, calibre_db.session, @@ -1017,6 +1022,16 @@ def create_book_on_upload(modify_date, meta): # flush content, get db_book.id available calibre_db.session.flush() + + # Handle identifiers now that db_book.id is available + identifier_list = [] + for type_key, type_value in meta.identifiers: + identifier_list.append(db.Identifiers(type_value, type_key, db_book.id)) + modification, warning = modify_identifiers(identifier_list, db_book.identifiers, calibre_db.session) + if warning: + flash(_("Identifiers are not Case Sensitive, Overwriting Old Identifier"), category="warning") + modify_date |= modification + return db_book, input_authors, title_dir, renamed_authors diff --git a/cps/epub.py b/cps/epub.py index 80c12c35..d358d038 100644 --- a/cps/epub.py +++ b/cps/epub.py @@ -63,13 +63,15 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): epub_metadata = {} - for s in ['title', 'description', 'creator', 'language', 'subject']: + for s in ['title', 'description', 'creator', 'language', 'subject', 'publisher', 'date']: tmp = p.xpath('dc:%s/text()' % s, namespaces=ns) if len(tmp) > 0: if s == 'creator': epub_metadata[s] = ' & '.join(split_authors(tmp)) elif s == 'subject': epub_metadata[s] = ', '.join(tmp) + elif s == 'date': + epub_metadata[s] = tmp[0][:10] else: epub_metadata[s] = tmp[0] else: @@ -78,6 +80,12 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): if epub_metadata['subject'] == 'Unknown': epub_metadata['subject'] = '' + if epub_metadata['publisher'] == u'Unknown': + epub_metadata['publisher'] = '' + + if epub_metadata['date'] == u'Unknown': + epub_metadata['date'] = '' + if epub_metadata['description'] == u'Unknown': description = tree.xpath("//*[local-name() = 'description']/text()") if len(description) > 0: @@ -92,6 +100,14 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): cover_file = parse_epub_cover(ns, tree, epub_zip, cover_path, tmp_file_path) + identifiers = [] + for node in p.xpath('dc:identifier', namespaces=ns): + identifier_name=node.attrib.values()[-1]; + identifier_value=node.text; + if identifier_name in ('uuid','calibre'): + continue; + identifiers.append( [identifier_name, identifier_value] ) + if not epub_metadata['title']: title = original_file_name else: @@ -108,7 +124,9 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension): series=epub_metadata['series'].encode('utf-8').decode('utf-8'), series_id=epub_metadata['series_id'].encode('utf-8').decode('utf-8'), languages=epub_metadata['language'], - publisher="") + publisher=epub_metadata['publisher'].encode('utf-8').decode('utf-8'), + pubdate=epub_metadata['date'], + identifiers=identifiers) def parse_epub_cover(ns, tree, epub_zip, cover_path, tmp_file_path): diff --git a/cps/fb2.py b/cps/fb2.py index 21586736..c4b89fd6 100644 --- a/cps/fb2.py +++ b/cps/fb2.py @@ -77,4 +77,6 @@ def get_fb2_info(tmp_file_path, original_file_extension): series="", series_id="", languages="", - publisher="") + publisher="", + pubdate="", + identifiers=[]) diff --git a/cps/uploader.py b/cps/uploader.py index 992d188c..7a0359b3 100644 --- a/cps/uploader.py +++ b/cps/uploader.py @@ -107,52 +107,10 @@ def default_meta(tmp_file_path, original_file_name, original_file_extension): series="", series_id="", languages="", - publisher="") - - -def parse_xmp(pdf_file): - """ - Parse XMP Metadata and prepare for BookMeta object - """ - try: - xmp_info = pdf_file.getXmpMetadata() - except Exception as ex: - log.debug('Can not read XMP metadata {}'.format(ex)) - return None - - if xmp_info: - try: - xmp_author = xmp_info.dc_creator # list - except AttributeError: - xmp_author = [''] - - if xmp_info.dc_title: - xmp_title = xmp_info.dc_title['x-default'] - else: - xmp_title = '' - - if xmp_info.dc_description: - xmp_description = xmp_info.dc_description['x-default'] - else: - xmp_description = '' - - languages = [] - try: - for i in xmp_info.dc_language: - #calibre-web currently only takes one language. - languages.append(isoLanguages.get_lang3(i)) - except AttributeError: - languages.append('') - - xmp_tags = ', '.join(xmp_info.dc_subject) - xmp_publisher = ', '.join(xmp_info.dc_publisher) - - return {'author': xmp_author, - 'title': xmp_title, - 'subject': xmp_description, - 'tags': xmp_tags, 'languages': languages, - 'publisher': xmp_publisher - } + publisher="", + pubdate="", + identifiers=[] + ) def parse_xmp(pdf_file): @@ -251,7 +209,9 @@ def pdf_meta(tmp_file_path, original_file_name, original_file_extension): series="", series_id="", languages=','.join(languages), - publisher=publisher) + publisher=publisher, + pubdate="", + identifiers=[]) def pdf_preview(tmp_file_path, tmp_dir): From adf6728f142378d1b3b2c81acae21d02858d6de3 Mon Sep 17 00:00:00 2001 From: Wulf Rajek <40003252+AnonTester@users.noreply.github.com> Date: Tue, 12 Apr 2022 00:22:05 +0100 Subject: [PATCH 06/13] Gracefully deal with incorrect dates --- cps/editbooks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cps/editbooks.py b/cps/editbooks.py index db87d891..b14f79e0 100755 --- a/cps/editbooks.py +++ b/cps/editbooks.py @@ -983,9 +983,9 @@ def create_book_on_upload(modify_date, meta): # combine path and normalize path from Windows systems path = os.path.join(author_dir, title_dir).replace('\\', '/') - if meta.pubdate != "": + try: pubdate = datetime.strptime(meta.pubdate[:10], "%Y-%m-%d") - else: + except: pubdate = datetime(101, 1, 1) # Calibre adds books with utc as timezone From 6184e2b7bc79b4b1a5fcd4251c73c64d522db18d Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Thu, 14 Apr 2022 19:58:15 +0200 Subject: [PATCH 07/13] Updated cve numbers --- SECURITY.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 78d5c6e2..1b93b5f9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -24,20 +24,20 @@ To receive fixes for security vulnerabilities it is required to always upgrade t | V 0.6.13 | JavaScript could get executed in the shelf title || | V 0.6.13 | Login with the old session cookie after logout. Thanks to @ibarrionuevo || | V 0.6.14 | CSRF was possible. Thanks to @mik317 and Hagai Wechsler (WhiteSource) |CVE-2021-25965| -| V 0.6.14 | Migrated some routes to POST-requests (CSRF protection). Thanks to @scara31 || -| V 0.6.15 | Fix for "javascript:" script links in identifier. Thanks to @scara31 || +| V 0.6.14 | Migrated some routes to POST-requests (CSRF protection). Thanks to @scara31 |CVE-2021-4164| +| V 0.6.15 | Fix for "javascript:" script links in identifier. Thanks to @scara31 |CVE-2021-4170| | V 0.6.15 | Cross-Site Scripting vulnerability on uploaded cover file names. Thanks to @ibarrionuevo || | V 0.6.15 | Creating public shelfs is now denied if user is missing the edit public shelf right. Thanks to @ibarrionuevo || | V 0.6.15 | Changed error message in case of trying to delete a shelf unauthorized. Thanks to @ibarrionuevo || -| V 0.6.16 | JavaScript could get executed on authors page. Thanks to @alicaz || -| V 0.6.16 | Localhost can no longer be used to upload covers. Thanks to @scara31 || -| V 0.6.16 | Another case where public shelfs could be created without permission is prevented. Thanks to @nhiephon || -| V 0.6.16 | It's prevented to get the name of a private shelfs. Thanks to @nhiephon || -| V 0.6.17 | The SSRF Protection can no longer be bypassed via an HTTP redirect. Thanks to @416e6e61 || -| V 0.6.17 | The SSRF Protection can no longer be bypassed via 0.0.0.0 and it's ipv6 equivalent. Thanks to @r0hanSH || +| V 0.6.16 | JavaScript could get executed on authors page. Thanks to @alicaz |CVE-2022-0352| +| V 0.6.16 | Localhost can no longer be used to upload covers. Thanks to @scara31 |CVE-2022-0339| +| V 0.6.16 | Another case where public shelfs could be created without permission is prevented. Thanks to @nhiephon |CVE-2022-0273| +| V 0.6.16 | It's prevented to get the name of a private shelfs. Thanks to @nhiephon |CVE-2022-0405| +| V 0.6.17 | The SSRF Protection can no longer be bypassed via an HTTP redirect. Thanks to @416e6e61 |CVE-2022-0767| +| V 0.6.17 | The SSRF Protection can no longer be bypassed via 0.0.0.0 and it's ipv6 equivalent. Thanks to @r0hanSH |CVE-2022-0766| | V 0.6.18 | Possible SQL Injection is prevented in user table Thanks to Iman Sharafaldin (Forward Security) || -| V 0.6.18 | The SSRF protection no longer can be bypassed by IPV6/IPV4 embedding. Thanks to @416e6e61 || -| V 0.6.18 | The SSRF protection no longer can be bypassed to connect to other servers in the local network. Thanks to @michaellrowley || +| V 0.6.18 | The SSRF protection no longer can be bypassed by IPV6/IPV4 embedding. Thanks to @416e6e61 |CVE-2022-0939| +| V 0.6.18 | The SSRF protection no longer can be bypassed to connect to other servers in the local network. Thanks to @michaellrowley |CVE-2022-0990| ## Statement regarding Log4j (CVE-2021-44228 and related) From 09b381101b8975b22df304e8d5d6069d66bb9784 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sat, 16 Apr 2022 17:01:41 +0200 Subject: [PATCH 08/13] Added "None" to list of file formats, tags, series, languages Unified languages.html and list.html template --- cps/db.py | 31 +++++- cps/templates/languages.html | 35 ------- cps/templates/list.html | 4 +- cps/web.py | 180 +++++++++++++++++++++++++++-------- 4 files changed, 169 insertions(+), 81 deletions(-) delete mode 100644 cps/templates/languages.html diff --git a/cps/db.py b/cps/db.py index d2977e07..ff392274 100644 --- a/cps/db.py +++ b/cps/db.py @@ -903,9 +903,19 @@ class CalibreDB: .join(books_languages_link).join(Books)\ .filter(self.common_filters(return_all_languages=return_all_languages)) \ .group_by(text('books_languages_link.lang_code')).all() + tags = list() for lang in languages: - lang[0].name = isoLanguages.get_language_name(get_locale(), lang[0].lang_code) - return sorted(languages, key=lambda x: x[0].name, reverse=reverse_order) + tag = Category(isoLanguages.get_language_name(get_locale(), lang[0].lang_code), lang[0].lang_code) + tags.append([tag, lang[1]]) + # Append all books without language to list + if not return_all_languages: + no_lang_count = (self.session.query(Books) + .outerjoin(books_languages_link).outerjoin(Languages) + .filter(Languages.lang_code == None) + .filter(self.common_filters()) + .count()) + tags.append([Category(_("None"), "none"), no_lang_count]) + return sorted(tags, key=lambda x: x[0].name, reverse=reverse_order) else: if not languages: languages = self.session.query(Languages) \ @@ -977,3 +987,20 @@ def lcase(s): _log = logger.create() _log.error_or_exception(ex) return s.lower() + + +class Category: + name = None + id = None + count = None + + def __init__(self, name, cat_id): + self.name = name + self.id = cat_id + self.count = 1 + +'''class Count: + count = None + + def __init__(self, count): + self.count = count''' diff --git a/cps/templates/languages.html b/cps/templates/languages.html deleted file mode 100644 index c83398d5..00000000 --- a/cps/templates/languages.html +++ /dev/null @@ -1,35 +0,0 @@ -{% extends "layout.html" %} -{% block body %} -

      {{title}}

      - -
      -
      - {% for lang in languages %} - {% if loop.index0 == (loop.length/2)|int and loop.length > 20 %} -
      -
      - {% endif %} -
      -
      {{lang[1]}}
      - -
      - {% endfor %} -
      -
      -{% endblock %} -{% block js %} - -{% endblock %} - diff --git a/cps/templates/list.html b/cps/templates/list.html index 71dbea11..ed59661e 100644 --- a/cps/templates/list.html +++ b/cps/templates/list.html @@ -14,7 +14,7 @@ {% endif %}
      {% for char in charlist%} -
      {{char.char}}
      +
      {{char[0]}}
      {% endfor %}
      @@ -30,7 +30,7 @@
      {% endif %}
      -
      {{entry.count}}
      +
      {{entry[1]}}
      {% if entry.name %}
      diff --git a/cps/web.py b/cps/web.py index e6ca4d03..ae4b12a8 100644 --- a/cps/web.py +++ b/cps/web.py @@ -307,10 +307,20 @@ def get_matching_tags(): return json_dumps -def generate_char_list(data_colum, db_link): - return (calibre_db.session.query(func.upper(func.substr(data_colum, 1, 1)).label('char')) +def generate_char_list(entries): # data_colum, db_link): + char_list = list() + for entry in entries: + upper_char = entry[0].name[0].upper() + if upper_char not in char_list: + char_list.append(upper_char) + return char_list + + +def query_char_list(data_colum, db_link): + results = (calibre_db.session.query(func.upper(func.substr(data_colum, 1, 1)).label('char')) .join(db_link).join(db.Books).filter(calibre_db.common_filters()) .group_by(func.upper(func.substr(data_colum, 1, 1))).all()) + return results def get_sort_function(sort_param, data): @@ -526,36 +536,66 @@ def render_author_books(page, author_id, order): def render_publisher_books(page, book_id, order): - publisher = calibre_db.session.query(db.Publishers).filter(db.Publishers.id == book_id).first() - if publisher: + if book_id == '-1': entries, random, pagination = calibre_db.fill_indexpage(page, 0, db.Books, - db.Books.publishers.any(db.Publishers.id == book_id), + db.Publishers.name == None, [db.Series.name, order[0][0], db.Books.series_index], True, config.config_read_column, + db.books_publishers_link, + db.Books.id == db.books_publishers_link.c.book, + db.Publishers, db.books_series_link, db.Books.id == db.books_series_link.c.book, db.Series) - return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id, - title=_(u"Publisher: %(name)s", name=publisher.name), - page="publisher", - order=order[1]) + publisher = _("None") else: - abort(404) + publisher = calibre_db.session.query(db.Publishers).filter(db.Publishers.id == book_id).first() + if publisher: + entries, random, pagination = calibre_db.fill_indexpage(page, 0, + db.Books, + db.Books.publishers.any( + db.Publishers.id == book_id), + [db.Series.name, order[0][0], + db.Books.series_index], + True, config.config_read_column, + db.books_series_link, + db.Books.id == db.books_series_link.c.book, + db.Series) + publisher = publisher.name + else: + abort(404) + + return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id, + title=_(u"Publisher: %(name)s", name=publisher), + page="publisher", + order=order[1]) def render_series_books(page, book_id, order): - name = calibre_db.session.query(db.Series).filter(db.Series.id == book_id).first() - if name: + if book_id == '-1': entries, random, pagination = calibre_db.fill_indexpage(page, 0, db.Books, - db.Books.series.any(db.Series.id == book_id), + db.Series.name == None, [order[0][0]], - True, config.config_read_column) - return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id, - title=_(u"Series: %(serie)s", serie=name.name), page="series", order=order[1]) + True, config.config_read_column, + db.books_series_link, + db.Books.id == db.books_series_link.c.book, + db.Series) + series_name = _("None") else: - abort(404) + series_name = calibre_db.session.query(db.Series).filter(db.Series.id == book_id).first() + if series_name: + entries, random, pagination = calibre_db.fill_indexpage(page, 0, + db.Books, + db.Books.series.any(db.Series.id == book_id), + [order[0][0]], + True, config.config_read_column) + series_name = series_name.name + else: + abort(404) + return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id, + title=_(u"Series: %(serie)s", serie=series_name), page="series", order=order[1]) def render_ratings_books(page, book_id, order): @@ -591,33 +631,61 @@ def render_formats_books(page, book_id, order): def render_category_books(page, book_id, order): - name = calibre_db.session.query(db.Tags).filter(db.Tags.id == book_id).first() - if name: + if book_id == '-1': entries, random, pagination = calibre_db.fill_indexpage(page, 0, db.Books, - db.Books.tags.any(db.Tags.id == book_id), + db.Tags.name == None, [order[0][0], db.Series.name, db.Books.series_index], True, config.config_read_column, + db.books_tags_link, + db.Books.id == db.books_tags_link.c.book, + db.Tags, db.books_series_link, db.Books.id == db.books_series_link.c.book, db.Series) - return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id, - title=_(u"Category: %(name)s", name=name.name), page="category", order=order[1]) + tagsname = _("None") else: - abort(404) + tagsname = calibre_db.session.query(db.Tags).filter(db.Tags.id == book_id).first() + if tagsname: + entries, random, pagination = calibre_db.fill_indexpage(page, 0, + db.Books, + db.Books.tags.any(db.Tags.id == book_id), + [order[0][0], db.Series.name, + db.Books.series_index], + True, config.config_read_column, + db.books_series_link, + db.Books.id == db.books_series_link.c.book, + db.Series) + tagsname = tagsname.name + else: + abort(404) + return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=book_id, + title=_(u"Category: %(name)s", name=tagsname), page="category", order=order[1]) def render_language_books(page, name, order): try: - lang_name = isoLanguages.get_language_name(get_locale(), name) + if name.lower() != "none": + lang_name = isoLanguages.get_language_name(get_locale(), name) + else: + lang_name = _("None") except KeyError: abort(404) - - entries, random, pagination = calibre_db.fill_indexpage(page, 0, - db.Books, - db.Books.languages.any(db.Languages.lang_code == name), - [order[0][0]], - True, config.config_read_column) + if name == "none": + entries, random, pagination = calibre_db.fill_indexpage(page, 0, + db.Books, + db.Languages.lang_code == None, + [order[0][0]], + True, config.config_read_column, + db.books_languages_link, + db.Books.id == db.books_languages_link.c.book, + db.Languages) + else: + entries, random, pagination = calibre_db.fill_indexpage(page, 0, + db.Books, + db.Books.languages.any(db.Languages.lang_code == name), + [order[0][0]], + True, config.config_read_column) return render_title_template('index.html', random=random, entries=entries, pagination=pagination, id=name, title=_(u"Language: %(name)s", name=lang_name), page="language", order=order[1]) @@ -880,7 +948,7 @@ def author_list(): entries = calibre_db.session.query(db.Authors, func.count('books_authors_link.book').label('count')) \ .join(db.books_authors_link).join(db.Books).filter(calibre_db.common_filters()) \ .group_by(text('books_authors_link.author')).order_by(order).all() - char_list = generate_char_list(db.Authors.sort, db.books_authors_link) + char_list = query_char_list(db.Authors.sort, db.books_authors_link) # If not creating a copy, readonly databases can not display authornames with "|" in it as changing the name # starts a change session author_copy = copy.deepcopy(entries) @@ -926,7 +994,14 @@ def publisher_list(): entries = calibre_db.session.query(db.Publishers, func.count('books_publishers_link.book').label('count')) \ .join(db.books_publishers_link).join(db.Books).filter(calibre_db.common_filters()) \ .group_by(text('books_publishers_link.publisher')).order_by(order).all() - char_list = generate_char_list(db.Publishers.name, db.books_publishers_link) + no_publisher_count = (calibre_db.session.query(db.Books) + .outerjoin(db.books_publishers_link).outerjoin(db.Publishers) + .filter(db.Publishers.name == None) + .filter(calibre_db.common_filters()) + .count()) + if no_publisher_count: + entries.append([db.Category(_("None"), "-1"), no_publisher_count]) + char_list = generate_char_list(entries) return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=char_list, title=_(u"Publishers"), page="publisherlist", data="publisher", order=order_no) else: @@ -943,11 +1018,18 @@ def series_list(): else: order = db.Series.sort.asc() order_no = 1 - char_list = generate_char_list(db.Series.sort, db.books_series_link) + char_list = query_char_list(db.Series.sort, db.books_series_link) if current_user.get_view_property('series', 'series_view') == 'list': entries = calibre_db.session.query(db.Series, func.count('books_series_link.book').label('count')) \ .join(db.books_series_link).join(db.Books).filter(calibre_db.common_filters()) \ .group_by(text('books_series_link.series')).order_by(order).all() + no_series_count = (calibre_db.session.query(db.Books) + .outerjoin(db.books_series_link).outerjoin(db.Series) + .filter(db.Series.name == None) + .filter(calibre_db.common_filters()) + .count()) + if no_series_count: + entries.append([db.Category(_("None"), "-1"), no_series_count]) return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=char_list, title=_(u"Series"), page="serieslist", data="series", order=order_no) else: @@ -976,6 +1058,12 @@ def ratings_list(): (db.Ratings.rating / 2).label('name')) \ .join(db.books_ratings_link).join(db.Books).filter(calibre_db.common_filters()) \ .group_by(text('books_ratings_link.rating')).order_by(order).all() + no_rating_count = (calibre_db.session.query(db.Books) + .outerjoin(db.books_ratings_link).outerjoin(db.Ratings) + .filter(db.Ratings.name == None) + .filter(calibre_db.common_filters()) + .count()) + entries.append([db.Category(_("None"), "-1"), no_rating_count]) return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(), title=_(u"Ratings list"), page="ratingslist", data="ratings", order=order_no) else: @@ -997,6 +1085,12 @@ def formats_list(): db.Data.format.label('format')) \ .join(db.Books).filter(calibre_db.common_filters()) \ .group_by(db.Data.format).order_by(order).all() + no_format_count = (calibre_db.session.query(db.Books).outerjoin(db.Data) + .filter(db.Data.format == None) + .filter(calibre_db.common_filters()) + .count()) + if no_format_count: + entries.append([db.Category(_("None"), "-1"), no_format_count]) return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(), title=_(u"File formats list"), page="formatslist", data="formats", order=order_no) else: @@ -1008,15 +1102,10 @@ def formats_list(): def language_overview(): if current_user.check_visibility(constants.SIDEBAR_LANGUAGE) and current_user.filter_language() == u"all": order_no = 0 if current_user.get_view_property('language', 'dir') == 'desc' else 1 - char_list = list() languages = calibre_db.speaking_language(reverse_order=not order_no, with_count=True) - for lang in languages: - upper_lang = lang[0].name[0].upper() - if upper_lang not in char_list: - char_list.append(upper_lang) - return render_title_template('languages.html', languages=languages, - charlist=char_list, title=_(u"Languages"), page="langlist", - data="language", order=order_no) + char_list = generate_char_list(languages) + return render_title_template('list.html', entries=languages, folder='web.books_list', charlist=char_list, + title=_(u"Languages"), page="langlist", data="language", order=order_no) else: abort(404) @@ -1034,7 +1123,14 @@ def category_list(): entries = calibre_db.session.query(db.Tags, func.count('books_tags_link.book').label('count')) \ .join(db.books_tags_link).join(db.Books).order_by(order).filter(calibre_db.common_filters()) \ .group_by(text('books_tags_link.tag')).all() - char_list = generate_char_list(db.Tags.name, db.books_tags_link) + no_tag_count = (calibre_db.session.query(db.Books) + .outerjoin(db.books_tags_link).outerjoin(db.Tags) + .filter(db.Tags.name == None) + .filter(calibre_db.common_filters()) + .count()) + if no_tag_count: + entries.append([db.Category(_("None"), "-1"), no_tag_count]) + char_list = generate_char_list(entries) return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=char_list, title=_(u"Categories"), page="catlist", data="category", order=order_no) else: From c61e5d6ac03deaff00d1b4b85f2204ffb56c556b Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 17 Apr 2022 11:54:46 +0200 Subject: [PATCH 09/13] Fix amazon metadata-provider --- cps/metadata_provider/amazon.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cps/metadata_provider/amazon.py b/cps/metadata_provider/amazon.py index fef63669..7de0d415 100644 --- a/cps/metadata_provider/amazon.py +++ b/cps/metadata_provider/amazon.py @@ -113,9 +113,9 @@ class Amazon(Metadata): except (AttributeError, TypeError): match.cover = "" return match, index - except Exception as e: - log.error_or_exception(e) - return + except Exception as e: + log.error_or_exception(e) + return val = list() if self.active: From c92d65aad3ea4f2247e34c2d5c7946852959cbd8 Mon Sep 17 00:00:00 2001 From: Ozzieisaacs Date: Sun, 17 Apr 2022 19:05:56 +0200 Subject: [PATCH 10/13] Catch more errors on import metadata provider --- cps/search_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cps/search_metadata.py b/cps/search_metadata.py index 4bbca58f..0070e78f 100644 --- a/cps/search_metadata.py +++ b/cps/search_metadata.py @@ -57,7 +57,7 @@ for f in modules: try: importlib.import_module("cps.metadata_provider." + a) new_list.append(a) - except ImportError as e: + except (ImportError, IndentationError, SyntaxError) as e: log.error("Import error for metadata source: {} - {}".format(a, e)) pass From 3cbbf6fa860f0e497794a808f84aac107f05bd75 Mon Sep 17 00:00:00 2001 From: xlivevil Date: Mon, 18 Apr 2022 12:42:05 +0800 Subject: [PATCH 11/13] Update Simplifield Chinese translation --- .../zh_Hans_CN/LC_MESSAGES/messages.mo | Bin 50015 -> 51107 bytes .../zh_Hans_CN/LC_MESSAGES/messages.po | 856 +++++++++--------- messages.pot | 825 +++++++++-------- 3 files changed, 865 insertions(+), 816 deletions(-) diff --git a/cps/translations/zh_Hans_CN/LC_MESSAGES/messages.mo b/cps/translations/zh_Hans_CN/LC_MESSAGES/messages.mo index d0ffc14cfa5de9b938d7fce5491833ef83bc8c9e..a2e59093ef91fea52df06200bc7adefc43ba2e1d 100644 GIT binary patch delta 13491 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 delta 12496 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 diff --git a/cps/translations/zh_Hans_CN/LC_MESSAGES/messages.po b/cps/translations/zh_Hans_CN/LC_MESSAGES/messages.po index 73bc4efa..1f7099cf 100644 --- a/cps/translations/zh_Hans_CN/LC_MESSAGES/messages.po +++ b/cps/translations/zh_Hans_CN/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Calibre-Web\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2022-01-15 09:19+0100\n" +"POT-Creation-Date: 2022-04-18 12:20+0800\n" "PO-Revision-Date: 2020-09-27 22:18+0800\n" "Last-Translator: xlivevil \n" "Language: zh_CN\n" @@ -16,578 +16,581 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.9.0\n" +"Generated-By: Babel 2.9.1\n" -#: cps/about.py:34 cps/about.py:49 cps/about.py:65 cps/converter.py:31 -msgid "not installed" -msgstr "未安装" - -#: cps/about.py:47 cps/about.py:63 -msgid "installed" -msgstr "已安装" - -#: cps/about.py:145 +#: cps/about.py:86 msgid "Statistics" msgstr "统计" -#: cps/admin.py:144 +#: cps/admin.py:141 msgid "Server restarted, please reload page" msgstr "服务器已重启,请刷新页面" -#: cps/admin.py:146 +#: cps/admin.py:143 msgid "Performing shutdown of server, please close window" msgstr "正在关闭服务器,请关闭窗口" -#: cps/admin.py:154 +#: cps/admin.py:151 msgid "Reconnect successful" msgstr "重新连接成功" -#: cps/admin.py:157 +#: cps/admin.py:154 msgid "Unknown command" msgstr "未知命令" -#: cps/admin.py:167 cps/editbooks.py:707 cps/editbooks.py:721 -#: cps/editbooks.py:866 cps/editbooks.py:868 cps/editbooks.py:895 -#: cps/editbooks.py:911 cps/updater.py:584 cps/uploader.py:93 -#: cps/uploader.py:103 +#: cps/admin.py:176 cps/editbooks.py:713 cps/editbooks.py:892 +#: cps/editbooks.py:894 cps/editbooks.py:930 cps/editbooks.py:947 +#: cps/updater.py:608 cps/uploader.py:93 cps/uploader.py:103 msgid "Unknown" msgstr "未知" -#: cps/admin.py:188 +#: cps/admin.py:197 msgid "Admin page" msgstr "管理页" -#: cps/admin.py:207 +#: cps/admin.py:217 msgid "Basic Configuration" msgstr "基本配置" -#: cps/admin.py:244 +#: cps/admin.py:255 msgid "UI Configuration" msgstr "界面配置" -#: cps/admin.py:277 cps/templates/admin.html:50 +#: cps/admin.py:289 cps/templates/admin.html:51 msgid "Edit Users" msgstr "管理用户" -#: cps/admin.py:318 cps/opds.py:109 cps/opds.py:198 cps/opds.py:275 -#: cps/opds.py:327 cps/templates/grid.html:13 cps/templates/languages.html:9 +#: cps/admin.py:333 cps/opds.py:529 cps/templates/grid.html:13 #: cps/templates/list.html:13 msgid "All" msgstr "全部" -#: cps/admin.py:343 cps/admin.py:1615 +#: cps/admin.py:360 cps/admin.py:1648 msgid "User not found" msgstr "找不到用户" -#: cps/admin.py:357 +#: cps/admin.py:374 msgid "{} users deleted successfully" msgstr "成功删除 {} 个用户" -#: cps/admin.py:379 cps/templates/config_view_edit.html:133 +#: cps/admin.py:397 cps/templates/config_view_edit.html:133 #: cps/templates/user_edit.html:45 cps/templates/user_table.html:81 msgid "Show All" msgstr "显示全部" -#: cps/admin.py:400 cps/admin.py:406 +#: cps/admin.py:418 cps/admin.py:424 msgid "Malformed request" msgstr "格式错误的请求" -#: cps/admin.py:418 cps/admin.py:1493 +#: cps/admin.py:436 cps/admin.py:1526 msgid "Guest Name can't be changed" msgstr "访客名称无法更改" -#: cps/admin.py:430 +#: cps/admin.py:448 msgid "Guest can't have this role" msgstr "游客无法拥有此角色" -#: cps/admin.py:442 cps/admin.py:1451 +#: cps/admin.py:460 cps/admin.py:1484 msgid "No admin user remaining, can't remove admin role" msgstr "理员账户不存在,无法删除管理员角色" -#: cps/admin.py:446 cps/admin.py:460 +#: cps/admin.py:464 cps/admin.py:478 msgid "Value has to be true or false" msgstr "值必须是 true 或 false" -#: cps/admin.py:448 +#: cps/admin.py:466 msgid "Invalid role" msgstr "无效角色" -#: cps/admin.py:452 +#: cps/admin.py:470 msgid "Guest can't have this view" msgstr "游客无法拥有此视图" -#: cps/admin.py:462 +#: cps/admin.py:480 msgid "Invalid view" msgstr "无效视图" -#: cps/admin.py:465 +#: cps/admin.py:483 msgid "Guest's Locale is determined automatically and can't be set" msgstr "游客的本地化是自动侦测而无法设置的" -#: cps/admin.py:469 +#: cps/admin.py:487 msgid "No Valid Locale Given" msgstr "无可用本地化" -#: cps/admin.py:480 +#: cps/admin.py:498 msgid "No Valid Book Language Given" msgstr "无有效书籍语言" -#: cps/admin.py:482 +#: cps/admin.py:500 cps/editbooks.py:1267 msgid "Parameter not found" msgstr "参数未找到" -#: cps/admin.py:533 +#: cps/admin.py:553 msgid "Invalid Read Column" msgstr "无效的阅读列" -#: cps/admin.py:539 +#: cps/admin.py:559 msgid "Invalid Restricted Column" msgstr "无效的限制列" -#: cps/admin.py:560 cps/admin.py:1323 +#: cps/admin.py:579 cps/admin.py:1355 msgid "Calibre-Web configuration updated" msgstr "Calibre-Web配置已更新" -#: cps/admin.py:572 +#: cps/admin.py:591 msgid "Do you really want to delete the Kobo Token?" msgstr "您确定删除Kobo Token吗?" -#: cps/admin.py:574 +#: cps/admin.py:593 msgid "Do you really want to delete this domain?" msgstr "您确定要删除此域吗?" -#: cps/admin.py:576 +#: cps/admin.py:595 msgid "Do you really want to delete this user?" msgstr "您确定要删除此用户吗?" -#: cps/admin.py:578 +#: cps/admin.py:597 msgid "Are you sure you want to delete this shelf?" msgstr "您确定要删除此书架吗?" -#: cps/admin.py:580 +#: cps/admin.py:599 msgid "Are you sure you want to change locales of selected user(s)?" msgstr "您确定要修改选定用户的本地化设置吗?" -#: cps/admin.py:582 +#: cps/admin.py:601 msgid "Are you sure you want to change visible book languages for selected user(s)?" msgstr "您确定要修改选定用户的可见书籍语言吗?" -#: cps/admin.py:584 +#: cps/admin.py:603 msgid "Are you sure you want to change the selected role for the selected user(s)?" msgstr "您确定要修改选定用户的选定角色吗?" -#: cps/admin.py:586 +#: cps/admin.py:605 msgid "Are you sure you want to change the selected restrictions for the selected user(s)?" msgstr "您确定要修改选定用户的选定限制吗?" -#: cps/admin.py:588 +#: cps/admin.py:607 msgid "Are you sure you want to change the selected visibility restrictions for the selected user(s)?" msgstr "您确定要修改选定用户的选定可视化限制吗?" -#: cps/admin.py:590 +#: cps/admin.py:610 msgid "Are you sure you want to change shelf sync behavior for the selected user(s)?" msgstr "您确定要更改所选用户的书架同步行为吗?" -#: cps/admin.py:592 +#: cps/admin.py:612 msgid "Are you sure you want to change Calibre library location?" msgstr "您确定要更改 Calibre 库位置吗?" -#: cps/admin.py:594 +#: cps/admin.py:614 msgid "Are you sure you want delete Calibre-Web's sync database to force a full sync with your Kobo Reader?" -msgstr "" +msgstr "您确定要删除 Calibre-Web 的同步数据库以强制与您的 Kobo Reader 进行完全同步吗" -#: cps/admin.py:743 +#: cps/admin.py:764 msgid "Tag not found" msgstr "标签未找到" -#: cps/admin.py:755 +#: cps/admin.py:776 msgid "Invalid Action" msgstr "无效的动作" -#: cps/admin.py:871 cps/admin.py:877 cps/admin.py:887 cps/admin.py:897 +#: cps/admin.py:893 cps/admin.py:899 cps/admin.py:909 cps/admin.py:919 #: cps/templates/modal_dialogs.html:29 cps/templates/user_table.html:41 #: cps/templates/user_table.html:58 msgid "Deny" msgstr "拒绝" -#: cps/admin.py:873 cps/admin.py:879 cps/admin.py:889 cps/admin.py:899 +#: cps/admin.py:895 cps/admin.py:901 cps/admin.py:911 cps/admin.py:921 #: cps/templates/modal_dialogs.html:28 cps/templates/user_table.html:44 #: cps/templates/user_table.html:61 msgid "Allow" msgstr "允许" -#: cps/admin.py:913 +#: cps/admin.py:936 msgid "{} sync entries deleted" -msgstr "" +msgstr "{} 同步项目被删除" -#: cps/admin.py:1036 +#: cps/admin.py:1059 msgid "client_secrets.json Is Not Configured For Web Application" msgstr "client_secrets.json 未为 Web 应用程序配置" -#: cps/admin.py:1081 +#: cps/admin.py:1104 msgid "Logfile Location is not Valid, Please Enter Correct Path" msgstr "日志文件路径无效,请输入正确的路径" -#: cps/admin.py:1087 +#: cps/admin.py:1110 msgid "Access Logfile Location is not Valid, Please Enter Correct Path" msgstr "访问日志路径无效,请输入正确的路径" -#: cps/admin.py:1117 +#: cps/admin.py:1140 msgid "Please Enter a LDAP Provider, Port, DN and User Object Identifier" msgstr "请输入LDAP主机、端口、DN和用户对象标识符" -#: cps/admin.py:1123 +#: cps/admin.py:1146 msgid "Please Enter a LDAP Service Account and Password" msgstr "请输入一个LDAP服务账号和密码 " -#: cps/admin.py:1126 +#: cps/admin.py:1149 msgid "Please Enter a LDAP Service Account" msgstr "请输入一个LDAP服务账号" -#: cps/admin.py:1131 +#: cps/admin.py:1154 #, python-format msgid "LDAP Group Object Filter Needs to Have One \"%s\" Format Identifier" msgstr "LDAP组对象过滤器需要一个具有“%s”格式标识符" -#: cps/admin.py:1133 +#: cps/admin.py:1156 msgid "LDAP Group Object Filter Has Unmatched Parenthesis" msgstr "LDAP组对象过滤器的括号不匹配" -#: cps/admin.py:1137 +#: cps/admin.py:1160 #, python-format msgid "LDAP User Object Filter needs to Have One \"%s\" Format Identifier" msgstr "LDAP用户对象过滤器需要一个具有“%s”格式标识符" -#: cps/admin.py:1139 +#: cps/admin.py:1162 msgid "LDAP User Object Filter Has Unmatched Parenthesis" msgstr "LDAP用户对象过滤器的括号不匹配" -#: cps/admin.py:1146 +#: cps/admin.py:1169 #, python-format msgid "LDAP Member User Filter needs to Have One \"%s\" Format Identifier" msgstr "LDAP成员用户过滤器需要有一个“%s”格式标识符" -#: cps/admin.py:1148 +#: cps/admin.py:1171 msgid "LDAP Member User Filter Has Unmatched Parenthesis" msgstr "LDAP成员用户过滤器中有不匹配的括号" -#: cps/admin.py:1155 +#: cps/admin.py:1178 msgid "LDAP CACertificate, Certificate or Key Location is not Valid, Please Enter Correct Path" msgstr "LDAP CA证书、证书或密钥位置无效,请输入正确的路径" -#: cps/admin.py:1197 cps/admin.py:1308 cps/admin.py:1405 cps/admin.py:1521 -#: cps/admin.py:1590 cps/shelf.py:100 cps/shelf.py:160 cps/shelf.py:203 -#: cps/shelf.py:279 cps/shelf.py:341 cps/shelf.py:376 cps/shelf.py:451 -msgid "Settings DB is not Writeable" -msgstr "设置数据库不可写入" +#: cps/admin.py:1223 cps/admin.py:1339 cps/admin.py:1437 cps/admin.py:1554 +#: cps/admin.py:1623 cps/editbooks.py:678 cps/editbooks.py:882 +#: cps/editbooks.py:1130 cps/shelf.py:100 cps/shelf.py:160 cps/shelf.py:203 +#: cps/shelf.py:278 cps/shelf.py:343 cps/shelf.py:380 cps/shelf.py:456 +#: cps/web.py:1742 +#, python-format +msgid "Database error: %(error)s." +msgstr "数据库错误:%(error)s。" -#: cps/admin.py:1208 +#: cps/admin.py:1235 msgid "DB Location is not Valid, Please Enter Correct Path" msgstr "数据库路径无效,请输入正确的路径" -#: cps/admin.py:1223 +#: cps/admin.py:1253 msgid "DB is not Writeable" msgstr "数据库不可写入" -#: cps/admin.py:1235 +#: cps/admin.py:1266 msgid "Keyfile Location is not Valid, Please Enter Correct Path" msgstr "密钥文件路径无效,请输入正确的路径" -#: cps/admin.py:1239 +#: cps/admin.py:1270 msgid "Certfile Location is not Valid, Please Enter Correct Path" msgstr "证书文件路径无效,请输入正确的路径" -#: cps/admin.py:1346 +#: cps/admin.py:1378 msgid "Database Settings updated" msgstr "数据库设置已更新" -#: cps/admin.py:1354 +#: cps/admin.py:1386 msgid "Database Configuration" msgstr "数据库配置" -#: cps/admin.py:1370 cps/web.py:1479 +#: cps/admin.py:1402 cps/web.py:1557 msgid "Please fill out all fields!" msgstr "请填写所有字段!" -#: cps/admin.py:1378 +#: cps/admin.py:1410 msgid "E-mail is not from valid domain" msgstr "邮箱不在有效域中" -#: cps/admin.py:1384 cps/admin.py:1543 +#: cps/admin.py:1416 cps/admin.py:1576 msgid "Add new user" msgstr "添加新用户" -#: cps/admin.py:1395 +#: cps/admin.py:1427 #, python-format msgid "User '%(user)s' created" msgstr "用户“%(user)s”已创建" -#: cps/admin.py:1401 +#: cps/admin.py:1433 msgid "Found an existing account for this e-mail address or name." msgstr "使用此邮箱或用户名的账号已经存在。" -#: cps/admin.py:1430 +#: cps/admin.py:1463 #, python-format msgid "User '%(nick)s' deleted" msgstr "用户“%(nick)s”已删除" -#: cps/admin.py:1432 cps/admin.py:1433 +#: cps/admin.py:1465 cps/admin.py:1466 msgid "Can't delete Guest User" msgstr "无法删除游客用户" -#: cps/admin.py:1436 +#: cps/admin.py:1469 msgid "No admin user remaining, can't delete user" msgstr "管理员账户不存在,无法删除用户" -#: cps/admin.py:1509 cps/admin.py:1634 +#: cps/admin.py:1542 cps/admin.py:1667 #, python-format msgid "Edit User %(nick)s" msgstr "编辑用户 %(nick)s" -#: cps/admin.py:1513 +#: cps/admin.py:1546 #, python-format msgid "User '%(nick)s' updated" msgstr "用户“%(nick)s”已更新" -#: cps/admin.py:1517 cps/admin.py:1649 cps/web.py:1504 cps/web.py:1564 +#: cps/admin.py:1550 cps/admin.py:1682 cps/web.py:1582 cps/web.py:1642 msgid "An unknown error occurred. Please try again later." msgstr "发生一个未知错误,请稍后再试。" -#: cps/admin.py:1552 cps/templates/admin.html:98 +#: cps/admin.py:1585 cps/templates/admin.html:100 msgid "Edit E-mail Server Settings" msgstr "编辑邮件服务器设置" -#: cps/admin.py:1571 +#: cps/admin.py:1604 msgid "Gmail Account Verification Successful" msgstr "G-Mail账号校验成功" -#: cps/admin.py:1597 +#: cps/admin.py:1630 #, python-format msgid "Test e-mail queued for sending to %(email)s, please check Tasks for result" msgstr "发送给%(email)s的测试邮件已进入队列。请检查任务结果" -#: cps/admin.py:1600 +#: cps/admin.py:1633 #, python-format msgid "There was an error sending the Test e-mail: %(res)s" msgstr "发送测试邮件时出错:%(res)s" -#: cps/admin.py:1602 +#: cps/admin.py:1635 msgid "Please configure your e-mail address first..." msgstr "请先配置您的邮箱地址..." -#: cps/admin.py:1604 +#: cps/admin.py:1637 msgid "E-mail server settings updated" msgstr "邮件服务器设置已更新" -#: cps/admin.py:1646 +#: cps/admin.py:1679 #, python-format msgid "Password for user %(user)s reset" msgstr "用户 %(user)s 的密码已重置" -#: cps/admin.py:1652 cps/web.py:1444 +#: cps/admin.py:1685 cps/web.py:1522 msgid "Please configure the SMTP mail settings first..." msgstr "请先配置SMTP邮箱设置..." -#: cps/admin.py:1663 +#: cps/admin.py:1696 msgid "Logfile viewer" msgstr "日志文件查看器" -#: cps/admin.py:1729 +#: cps/admin.py:1762 msgid "Requesting update package" msgstr "正在请求更新包" -#: cps/admin.py:1730 +#: cps/admin.py:1763 msgid "Downloading update package" msgstr "正在下载更新包" -#: cps/admin.py:1731 +#: cps/admin.py:1764 msgid "Unzipping update package" msgstr "正在解压更新包" -#: cps/admin.py:1732 +#: cps/admin.py:1765 msgid "Replacing files" msgstr "正在替换文件" -#: cps/admin.py:1733 +#: cps/admin.py:1766 msgid "Database connections are closed" msgstr "数据库连接已关闭" -#: cps/admin.py:1734 +#: cps/admin.py:1767 msgid "Stopping server" msgstr "正在停止服务器" -#: cps/admin.py:1735 +#: cps/admin.py:1768 msgid "Update finished, please press okay and reload page" msgstr "更新完成,请点击确定并刷新页面" -#: cps/admin.py:1736 cps/admin.py:1737 cps/admin.py:1738 cps/admin.py:1739 -#: cps/admin.py:1740 cps/admin.py:1741 +#: cps/admin.py:1769 cps/admin.py:1770 cps/admin.py:1771 cps/admin.py:1772 +#: cps/admin.py:1773 cps/admin.py:1774 msgid "Update failed:" msgstr "更新失败:" -#: cps/admin.py:1736 cps/updater.py:385 cps/updater.py:595 cps/updater.py:597 +#: cps/admin.py:1769 cps/updater.py:384 cps/updater.py:619 cps/updater.py:621 msgid "HTTP Error" msgstr "HTTP错误" -#: cps/admin.py:1737 cps/updater.py:387 cps/updater.py:599 +#: cps/admin.py:1770 cps/updater.py:386 cps/updater.py:623 msgid "Connection error" msgstr "连接错误" -#: cps/admin.py:1738 cps/updater.py:389 cps/updater.py:601 +#: cps/admin.py:1771 cps/updater.py:388 cps/updater.py:625 msgid "Timeout while establishing connection" msgstr "建立连接超时" -#: cps/admin.py:1739 cps/updater.py:391 cps/updater.py:603 +#: cps/admin.py:1772 cps/updater.py:390 cps/updater.py:627 msgid "General error" msgstr "一般错误" -#: cps/admin.py:1740 +#: cps/admin.py:1773 msgid "Update file could not be saved in temp dir" msgstr "更新文件无法保存在临时目录中" -#: cps/admin.py:1741 +#: cps/admin.py:1774 msgid "Files could not be replaced during update" msgstr "更新期间无法替换文件" -#: cps/admin.py:1765 +#: cps/admin.py:1798 msgid "Failed to extract at least One LDAP User" msgstr "未能提取至少一个LDAP用户" -#: cps/admin.py:1810 +#: cps/admin.py:1843 msgid "Failed to Create at Least One LDAP User" msgstr "未能创建至少一个LDAP用户" -#: cps/admin.py:1823 +#: cps/admin.py:1856 #, python-format msgid "Error: %(ldaperror)s" msgstr "错误:%(ldaperror)s" -#: cps/admin.py:1827 +#: cps/admin.py:1860 msgid "Error: No user returned in response of LDAP server" msgstr "错误:在LDAP服务器的响应中没有返回用户" -#: cps/admin.py:1860 +#: cps/admin.py:1893 msgid "At Least One LDAP User Not Found in Database" msgstr "数据库中没有找到至少一个LDAP用户" -#: cps/admin.py:1862 +#: cps/admin.py:1895 msgid "{} User Successfully Imported" msgstr "{} 用户被成功导入" #: cps/converter.py:30 -msgid "not configured" -msgstr "未配置" +msgid "not installed" +msgstr "未安装" -#: cps/converter.py:32 +#: cps/converter.py:31 msgid "Execution permissions missing" msgstr "缺少执行权限" -#: cps/db.py:651 cps/web.py:667 cps/web.py:1155 +#: cps/db.py:674 cps/web.py:710 cps/web.py:1222 #, python-format msgid "Custom Column No.%(column)d is not existing in calibre database" msgstr "自定义列号:%(column)d在Calibre数据库中不存在" -#: cps/editbooks.py:300 cps/editbooks.py:302 +#: cps/db.py:917 cps/templates/config_edit.html:204 +#: cps/templates/config_view_edit.html:62 cps/templates/email_edit.html:41 +#: cps/web.py:551 cps/web.py:585 cps/web.py:646 cps/web.py:671 cps/web.py:1003 +#: cps/web.py:1032 cps/web.py:1066 cps/web.py:1093 cps/web.py:1132 +msgid "None" +msgstr "无" + +#: cps/editbooks.py:295 cps/editbooks.py:297 msgid "Book Format Successfully Deleted" msgstr "书籍格式已成功删除" -#: cps/editbooks.py:309 cps/editbooks.py:311 +#: cps/editbooks.py:304 cps/editbooks.py:306 msgid "Book Successfully Deleted" msgstr "书籍已成功删除" -#: cps/editbooks.py:361 +#: cps/editbooks.py:358 msgid "You are missing permissions to delete books" -msgstr "" +msgstr "您没有删除书籍的权限" -#: cps/editbooks.py:376 cps/editbooks.py:763 cps/web.py:523 cps/web.py:1703 -#: cps/web.py:1744 cps/web.py:1811 +#: cps/editbooks.py:373 cps/editbooks.py:765 cps/web.py:518 cps/web.py:1783 +#: cps/web.py:1825 cps/web.py:1870 msgid "Oops! Selected book title is unavailable. File does not exist or is not accessible" msgstr "糟糕!选择书名无法打开。文件不存在或者文件不可访问" -#: cps/editbooks.py:410 +#: cps/editbooks.py:408 msgid "edit metadata" msgstr "编辑元数据" -#: cps/editbooks.py:458 +#: cps/editbooks.py:457 #, python-format msgid "%(seriesindex)s is not a valid number, skipping" msgstr "%(seriesindex)s 不是一个有效的数值,忽略" -#: cps/editbooks.py:494 cps/editbooks.py:958 +#: cps/editbooks.py:493 cps/editbooks.py:1001 #, python-format msgid "'%(langname)s' is not a valid language" msgstr "'%(langname)s' 不是一种有效语言" -#: cps/editbooks.py:634 cps/editbooks.py:985 +#: cps/editbooks.py:634 +msgid "User has no rights to upload additional file formats" +msgstr "用户没有权限上传其他文件格式" + +#: cps/editbooks.py:639 cps/editbooks.py:1029 #, python-format msgid "File extension '%(ext)s' is not allowed to be uploaded to this server" msgstr "不能上传文件扩展名为“%(ext)s”的文件到此服务器" -#: cps/editbooks.py:638 cps/editbooks.py:989 +#: cps/editbooks.py:643 cps/editbooks.py:1033 msgid "File to be uploaded must have an extension" msgstr "要上传的文件必须具有扩展名" -#: cps/editbooks.py:650 +#: cps/editbooks.py:655 #, python-format msgid "Failed to create path %(path)s (Permission denied)." msgstr "创建路径 %(path)s 失败(权限拒绝)。" -#: cps/editbooks.py:655 +#: cps/editbooks.py:660 #, python-format msgid "Failed to store file %(file)s." msgstr "保存文件 %(file)s 失败。" -#: cps/editbooks.py:673 cps/editbooks.py:1076 cps/web.py:1664 -#, python-format -msgid "Database error: %(error)s." -msgstr "数据库错误:%(error)s。" - -#: cps/editbooks.py:678 +#: cps/editbooks.py:683 #, python-format msgid "File format %(ext)s added to %(book)s" msgstr "已添加 %(ext)s 格式到 %(book)s" -#: cps/editbooks.py:814 +#: cps/editbooks.py:697 cps/editbooks.py:809 +msgid "User has no rights to upload cover" +msgstr "用户没有权限上传封面" + +#: cps/editbooks.py:828 msgid "Identifiers are not Case Sensitive, Overwriting Old Identifier" msgstr "标识符不区分大小写,覆盖旧标识符" -#: cps/editbooks.py:848 +#: cps/editbooks.py:869 msgid "Metadata successfully updated" msgstr "已成功更新元数据" -#: cps/editbooks.py:861 -msgid "Error editing book, please check logfile for details" -msgstr "编辑书籍出错,请检查日志文件以获取详细信息" +#: cps/editbooks.py:887 +msgid "Error editing book: {}" +msgstr "编辑书籍时出错: {}" -#: cps/editbooks.py:899 +#: cps/editbooks.py:951 msgid "Uploaded book probably exists in the library, consider to change before upload new: " msgstr "上传的书籍可能已经存在,建议修改后重新上传: " -#: cps/editbooks.py:997 +#: cps/editbooks.py:1041 #, python-format msgid "File %(filename)s could not saved to temp dir" msgstr "文件 %(filename)s 无法保存到临时目录" -#: cps/editbooks.py:1016 +#: cps/editbooks.py:1061 #, python-format msgid "Failed to Move Cover File %(file)s: %(error)s" msgstr "移动封面文件失败 %(file)s:%(error)s" -#: cps/editbooks.py:1063 +#: cps/editbooks.py:1117 #, python-format msgid "File %(file)s uploaded" msgstr "文件 %(file)s 已上传" -#: cps/editbooks.py:1088 +#: cps/editbooks.py:1143 msgid "Source or destination format for conversion missing" msgstr "转换的源或目的格式缺失" -#: cps/editbooks.py:1096 +#: cps/editbooks.py:1151 #, python-format msgid "Book successfully queued for converting to %(book_format)s" msgstr "书籍已经被成功加入到 %(book_format)s 格式转换队列" -#: cps/editbooks.py:1100 +#: cps/editbooks.py:1155 #, python-format msgid "There was an error converting this book: %(res)s" msgstr "转换此书籍时出现错误: %(res)s" @@ -600,174 +603,190 @@ msgstr "Google Drive 设置未完成,请尝试停用并再次激活Google云 msgid "Callback domain is not verified, please follow steps to verify domain in google developer console" msgstr "回调域名尚未被校验,请在google开发者控制台按步骤校验域名" -#: cps/helper.py:77 +#: cps/helper.py:81 #, python-format msgid "%(format)s format not found for book id: %(book)d" msgstr "找不到id为 %(book)d 的书籍的 %(format)s 格式" -#: cps/helper.py:83 cps/tasks/convert.py:73 +#: cps/helper.py:87 cps/tasks/convert.py:75 #, python-format msgid "%(format)s not found on Google Drive: %(fn)s" msgstr "Google Drive %(fn)s 上找不到 %(format)s" -#: cps/helper.py:88 +#: cps/helper.py:92 #, python-format msgid "%(format)s not found: %(fn)s" msgstr "找不到 %(format)s:%(fn)s" -#: cps/helper.py:93 cps/helper.py:217 cps/templates/detail.html:41 +#: cps/helper.py:97 cps/helper.py:221 cps/templates/detail.html:41 #: cps/templates/detail.html:45 msgid "Send to Kindle" msgstr "发送到Kindle" -#: cps/helper.py:94 cps/helper.py:111 cps/helper.py:219 +#: cps/helper.py:98 cps/helper.py:115 cps/helper.py:223 msgid "This e-mail has been sent via Calibre-Web." msgstr "此邮件已经通过Calibre-Web发送。" -#: cps/helper.py:109 +#: cps/helper.py:113 msgid "Calibre-Web test e-mail" msgstr "Calibre-Web测试邮件" -#: cps/helper.py:110 +#: cps/helper.py:114 msgid "Test e-mail" msgstr "测试邮件" -#: cps/helper.py:127 +#: cps/helper.py:131 msgid "Get Started with Calibre-Web" msgstr "开启Calibre-Web之旅" -#: cps/helper.py:132 +#: cps/helper.py:136 #, python-format msgid "Registration e-mail for user: %(name)s" msgstr "用户注册电子邮件:%(name)s" -#: cps/helper.py:143 cps/helper.py:149 +#: cps/helper.py:147 cps/helper.py:153 #, python-format msgid "Convert %(orig)s to %(format)s and send to Kindle" msgstr "转换 %(orig)s 到 %(format)s 并发送到Kindle" -#: cps/helper.py:168 cps/helper.py:172 cps/helper.py:176 +#: cps/helper.py:172 cps/helper.py:176 cps/helper.py:180 #, python-format msgid "Send %(format)s to Kindle" msgstr "发送 %(format)s 到Kindle" -#: cps/helper.py:216 cps/tasks/convert.py:90 +#: cps/helper.py:220 cps/tasks/convert.py:92 #, python-format msgid "%(book)s send to Kindle" msgstr "%(book)s发送到Kindle" -#: cps/helper.py:221 +#: cps/helper.py:225 msgid "The requested file could not be read. Maybe wrong permissions?" msgstr "无法读取请求的文件。可能有错误的权限设置?" -#: cps/helper.py:313 +#: cps/helper.py:353 +msgid "Read status could not set: {}" +msgstr "阅读状态无法设置: {}" + +#: cps/helper.py:376 #, python-format msgid "Deleting bookfolder for book %(id)s failed, path has subfolders: %(path)s" msgstr "删除书的文件夹%(id)s失败,路径有子文件夹:%(path)s" -#: cps/helper.py:319 +#: cps/helper.py:382 #, python-format msgid "Deleting book %(id)s failed: %(message)s" msgstr "删除书籍 %(id)s失败:%(message)s" -#: cps/helper.py:330 +#: cps/helper.py:393 #, python-format msgid "Deleting book %(id)s from database only, book path in database not valid: %(path)s" msgstr "仅从数据库中删除书籍 %(id)s,数据库中的书籍路径无效: %(path)s" -#: cps/helper.py:385 +#: cps/helper.py:458 #, python-format -msgid "Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s" -msgstr "将标题从“%(src)s”改为“%(dest)s”时失败,出错信息:%(error)s" - -#: cps/helper.py:400 -#, python-format -msgid "Rename file in path '%(src)s' to '%(dest)s' failed with error: %(error)s" -msgstr "从“%(src)s”重命名为“%(dest)s”失败,出错信息:%(error)s" +msgid "Rename author from: '%(src)s' to '%(dest)s' failed with error: %(error)s" +msgstr "将作者从“%(src)s”改为“%(dest)s”时失败,出错信息:%(error)s" -#: cps/helper.py:425 cps/helper.py:435 cps/helper.py:443 +#: cps/helper.py:529 cps/helper.py:538 #, python-format msgid "File %(file)s not found on Google Drive" msgstr "Google Drive上找不到文件 %(file)s" -#: cps/helper.py:464 +#: cps/helper.py:572 +#, python-format +msgid "Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s" +msgstr "将标题从“%(src)s”改为“%(dest)s”时失败,出错信息:%(error)s" + +#: cps/helper.py:592 +msgid "Error in rename file in path: {}" +msgstr "重命名此路径: {}文件时出错" + +#: cps/helper.py:610 #, python-format msgid "Book path %(path)s not found on Google Drive" msgstr "Google Drive上找不到书籍路径 %(path)s" -#: cps/helper.py:504 cps/web.py:1659 +#: cps/helper.py:651 cps/web.py:1737 msgid "Found an existing account for this e-mail address" msgstr "使用此邮箱的账号已经存在。" -#: cps/helper.py:512 +#: cps/helper.py:659 msgid "This username is already taken" msgstr "此用户名已被使用" -#: cps/helper.py:522 +#: cps/helper.py:669 msgid "Invalid e-mail address format" msgstr "无效的邮件地址格式" -#: cps/helper.py:595 +#: cps/helper.py:754 +msgid "Python modul 'advocate' is not installed but is needed for cover downloads" +msgstr "下载封面必须的 Python 模块'advocate' 未安装" + +#: cps/helper.py:767 msgid "Error Downloading Cover" msgstr "下载封面时出错" -#: cps/helper.py:598 +#: cps/helper.py:770 msgid "Cover Format Error" msgstr "封面格式出错" -#: cps/helper.py:608 +#: cps/helper.py:773 +msgid "You are not allowed to access localhost or the local network for cover uploads" +msgstr "您没有访问本地主机或本地网络进行封面上传" + +#: cps/helper.py:783 msgid "Failed to create path for cover" msgstr "创建封面路径失败" -#: cps/helper.py:624 +#: cps/helper.py:799 msgid "Cover-file is not a valid image file, or could not be stored" msgstr "封面文件不是有效的图片文件,或者无法存储" -#: cps/helper.py:635 +#: cps/helper.py:810 msgid "Only jpg/jpeg/png/webp/bmp files are supported as coverfile" msgstr "封面文件只支持jpg/jpeg/png/webp/bmp文件" -#: cps/helper.py:648 +#: cps/helper.py:822 msgid "Invalid cover file content" msgstr "封面文件内容无效" -#: cps/helper.py:652 +#: cps/helper.py:826 msgid "Only jpg/jpeg files are supported as coverfile" msgstr "仅将jpg、jpeg文件作为封面文件" -#: cps/helper.py:704 +#: cps/helper.py:878 msgid "Unrar binary file not found" msgstr "找不到Unrar执行文件" -#: cps/helper.py:715 +#: cps/helper.py:889 msgid "Error excecuting UnRar" msgstr "执行UnRar时出错" -#: cps/helper.py:763 +#: cps/helper.py:937 msgid "Waiting" msgstr "等待中" -#: cps/helper.py:765 +#: cps/helper.py:939 msgid "Failed" msgstr "失败" -#: cps/helper.py:767 +#: cps/helper.py:941 msgid "Started" msgstr "已开始" -#: cps/helper.py:769 +#: cps/helper.py:943 msgid "Finished" msgstr "已完成" -#: cps/helper.py:771 +#: cps/helper.py:945 msgid "Unknown Status" msgstr "未知状态" -#: cps/kobo_auth.py:127 -msgid "PLease access calibre-web from non localhost to get valid api_endpoint for kobo device" +#: cps/kobo_auth.py:128 +msgid "Please access Calibre-Web from non localhost to get valid api_endpoint for kobo device" msgstr "请不要使用localhost访问Calibre-Web,以便Kobo设备能获取有效的api_endpoint" -#: cps/kobo_auth.py:130 cps/kobo_auth.py:158 +#: cps/kobo_auth.py:154 msgid "Kobo Setup" msgstr "Kobo 设置" @@ -776,7 +795,7 @@ msgstr "Kobo 设置" msgid "Register with %(provider)s" msgstr "使用 %(provider)s 注册" -#: cps/oauth_bb.py:138 cps/remotelogin.py:133 cps/web.py:1535 +#: cps/oauth_bb.py:138 cps/remotelogin.py:130 cps/web.py:1613 #, python-format msgid "you are now logged in as: '%(nickname)s'" msgstr "您现在已以“%(nickname)s”身份登录" @@ -837,163 +856,163 @@ msgstr "Google Oauth 错误,请重试。" msgid "Google Oauth error: {}" msgstr "Google Oauth 错误: {}" -#: cps/opds.py:384 +#: cps/opds.py:298 msgid "{} Stars" msgstr "{} 星" -#: cps/remotelogin.py:65 cps/templates/layout.html:84 -#: cps/templates/login.html:4 cps/templates/login.html:21 cps/web.py:1584 +#: cps/remotelogin.py:62 cps/templates/layout.html:84 +#: cps/templates/login.html:4 cps/templates/login.html:21 cps/web.py:1662 msgid "Login" msgstr "登录" -#: cps/remotelogin.py:77 cps/remotelogin.py:111 +#: cps/remotelogin.py:74 cps/remotelogin.py:108 msgid "Token not found" msgstr "找不到Token" -#: cps/remotelogin.py:86 cps/remotelogin.py:119 +#: cps/remotelogin.py:83 cps/remotelogin.py:116 msgid "Token has expired" msgstr "Token已过期" -#: cps/remotelogin.py:95 +#: cps/remotelogin.py:92 msgid "Success! Please return to your device" msgstr "成功!请返回您的设备" -#: cps/render_template.py:39 cps/web.py:416 +#: cps/render_template.py:41 cps/web.py:407 msgid "Books" msgstr "书籍" -#: cps/render_template.py:41 +#: cps/render_template.py:43 msgid "Show recent books" msgstr "显示最近书籍" -#: cps/render_template.py:42 cps/templates/index.xml:25 +#: cps/render_template.py:44 cps/templates/index.xml:25 msgid "Hot Books" msgstr "热门书籍" -#: cps/render_template.py:44 +#: cps/render_template.py:46 msgid "Show Hot Books" msgstr "显示热门书籍" -#: cps/render_template.py:46 cps/render_template.py:51 +#: cps/render_template.py:48 cps/render_template.py:53 msgid "Downloaded Books" msgstr "下载历史" -#: cps/render_template.py:48 cps/render_template.py:53 +#: cps/render_template.py:50 cps/render_template.py:55 #: cps/templates/user_table.html:167 msgid "Show Downloaded Books" msgstr "显示下载过的书籍" -#: cps/render_template.py:56 cps/templates/index.xml:32 cps/web.py:430 +#: cps/render_template.py:58 cps/templates/index.xml:32 cps/web.py:422 msgid "Top Rated Books" msgstr "最高评分书籍" -#: cps/render_template.py:58 cps/templates/user_table.html:161 +#: cps/render_template.py:60 cps/templates/user_table.html:161 msgid "Show Top Rated Books" msgstr "显示最高评分书籍" -#: cps/render_template.py:59 cps/templates/index.xml:54 -#: cps/templates/index.xml:58 cps/web.py:676 +#: cps/render_template.py:61 cps/templates/index.xml:54 +#: cps/templates/index.xml:58 cps/web.py:729 msgid "Read Books" msgstr "已读书籍" -#: cps/render_template.py:61 +#: cps/render_template.py:63 msgid "Show read and unread" msgstr "显示阅读状态" -#: cps/render_template.py:63 cps/templates/index.xml:61 -#: cps/templates/index.xml:65 cps/web.py:679 +#: cps/render_template.py:65 cps/templates/index.xml:61 +#: cps/templates/index.xml:65 cps/web.py:732 msgid "Unread Books" msgstr "未读书籍" -#: cps/render_template.py:65 +#: cps/render_template.py:67 msgid "Show unread" msgstr "显示未读" -#: cps/render_template.py:66 +#: cps/render_template.py:68 msgid "Discover" msgstr "发现" -#: cps/render_template.py:68 cps/templates/index.xml:50 +#: cps/render_template.py:70 cps/templates/index.xml:50 #: cps/templates/user_table.html:162 msgid "Show Random Books" msgstr "显示随机书籍" -#: cps/render_template.py:69 cps/templates/book_table.html:67 -#: cps/templates/index.xml:83 cps/web.py:1041 +#: cps/render_template.py:71 cps/templates/book_table.html:67 +#: cps/templates/index.xml:83 cps/web.py:1135 msgid "Categories" msgstr "分类" -#: cps/render_template.py:71 cps/templates/user_table.html:158 +#: cps/render_template.py:73 cps/templates/user_table.html:158 msgid "Show category selection" msgstr "显示分类选择" -#: cps/render_template.py:72 cps/templates/book_edit.html:90 +#: cps/render_template.py:74 cps/templates/book_edit.html:90 #: cps/templates/book_table.html:68 cps/templates/index.xml:90 -#: cps/templates/search_form.html:69 cps/web.py:948 cps/web.py:959 +#: cps/templates/search_form.html:69 cps/web.py:1034 cps/web.py:1041 msgid "Series" msgstr "丛书" -#: cps/render_template.py:74 cps/templates/user_table.html:157 +#: cps/render_template.py:76 cps/templates/user_table.html:157 msgid "Show series selection" msgstr "显示丛书选择" -#: cps/render_template.py:75 cps/templates/book_table.html:66 +#: cps/render_template.py:77 cps/templates/book_table.html:66 #: cps/templates/index.xml:69 msgid "Authors" msgstr "作者" -#: cps/render_template.py:77 cps/templates/user_table.html:160 +#: cps/render_template.py:79 cps/templates/user_table.html:160 msgid "Show author selection" msgstr "显示作者选择" -#: cps/render_template.py:79 cps/templates/book_table.html:72 -#: cps/templates/index.xml:76 cps/web.py:925 +#: cps/render_template.py:81 cps/templates/book_table.html:72 +#: cps/templates/index.xml:76 cps/web.py:1006 msgid "Publishers" msgstr "出版社" -#: cps/render_template.py:81 cps/templates/user_table.html:163 +#: cps/render_template.py:83 cps/templates/user_table.html:163 msgid "Show publisher selection" msgstr "显示出版社选择" -#: cps/render_template.py:82 cps/templates/book_table.html:70 +#: cps/render_template.py:84 cps/templates/book_table.html:70 #: cps/templates/index.xml:97 cps/templates/search_form.html:107 -#: cps/web.py:1018 +#: cps/web.py:1108 msgid "Languages" msgstr "语言" -#: cps/render_template.py:85 cps/templates/user_table.html:155 +#: cps/render_template.py:87 cps/templates/user_table.html:155 msgid "Show language selection" msgstr "显示语言选择" -#: cps/render_template.py:86 cps/templates/index.xml:104 +#: cps/render_template.py:88 cps/templates/index.xml:104 msgid "Ratings" msgstr "评分" -#: cps/render_template.py:88 cps/templates/user_table.html:164 +#: cps/render_template.py:90 cps/templates/user_table.html:164 msgid "Show ratings selection" msgstr "显示评分选择" -#: cps/render_template.py:89 cps/templates/index.xml:112 +#: cps/render_template.py:91 cps/templates/index.xml:112 msgid "File formats" msgstr "文件格式" -#: cps/render_template.py:91 cps/templates/user_table.html:165 +#: cps/render_template.py:93 cps/templates/user_table.html:165 msgid "Show file formats selection" msgstr "显示文件格式选择" -#: cps/render_template.py:93 cps/web.py:703 +#: cps/render_template.py:95 cps/web.py:755 msgid "Archived Books" msgstr "归档书籍" -#: cps/render_template.py:95 cps/templates/user_table.html:166 +#: cps/render_template.py:97 cps/templates/user_table.html:166 msgid "Show archived books" msgstr "显示归档书籍" -#: cps/render_template.py:97 cps/web.py:780 +#: cps/render_template.py:100 cps/web.py:837 msgid "Books List" msgstr "书籍列表" -#: cps/render_template.py:99 cps/templates/user_table.html:168 +#: cps/render_template.py:102 cps/templates/user_table.html:168 msgid "Show Books List" msgstr "显示书籍列表" @@ -1017,7 +1036,7 @@ msgstr "此书籍已被添加到书架:%(sname)s" #: cps/shelf.py:126 msgid "You are not allowed to add a book to the shelf" -msgstr "你没有向书架添加书籍的权限" +msgstr "您没有向书架添加书籍的权限" #: cps/shelf.py:144 #, python-format @@ -1041,267 +1060,270 @@ msgstr "此书已从书架 %(sname)s 中删除" #: cps/shelf.py:218 msgid "Sorry you are not allowed to remove a book from this shelf" -msgstr "抱歉,你没有从这个书架删除书籍的权限" +msgstr "抱歉,您没有从这个书架删除书籍的权限" #: cps/shelf.py:228 cps/templates/layout.html:140 msgid "Create a Shelf" msgstr "创建书架" -#: cps/shelf.py:237 +#: cps/shelf.py:236 msgid "Sorry you are not allowed to edit this shelf" msgstr "对不起,您没有编辑这个书架的权限" -#: cps/shelf.py:239 +#: cps/shelf.py:238 msgid "Edit a shelf" msgstr "编辑书架" -#: cps/shelf.py:249 +#: cps/shelf.py:248 msgid "Sorry you are not allowed to create a public shelf" -msgstr "抱歉,你没有创建公开书架的权限" +msgstr "抱歉,您没有创建公开书架的权限" -#: cps/shelf.py:266 +#: cps/shelf.py:265 #, python-format msgid "Shelf %(title)s created" msgstr "书架 %(title)s 已创建" -#: cps/shelf.py:269 +#: cps/shelf.py:268 #, python-format msgid "Shelf %(title)s changed" msgstr "书架 %(title)s 已修改" -#: cps/shelf.py:283 +#: cps/shelf.py:282 msgid "There was an error" msgstr "发生错误" -#: cps/shelf.py:305 +#: cps/shelf.py:304 #, python-format msgid "A public shelf with the name '%(title)s' already exists." msgstr "公共书架:%(title)s已经存在已经存在。" -#: cps/shelf.py:316 +#: cps/shelf.py:315 #, python-format msgid "A private shelf with the name '%(title)s' already exists." msgstr "私有书架:%(title)s已经存在已经存在。" #: cps/shelf.py:337 -#, fuzzy +msgid "Error deleting Shelf" +msgstr "删除书架时出错" + +#: cps/shelf.py:339 msgid "Shelf successfully deleted" -msgstr "书籍已成功删除" +msgstr "书架已成功删除" -#: cps/shelf.py:386 +#: cps/shelf.py:389 #, python-format msgid "Change order of Shelf: '%(name)s'" msgstr "修改书架 %(name)s 顺序" -#: cps/shelf.py:456 +#: cps/shelf.py:461 #, python-format msgid "Shelf: '%(name)s'" msgstr "书架:%(name)s" -#: cps/shelf.py:460 +#: cps/shelf.py:465 msgid "Error opening shelf. Shelf does not exist or is not accessible" msgstr "打开书架出错。书架不存在或不可访问" -#: cps/updater.py:403 cps/updater.py:414 cps/updater.py:514 cps/updater.py:529 +#: cps/updater.py:426 cps/updater.py:437 cps/updater.py:538 cps/updater.py:553 msgid "Unexpected data while reading update information" msgstr "读取更新信息时出现意外数据" -#: cps/updater.py:410 cps/updater.py:521 +#: cps/updater.py:433 cps/updater.py:545 msgid "No update available. You already have the latest version installed" msgstr "无可用更新。您已经安装了最新版本" -#: cps/updater.py:428 +#: cps/updater.py:451 msgid "A new update is available. Click on the button below to update to the latest version." msgstr "有新的更新。单击下面的按钮以更新到最新版本。" -#: cps/updater.py:446 +#: cps/updater.py:469 msgid "Could not fetch update information" msgstr "无法获取更新信息" -#: cps/updater.py:456 +#: cps/updater.py:479 msgid "Click on the button below to update to the latest stable version." msgstr "点击下面按钮更新到最新稳定版本。" -#: cps/updater.py:465 cps/updater.py:479 cps/updater.py:490 +#: cps/updater.py:488 cps/updater.py:502 cps/updater.py:513 #, python-format msgid "A new update is available. Click on the button below to update to version: %(version)s" msgstr "有新的更新。单击下面的按钮以更新到版本: %(version)s" -#: cps/updater.py:507 +#: cps/updater.py:531 msgid "No release information available" msgstr "无可用发布信息" -#: cps/templates/index.html:5 cps/web.py:440 +#: cps/templates/index.html:5 cps/web.py:434 msgid "Discover (Random Books)" msgstr "发现(随机书籍)" -#: cps/web.py:471 +#: cps/web.py:470 msgid "Hot Books (Most Downloaded)" msgstr "热门书籍(最多下载)" -#: cps/web.py:507 +#: cps/web.py:501 #, python-format msgid "Downloaded books by %(user)s" msgstr "%(user)s 下载过的书籍" -#: cps/web.py:539 +#: cps/web.py:534 #, python-format msgid "Author: %(name)s" msgstr "作者:%(name)s" -#: cps/web.py:554 +#: cps/web.py:570 #, python-format msgid "Publisher: %(name)s" msgstr "出版社:%(name)s" -#: cps/web.py:569 +#: cps/web.py:598 #, python-format msgid "Series: %(serie)s" msgstr "丛书:%(serie)s" -#: cps/web.py:582 +#: cps/web.py:610 #, python-format msgid "Rating: %(rating)s stars" msgstr "评分:%(rating)s 星" -#: cps/web.py:597 +#: cps/web.py:626 #, python-format msgid "File format: %(format)s" msgstr "文件格式:%(format)s" -#: cps/web.py:615 +#: cps/web.py:663 #, python-format msgid "Category: %(name)s" msgstr "分类:%(name)s" -#: cps/web.py:631 +#: cps/web.py:690 #, python-format msgid "Language: %(name)s" msgstr "语言:%(name)s" -#: cps/templates/layout.html:56 cps/web.py:737 cps/web.py:1371 +#: cps/templates/layout.html:56 cps/web.py:789 cps/web.py:1444 msgid "Advanced Search" msgstr "高级搜索" #: cps/templates/book_edit.html:235 cps/templates/feed.xml:33 #: cps/templates/index.xml:11 cps/templates/layout.html:45 #: cps/templates/layout.html:48 cps/templates/search_form.html:226 -#: cps/web.py:750 cps/web.py:1077 +#: cps/web.py:807 cps/web.py:1164 msgid "Search" msgstr "搜索" -#: cps/templates/admin.html:16 cps/web.py:903 +#: cps/templates/admin.html:16 cps/web.py:979 msgid "Downloads" msgstr "下载次数" -#: cps/web.py:980 +#: cps/web.py:1068 msgid "Ratings list" msgstr "评分列表" -#: cps/web.py:1001 +#: cps/web.py:1095 msgid "File formats list" msgstr "文件格式列表" -#: cps/templates/layout.html:73 cps/templates/tasks.html:7 cps/web.py:1055 +#: cps/templates/layout.html:73 cps/templates/tasks.html:7 cps/web.py:1149 msgid "Tasks" msgstr "任务列表" -#: cps/web.py:1215 +#: cps/web.py:1286 msgid "Published after " msgstr "出版时间晚于 " -#: cps/web.py:1222 +#: cps/web.py:1293 msgid "Published before " msgstr "出版时间早于 " -#: cps/web.py:1244 +#: cps/web.py:1315 #, python-format msgid "Rating <= %(rating)s" msgstr "评分 <= %(rating)s" -#: cps/web.py:1246 +#: cps/web.py:1317 #, python-format msgid "Rating >= %(rating)s" msgstr "评分 >= %(rating)s" -#: cps/web.py:1248 +#: cps/web.py:1319 #, python-format msgid "Read Status = %(status)s" msgstr "阅读状态 = %(status)s" -#: cps/web.py:1353 +#: cps/web.py:1425 msgid "Error on search for custom columns, please restart Calibre-Web" msgstr "搜索自定义列时出错,请重启 Calibre-Web" -#: cps/web.py:1449 +#: cps/web.py:1527 #, python-format msgid "Book successfully queued for sending to %(kindlemail)s" msgstr "书籍已经成功加入 %(kindlemail)s 的发送队列" -#: cps/web.py:1453 +#: cps/web.py:1531 #, python-format msgid "Oops! There was an error sending this book: %(res)s" msgstr "糟糕!发送这本书籍的时候出现错误:%(res)s" -#: cps/web.py:1455 +#: cps/web.py:1533 msgid "Please update your profile with a valid Send to Kindle E-mail Address." msgstr "请先配置您的kindle邮箱。" -#: cps/web.py:1472 +#: cps/web.py:1550 msgid "E-Mail server is not configured, please contact your administrator!" msgstr "邮件服务未配置,请联系网站管理员!" -#: cps/templates/layout.html:85 cps/templates/register.html:17 cps/web.py:1473 -#: cps/web.py:1480 cps/web.py:1486 cps/web.py:1505 cps/web.py:1509 -#: cps/web.py:1515 +#: cps/templates/layout.html:85 cps/templates/register.html:17 cps/web.py:1551 +#: cps/web.py:1558 cps/web.py:1564 cps/web.py:1583 cps/web.py:1587 +#: cps/web.py:1593 msgid "Register" msgstr "注册" -#: cps/web.py:1507 +#: cps/web.py:1585 msgid "Your e-mail is not allowed to register" msgstr "您的电子邮件不允许注册" -#: cps/web.py:1510 +#: cps/web.py:1588 msgid "Confirmation e-mail was send to your e-mail account." msgstr "确认邮件已经发送到您的邮箱。" -#: cps/web.py:1524 +#: cps/web.py:1602 msgid "Cannot activate LDAP authentication" msgstr "无法激活LDAP认证" -#: cps/web.py:1543 +#: cps/web.py:1621 #, python-format msgid "Fallback Login as: '%(nickname)s', LDAP Server not reachable, or user not known" msgstr "后备登录“%(nickname)s”:无法访问LDAP服务器,或用户未知" -#: cps/web.py:1549 +#: cps/web.py:1627 #, python-format msgid "Could not login: %(message)s" msgstr "无法登录:%(message)s" -#: cps/web.py:1553 cps/web.py:1578 +#: cps/web.py:1631 cps/web.py:1656 msgid "Wrong Username or Password" msgstr "用户名或密码错误" -#: cps/web.py:1560 +#: cps/web.py:1638 msgid "New Password was send to your email address" msgstr "新密码已发送到您的邮箱" -#: cps/web.py:1566 +#: cps/web.py:1644 msgid "Please enter valid username to reset password" msgstr "请输入有效的用户名进行密码重置" -#: cps/web.py:1573 +#: cps/web.py:1651 #, python-format msgid "You are now logged in as: '%(nickname)s'" msgstr "您现在已以“%(nickname)s”登录" -#: cps/web.py:1639 cps/web.py:1688 +#: cps/web.py:1717 cps/web.py:1766 #, python-format msgid "%(name)s's profile" msgstr "%(name)s 的用户配置" -#: cps/web.py:1655 +#: cps/web.py:1733 msgid "Profile updated" msgstr "资料已更新" @@ -1309,36 +1331,36 @@ msgstr "资料已更新" msgid "Found no valid gmail.json file with OAuth information" msgstr "找不到包含 OAuth 信息的有效 gmail.json 文件" -#: cps/tasks/convert.py:137 +#: cps/tasks/convert.py:154 #, python-format msgid "Calibre ebook-convert %(tool)s not found" msgstr "Calibre 电子书转换器%(tool)s没有发现" -#: cps/tasks/convert.py:163 +#: cps/tasks/convert.py:187 #, python-format msgid "%(format)s format not found on disk" msgstr "硬盘上找不到 %(format)s 格式" -#: cps/tasks/convert.py:167 +#: cps/tasks/convert.py:191 msgid "Ebook converter failed with unknown error" msgstr "发生未知错误,书籍转换失败" -#: cps/tasks/convert.py:177 +#: cps/tasks/convert.py:201 #, python-format msgid "Kepubify-converter failed: %(error)s" msgstr "Kepubify 转换失败:%(error)s" -#: cps/tasks/convert.py:199 +#: cps/tasks/convert.py:223 #, python-format msgid "Converted file not found or more than one file in folder %(folder)s" msgstr "找不到转换后的文件或文件夹%(folder)s中有多个文件" -#: cps/tasks/convert.py:222 +#: cps/tasks/convert.py:246 #, python-format msgid "Ebook-converter failed: %(error)s" msgstr "电子书转换器失败: %(error)s" -#: cps/tasks/convert.py:245 +#: cps/tasks/convert.py:269 #, python-format msgid "Calibre failed with error: %(error)s" msgstr "Calibre 运行失败,错误信息:%(error)s" @@ -1393,7 +1415,7 @@ msgid "Edit" msgstr "编辑书籍" #: cps/templates/admin.html:25 cps/templates/book_edit.html:16 -#: cps/templates/book_table.html:97 cps/templates/modal_dialogs.html:63 +#: cps/templates/book_table.html:100 cps/templates/modal_dialogs.html:63 #: cps/templates/modal_dialogs.html:116 cps/templates/user_edit.html:67 #: cps/templates/user_table.html:149 msgid "Delete" @@ -1403,179 +1425,179 @@ msgstr "删除数据" msgid "Public Shelf" msgstr "公共书架" -#: cps/templates/admin.html:51 +#: cps/templates/admin.html:53 msgid "Add New User" msgstr "添加新用户" -#: cps/templates/admin.html:53 +#: cps/templates/admin.html:55 msgid "Import LDAP Users" msgstr "导入LDAP用户" -#: cps/templates/admin.html:60 +#: cps/templates/admin.html:62 msgid "E-mail Server Settings" msgstr "SMTP邮件服务器设置" -#: cps/templates/admin.html:65 cps/templates/email_edit.html:31 +#: cps/templates/admin.html:67 cps/templates/email_edit.html:31 msgid "SMTP Hostname" msgstr "SMTP主机名" -#: cps/templates/admin.html:69 cps/templates/email_edit.html:35 +#: cps/templates/admin.html:71 cps/templates/email_edit.html:35 msgid "SMTP Port" msgstr "SMTP端口" -#: cps/templates/admin.html:73 cps/templates/email_edit.html:39 +#: cps/templates/admin.html:75 cps/templates/email_edit.html:39 msgid "Encryption" msgstr "加密" -#: cps/templates/admin.html:77 cps/templates/email_edit.html:47 +#: cps/templates/admin.html:79 cps/templates/email_edit.html:47 msgid "SMTP Login" msgstr "SMTP用户名" -#: cps/templates/admin.html:81 cps/templates/admin.html:92 +#: cps/templates/admin.html:83 cps/templates/admin.html:94 #: cps/templates/email_edit.html:55 msgid "From E-mail" msgstr "发件人邮箱" -#: cps/templates/admin.html:88 +#: cps/templates/admin.html:90 msgid "E-Mail Service" msgstr "电子邮件服务" -#: cps/templates/admin.html:89 +#: cps/templates/admin.html:91 msgid "Gmail via Oauth2" msgstr "通过Oauth2的Gmail" -#: cps/templates/admin.html:104 +#: cps/templates/admin.html:106 msgid "Configuration" msgstr "配置" -#: cps/templates/admin.html:107 +#: cps/templates/admin.html:109 msgid "Calibre Database Directory" msgstr "Calibre 数据库路径" -#: cps/templates/admin.html:111 cps/templates/config_edit.html:68 +#: cps/templates/admin.html:113 cps/templates/config_edit.html:68 msgid "Log Level" msgstr "日志级别" -#: cps/templates/admin.html:115 +#: cps/templates/admin.html:117 msgid "Port" msgstr "端口" -#: cps/templates/admin.html:120 +#: cps/templates/admin.html:122 msgid "External Port" msgstr "扩展端口" -#: cps/templates/admin.html:127 cps/templates/config_view_edit.html:28 +#: cps/templates/admin.html:129 cps/templates/config_view_edit.html:28 msgid "Books per Page" msgstr "每页书籍数" -#: cps/templates/admin.html:131 +#: cps/templates/admin.html:133 msgid "Uploads" msgstr "上传" -#: cps/templates/admin.html:135 +#: cps/templates/admin.html:137 msgid "Anonymous Browsing" msgstr "匿名浏览" -#: cps/templates/admin.html:139 +#: cps/templates/admin.html:141 msgid "Public Registration" msgstr "开放注册" -#: cps/templates/admin.html:143 +#: cps/templates/admin.html:145 msgid "Magic Link Remote Login" msgstr "魔法链接远程登录" -#: cps/templates/admin.html:147 +#: cps/templates/admin.html:149 msgid "Reverse Proxy Login" msgstr "反向代理登录" -#: cps/templates/admin.html:152 cps/templates/config_edit.html:173 +#: cps/templates/admin.html:154 cps/templates/config_edit.html:173 msgid "Reverse Proxy Header Name" msgstr "反向代理头部名称" -#: cps/templates/admin.html:157 +#: cps/templates/admin.html:159 msgid "Edit Calibre Database Configuration" msgstr "编辑Calibre数据库配置" -#: cps/templates/admin.html:158 +#: cps/templates/admin.html:160 msgid "Edit Basic Configuration" msgstr "编辑基本配置" -#: cps/templates/admin.html:159 +#: cps/templates/admin.html:161 msgid "Edit UI Configuration" msgstr "编辑界面配置" -#: cps/templates/admin.html:164 +#: cps/templates/admin.html:166 msgid "Administration" msgstr "管理" -#: cps/templates/admin.html:165 +#: cps/templates/admin.html:167 msgid "Download Debug Package" msgstr "下载Debug包" -#: cps/templates/admin.html:166 +#: cps/templates/admin.html:168 msgid "View Logs" msgstr "查看日志文件" -#: cps/templates/admin.html:169 +#: cps/templates/admin.html:171 msgid "Reconnect Calibre Database" msgstr "重新连接到Calibre数据库" -#: cps/templates/admin.html:170 +#: cps/templates/admin.html:172 msgid "Restart" msgstr "重启" -#: cps/templates/admin.html:171 +#: cps/templates/admin.html:173 msgid "Shutdown" msgstr "停止" -#: cps/templates/admin.html:176 +#: cps/templates/admin.html:178 msgid "Update" msgstr "更新" -#: cps/templates/admin.html:180 +#: cps/templates/admin.html:182 msgid "Version" msgstr "版本" -#: cps/templates/admin.html:181 +#: cps/templates/admin.html:183 msgid "Details" msgstr "详情" -#: cps/templates/admin.html:187 +#: cps/templates/admin.html:189 msgid "Current version" msgstr "当前版本" -#: cps/templates/admin.html:194 +#: cps/templates/admin.html:196 msgid "Check for Update" msgstr "检查更新" -#: cps/templates/admin.html:195 +#: cps/templates/admin.html:197 msgid "Perform Update" msgstr "执行更新" -#: cps/templates/admin.html:208 +#: cps/templates/admin.html:210 msgid "Are you sure you want to restart?" msgstr "您确定要重启吗?" -#: cps/templates/admin.html:213 cps/templates/admin.html:227 -#: cps/templates/admin.html:247 cps/templates/config_db.html:70 +#: cps/templates/admin.html:215 cps/templates/admin.html:229 +#: cps/templates/admin.html:249 cps/templates/config_db.html:70 msgid "OK" msgstr "确定" -#: cps/templates/admin.html:214 cps/templates/admin.html:228 -#: cps/templates/book_edit.html:213 cps/templates/book_table.html:124 +#: cps/templates/admin.html:216 cps/templates/admin.html:230 +#: cps/templates/book_edit.html:213 cps/templates/book_table.html:127 #: cps/templates/config_db.html:54 cps/templates/config_edit.html:359 -#: cps/templates/config_view_edit.html:173 cps/templates/modal_dialogs.html:64 +#: cps/templates/config_view_edit.html:175 cps/templates/modal_dialogs.html:64 #: cps/templates/modal_dialogs.html:99 cps/templates/modal_dialogs.html:117 #: cps/templates/modal_dialogs.html:135 cps/templates/shelf_edit.html:27 #: cps/templates/user_edit.html:144 msgid "Cancel" msgstr "取消" -#: cps/templates/admin.html:226 +#: cps/templates/admin.html:228 msgid "Are you sure you want to shutdown?" msgstr "您确定要关闭吗?" -#: cps/templates/admin.html:238 +#: cps/templates/admin.html:240 msgid "Updating, please do not reload this page" msgstr "正在更新,请不要刷新页面" @@ -1587,44 +1609,43 @@ msgstr "通过" msgid "In Library" msgstr "在书库" -#: cps/templates/author.html:26 cps/templates/index.html:72 -#: cps/templates/search.html:29 cps/templates/shelf.html:19 +#: cps/templates/author.html:26 cps/templates/index.html:73 +#: cps/templates/search.html:30 cps/templates/shelf.html:19 msgid "Sort according to book date, newest first" msgstr "按图书日期排序,最新优先" -#: cps/templates/author.html:27 cps/templates/index.html:73 -#: cps/templates/search.html:30 cps/templates/shelf.html:20 +#: cps/templates/author.html:27 cps/templates/index.html:74 +#: cps/templates/search.html:31 cps/templates/shelf.html:20 msgid "Sort according to book date, oldest first" msgstr "按图书日期排序,最旧优先" -#: cps/templates/author.html:28 cps/templates/index.html:74 -#: cps/templates/search.html:31 cps/templates/shelf.html:21 +#: cps/templates/author.html:28 cps/templates/index.html:75 +#: cps/templates/search.html:32 cps/templates/shelf.html:21 msgid "Sort title in alphabetical order" msgstr "按标题按字母顺序排序" -#: cps/templates/author.html:29 cps/templates/index.html:75 -#: cps/templates/search.html:32 cps/templates/shelf.html:22 +#: cps/templates/author.html:29 cps/templates/index.html:76 +#: cps/templates/search.html:33 cps/templates/shelf.html:22 msgid "Sort title in reverse alphabetical order" msgstr "按标题逆字母顺序排序" -#: cps/templates/author.html:30 cps/templates/index.html:78 -#: cps/templates/search.html:35 cps/templates/shelf.html:25 +#: cps/templates/author.html:30 cps/templates/index.html:79 +#: cps/templates/search.html:36 cps/templates/shelf.html:25 msgid "Sort according to publishing date, newest first" msgstr "按出版日期排序,最新优先" -#: cps/templates/author.html:31 cps/templates/index.html:79 -#: cps/templates/search.html:36 cps/templates/shelf.html:26 +#: cps/templates/author.html:31 cps/templates/index.html:80 +#: cps/templates/search.html:37 cps/templates/shelf.html:26 msgid "Sort according to publishing date, oldest first" msgstr "按出版日期排序,最旧优先" -#: cps/templates/author.html:57 cps/templates/author.html:117 -#: cps/templates/discover.html:30 cps/templates/index.html:29 -#: cps/templates/index.html:111 cps/templates/search.html:65 -#: cps/templates/shelf.html:54 +#: cps/templates/author.html:56 cps/templates/author.html:115 +#: cps/templates/index.html:29 cps/templates/index.html:112 +#: cps/templates/search.html:66 cps/templates/shelf.html:54 msgid "reduce" msgstr "减少" -#: cps/templates/author.html:101 +#: cps/templates/author.html:99 msgid "More by" msgstr "更多" @@ -1749,7 +1770,7 @@ msgid "Fetch Metadata" msgstr "获取元数据" #: cps/templates/book_edit.html:212 cps/templates/config_db.html:53 -#: cps/templates/config_edit.html:358 cps/templates/config_view_edit.html:172 +#: cps/templates/config_edit.html:358 cps/templates/config_view_edit.html:174 #: cps/templates/email_edit.html:65 cps/templates/shelf_edit.html:25 #: cps/templates/shelf_order.html:41 cps/templates/user_edit.html:142 msgid "Save" @@ -1876,26 +1897,34 @@ msgstr "输入简介" msgid "Comments" msgstr "简介" -#: cps/templates/book_table.html:77 cps/templates/book_table.html:79 -#: cps/templates/book_table.html:81 cps/templates/book_table.html:83 -#: cps/templates/book_table.html:87 cps/templates/book_table.html:89 -#: cps/templates/book_table.html:91 cps/templates/book_table.html:93 +#: cps/templates/book_table.html:75 +msgid "Archiv Status" +msgstr "存档状态" + +#: cps/templates/book_table.html:77 cps/templates/search_form.html:42 +msgid "Read Status" +msgstr "阅读状态" + +#: cps/templates/book_table.html:80 cps/templates/book_table.html:82 +#: cps/templates/book_table.html:84 cps/templates/book_table.html:86 +#: cps/templates/book_table.html:90 cps/templates/book_table.html:92 +#: cps/templates/book_table.html:96 msgid "Enter " msgstr "输入" -#: cps/templates/book_table.html:110 cps/templates/modal_dialogs.html:46 +#: cps/templates/book_table.html:113 cps/templates/modal_dialogs.html:46 msgid "Are you really sure?" msgstr "您真的确认?" -#: cps/templates/book_table.html:114 +#: cps/templates/book_table.html:117 msgid "Books with Title will be merged from:" msgstr "这本书籍将被合并:" -#: cps/templates/book_table.html:118 +#: cps/templates/book_table.html:121 msgid "Into Book with Title:" msgstr "合并到这本书籍:" -#: cps/templates/book_table.html:123 +#: cps/templates/book_table.html:126 msgid "Merge" msgstr "合并" @@ -2071,11 +2100,6 @@ msgstr "LDAP服务器端口" msgid "LDAP Encryption" msgstr "LDAP 加密" -#: cps/templates/config_edit.html:204 cps/templates/config_view_edit.html:62 -#: cps/templates/email_edit.html:41 -msgid "None" -msgstr "无" - #: cps/templates/config_edit.html:205 msgid "TLS" msgstr "TLS协议" @@ -2292,11 +2316,11 @@ msgstr "新用户默认显示权限" msgid "Show Random Books in Detail View" msgstr "在主页显示随机书籍" -#: cps/templates/config_view_edit.html:165 cps/templates/user_edit.html:87 +#: cps/templates/config_view_edit.html:166 cps/templates/user_edit.html:87 msgid "Add Allowed/Denied Tags" msgstr "添加显示或隐藏书籍的标签值" -#: cps/templates/config_view_edit.html:166 +#: cps/templates/config_view_edit.html:167 msgid "Add Allowed/Denied custom column values" msgstr "添加显示或隐藏书籍的自定义栏目值" @@ -2345,13 +2369,13 @@ msgstr "归档" msgid "Description:" msgstr "简介:" -#: cps/templates/detail.html:256 cps/templates/search.html:14 +#: cps/templates/detail.html:256 cps/templates/search.html:15 msgid "Add to shelf" msgstr "添加到书架" #: cps/templates/detail.html:267 cps/templates/detail.html:284 #: cps/templates/feed.xml:79 cps/templates/layout.html:137 -#: cps/templates/search.html:20 +#: cps/templates/search.html:21 msgid "(Public)" msgstr "(公共)" @@ -2429,10 +2453,14 @@ msgstr "禁止注册的域名(黑名单)" msgid "Next" msgstr "下一个" -#: cps/templates/generate_kobo_auth_url.html:5 +#: cps/templates/generate_kobo_auth_url.html:6 msgid "Open the .kobo/Kobo eReader.conf file in a text editor and add (or edit):" msgstr "在文本编辑器中打开.kobo/Kobo eReader.conf,添加(或编辑):" +#: cps/templates/generate_kobo_auth_url.html:11 +msgid "Kobo Token:" +msgstr "Kobo Token:" + #: cps/templates/http_error.html:31 msgid "Calibre-Web Instance is unconfigured, please contact your administrator" msgstr "Calibre-Web 实例未配置,请联系您的管理员!" @@ -2449,29 +2477,29 @@ msgstr "回到首页" msgid "Logout User" msgstr "登出账号" -#: cps/templates/index.html:69 +#: cps/templates/index.html:70 msgid "Sort ascending according to download count" msgstr "按下载数排序" -#: cps/templates/index.html:70 +#: cps/templates/index.html:71 msgid "Sort descending according to download count" msgstr "按下载数逆序排序" -#: cps/templates/index.html:76 cps/templates/search.html:33 +#: cps/templates/index.html:77 cps/templates/search.html:34 #: cps/templates/shelf.html:23 msgid "Sort authors in alphabetical order" msgstr "按作者字母顺序排序" -#: cps/templates/index.html:77 cps/templates/search.html:34 +#: cps/templates/index.html:78 cps/templates/search.html:35 #: cps/templates/shelf.html:24 msgid "Sort authors in reverse alphabetical order" msgstr "按作者逆字母顺序排序" -#: cps/templates/index.html:81 +#: cps/templates/index.html:82 msgid "Sort ascending according to series index" msgstr "按丛书编号排序" -#: cps/templates/index.html:82 +#: cps/templates/index.html:83 msgid "Sort descending according to series index" msgstr "按丛书编号逆排序" @@ -2901,10 +2929,6 @@ msgstr "出版日期从" msgid "Published Date To" msgstr "出版日期到" -#: cps/templates/search_form.html:42 -msgid "Read Status" -msgstr "阅读状态" - #: cps/templates/search_form.html:59 msgid "Exclude Tags" msgstr "排除标签" @@ -3067,7 +3091,7 @@ msgstr "新建或查看" #: cps/templates/user_edit.html:70 msgid "Force full kobo sync" -msgstr "" +msgstr "强制与kobo完全同步" #: cps/templates/user_edit.html:88 msgid "Add allowed/Denied Custom Column Values" diff --git a/messages.pot b/messages.pot index 7fc39068..b3c09809 100644 --- a/messages.pot +++ b/messages.pot @@ -8,585 +8,588 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2022-01-15 09:19+0100\n" +"POT-Creation-Date: 2022-04-18 12:20+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.9.0\n" +"Generated-By: Babel 2.9.1\n" -#: cps/about.py:34 cps/about.py:49 cps/about.py:65 cps/converter.py:31 -msgid "not installed" -msgstr "" - -#: cps/about.py:47 cps/about.py:63 -msgid "installed" -msgstr "" - -#: cps/about.py:145 +#: cps/about.py:86 msgid "Statistics" msgstr "" -#: cps/admin.py:144 +#: cps/admin.py:141 msgid "Server restarted, please reload page" msgstr "" -#: cps/admin.py:146 +#: cps/admin.py:143 msgid "Performing shutdown of server, please close window" msgstr "" -#: cps/admin.py:154 +#: cps/admin.py:151 msgid "Reconnect successful" msgstr "" -#: cps/admin.py:157 +#: cps/admin.py:154 msgid "Unknown command" msgstr "" -#: cps/admin.py:167 cps/editbooks.py:707 cps/editbooks.py:721 -#: cps/editbooks.py:866 cps/editbooks.py:868 cps/editbooks.py:895 -#: cps/editbooks.py:911 cps/updater.py:584 cps/uploader.py:93 -#: cps/uploader.py:103 +#: cps/admin.py:176 cps/editbooks.py:713 cps/editbooks.py:892 +#: cps/editbooks.py:894 cps/editbooks.py:930 cps/editbooks.py:947 +#: cps/updater.py:608 cps/uploader.py:93 cps/uploader.py:103 msgid "Unknown" msgstr "" -#: cps/admin.py:188 +#: cps/admin.py:197 msgid "Admin page" msgstr "" -#: cps/admin.py:207 +#: cps/admin.py:217 msgid "Basic Configuration" msgstr "" -#: cps/admin.py:244 +#: cps/admin.py:255 msgid "UI Configuration" msgstr "" -#: cps/admin.py:277 cps/templates/admin.html:50 +#: cps/admin.py:289 cps/templates/admin.html:51 msgid "Edit Users" msgstr "" -#: cps/admin.py:318 cps/opds.py:109 cps/opds.py:198 cps/opds.py:275 -#: cps/opds.py:327 cps/templates/grid.html:13 cps/templates/languages.html:9 +#: cps/admin.py:333 cps/opds.py:529 cps/templates/grid.html:13 #: cps/templates/list.html:13 msgid "All" msgstr "" -#: cps/admin.py:343 cps/admin.py:1615 +#: cps/admin.py:360 cps/admin.py:1648 msgid "User not found" msgstr "" -#: cps/admin.py:357 +#: cps/admin.py:374 msgid "{} users deleted successfully" msgstr "" -#: cps/admin.py:379 cps/templates/config_view_edit.html:133 +#: cps/admin.py:397 cps/templates/config_view_edit.html:133 #: cps/templates/user_edit.html:45 cps/templates/user_table.html:81 msgid "Show All" msgstr "" -#: cps/admin.py:400 cps/admin.py:406 +#: cps/admin.py:418 cps/admin.py:424 msgid "Malformed request" msgstr "" -#: cps/admin.py:418 cps/admin.py:1493 +#: cps/admin.py:436 cps/admin.py:1526 msgid "Guest Name can't be changed" msgstr "" -#: cps/admin.py:430 +#: cps/admin.py:448 msgid "Guest can't have this role" msgstr "" -#: cps/admin.py:442 cps/admin.py:1451 +#: cps/admin.py:460 cps/admin.py:1484 msgid "No admin user remaining, can't remove admin role" msgstr "" -#: cps/admin.py:446 cps/admin.py:460 +#: cps/admin.py:464 cps/admin.py:478 msgid "Value has to be true or false" msgstr "" -#: cps/admin.py:448 +#: cps/admin.py:466 msgid "Invalid role" msgstr "" -#: cps/admin.py:452 +#: cps/admin.py:470 msgid "Guest can't have this view" msgstr "" -#: cps/admin.py:462 +#: cps/admin.py:480 msgid "Invalid view" msgstr "" -#: cps/admin.py:465 +#: cps/admin.py:483 msgid "Guest's Locale is determined automatically and can't be set" msgstr "" -#: cps/admin.py:469 +#: cps/admin.py:487 msgid "No Valid Locale Given" msgstr "" -#: cps/admin.py:480 +#: cps/admin.py:498 msgid "No Valid Book Language Given" msgstr "" -#: cps/admin.py:482 +#: cps/admin.py:500 cps/editbooks.py:1267 msgid "Parameter not found" msgstr "" -#: cps/admin.py:533 +#: cps/admin.py:553 msgid "Invalid Read Column" msgstr "" -#: cps/admin.py:539 +#: cps/admin.py:559 msgid "Invalid Restricted Column" msgstr "" -#: cps/admin.py:560 cps/admin.py:1323 +#: cps/admin.py:579 cps/admin.py:1355 msgid "Calibre-Web configuration updated" msgstr "" -#: cps/admin.py:572 +#: cps/admin.py:591 msgid "Do you really want to delete the Kobo Token?" msgstr "" -#: cps/admin.py:574 +#: cps/admin.py:593 msgid "Do you really want to delete this domain?" msgstr "" -#: cps/admin.py:576 +#: cps/admin.py:595 msgid "Do you really want to delete this user?" msgstr "" -#: cps/admin.py:578 +#: cps/admin.py:597 msgid "Are you sure you want to delete this shelf?" msgstr "" -#: cps/admin.py:580 +#: cps/admin.py:599 msgid "Are you sure you want to change locales of selected user(s)?" msgstr "" -#: cps/admin.py:582 +#: cps/admin.py:601 msgid "Are you sure you want to change visible book languages for selected user(s)?" msgstr "" -#: cps/admin.py:584 +#: cps/admin.py:603 msgid "Are you sure you want to change the selected role for the selected user(s)?" msgstr "" -#: cps/admin.py:586 +#: cps/admin.py:605 msgid "Are you sure you want to change the selected restrictions for the selected user(s)?" msgstr "" -#: cps/admin.py:588 +#: cps/admin.py:607 msgid "Are you sure you want to change the selected visibility restrictions for the selected user(s)?" msgstr "" -#: cps/admin.py:590 +#: cps/admin.py:610 msgid "Are you sure you want to change shelf sync behavior for the selected user(s)?" msgstr "" -#: cps/admin.py:592 +#: cps/admin.py:612 msgid "Are you sure you want to change Calibre library location?" msgstr "" -#: cps/admin.py:594 +#: cps/admin.py:614 msgid "Are you sure you want delete Calibre-Web's sync database to force a full sync with your Kobo Reader?" msgstr "" -#: cps/admin.py:743 +#: cps/admin.py:764 msgid "Tag not found" msgstr "" -#: cps/admin.py:755 +#: cps/admin.py:776 msgid "Invalid Action" msgstr "" -#: cps/admin.py:871 cps/admin.py:877 cps/admin.py:887 cps/admin.py:897 +#: cps/admin.py:893 cps/admin.py:899 cps/admin.py:909 cps/admin.py:919 #: cps/templates/modal_dialogs.html:29 cps/templates/user_table.html:41 #: cps/templates/user_table.html:58 msgid "Deny" msgstr "" -#: cps/admin.py:873 cps/admin.py:879 cps/admin.py:889 cps/admin.py:899 +#: cps/admin.py:895 cps/admin.py:901 cps/admin.py:911 cps/admin.py:921 #: cps/templates/modal_dialogs.html:28 cps/templates/user_table.html:44 #: cps/templates/user_table.html:61 msgid "Allow" msgstr "" -#: cps/admin.py:913 +#: cps/admin.py:936 msgid "{} sync entries deleted" msgstr "" -#: cps/admin.py:1036 +#: cps/admin.py:1059 msgid "client_secrets.json Is Not Configured For Web Application" msgstr "" -#: cps/admin.py:1081 +#: cps/admin.py:1104 msgid "Logfile Location is not Valid, Please Enter Correct Path" msgstr "" -#: cps/admin.py:1087 +#: cps/admin.py:1110 msgid "Access Logfile Location is not Valid, Please Enter Correct Path" msgstr "" -#: cps/admin.py:1117 +#: cps/admin.py:1140 msgid "Please Enter a LDAP Provider, Port, DN and User Object Identifier" msgstr "" -#: cps/admin.py:1123 +#: cps/admin.py:1146 msgid "Please Enter a LDAP Service Account and Password" msgstr "" -#: cps/admin.py:1126 +#: cps/admin.py:1149 msgid "Please Enter a LDAP Service Account" msgstr "" -#: cps/admin.py:1131 +#: cps/admin.py:1154 #, python-format msgid "LDAP Group Object Filter Needs to Have One \"%s\" Format Identifier" msgstr "" -#: cps/admin.py:1133 +#: cps/admin.py:1156 msgid "LDAP Group Object Filter Has Unmatched Parenthesis" msgstr "" -#: cps/admin.py:1137 +#: cps/admin.py:1160 #, python-format msgid "LDAP User Object Filter needs to Have One \"%s\" Format Identifier" msgstr "" -#: cps/admin.py:1139 +#: cps/admin.py:1162 msgid "LDAP User Object Filter Has Unmatched Parenthesis" msgstr "" -#: cps/admin.py:1146 +#: cps/admin.py:1169 #, python-format msgid "LDAP Member User Filter needs to Have One \"%s\" Format Identifier" msgstr "" -#: cps/admin.py:1148 +#: cps/admin.py:1171 msgid "LDAP Member User Filter Has Unmatched Parenthesis" msgstr "" -#: cps/admin.py:1155 +#: cps/admin.py:1178 msgid "LDAP CACertificate, Certificate or Key Location is not Valid, Please Enter Correct Path" msgstr "" -#: cps/admin.py:1197 cps/admin.py:1308 cps/admin.py:1405 cps/admin.py:1521 -#: cps/admin.py:1590 cps/shelf.py:100 cps/shelf.py:160 cps/shelf.py:203 -#: cps/shelf.py:279 cps/shelf.py:341 cps/shelf.py:376 cps/shelf.py:451 -msgid "Settings DB is not Writeable" +#: cps/admin.py:1223 cps/admin.py:1339 cps/admin.py:1437 cps/admin.py:1554 +#: cps/admin.py:1623 cps/editbooks.py:678 cps/editbooks.py:882 +#: cps/editbooks.py:1130 cps/shelf.py:100 cps/shelf.py:160 cps/shelf.py:203 +#: cps/shelf.py:278 cps/shelf.py:343 cps/shelf.py:380 cps/shelf.py:456 +#: cps/web.py:1742 +#, python-format +msgid "Database error: %(error)s." msgstr "" -#: cps/admin.py:1208 +#: cps/admin.py:1235 msgid "DB Location is not Valid, Please Enter Correct Path" msgstr "" -#: cps/admin.py:1223 +#: cps/admin.py:1253 msgid "DB is not Writeable" msgstr "" -#: cps/admin.py:1235 +#: cps/admin.py:1266 msgid "Keyfile Location is not Valid, Please Enter Correct Path" msgstr "" -#: cps/admin.py:1239 +#: cps/admin.py:1270 msgid "Certfile Location is not Valid, Please Enter Correct Path" msgstr "" -#: cps/admin.py:1346 +#: cps/admin.py:1378 msgid "Database Settings updated" msgstr "" -#: cps/admin.py:1354 +#: cps/admin.py:1386 msgid "Database Configuration" msgstr "" -#: cps/admin.py:1370 cps/web.py:1479 +#: cps/admin.py:1402 cps/web.py:1557 msgid "Please fill out all fields!" msgstr "" -#: cps/admin.py:1378 +#: cps/admin.py:1410 msgid "E-mail is not from valid domain" msgstr "" -#: cps/admin.py:1384 cps/admin.py:1543 +#: cps/admin.py:1416 cps/admin.py:1576 msgid "Add new user" msgstr "" -#: cps/admin.py:1395 +#: cps/admin.py:1427 #, python-format msgid "User '%(user)s' created" msgstr "" -#: cps/admin.py:1401 +#: cps/admin.py:1433 msgid "Found an existing account for this e-mail address or name." msgstr "" -#: cps/admin.py:1430 +#: cps/admin.py:1463 #, python-format msgid "User '%(nick)s' deleted" msgstr "" -#: cps/admin.py:1432 cps/admin.py:1433 +#: cps/admin.py:1465 cps/admin.py:1466 msgid "Can't delete Guest User" msgstr "" -#: cps/admin.py:1436 +#: cps/admin.py:1469 msgid "No admin user remaining, can't delete user" msgstr "" -#: cps/admin.py:1509 cps/admin.py:1634 +#: cps/admin.py:1542 cps/admin.py:1667 #, python-format msgid "Edit User %(nick)s" msgstr "" -#: cps/admin.py:1513 +#: cps/admin.py:1546 #, python-format msgid "User '%(nick)s' updated" msgstr "" -#: cps/admin.py:1517 cps/admin.py:1649 cps/web.py:1504 cps/web.py:1564 +#: cps/admin.py:1550 cps/admin.py:1682 cps/web.py:1582 cps/web.py:1642 msgid "An unknown error occurred. Please try again later." msgstr "" -#: cps/admin.py:1552 cps/templates/admin.html:98 +#: cps/admin.py:1585 cps/templates/admin.html:100 msgid "Edit E-mail Server Settings" msgstr "" -#: cps/admin.py:1571 +#: cps/admin.py:1604 msgid "Gmail Account Verification Successful" msgstr "" -#: cps/admin.py:1597 +#: cps/admin.py:1630 #, python-format msgid "Test e-mail queued for sending to %(email)s, please check Tasks for result" msgstr "" -#: cps/admin.py:1600 +#: cps/admin.py:1633 #, python-format msgid "There was an error sending the Test e-mail: %(res)s" msgstr "" -#: cps/admin.py:1602 +#: cps/admin.py:1635 msgid "Please configure your e-mail address first..." msgstr "" -#: cps/admin.py:1604 +#: cps/admin.py:1637 msgid "E-mail server settings updated" msgstr "" -#: cps/admin.py:1646 +#: cps/admin.py:1679 #, python-format msgid "Password for user %(user)s reset" msgstr "" -#: cps/admin.py:1652 cps/web.py:1444 +#: cps/admin.py:1685 cps/web.py:1522 msgid "Please configure the SMTP mail settings first..." msgstr "" -#: cps/admin.py:1663 +#: cps/admin.py:1696 msgid "Logfile viewer" msgstr "" -#: cps/admin.py:1729 +#: cps/admin.py:1762 msgid "Requesting update package" msgstr "" -#: cps/admin.py:1730 +#: cps/admin.py:1763 msgid "Downloading update package" msgstr "" -#: cps/admin.py:1731 +#: cps/admin.py:1764 msgid "Unzipping update package" msgstr "" -#: cps/admin.py:1732 +#: cps/admin.py:1765 msgid "Replacing files" msgstr "" -#: cps/admin.py:1733 +#: cps/admin.py:1766 msgid "Database connections are closed" msgstr "" -#: cps/admin.py:1734 +#: cps/admin.py:1767 msgid "Stopping server" msgstr "" -#: cps/admin.py:1735 +#: cps/admin.py:1768 msgid "Update finished, please press okay and reload page" msgstr "" -#: cps/admin.py:1736 cps/admin.py:1737 cps/admin.py:1738 cps/admin.py:1739 -#: cps/admin.py:1740 cps/admin.py:1741 +#: cps/admin.py:1769 cps/admin.py:1770 cps/admin.py:1771 cps/admin.py:1772 +#: cps/admin.py:1773 cps/admin.py:1774 msgid "Update failed:" msgstr "" -#: cps/admin.py:1736 cps/updater.py:385 cps/updater.py:595 cps/updater.py:597 +#: cps/admin.py:1769 cps/updater.py:384 cps/updater.py:619 cps/updater.py:621 msgid "HTTP Error" msgstr "" -#: cps/admin.py:1737 cps/updater.py:387 cps/updater.py:599 +#: cps/admin.py:1770 cps/updater.py:386 cps/updater.py:623 msgid "Connection error" msgstr "" -#: cps/admin.py:1738 cps/updater.py:389 cps/updater.py:601 +#: cps/admin.py:1771 cps/updater.py:388 cps/updater.py:625 msgid "Timeout while establishing connection" msgstr "" -#: cps/admin.py:1739 cps/updater.py:391 cps/updater.py:603 +#: cps/admin.py:1772 cps/updater.py:390 cps/updater.py:627 msgid "General error" msgstr "" -#: cps/admin.py:1740 +#: cps/admin.py:1773 msgid "Update file could not be saved in temp dir" msgstr "" -#: cps/admin.py:1741 +#: cps/admin.py:1774 msgid "Files could not be replaced during update" msgstr "" -#: cps/admin.py:1765 +#: cps/admin.py:1798 msgid "Failed to extract at least One LDAP User" msgstr "" -#: cps/admin.py:1810 +#: cps/admin.py:1843 msgid "Failed to Create at Least One LDAP User" msgstr "" -#: cps/admin.py:1823 +#: cps/admin.py:1856 #, python-format msgid "Error: %(ldaperror)s" msgstr "" -#: cps/admin.py:1827 +#: cps/admin.py:1860 msgid "Error: No user returned in response of LDAP server" msgstr "" -#: cps/admin.py:1860 +#: cps/admin.py:1893 msgid "At Least One LDAP User Not Found in Database" msgstr "" -#: cps/admin.py:1862 +#: cps/admin.py:1895 msgid "{} User Successfully Imported" msgstr "" #: cps/converter.py:30 -msgid "not configured" +msgid "not installed" msgstr "" -#: cps/converter.py:32 +#: cps/converter.py:31 msgid "Execution permissions missing" msgstr "" -#: cps/db.py:651 cps/web.py:667 cps/web.py:1155 +#: cps/db.py:674 cps/web.py:710 cps/web.py:1222 #, python-format msgid "Custom Column No.%(column)d is not existing in calibre database" msgstr "" -#: cps/editbooks.py:300 cps/editbooks.py:302 +#: cps/db.py:917 cps/templates/config_edit.html:204 +#: cps/templates/config_view_edit.html:62 cps/templates/email_edit.html:41 +#: cps/web.py:551 cps/web.py:585 cps/web.py:646 cps/web.py:671 cps/web.py:1003 +#: cps/web.py:1032 cps/web.py:1066 cps/web.py:1093 cps/web.py:1132 +msgid "None" +msgstr "" + +#: cps/editbooks.py:295 cps/editbooks.py:297 msgid "Book Format Successfully Deleted" msgstr "" -#: cps/editbooks.py:309 cps/editbooks.py:311 +#: cps/editbooks.py:304 cps/editbooks.py:306 msgid "Book Successfully Deleted" msgstr "" -#: cps/editbooks.py:361 +#: cps/editbooks.py:358 msgid "You are missing permissions to delete books" msgstr "" -#: cps/editbooks.py:376 cps/editbooks.py:763 cps/web.py:523 cps/web.py:1703 -#: cps/web.py:1744 cps/web.py:1811 +#: cps/editbooks.py:373 cps/editbooks.py:765 cps/web.py:518 cps/web.py:1783 +#: cps/web.py:1825 cps/web.py:1870 msgid "Oops! Selected book title is unavailable. File does not exist or is not accessible" msgstr "" -#: cps/editbooks.py:410 +#: cps/editbooks.py:408 msgid "edit metadata" msgstr "" -#: cps/editbooks.py:458 +#: cps/editbooks.py:457 #, python-format msgid "%(seriesindex)s is not a valid number, skipping" msgstr "" -#: cps/editbooks.py:494 cps/editbooks.py:958 +#: cps/editbooks.py:493 cps/editbooks.py:1001 #, python-format msgid "'%(langname)s' is not a valid language" msgstr "" -#: cps/editbooks.py:634 cps/editbooks.py:985 +#: cps/editbooks.py:634 +msgid "User has no rights to upload additional file formats" +msgstr "" + +#: cps/editbooks.py:639 cps/editbooks.py:1029 #, python-format msgid "File extension '%(ext)s' is not allowed to be uploaded to this server" msgstr "" -#: cps/editbooks.py:638 cps/editbooks.py:989 +#: cps/editbooks.py:643 cps/editbooks.py:1033 msgid "File to be uploaded must have an extension" msgstr "" -#: cps/editbooks.py:650 +#: cps/editbooks.py:655 #, python-format msgid "Failed to create path %(path)s (Permission denied)." msgstr "" -#: cps/editbooks.py:655 +#: cps/editbooks.py:660 #, python-format msgid "Failed to store file %(file)s." msgstr "" -#: cps/editbooks.py:673 cps/editbooks.py:1076 cps/web.py:1664 +#: cps/editbooks.py:683 #, python-format -msgid "Database error: %(error)s." +msgid "File format %(ext)s added to %(book)s" msgstr "" -#: cps/editbooks.py:678 -#, python-format -msgid "File format %(ext)s added to %(book)s" +#: cps/editbooks.py:697 cps/editbooks.py:809 +msgid "User has no rights to upload cover" msgstr "" -#: cps/editbooks.py:814 +#: cps/editbooks.py:828 msgid "Identifiers are not Case Sensitive, Overwriting Old Identifier" msgstr "" -#: cps/editbooks.py:848 +#: cps/editbooks.py:869 msgid "Metadata successfully updated" msgstr "" -#: cps/editbooks.py:861 -msgid "Error editing book, please check logfile for details" +#: cps/editbooks.py:887 +msgid "Error editing book: {}" msgstr "" -#: cps/editbooks.py:899 +#: cps/editbooks.py:951 msgid "Uploaded book probably exists in the library, consider to change before upload new: " msgstr "" -#: cps/editbooks.py:997 +#: cps/editbooks.py:1041 #, python-format msgid "File %(filename)s could not saved to temp dir" msgstr "" -#: cps/editbooks.py:1016 +#: cps/editbooks.py:1061 #, python-format msgid "Failed to Move Cover File %(file)s: %(error)s" msgstr "" -#: cps/editbooks.py:1063 +#: cps/editbooks.py:1117 #, python-format msgid "File %(file)s uploaded" msgstr "" -#: cps/editbooks.py:1088 +#: cps/editbooks.py:1143 msgid "Source or destination format for conversion missing" msgstr "" -#: cps/editbooks.py:1096 +#: cps/editbooks.py:1151 #, python-format msgid "Book successfully queued for converting to %(book_format)s" msgstr "" -#: cps/editbooks.py:1100 +#: cps/editbooks.py:1155 #, python-format msgid "There was an error converting this book: %(res)s" msgstr "" @@ -599,174 +602,190 @@ msgstr "" msgid "Callback domain is not verified, please follow steps to verify domain in google developer console" msgstr "" -#: cps/helper.py:77 +#: cps/helper.py:81 #, python-format msgid "%(format)s format not found for book id: %(book)d" msgstr "" -#: cps/helper.py:83 cps/tasks/convert.py:73 +#: cps/helper.py:87 cps/tasks/convert.py:75 #, python-format msgid "%(format)s not found on Google Drive: %(fn)s" msgstr "" -#: cps/helper.py:88 +#: cps/helper.py:92 #, python-format msgid "%(format)s not found: %(fn)s" msgstr "" -#: cps/helper.py:93 cps/helper.py:217 cps/templates/detail.html:41 +#: cps/helper.py:97 cps/helper.py:221 cps/templates/detail.html:41 #: cps/templates/detail.html:45 msgid "Send to Kindle" msgstr "" -#: cps/helper.py:94 cps/helper.py:111 cps/helper.py:219 +#: cps/helper.py:98 cps/helper.py:115 cps/helper.py:223 msgid "This e-mail has been sent via Calibre-Web." msgstr "" -#: cps/helper.py:109 +#: cps/helper.py:113 msgid "Calibre-Web test e-mail" msgstr "" -#: cps/helper.py:110 +#: cps/helper.py:114 msgid "Test e-mail" msgstr "" -#: cps/helper.py:127 +#: cps/helper.py:131 msgid "Get Started with Calibre-Web" msgstr "" -#: cps/helper.py:132 +#: cps/helper.py:136 #, python-format msgid "Registration e-mail for user: %(name)s" msgstr "" -#: cps/helper.py:143 cps/helper.py:149 +#: cps/helper.py:147 cps/helper.py:153 #, python-format msgid "Convert %(orig)s to %(format)s and send to Kindle" msgstr "" -#: cps/helper.py:168 cps/helper.py:172 cps/helper.py:176 +#: cps/helper.py:172 cps/helper.py:176 cps/helper.py:180 #, python-format msgid "Send %(format)s to Kindle" msgstr "" -#: cps/helper.py:216 cps/tasks/convert.py:90 +#: cps/helper.py:220 cps/tasks/convert.py:92 #, python-format msgid "%(book)s send to Kindle" msgstr "" -#: cps/helper.py:221 +#: cps/helper.py:225 msgid "The requested file could not be read. Maybe wrong permissions?" msgstr "" -#: cps/helper.py:313 +#: cps/helper.py:353 +msgid "Read status could not set: {}" +msgstr "" + +#: cps/helper.py:376 #, python-format msgid "Deleting bookfolder for book %(id)s failed, path has subfolders: %(path)s" msgstr "" -#: cps/helper.py:319 +#: cps/helper.py:382 #, python-format msgid "Deleting book %(id)s failed: %(message)s" msgstr "" -#: cps/helper.py:330 +#: cps/helper.py:393 #, python-format msgid "Deleting book %(id)s from database only, book path in database not valid: %(path)s" msgstr "" -#: cps/helper.py:385 +#: cps/helper.py:458 #, python-format -msgid "Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s" +msgid "Rename author from: '%(src)s' to '%(dest)s' failed with error: %(error)s" msgstr "" -#: cps/helper.py:400 +#: cps/helper.py:529 cps/helper.py:538 #, python-format -msgid "Rename file in path '%(src)s' to '%(dest)s' failed with error: %(error)s" +msgid "File %(file)s not found on Google Drive" msgstr "" -#: cps/helper.py:425 cps/helper.py:435 cps/helper.py:443 +#: cps/helper.py:572 #, python-format -msgid "File %(file)s not found on Google Drive" +msgid "Rename title from: '%(src)s' to '%(dest)s' failed with error: %(error)s" +msgstr "" + +#: cps/helper.py:592 +msgid "Error in rename file in path: {}" msgstr "" -#: cps/helper.py:464 +#: cps/helper.py:610 #, python-format msgid "Book path %(path)s not found on Google Drive" msgstr "" -#: cps/helper.py:504 cps/web.py:1659 +#: cps/helper.py:651 cps/web.py:1737 msgid "Found an existing account for this e-mail address" msgstr "" -#: cps/helper.py:512 +#: cps/helper.py:659 msgid "This username is already taken" msgstr "" -#: cps/helper.py:522 +#: cps/helper.py:669 msgid "Invalid e-mail address format" msgstr "" -#: cps/helper.py:595 +#: cps/helper.py:754 +msgid "Python modul 'advocate' is not installed but is needed for cover downloads" +msgstr "" + +#: cps/helper.py:767 msgid "Error Downloading Cover" msgstr "" -#: cps/helper.py:598 +#: cps/helper.py:770 msgid "Cover Format Error" msgstr "" -#: cps/helper.py:608 +#: cps/helper.py:773 +msgid "You are not allowed to access localhost or the local network for cover uploads" +msgstr "" + +#: cps/helper.py:783 msgid "Failed to create path for cover" msgstr "" -#: cps/helper.py:624 +#: cps/helper.py:799 msgid "Cover-file is not a valid image file, or could not be stored" msgstr "" -#: cps/helper.py:635 +#: cps/helper.py:810 msgid "Only jpg/jpeg/png/webp/bmp files are supported as coverfile" msgstr "" -#: cps/helper.py:648 +#: cps/helper.py:822 msgid "Invalid cover file content" msgstr "" -#: cps/helper.py:652 +#: cps/helper.py:826 msgid "Only jpg/jpeg files are supported as coverfile" msgstr "" -#: cps/helper.py:704 +#: cps/helper.py:878 msgid "Unrar binary file not found" msgstr "" -#: cps/helper.py:715 +#: cps/helper.py:889 msgid "Error excecuting UnRar" msgstr "" -#: cps/helper.py:763 +#: cps/helper.py:937 msgid "Waiting" msgstr "" -#: cps/helper.py:765 +#: cps/helper.py:939 msgid "Failed" msgstr "" -#: cps/helper.py:767 +#: cps/helper.py:941 msgid "Started" msgstr "" -#: cps/helper.py:769 +#: cps/helper.py:943 msgid "Finished" msgstr "" -#: cps/helper.py:771 +#: cps/helper.py:945 msgid "Unknown Status" msgstr "" -#: cps/kobo_auth.py:127 -msgid "PLease access calibre-web from non localhost to get valid api_endpoint for kobo device" +#: cps/kobo_auth.py:128 +msgid "Please access Calibre-Web from non localhost to get valid api_endpoint for kobo device" msgstr "" -#: cps/kobo_auth.py:130 cps/kobo_auth.py:158 +#: cps/kobo_auth.py:154 msgid "Kobo Setup" msgstr "" @@ -775,7 +794,7 @@ msgstr "" msgid "Register with %(provider)s" msgstr "" -#: cps/oauth_bb.py:138 cps/remotelogin.py:133 cps/web.py:1535 +#: cps/oauth_bb.py:138 cps/remotelogin.py:130 cps/web.py:1613 #, python-format msgid "you are now logged in as: '%(nickname)s'" msgstr "" @@ -836,163 +855,163 @@ msgstr "" msgid "Google Oauth error: {}" msgstr "" -#: cps/opds.py:384 +#: cps/opds.py:298 msgid "{} Stars" msgstr "" -#: cps/remotelogin.py:65 cps/templates/layout.html:84 -#: cps/templates/login.html:4 cps/templates/login.html:21 cps/web.py:1584 +#: cps/remotelogin.py:62 cps/templates/layout.html:84 +#: cps/templates/login.html:4 cps/templates/login.html:21 cps/web.py:1662 msgid "Login" msgstr "" -#: cps/remotelogin.py:77 cps/remotelogin.py:111 +#: cps/remotelogin.py:74 cps/remotelogin.py:108 msgid "Token not found" msgstr "" -#: cps/remotelogin.py:86 cps/remotelogin.py:119 +#: cps/remotelogin.py:83 cps/remotelogin.py:116 msgid "Token has expired" msgstr "" -#: cps/remotelogin.py:95 +#: cps/remotelogin.py:92 msgid "Success! Please return to your device" msgstr "" -#: cps/render_template.py:39 cps/web.py:416 +#: cps/render_template.py:41 cps/web.py:407 msgid "Books" msgstr "" -#: cps/render_template.py:41 +#: cps/render_template.py:43 msgid "Show recent books" msgstr "" -#: cps/render_template.py:42 cps/templates/index.xml:25 +#: cps/render_template.py:44 cps/templates/index.xml:25 msgid "Hot Books" msgstr "" -#: cps/render_template.py:44 +#: cps/render_template.py:46 msgid "Show Hot Books" msgstr "" -#: cps/render_template.py:46 cps/render_template.py:51 +#: cps/render_template.py:48 cps/render_template.py:53 msgid "Downloaded Books" msgstr "" -#: cps/render_template.py:48 cps/render_template.py:53 +#: cps/render_template.py:50 cps/render_template.py:55 #: cps/templates/user_table.html:167 msgid "Show Downloaded Books" msgstr "" -#: cps/render_template.py:56 cps/templates/index.xml:32 cps/web.py:430 +#: cps/render_template.py:58 cps/templates/index.xml:32 cps/web.py:422 msgid "Top Rated Books" msgstr "" -#: cps/render_template.py:58 cps/templates/user_table.html:161 +#: cps/render_template.py:60 cps/templates/user_table.html:161 msgid "Show Top Rated Books" msgstr "" -#: cps/render_template.py:59 cps/templates/index.xml:54 -#: cps/templates/index.xml:58 cps/web.py:676 +#: cps/render_template.py:61 cps/templates/index.xml:54 +#: cps/templates/index.xml:58 cps/web.py:729 msgid "Read Books" msgstr "" -#: cps/render_template.py:61 +#: cps/render_template.py:63 msgid "Show read and unread" msgstr "" -#: cps/render_template.py:63 cps/templates/index.xml:61 -#: cps/templates/index.xml:65 cps/web.py:679 +#: cps/render_template.py:65 cps/templates/index.xml:61 +#: cps/templates/index.xml:65 cps/web.py:732 msgid "Unread Books" msgstr "" -#: cps/render_template.py:65 +#: cps/render_template.py:67 msgid "Show unread" msgstr "" -#: cps/render_template.py:66 +#: cps/render_template.py:68 msgid "Discover" msgstr "" -#: cps/render_template.py:68 cps/templates/index.xml:50 +#: cps/render_template.py:70 cps/templates/index.xml:50 #: cps/templates/user_table.html:162 msgid "Show Random Books" msgstr "" -#: cps/render_template.py:69 cps/templates/book_table.html:67 -#: cps/templates/index.xml:83 cps/web.py:1041 +#: cps/render_template.py:71 cps/templates/book_table.html:67 +#: cps/templates/index.xml:83 cps/web.py:1135 msgid "Categories" msgstr "" -#: cps/render_template.py:71 cps/templates/user_table.html:158 +#: cps/render_template.py:73 cps/templates/user_table.html:158 msgid "Show category selection" msgstr "" -#: cps/render_template.py:72 cps/templates/book_edit.html:90 +#: cps/render_template.py:74 cps/templates/book_edit.html:90 #: cps/templates/book_table.html:68 cps/templates/index.xml:90 -#: cps/templates/search_form.html:69 cps/web.py:948 cps/web.py:959 +#: cps/templates/search_form.html:69 cps/web.py:1034 cps/web.py:1041 msgid "Series" msgstr "" -#: cps/render_template.py:74 cps/templates/user_table.html:157 +#: cps/render_template.py:76 cps/templates/user_table.html:157 msgid "Show series selection" msgstr "" -#: cps/render_template.py:75 cps/templates/book_table.html:66 +#: cps/render_template.py:77 cps/templates/book_table.html:66 #: cps/templates/index.xml:69 msgid "Authors" msgstr "" -#: cps/render_template.py:77 cps/templates/user_table.html:160 +#: cps/render_template.py:79 cps/templates/user_table.html:160 msgid "Show author selection" msgstr "" -#: cps/render_template.py:79 cps/templates/book_table.html:72 -#: cps/templates/index.xml:76 cps/web.py:925 +#: cps/render_template.py:81 cps/templates/book_table.html:72 +#: cps/templates/index.xml:76 cps/web.py:1006 msgid "Publishers" msgstr "" -#: cps/render_template.py:81 cps/templates/user_table.html:163 +#: cps/render_template.py:83 cps/templates/user_table.html:163 msgid "Show publisher selection" msgstr "" -#: cps/render_template.py:82 cps/templates/book_table.html:70 +#: cps/render_template.py:84 cps/templates/book_table.html:70 #: cps/templates/index.xml:97 cps/templates/search_form.html:107 -#: cps/web.py:1018 +#: cps/web.py:1108 msgid "Languages" msgstr "" -#: cps/render_template.py:85 cps/templates/user_table.html:155 +#: cps/render_template.py:87 cps/templates/user_table.html:155 msgid "Show language selection" msgstr "" -#: cps/render_template.py:86 cps/templates/index.xml:104 +#: cps/render_template.py:88 cps/templates/index.xml:104 msgid "Ratings" msgstr "" -#: cps/render_template.py:88 cps/templates/user_table.html:164 +#: cps/render_template.py:90 cps/templates/user_table.html:164 msgid "Show ratings selection" msgstr "" -#: cps/render_template.py:89 cps/templates/index.xml:112 +#: cps/render_template.py:91 cps/templates/index.xml:112 msgid "File formats" msgstr "" -#: cps/render_template.py:91 cps/templates/user_table.html:165 +#: cps/render_template.py:93 cps/templates/user_table.html:165 msgid "Show file formats selection" msgstr "" -#: cps/render_template.py:93 cps/web.py:703 +#: cps/render_template.py:95 cps/web.py:755 msgid "Archived Books" msgstr "" -#: cps/render_template.py:95 cps/templates/user_table.html:166 +#: cps/render_template.py:97 cps/templates/user_table.html:166 msgid "Show archived books" msgstr "" -#: cps/render_template.py:97 cps/web.py:780 +#: cps/render_template.py:100 cps/web.py:837 msgid "Books List" msgstr "" -#: cps/render_template.py:99 cps/templates/user_table.html:168 +#: cps/render_template.py:102 cps/templates/user_table.html:168 msgid "Show Books List" msgstr "" @@ -1046,260 +1065,264 @@ msgstr "" msgid "Create a Shelf" msgstr "" -#: cps/shelf.py:237 +#: cps/shelf.py:236 msgid "Sorry you are not allowed to edit this shelf" msgstr "" -#: cps/shelf.py:239 +#: cps/shelf.py:238 msgid "Edit a shelf" msgstr "" -#: cps/shelf.py:249 +#: cps/shelf.py:248 msgid "Sorry you are not allowed to create a public shelf" msgstr "" -#: cps/shelf.py:266 +#: cps/shelf.py:265 #, python-format msgid "Shelf %(title)s created" msgstr "" -#: cps/shelf.py:269 +#: cps/shelf.py:268 #, python-format msgid "Shelf %(title)s changed" msgstr "" -#: cps/shelf.py:283 +#: cps/shelf.py:282 msgid "There was an error" msgstr "" -#: cps/shelf.py:305 +#: cps/shelf.py:304 #, python-format msgid "A public shelf with the name '%(title)s' already exists." msgstr "" -#: cps/shelf.py:316 +#: cps/shelf.py:315 #, python-format msgid "A private shelf with the name '%(title)s' already exists." msgstr "" #: cps/shelf.py:337 +msgid "Error deleting Shelf" +msgstr "" + +#: cps/shelf.py:339 msgid "Shelf successfully deleted" msgstr "" -#: cps/shelf.py:386 +#: cps/shelf.py:389 #, python-format msgid "Change order of Shelf: '%(name)s'" msgstr "" -#: cps/shelf.py:456 +#: cps/shelf.py:461 #, python-format msgid "Shelf: '%(name)s'" msgstr "" -#: cps/shelf.py:460 +#: cps/shelf.py:465 msgid "Error opening shelf. Shelf does not exist or is not accessible" msgstr "" -#: cps/updater.py:403 cps/updater.py:414 cps/updater.py:514 cps/updater.py:529 +#: cps/updater.py:426 cps/updater.py:437 cps/updater.py:538 cps/updater.py:553 msgid "Unexpected data while reading update information" msgstr "" -#: cps/updater.py:410 cps/updater.py:521 +#: cps/updater.py:433 cps/updater.py:545 msgid "No update available. You already have the latest version installed" msgstr "" -#: cps/updater.py:428 +#: cps/updater.py:451 msgid "A new update is available. Click on the button below to update to the latest version." msgstr "" -#: cps/updater.py:446 +#: cps/updater.py:469 msgid "Could not fetch update information" msgstr "" -#: cps/updater.py:456 +#: cps/updater.py:479 msgid "Click on the button below to update to the latest stable version." msgstr "" -#: cps/updater.py:465 cps/updater.py:479 cps/updater.py:490 +#: cps/updater.py:488 cps/updater.py:502 cps/updater.py:513 #, python-format msgid "A new update is available. Click on the button below to update to version: %(version)s" msgstr "" -#: cps/updater.py:507 +#: cps/updater.py:531 msgid "No release information available" msgstr "" -#: cps/templates/index.html:5 cps/web.py:440 +#: cps/templates/index.html:5 cps/web.py:434 msgid "Discover (Random Books)" msgstr "" -#: cps/web.py:471 +#: cps/web.py:470 msgid "Hot Books (Most Downloaded)" msgstr "" -#: cps/web.py:507 +#: cps/web.py:501 #, python-format msgid "Downloaded books by %(user)s" msgstr "" -#: cps/web.py:539 +#: cps/web.py:534 #, python-format msgid "Author: %(name)s" msgstr "" -#: cps/web.py:554 +#: cps/web.py:570 #, python-format msgid "Publisher: %(name)s" msgstr "" -#: cps/web.py:569 +#: cps/web.py:598 #, python-format msgid "Series: %(serie)s" msgstr "" -#: cps/web.py:582 +#: cps/web.py:610 #, python-format msgid "Rating: %(rating)s stars" msgstr "" -#: cps/web.py:597 +#: cps/web.py:626 #, python-format msgid "File format: %(format)s" msgstr "" -#: cps/web.py:615 +#: cps/web.py:663 #, python-format msgid "Category: %(name)s" msgstr "" -#: cps/web.py:631 +#: cps/web.py:690 #, python-format msgid "Language: %(name)s" msgstr "" -#: cps/templates/layout.html:56 cps/web.py:737 cps/web.py:1371 +#: cps/templates/layout.html:56 cps/web.py:789 cps/web.py:1444 msgid "Advanced Search" msgstr "" #: cps/templates/book_edit.html:235 cps/templates/feed.xml:33 #: cps/templates/index.xml:11 cps/templates/layout.html:45 #: cps/templates/layout.html:48 cps/templates/search_form.html:226 -#: cps/web.py:750 cps/web.py:1077 +#: cps/web.py:807 cps/web.py:1164 msgid "Search" msgstr "" -#: cps/templates/admin.html:16 cps/web.py:903 +#: cps/templates/admin.html:16 cps/web.py:979 msgid "Downloads" msgstr "" -#: cps/web.py:980 +#: cps/web.py:1068 msgid "Ratings list" msgstr "" -#: cps/web.py:1001 +#: cps/web.py:1095 msgid "File formats list" msgstr "" -#: cps/templates/layout.html:73 cps/templates/tasks.html:7 cps/web.py:1055 +#: cps/templates/layout.html:73 cps/templates/tasks.html:7 cps/web.py:1149 msgid "Tasks" msgstr "" -#: cps/web.py:1215 +#: cps/web.py:1286 msgid "Published after " msgstr "" -#: cps/web.py:1222 +#: cps/web.py:1293 msgid "Published before " msgstr "" -#: cps/web.py:1244 +#: cps/web.py:1315 #, python-format msgid "Rating <= %(rating)s" msgstr "" -#: cps/web.py:1246 +#: cps/web.py:1317 #, python-format msgid "Rating >= %(rating)s" msgstr "" -#: cps/web.py:1248 +#: cps/web.py:1319 #, python-format msgid "Read Status = %(status)s" msgstr "" -#: cps/web.py:1353 +#: cps/web.py:1425 msgid "Error on search for custom columns, please restart Calibre-Web" msgstr "" -#: cps/web.py:1449 +#: cps/web.py:1527 #, python-format msgid "Book successfully queued for sending to %(kindlemail)s" msgstr "" -#: cps/web.py:1453 +#: cps/web.py:1531 #, python-format msgid "Oops! There was an error sending this book: %(res)s" msgstr "" -#: cps/web.py:1455 +#: cps/web.py:1533 msgid "Please update your profile with a valid Send to Kindle E-mail Address." msgstr "" -#: cps/web.py:1472 +#: cps/web.py:1550 msgid "E-Mail server is not configured, please contact your administrator!" msgstr "" -#: cps/templates/layout.html:85 cps/templates/register.html:17 cps/web.py:1473 -#: cps/web.py:1480 cps/web.py:1486 cps/web.py:1505 cps/web.py:1509 -#: cps/web.py:1515 +#: cps/templates/layout.html:85 cps/templates/register.html:17 cps/web.py:1551 +#: cps/web.py:1558 cps/web.py:1564 cps/web.py:1583 cps/web.py:1587 +#: cps/web.py:1593 msgid "Register" msgstr "" -#: cps/web.py:1507 +#: cps/web.py:1585 msgid "Your e-mail is not allowed to register" msgstr "" -#: cps/web.py:1510 +#: cps/web.py:1588 msgid "Confirmation e-mail was send to your e-mail account." msgstr "" -#: cps/web.py:1524 +#: cps/web.py:1602 msgid "Cannot activate LDAP authentication" msgstr "" -#: cps/web.py:1543 +#: cps/web.py:1621 #, python-format msgid "Fallback Login as: '%(nickname)s', LDAP Server not reachable, or user not known" msgstr "" -#: cps/web.py:1549 +#: cps/web.py:1627 #, python-format msgid "Could not login: %(message)s" msgstr "" -#: cps/web.py:1553 cps/web.py:1578 +#: cps/web.py:1631 cps/web.py:1656 msgid "Wrong Username or Password" msgstr "" -#: cps/web.py:1560 +#: cps/web.py:1638 msgid "New Password was send to your email address" msgstr "" -#: cps/web.py:1566 +#: cps/web.py:1644 msgid "Please enter valid username to reset password" msgstr "" -#: cps/web.py:1573 +#: cps/web.py:1651 #, python-format msgid "You are now logged in as: '%(nickname)s'" msgstr "" -#: cps/web.py:1639 cps/web.py:1688 +#: cps/web.py:1717 cps/web.py:1766 #, python-format msgid "%(name)s's profile" msgstr "" -#: cps/web.py:1655 +#: cps/web.py:1733 msgid "Profile updated" msgstr "" @@ -1307,36 +1330,36 @@ msgstr "" msgid "Found no valid gmail.json file with OAuth information" msgstr "" -#: cps/tasks/convert.py:137 +#: cps/tasks/convert.py:154 #, python-format msgid "Calibre ebook-convert %(tool)s not found" msgstr "" -#: cps/tasks/convert.py:163 +#: cps/tasks/convert.py:187 #, python-format msgid "%(format)s format not found on disk" msgstr "" -#: cps/tasks/convert.py:167 +#: cps/tasks/convert.py:191 msgid "Ebook converter failed with unknown error" msgstr "" -#: cps/tasks/convert.py:177 +#: cps/tasks/convert.py:201 #, python-format msgid "Kepubify-converter failed: %(error)s" msgstr "" -#: cps/tasks/convert.py:199 +#: cps/tasks/convert.py:223 #, python-format msgid "Converted file not found or more than one file in folder %(folder)s" msgstr "" -#: cps/tasks/convert.py:222 +#: cps/tasks/convert.py:246 #, python-format msgid "Ebook-converter failed: %(error)s" msgstr "" -#: cps/tasks/convert.py:245 +#: cps/tasks/convert.py:269 #, python-format msgid "Calibre failed with error: %(error)s" msgstr "" @@ -1391,7 +1414,7 @@ msgid "Edit" msgstr "" #: cps/templates/admin.html:25 cps/templates/book_edit.html:16 -#: cps/templates/book_table.html:97 cps/templates/modal_dialogs.html:63 +#: cps/templates/book_table.html:100 cps/templates/modal_dialogs.html:63 #: cps/templates/modal_dialogs.html:116 cps/templates/user_edit.html:67 #: cps/templates/user_table.html:149 msgid "Delete" @@ -1401,179 +1424,179 @@ msgstr "" msgid "Public Shelf" msgstr "" -#: cps/templates/admin.html:51 +#: cps/templates/admin.html:53 msgid "Add New User" msgstr "" -#: cps/templates/admin.html:53 +#: cps/templates/admin.html:55 msgid "Import LDAP Users" msgstr "" -#: cps/templates/admin.html:60 +#: cps/templates/admin.html:62 msgid "E-mail Server Settings" msgstr "" -#: cps/templates/admin.html:65 cps/templates/email_edit.html:31 +#: cps/templates/admin.html:67 cps/templates/email_edit.html:31 msgid "SMTP Hostname" msgstr "" -#: cps/templates/admin.html:69 cps/templates/email_edit.html:35 +#: cps/templates/admin.html:71 cps/templates/email_edit.html:35 msgid "SMTP Port" msgstr "" -#: cps/templates/admin.html:73 cps/templates/email_edit.html:39 +#: cps/templates/admin.html:75 cps/templates/email_edit.html:39 msgid "Encryption" msgstr "" -#: cps/templates/admin.html:77 cps/templates/email_edit.html:47 +#: cps/templates/admin.html:79 cps/templates/email_edit.html:47 msgid "SMTP Login" msgstr "" -#: cps/templates/admin.html:81 cps/templates/admin.html:92 +#: cps/templates/admin.html:83 cps/templates/admin.html:94 #: cps/templates/email_edit.html:55 msgid "From E-mail" msgstr "" -#: cps/templates/admin.html:88 +#: cps/templates/admin.html:90 msgid "E-Mail Service" msgstr "" -#: cps/templates/admin.html:89 +#: cps/templates/admin.html:91 msgid "Gmail via Oauth2" msgstr "" -#: cps/templates/admin.html:104 +#: cps/templates/admin.html:106 msgid "Configuration" msgstr "" -#: cps/templates/admin.html:107 +#: cps/templates/admin.html:109 msgid "Calibre Database Directory" msgstr "" -#: cps/templates/admin.html:111 cps/templates/config_edit.html:68 +#: cps/templates/admin.html:113 cps/templates/config_edit.html:68 msgid "Log Level" msgstr "" -#: cps/templates/admin.html:115 +#: cps/templates/admin.html:117 msgid "Port" msgstr "" -#: cps/templates/admin.html:120 +#: cps/templates/admin.html:122 msgid "External Port" msgstr "" -#: cps/templates/admin.html:127 cps/templates/config_view_edit.html:28 +#: cps/templates/admin.html:129 cps/templates/config_view_edit.html:28 msgid "Books per Page" msgstr "" -#: cps/templates/admin.html:131 +#: cps/templates/admin.html:133 msgid "Uploads" msgstr "" -#: cps/templates/admin.html:135 +#: cps/templates/admin.html:137 msgid "Anonymous Browsing" msgstr "" -#: cps/templates/admin.html:139 +#: cps/templates/admin.html:141 msgid "Public Registration" msgstr "" -#: cps/templates/admin.html:143 +#: cps/templates/admin.html:145 msgid "Magic Link Remote Login" msgstr "" -#: cps/templates/admin.html:147 +#: cps/templates/admin.html:149 msgid "Reverse Proxy Login" msgstr "" -#: cps/templates/admin.html:152 cps/templates/config_edit.html:173 +#: cps/templates/admin.html:154 cps/templates/config_edit.html:173 msgid "Reverse Proxy Header Name" msgstr "" -#: cps/templates/admin.html:157 +#: cps/templates/admin.html:159 msgid "Edit Calibre Database Configuration" msgstr "" -#: cps/templates/admin.html:158 +#: cps/templates/admin.html:160 msgid "Edit Basic Configuration" msgstr "" -#: cps/templates/admin.html:159 +#: cps/templates/admin.html:161 msgid "Edit UI Configuration" msgstr "" -#: cps/templates/admin.html:164 +#: cps/templates/admin.html:166 msgid "Administration" msgstr "" -#: cps/templates/admin.html:165 +#: cps/templates/admin.html:167 msgid "Download Debug Package" msgstr "" -#: cps/templates/admin.html:166 +#: cps/templates/admin.html:168 msgid "View Logs" msgstr "" -#: cps/templates/admin.html:169 +#: cps/templates/admin.html:171 msgid "Reconnect Calibre Database" msgstr "" -#: cps/templates/admin.html:170 +#: cps/templates/admin.html:172 msgid "Restart" msgstr "" -#: cps/templates/admin.html:171 +#: cps/templates/admin.html:173 msgid "Shutdown" msgstr "" -#: cps/templates/admin.html:176 +#: cps/templates/admin.html:178 msgid "Update" msgstr "" -#: cps/templates/admin.html:180 +#: cps/templates/admin.html:182 msgid "Version" msgstr "" -#: cps/templates/admin.html:181 +#: cps/templates/admin.html:183 msgid "Details" msgstr "" -#: cps/templates/admin.html:187 +#: cps/templates/admin.html:189 msgid "Current version" msgstr "" -#: cps/templates/admin.html:194 +#: cps/templates/admin.html:196 msgid "Check for Update" msgstr "" -#: cps/templates/admin.html:195 +#: cps/templates/admin.html:197 msgid "Perform Update" msgstr "" -#: cps/templates/admin.html:208 +#: cps/templates/admin.html:210 msgid "Are you sure you want to restart?" msgstr "" -#: cps/templates/admin.html:213 cps/templates/admin.html:227 -#: cps/templates/admin.html:247 cps/templates/config_db.html:70 +#: cps/templates/admin.html:215 cps/templates/admin.html:229 +#: cps/templates/admin.html:249 cps/templates/config_db.html:70 msgid "OK" msgstr "" -#: cps/templates/admin.html:214 cps/templates/admin.html:228 -#: cps/templates/book_edit.html:213 cps/templates/book_table.html:124 +#: cps/templates/admin.html:216 cps/templates/admin.html:230 +#: cps/templates/book_edit.html:213 cps/templates/book_table.html:127 #: cps/templates/config_db.html:54 cps/templates/config_edit.html:359 -#: cps/templates/config_view_edit.html:173 cps/templates/modal_dialogs.html:64 +#: cps/templates/config_view_edit.html:175 cps/templates/modal_dialogs.html:64 #: cps/templates/modal_dialogs.html:99 cps/templates/modal_dialogs.html:117 #: cps/templates/modal_dialogs.html:135 cps/templates/shelf_edit.html:27 #: cps/templates/user_edit.html:144 msgid "Cancel" msgstr "" -#: cps/templates/admin.html:226 +#: cps/templates/admin.html:228 msgid "Are you sure you want to shutdown?" msgstr "" -#: cps/templates/admin.html:238 +#: cps/templates/admin.html:240 msgid "Updating, please do not reload this page" msgstr "" @@ -1585,44 +1608,43 @@ msgstr "" msgid "In Library" msgstr "" -#: cps/templates/author.html:26 cps/templates/index.html:72 -#: cps/templates/search.html:29 cps/templates/shelf.html:19 +#: cps/templates/author.html:26 cps/templates/index.html:73 +#: cps/templates/search.html:30 cps/templates/shelf.html:19 msgid "Sort according to book date, newest first" msgstr "" -#: cps/templates/author.html:27 cps/templates/index.html:73 -#: cps/templates/search.html:30 cps/templates/shelf.html:20 +#: cps/templates/author.html:27 cps/templates/index.html:74 +#: cps/templates/search.html:31 cps/templates/shelf.html:20 msgid "Sort according to book date, oldest first" msgstr "" -#: cps/templates/author.html:28 cps/templates/index.html:74 -#: cps/templates/search.html:31 cps/templates/shelf.html:21 +#: cps/templates/author.html:28 cps/templates/index.html:75 +#: cps/templates/search.html:32 cps/templates/shelf.html:21 msgid "Sort title in alphabetical order" msgstr "" -#: cps/templates/author.html:29 cps/templates/index.html:75 -#: cps/templates/search.html:32 cps/templates/shelf.html:22 +#: cps/templates/author.html:29 cps/templates/index.html:76 +#: cps/templates/search.html:33 cps/templates/shelf.html:22 msgid "Sort title in reverse alphabetical order" msgstr "" -#: cps/templates/author.html:30 cps/templates/index.html:78 -#: cps/templates/search.html:35 cps/templates/shelf.html:25 +#: cps/templates/author.html:30 cps/templates/index.html:79 +#: cps/templates/search.html:36 cps/templates/shelf.html:25 msgid "Sort according to publishing date, newest first" msgstr "" -#: cps/templates/author.html:31 cps/templates/index.html:79 -#: cps/templates/search.html:36 cps/templates/shelf.html:26 +#: cps/templates/author.html:31 cps/templates/index.html:80 +#: cps/templates/search.html:37 cps/templates/shelf.html:26 msgid "Sort according to publishing date, oldest first" msgstr "" -#: cps/templates/author.html:57 cps/templates/author.html:117 -#: cps/templates/discover.html:30 cps/templates/index.html:29 -#: cps/templates/index.html:111 cps/templates/search.html:65 -#: cps/templates/shelf.html:54 +#: cps/templates/author.html:56 cps/templates/author.html:115 +#: cps/templates/index.html:29 cps/templates/index.html:112 +#: cps/templates/search.html:66 cps/templates/shelf.html:54 msgid "reduce" msgstr "" -#: cps/templates/author.html:101 +#: cps/templates/author.html:99 msgid "More by" msgstr "" @@ -1747,7 +1769,7 @@ msgid "Fetch Metadata" msgstr "" #: cps/templates/book_edit.html:212 cps/templates/config_db.html:53 -#: cps/templates/config_edit.html:358 cps/templates/config_view_edit.html:172 +#: cps/templates/config_edit.html:358 cps/templates/config_view_edit.html:174 #: cps/templates/email_edit.html:65 cps/templates/shelf_edit.html:25 #: cps/templates/shelf_order.html:41 cps/templates/user_edit.html:142 msgid "Save" @@ -1874,26 +1896,34 @@ msgstr "" msgid "Comments" msgstr "" -#: cps/templates/book_table.html:77 cps/templates/book_table.html:79 -#: cps/templates/book_table.html:81 cps/templates/book_table.html:83 -#: cps/templates/book_table.html:87 cps/templates/book_table.html:89 -#: cps/templates/book_table.html:91 cps/templates/book_table.html:93 +#: cps/templates/book_table.html:75 +msgid "Archiv Status" +msgstr "" + +#: cps/templates/book_table.html:77 cps/templates/search_form.html:42 +msgid "Read Status" +msgstr "" + +#: cps/templates/book_table.html:80 cps/templates/book_table.html:82 +#: cps/templates/book_table.html:84 cps/templates/book_table.html:86 +#: cps/templates/book_table.html:90 cps/templates/book_table.html:92 +#: cps/templates/book_table.html:96 msgid "Enter " msgstr "" -#: cps/templates/book_table.html:110 cps/templates/modal_dialogs.html:46 +#: cps/templates/book_table.html:113 cps/templates/modal_dialogs.html:46 msgid "Are you really sure?" msgstr "" -#: cps/templates/book_table.html:114 +#: cps/templates/book_table.html:117 msgid "Books with Title will be merged from:" msgstr "" -#: cps/templates/book_table.html:118 +#: cps/templates/book_table.html:121 msgid "Into Book with Title:" msgstr "" -#: cps/templates/book_table.html:123 +#: cps/templates/book_table.html:126 msgid "Merge" msgstr "" @@ -2069,11 +2099,6 @@ msgstr "" msgid "LDAP Encryption" msgstr "" -#: cps/templates/config_edit.html:204 cps/templates/config_view_edit.html:62 -#: cps/templates/email_edit.html:41 -msgid "None" -msgstr "" - #: cps/templates/config_edit.html:205 msgid "TLS" msgstr "" @@ -2290,11 +2315,11 @@ msgstr "" msgid "Show Random Books in Detail View" msgstr "" -#: cps/templates/config_view_edit.html:165 cps/templates/user_edit.html:87 +#: cps/templates/config_view_edit.html:166 cps/templates/user_edit.html:87 msgid "Add Allowed/Denied Tags" msgstr "" -#: cps/templates/config_view_edit.html:166 +#: cps/templates/config_view_edit.html:167 msgid "Add Allowed/Denied custom column values" msgstr "" @@ -2343,13 +2368,13 @@ msgstr "" msgid "Description:" msgstr "" -#: cps/templates/detail.html:256 cps/templates/search.html:14 +#: cps/templates/detail.html:256 cps/templates/search.html:15 msgid "Add to shelf" msgstr "" #: cps/templates/detail.html:267 cps/templates/detail.html:284 #: cps/templates/feed.xml:79 cps/templates/layout.html:137 -#: cps/templates/search.html:20 +#: cps/templates/search.html:21 msgid "(Public)" msgstr "" @@ -2427,10 +2452,14 @@ msgstr "" msgid "Next" msgstr "" -#: cps/templates/generate_kobo_auth_url.html:5 +#: cps/templates/generate_kobo_auth_url.html:6 msgid "Open the .kobo/Kobo eReader.conf file in a text editor and add (or edit):" msgstr "" +#: cps/templates/generate_kobo_auth_url.html:11 +msgid "Kobo Token:" +msgstr "" + #: cps/templates/http_error.html:31 msgid "Calibre-Web Instance is unconfigured, please contact your administrator" msgstr "" @@ -2447,29 +2476,29 @@ msgstr "" msgid "Logout User" msgstr "" -#: cps/templates/index.html:69 +#: cps/templates/index.html:70 msgid "Sort ascending according to download count" msgstr "" -#: cps/templates/index.html:70 +#: cps/templates/index.html:71 msgid "Sort descending according to download count" msgstr "" -#: cps/templates/index.html:76 cps/templates/search.html:33 +#: cps/templates/index.html:77 cps/templates/search.html:34 #: cps/templates/shelf.html:23 msgid "Sort authors in alphabetical order" msgstr "" -#: cps/templates/index.html:77 cps/templates/search.html:34 +#: cps/templates/index.html:78 cps/templates/search.html:35 #: cps/templates/shelf.html:24 msgid "Sort authors in reverse alphabetical order" msgstr "" -#: cps/templates/index.html:81 +#: cps/templates/index.html:82 msgid "Sort ascending according to series index" msgstr "" -#: cps/templates/index.html:82 +#: cps/templates/index.html:83 msgid "Sort descending according to series index" msgstr "" @@ -2899,10 +2928,6 @@ msgstr "" msgid "Published Date To" msgstr "" -#: cps/templates/search_form.html:42 -msgid "Read Status" -msgstr "" - #: cps/templates/search_form.html:59 msgid "Exclude Tags" msgstr "" From a8680a45cadca5bdf5c20c17210ce15842a7437d Mon Sep 17 00:00:00 2001 From: Ozzie Isaacs Date: Tue, 19 Apr 2022 15:05:41 +0200 Subject: [PATCH 12/13] Bugfixes from Testrun Update teststatus --- cps/db.py | 7 +- cps/metadata_provider/amazon.py | 18 +- cps/templates/list.html | 2 +- cps/web.py | 40 +- test/Calibre-Web TestSummary_Linux.html | 2083 +++++++++++++++++------ 5 files changed, 1568 insertions(+), 582 deletions(-) diff --git a/cps/db.py b/cps/db.py index ff392274..68e02b81 100644 --- a/cps/db.py +++ b/cps/db.py @@ -914,7 +914,8 @@ class CalibreDB: .filter(Languages.lang_code == None) .filter(self.common_filters()) .count()) - tags.append([Category(_("None"), "none"), no_lang_count]) + if no_lang_count: + tags.append([Category(_("None"), "none"), no_lang_count]) return sorted(tags, key=lambda x: x[0].name, reverse=reverse_order) else: if not languages: @@ -993,10 +994,12 @@ class Category: name = None id = None count = None + rating = None - def __init__(self, name, cat_id): + def __init__(self, name, cat_id, rating=None): self.name = name self.id = cat_id + self.rating = rating self.count = 1 '''class Count: diff --git a/cps/metadata_provider/amazon.py b/cps/metadata_provider/amazon.py index 7de0d415..da3aed79 100644 --- a/cps/metadata_provider/amazon.py +++ b/cps/metadata_provider/amazon.py @@ -56,13 +56,13 @@ class Amazon(Metadata): self, query: str, generic_cover: str = "", locale: str = "en" ) -> Optional[List[MetaRecord]]: #timer=time() - def inner(link,index) -> tuple[dict,int]: + def inner(link, index) -> [dict, int]: with self.session as session: try: r = session.get(f"https://www.amazon.com/{link}") r.raise_for_status() - except Exception as e: - log.warning(e) + except Exception as ex: + log.warning(ex) return long_soup = BS(r.text, "lxml") #~4sec :/ soup2 = long_soup.find("div", attrs={"cel_widget_id": "dpx-books-ppd_csm_instrumentation_wrapper"}) @@ -126,16 +126,16 @@ class Amazon(Metadata): headers=self.headers) results.raise_for_status() except requests.exceptions.HTTPError as e: - log.error_or_exception(e) - return None + log.error_or_exception(e) + return None except Exception as e: - log.warning(e) - return None + log.warning(e) + return None soup = BS(results.text, 'html.parser') links_list = [next(filter(lambda i: "digital-text" in i["href"], x.findAll("a")))["href"] for x in soup.findAll("div", attrs={"data-component-type": "s-search-result"})] with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: fut = {executor.submit(inner, link, index) for index, link in enumerate(links_list[:5])} - val=list(map(lambda x : x.result() ,concurrent.futures.as_completed(fut))) - result=list(filter(lambda x: x, val)) + val = list(map(lambda x : x.result() ,concurrent.futures.as_completed(fut))) + result = list(filter(lambda x: x, val)) return [x[0] for x in sorted(result, key=itemgetter(1))] #sort by amazons listing order for best relevance diff --git a/cps/templates/list.html b/cps/templates/list.html index ed59661e..a9089823 100644 --- a/cps/templates/list.html +++ b/cps/templates/list.html @@ -29,7 +29,7 @@
      {% endif %} -
      +
      {{entry[1]}}
      {% if entry.name %} diff --git a/cps/web.py b/cps/web.py index ae4b12a8..517539f8 100644 --- a/cps/web.py +++ b/cps/web.py @@ -599,17 +599,29 @@ def render_series_books(page, book_id, order): def render_ratings_books(page, book_id, order): - name = calibre_db.session.query(db.Ratings).filter(db.Ratings.id == book_id).first() - entries, random, pagination = calibre_db.fill_indexpage(page, 0, - db.Books, - db.Books.ratings.any(db.Ratings.id == book_id), - [order[0][0]], - True, config.config_read_column) - if name and name.rating <= 10: + if book_id == '-1': + entries, random, pagination = calibre_db.fill_indexpage(page, 0, + db.Books, + db.Books.ratings == None, + [order[0][0]], + True, config.config_read_column, + db.books_series_link, + db.Books.id == db.books_series_link.c.book, + db.Series) + title = _(u"Rating: None") + rating = -1 + else: + name = calibre_db.session.query(db.Ratings).filter(db.Ratings.id == book_id).first() + entries, random, pagination = calibre_db.fill_indexpage(page, 0, + db.Books, + db.Books.ratings.any(db.Ratings.id == book_id), + [order[0][0]], + True, config.config_read_column) + title = _(u"Rating: %(rating)s stars", rating=int(name.rating / 2)) + rating = name.rating + if title and rating <= 10: return render_title_template('index.html', random=random, pagination=pagination, entries=entries, id=book_id, - title=_(u"Rating: %(rating)s stars", rating=int(name.rating / 2)), - page="ratings", - order=order[1]) + title=title, page="ratings", order=order[1]) else: abort(404) @@ -1001,6 +1013,7 @@ def publisher_list(): .count()) if no_publisher_count: entries.append([db.Category(_("None"), "-1"), no_publisher_count]) + entries = sorted(entries, key=lambda x: x[0].name, reverse=not order_no) char_list = generate_char_list(entries) return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=char_list, title=_(u"Publishers"), page="publisherlist", data="publisher", order=order_no) @@ -1030,6 +1043,7 @@ def series_list(): .count()) if no_series_count: entries.append([db.Category(_("None"), "-1"), no_series_count]) + entries = sorted(entries, key=lambda x: x[0].name, reverse=not order_no) return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=char_list, title=_(u"Series"), page="serieslist", data="series", order=order_no) else: @@ -1060,10 +1074,11 @@ def ratings_list(): .group_by(text('books_ratings_link.rating')).order_by(order).all() no_rating_count = (calibre_db.session.query(db.Books) .outerjoin(db.books_ratings_link).outerjoin(db.Ratings) - .filter(db.Ratings.name == None) + .filter(db.Ratings.rating == None) .filter(calibre_db.common_filters()) .count()) - entries.append([db.Category(_("None"), "-1"), no_rating_count]) + entries.append([db.Category(_("None"), "-1", -1), no_rating_count]) + entries = sorted(entries, key=lambda x: x[0].rating, reverse=not order_no) return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=list(), title=_(u"Ratings list"), page="ratingslist", data="ratings", order=order_no) else: @@ -1130,6 +1145,7 @@ def category_list(): .count()) if no_tag_count: entries.append([db.Category(_("None"), "-1"), no_tag_count]) + entries = sorted(entries, key=lambda x: x[0].name, reverse=not order_no) char_list = generate_char_list(entries) return render_title_template('list.html', entries=entries, folder='web.books_list', charlist=char_list, title=_(u"Categories"), page="catlist", data="category", order=order_no) diff --git a/test/Calibre-Web TestSummary_Linux.html b/test/Calibre-Web TestSummary_Linux.html index 392fdbea..d384225f 100644 --- a/test/Calibre-Web TestSummary_Linux.html +++ b/test/Calibre-Web TestSummary_Linux.html @@ -37,20 +37,20 @@
      -

      Start Time: 2022-04-03 07:19:10

      +

      Start Time: 2022-04-18 20:29:19

      -

      Stop Time: 2022-04-03 12:55:38

      +

      Stop Time: 2022-04-19 07:55:03

      -

      Duration: 4h 47 min

      +

      Duration: 10h 36 min

      @@ -102,15 +102,15 @@ - + TestAnonymous - 13 - 13 - 0 - 0 + 18 + 5 + 6 + 7 0 - Detail + Detail @@ -134,11 +134,33 @@ - +
      TestAnonymous - test_guest_change_visibility_category
      - PASS + +
      + FAIL +
      + + + + @@ -161,20 +183,64 @@ - +
      TestAnonymous - test_guest_change_visibility_language
      - PASS + +
      + FAIL +
      + + + + - +
      TestAnonymous - test_guest_change_visibility_publisher
      - PASS + +
      + FAIL +
      + + + + @@ -188,67 +254,368 @@ - +
      TestAnonymous - test_guest_change_visibility_rating
      - PASS + +
      + ERROR +
      + + + + + + + + + + +
      TestAnonymous - test_guest_change_visibility_rating
      + + +
      + ERROR +
      + + + + - +
      TestAnonymous - test_guest_change_visibility_series
      - PASS + +
      + FAIL +
      + + + + + + + + + + +
      TestAnonymous - test_guest_change_visibility_series
      + + +
      + ERROR +
      + + + + - +
      TestAnonymous - test_guest_random_books_available
      - PASS + +
      + FAIL +
      + + + + - + + +
      TestAnonymous - test_guest_random_books_available
      + + +
      + ERROR +
      + + + + + + + + +
      TestAnonymous - test_guest_restricted_settings_visibility
      - PASS + +
      + ERROR +
      + + + + + + + + + + +
      TestAnonymous - test_guest_restricted_settings_visibility
      + + +
      + ERROR +
      + + + + - +
      TestAnonymous - test_guest_visibility_sidebar
      - PASS + +
      + FAIL +
      + + + + + + + + + + +
      TestAnonymous - test_guest_visibility_sidebar
      + + +
      + ERROR +
      + + + + - - TestCli - 9 - 9 + + _ErrorHolder + 1 0 0 + 1 0 - Detail + Detail - + + +
      tearDownClass (test_anonymous)
      + + +
      + ERROR +
      + + + + + + + + + + + TestCli + 9 + 4 + 4 + 1 + 0 + + Detail + + + + + +
      TestCli - test_already_started
      @@ -257,34 +624,121 @@ - +
      TestCli - test_bind_to_single_interface
      - PASS + +
      + FAIL +
      + + + + - +
      TestCli - test_change_password
      - PASS + +
      + FAIL +
      + + + + - +
      TestCli - test_cli_SSL_files
      - PASS + +
      + FAIL +
      + + + + - +
      TestCli - test_cli_different_folder
      @@ -293,16 +747,46 @@ - +
      TestCli - test_cli_different_settings_database
      - PASS + +
      + ERROR +
      + + + + - +
      TestCli - test_dryrun_update
      @@ -311,7 +795,7 @@ - +
      TestCli - test_environ_port_setting
      @@ -320,11 +804,43 @@ - +
      TestCli - test_settingsdb_not_writeable
      - PASS + +
      + FAIL +
      + + + + @@ -332,19 +848,28 @@ TestCliGdrivedb - 2 - 2 + 3 + 3 0 0 0 - Detail + Detail - + + +
      TestCliGdrivedb - test_cli_gdrive_folder
      + + PASS + + + + +
      TestCliGdrivedb - test_cli_gdrive_location
      @@ -353,7 +878,7 @@ - +
      TestCliGdrivedb - test_gdrive_db_nonwrite
      @@ -371,13 +896,13 @@ 0 0 - Detail + Detail - +
      TestCoverEditBooks - test_invalid_jpg_hdd
      @@ -386,7 +911,7 @@ - +
      TestCoverEditBooks - test_upload_jpg
      @@ -396,25 +921,45 @@ - + TestDeleteDatabase 1 - 1 0 + 1 0 0 - Detail + Detail - +
      TestDeleteDatabase - test_delete_books_in_database
      - PASS + +
      + FAIL +
      + + + + @@ -428,13 +973,13 @@ 0 0 - Detail + Detail - +
      TestEbookConvertCalibre - test_calibre_log
      @@ -443,7 +988,7 @@ - +
      TestEbookConvertCalibre - test_convert_deactivate
      @@ -452,7 +997,7 @@ - +
      TestEbookConvertCalibre - test_convert_email
      @@ -461,7 +1006,7 @@ - +
      TestEbookConvertCalibre - test_convert_failed_and_email
      @@ -470,7 +1015,7 @@ - +
      TestEbookConvertCalibre - test_convert_only
      @@ -479,7 +1024,7 @@ - +
      TestEbookConvertCalibre - test_convert_options
      @@ -488,7 +1033,7 @@ - +
      TestEbookConvertCalibre - test_convert_parameter
      @@ -497,7 +1042,7 @@ - +
      TestEbookConvertCalibre - test_convert_wrong_excecutable
      @@ -506,7 +1051,7 @@ - +
      TestEbookConvertCalibre - test_convert_xss
      @@ -515,7 +1060,7 @@ - +
      TestEbookConvertCalibre - test_email_failed
      @@ -524,7 +1069,7 @@ - +
      TestEbookConvertCalibre - test_email_only
      @@ -533,7 +1078,7 @@ - +
      TestEbookConvertCalibre - test_kindle_send_not_configured
      @@ -542,7 +1087,7 @@ - +
      TestEbookConvertCalibre - test_ssl_smtp_setup_error
      @@ -551,7 +1096,7 @@ - +
      TestEbookConvertCalibre - test_starttls_smtp_setup_error
      @@ -560,7 +1105,7 @@ - +
      TestEbookConvertCalibre - test_user_convert_xss
      @@ -578,13 +1123,13 @@ 0 0 - Detail + Detail - +
      TestEbookConvertCalibreGDrive - test_convert_email
      @@ -593,7 +1138,7 @@ - +
      TestEbookConvertCalibreGDrive - test_convert_failed_and_email
      @@ -602,7 +1147,7 @@ - +
      TestEbookConvertCalibreGDrive - test_convert_only
      @@ -611,7 +1156,7 @@ - +
      TestEbookConvertCalibreGDrive - test_convert_parameter
      @@ -620,7 +1165,7 @@ - +
      TestEbookConvertCalibreGDrive - test_email_failed
      @@ -629,7 +1174,7 @@ - +
      TestEbookConvertCalibreGDrive - test_email_only
      @@ -647,13 +1192,13 @@ 0 0 - Detail + Detail - +
      TestEbookConvertKepubify - test_convert_deactivate
      @@ -662,7 +1207,7 @@ - +
      TestEbookConvertKepubify - test_convert_only
      @@ -671,7 +1216,7 @@ - +
      TestEbookConvertKepubify - test_convert_wrong_excecutable
      @@ -689,13 +1234,13 @@ 0 0 - Detail + Detail - +
      TestEbookConvertGDriveKepubify - test_convert_deactivate
      @@ -704,7 +1249,7 @@ - +
      TestEbookConvertGDriveKepubify - test_convert_only
      @@ -713,7 +1258,7 @@ - +
      TestEbookConvertGDriveKepubify - test_convert_wrong_excecutable
      @@ -731,13 +1276,13 @@ 0 1 - Detail + Detail - +
      TestEditAdditionalBooks - test_cbz_comicinfo
      @@ -746,7 +1291,7 @@ - +
      TestEditAdditionalBooks - test_change_upload_formats
      @@ -755,7 +1300,7 @@ - +
      TestEditAdditionalBooks - test_delete_book
      @@ -764,7 +1309,7 @@ - +
      TestEditAdditionalBooks - test_delete_role
      @@ -773,7 +1318,7 @@ - +
      TestEditAdditionalBooks - test_details_popup
      @@ -782,7 +1327,7 @@ - +
      TestEditAdditionalBooks - test_edit_book_identifier
      @@ -791,7 +1336,7 @@ - +
      TestEditAdditionalBooks - test_edit_book_identifier_capital
      @@ -800,7 +1345,7 @@ - +
      TestEditAdditionalBooks - test_edit_book_identifier_standard
      @@ -809,7 +1354,7 @@ - +
      TestEditAdditionalBooks - test_edit_special_book_identifier
      @@ -818,7 +1363,7 @@ - +
      TestEditAdditionalBooks - test_title_sort
      @@ -827,7 +1372,7 @@ - +
      TestEditAdditionalBooks - test_upload_cbz_coverformats
      @@ -836,7 +1381,7 @@ - +
      TestEditAdditionalBooks - test_upload_edit_role
      @@ -845,7 +1390,7 @@ - +
      TestEditAdditionalBooks - test_upload_metadata_cbr
      @@ -854,7 +1399,7 @@ - +
      TestEditAdditionalBooks - test_upload_metadata_cbt
      @@ -863,7 +1408,7 @@ - +
      TestEditAdditionalBooks - test_xss_author_edit
      @@ -872,7 +1417,7 @@ - +
      TestEditAdditionalBooks - test_xss_comment_edit
      @@ -881,7 +1426,7 @@ - +
      TestEditAdditionalBooks - test_xss_custom_comment_edit
      @@ -899,13 +1444,13 @@ 0 1 - Detail + Detail - +
      TestEditBooks - test_download_book
      @@ -914,7 +1459,7 @@ - +
      TestEditBooks - test_edit_author
      @@ -923,7 +1468,7 @@ - +
      TestEditBooks - test_edit_category
      @@ -932,7 +1477,7 @@ - +
      TestEditBooks - test_edit_comments
      @@ -941,7 +1486,7 @@ - +
      TestEditBooks - test_edit_custom_bool
      @@ -950,7 +1495,7 @@ - +
      TestEditBooks - test_edit_custom_categories
      @@ -959,7 +1504,7 @@ - +
      TestEditBooks - test_edit_custom_comment
      @@ -968,7 +1513,7 @@ - +
      TestEditBooks - test_edit_custom_date
      @@ -977,7 +1522,7 @@ - +
      TestEditBooks - test_edit_custom_float
      @@ -986,7 +1531,7 @@ - +
      TestEditBooks - test_edit_custom_int
      @@ -995,7 +1540,7 @@ - +
      TestEditBooks - test_edit_custom_rating
      @@ -1004,7 +1549,7 @@ - +
      TestEditBooks - test_edit_custom_single_select
      @@ -1013,7 +1558,7 @@ - +
      TestEditBooks - test_edit_custom_text
      @@ -1022,7 +1567,7 @@ - +
      TestEditBooks - test_edit_language
      @@ -1031,7 +1576,7 @@ - +
      TestEditBooks - test_edit_publisher
      @@ -1040,7 +1585,7 @@ - +
      TestEditBooks - test_edit_publishing_date
      @@ -1049,7 +1594,7 @@ - +
      TestEditBooks - test_edit_rating
      @@ -1058,7 +1603,7 @@ - +
      TestEditBooks - test_edit_series
      @@ -1067,7 +1612,7 @@ - +
      TestEditBooks - test_edit_title
      @@ -1076,19 +1621,19 @@ - +
      TestEditBooks - test_rename_uppercase_lowercase
      - SKIP + SKIP
      -