Merge remote-tracking branch 'refs/remotes/janeczku/master'
# Conflicts: # cps/translations/pl/LC_MESSAGES/messages.mo # cps/translations/pl/LC_MESSAGES/messages.po
This commit is contained in:
commit
55ee323ca1
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -23,3 +23,9 @@ cps/static/[0-9]*
|
|||
*.bak
|
||||
*.log.*
|
||||
tags
|
||||
|
||||
settings.yaml
|
||||
gdrive_credentials
|
||||
|
||||
#kindlegen
|
||||
vendor/kindlegen
|
||||
|
|
27
cps.py
27
cps.py
|
@ -6,20 +6,33 @@ import sys
|
|||
|
||||
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||
# Insert local directories into path
|
||||
sys.path.insert(0, os.path.join(base_path, 'vendor'))
|
||||
sys.path.append(base_path)
|
||||
sys.path.append(os.path.join(base_path, 'cps'))
|
||||
sys.path.append(os.path.join(base_path, 'vendor'))
|
||||
|
||||
from cps import web
|
||||
from tornado.wsgi import WSGIContainer
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
try:
|
||||
from gevent.wsgi import WSGIServer
|
||||
gevent_present = True
|
||||
except ImportError:
|
||||
from tornado.wsgi import WSGIContainer
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
gevent_present = False
|
||||
|
||||
if __name__ == '__main__':
|
||||
if web.ub.DEVELOPMENT:
|
||||
web.app.run(host="0.0.0.0", port=web.ub.config.config_port, debug=True)
|
||||
else:
|
||||
http_server = HTTPServer(WSGIContainer(web.app))
|
||||
http_server.listen(web.ub.config.config_port)
|
||||
IOLoop.instance().start()
|
||||
if gevent_present:
|
||||
web.app.logger.info('Attempting to start gevent')
|
||||
web.start_gevent()
|
||||
else:
|
||||
web.app.logger.info('Falling back to Tornado')
|
||||
http_server = HTTPServer(WSGIContainer(web.app))
|
||||
http_server.listen(web.ub.config.config_port)
|
||||
IOLoop.instance().start()
|
||||
IOLoop.instance().close(True)
|
||||
|
||||
if web.helper.global_task == 0:
|
||||
web.app.logger.info("Performing restart of Calibre-web")
|
||||
|
|
|
@ -14,28 +14,28 @@ try:
|
|||
from wand.image import Image
|
||||
from wand import version as ImageVersion
|
||||
use_generic_pdf_cover = False
|
||||
except ImportError, e:
|
||||
except ImportError as e:
|
||||
logger.warning('cannot import Image, generating pdf covers for pdf uploads will not work: %s', e)
|
||||
use_generic_pdf_cover = True
|
||||
try:
|
||||
from PyPDF2 import PdfFileReader
|
||||
from PyPDF2 import __version__ as PyPdfVersion
|
||||
use_pdf_meta = True
|
||||
except ImportError, e:
|
||||
except ImportError as e:
|
||||
logger.warning('cannot import PyPDF2, extracting pdf metadata will not work: %s', e)
|
||||
use_pdf_meta = False
|
||||
|
||||
try:
|
||||
import epub
|
||||
use_epub_meta = True
|
||||
except ImportError, e:
|
||||
except ImportError as e:
|
||||
logger.warning('cannot import epub, extracting epub metadata will not work: %s', e)
|
||||
use_epub_meta = False
|
||||
|
||||
try:
|
||||
import fb2
|
||||
use_fb2_meta = True
|
||||
except ImportError, e:
|
||||
except ImportError as e:
|
||||
logger.warning('cannot import fb2, extracting fb2 metadata will not work: %s', e)
|
||||
use_fb2_meta = False
|
||||
|
||||
|
@ -48,7 +48,7 @@ def process(tmp_file_path, original_file_name, original_file_extension):
|
|||
return epub.get_epub_info(tmp_file_path, original_file_name, original_file_extension)
|
||||
if ".FB2" == original_file_extension.upper() and use_fb2_meta is True:
|
||||
return fb2.get_fb2_info(tmp_file_path, original_file_extension)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
logger.warning('cannot parse metadata, using default: %s', e)
|
||||
return default_meta(tmp_file_path, original_file_name, original_file_extension)
|
||||
|
||||
|
@ -63,7 +63,8 @@ def default_meta(tmp_file_path, original_file_name, original_file_extension):
|
|||
description="",
|
||||
tags="",
|
||||
series="",
|
||||
series_id="")
|
||||
series_id="",
|
||||
languages="")
|
||||
|
||||
|
||||
def pdf_meta(tmp_file_path, original_file_name, original_file_extension):
|
||||
|
@ -91,7 +92,8 @@ def pdf_meta(tmp_file_path, original_file_name, original_file_extension):
|
|||
description=subject,
|
||||
tags="",
|
||||
series="",
|
||||
series_id="")
|
||||
series_id="",
|
||||
languages="")
|
||||
|
||||
|
||||
def pdf_preview(tmp_file_path, tmp_dir):
|
||||
|
|
89
cps/db.py
89
cps/db.py
|
@ -11,10 +11,8 @@ from ub import config
|
|||
import ub
|
||||
|
||||
session = None
|
||||
cc_exceptions = None
|
||||
cc_exceptions = ['datetime', 'int', 'comments', 'float', 'composite', 'series']
|
||||
cc_classes = None
|
||||
cc_ids = None
|
||||
books_custom_column_links = None
|
||||
engine = None
|
||||
|
||||
|
||||
|
@ -247,7 +245,7 @@ class Books(Base):
|
|||
identifiers = relationship('Identifiers', backref='books')
|
||||
|
||||
def __init__(self, title, sort, author_sort, timestamp, pubdate, series_index, last_modified, path, has_cover,
|
||||
authors, tags):
|
||||
authors, tags, languages = None):
|
||||
self.title = title
|
||||
self.sort = sort
|
||||
self.author_sort = author_sort
|
||||
|
@ -283,22 +281,19 @@ class Custom_Columns(Base):
|
|||
|
||||
|
||||
def setup_db():
|
||||
global session
|
||||
global cc_exceptions
|
||||
global cc_classes
|
||||
global cc_ids
|
||||
global books_custom_column_links
|
||||
global engine
|
||||
global session
|
||||
global cc_classes
|
||||
|
||||
if config.config_calibre_dir is None or config.config_calibre_dir == u'':
|
||||
return False
|
||||
|
||||
dbpath = os.path.join(config.config_calibre_dir, "metadata.db")
|
||||
engine = create_engine('sqlite:///{0}'.format(dbpath.encode('utf-8')), echo=False, isolation_level="SERIALIZABLE")
|
||||
#engine = create_engine('sqlite:///{0}'.format(dbpath.encode('utf-8')), echo=False, isolation_level="SERIALIZABLE")
|
||||
engine = create_engine('sqlite:///'+ dbpath, echo=False, isolation_level="SERIALIZABLE")
|
||||
try:
|
||||
conn = engine.connect()
|
||||
|
||||
except:
|
||||
except Exception as e:
|
||||
content = ub.session.query(ub.Settings).first()
|
||||
content.config_calibre_dir = None
|
||||
content.db_configured = False
|
||||
|
@ -311,43 +306,43 @@ def setup_db():
|
|||
config.loadSettings()
|
||||
conn.connection.create_function('title_sort', 1, title_sort)
|
||||
|
||||
cc = conn.execute("SELECT id, datatype FROM custom_columns")
|
||||
if not cc_classes:
|
||||
cc = conn.execute("SELECT id, datatype FROM custom_columns")
|
||||
|
||||
cc_ids = []
|
||||
cc_exceptions = ['datetime', 'int', 'comments', 'float', 'composite', 'series']
|
||||
books_custom_column_links = {}
|
||||
cc_classes = {}
|
||||
for row in cc:
|
||||
if row.datatype not in cc_exceptions:
|
||||
books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', Base.metadata,
|
||||
Column('book', Integer, ForeignKey('books.id'),
|
||||
primary_key=True),
|
||||
Column('value', Integer,
|
||||
ForeignKey('custom_column_' + str(row.id) + '.id'),
|
||||
primary_key=True)
|
||||
)
|
||||
cc_ids.append([row.id, row.datatype])
|
||||
if row.datatype == 'bool':
|
||||
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
|
||||
'id': Column(Integer, primary_key=True),
|
||||
'book': Column(Integer, ForeignKey('books.id')),
|
||||
'value': Column(Boolean)}
|
||||
cc_ids = []
|
||||
books_custom_column_links = {}
|
||||
cc_classes = {}
|
||||
for row in cc:
|
||||
if row.datatype not in cc_exceptions:
|
||||
books_custom_column_links[row.id] = Table('books_custom_column_' + str(row.id) + '_link', Base.metadata,
|
||||
Column('book', Integer, ForeignKey('books.id'),
|
||||
primary_key=True),
|
||||
Column('value', Integer,
|
||||
ForeignKey('custom_column_' + str(row.id) + '.id'),
|
||||
primary_key=True)
|
||||
)
|
||||
cc_ids.append([row.id, row.datatype])
|
||||
if row.datatype == 'bool':
|
||||
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
|
||||
'id': Column(Integer, primary_key=True),
|
||||
'book': Column(Integer, ForeignKey('books.id')),
|
||||
'value': Column(Boolean)}
|
||||
else:
|
||||
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
|
||||
'id': Column(Integer, primary_key=True),
|
||||
'value': Column(String)}
|
||||
cc_classes[row.id] = type('Custom_Column_' + str(row.id), (Base,), ccdict)
|
||||
|
||||
for id in cc_ids:
|
||||
if id[1] == 'bool':
|
||||
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
|
||||
primaryjoin=(
|
||||
Books.id == cc_classes[id[0]].book),
|
||||
backref='books'))
|
||||
else:
|
||||
ccdict = {'__tablename__': 'custom_column_' + str(row.id),
|
||||
'id': Column(Integer, primary_key=True),
|
||||
'value': Column(String)}
|
||||
cc_classes[row.id] = type('Custom_Column_' + str(row.id), (Base,), ccdict)
|
||||
|
||||
for id in cc_ids:
|
||||
if id[1] == 'bool':
|
||||
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
|
||||
primaryjoin=(
|
||||
Books.id == cc_classes[id[0]].book),
|
||||
backref='books'))
|
||||
else:
|
||||
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
|
||||
secondary=books_custom_column_links[id[0]],
|
||||
backref='books'))
|
||||
setattr(Books, 'custom_column_' + str(id[0]), relationship(cc_classes[id[0]],
|
||||
secondary=books_custom_column_links[id[0]],
|
||||
backref='books'))
|
||||
|
||||
# Base.metadata.create_all(engine)
|
||||
Session = sessionmaker()
|
||||
|
|
49
cps/epub.py
49
cps/epub.py
|
@ -5,7 +5,7 @@ import zipfile
|
|||
from lxml import etree
|
||||
import os
|
||||
import uploader
|
||||
|
||||
from iso639 import languages as isoLanguages
|
||||
|
||||
def extractCover(zip, coverFile, coverpath, tmp_file_name):
|
||||
if coverFile is None:
|
||||
|
@ -41,23 +41,53 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
|
|||
p = tree.xpath('/pkg:package/pkg:metadata', namespaces=ns)[0]
|
||||
|
||||
epub_metadata = {}
|
||||
for s in ['title', 'description', 'creator']:
|
||||
|
||||
for s in ['title', 'description', 'creator', 'language']:
|
||||
tmp = p.xpath('dc:%s/text()' % s, namespaces=ns)
|
||||
if len(tmp) > 0:
|
||||
epub_metadata[s] = p.xpath('dc:%s/text()' % s, namespaces=ns)[0]
|
||||
else:
|
||||
epub_metadata[s] = "Unknown"
|
||||
|
||||
if epub_metadata['description'] == "Unknown":
|
||||
description = tree.xpath("//*[local-name() = 'description']/text()")
|
||||
if len(description) > 0:
|
||||
epub_metadata['description'] = description
|
||||
else:
|
||||
epub_metadata['description'] = ""
|
||||
|
||||
if epub_metadata['language'] == "Unknown":
|
||||
epub_metadata['language'] == ""
|
||||
else:
|
||||
lang = epub_metadata['language'].split('-', 1)[0].lower()
|
||||
if len(lang) == 2:
|
||||
epub_metadata['language'] = isoLanguages.get(part1=lang).name
|
||||
elif len(lang) == 3:
|
||||
epub_metadata['language'] = isoLanguages.get(part3=lang).name
|
||||
else:
|
||||
epub_metadata['language'] = ""
|
||||
|
||||
coversection = tree.xpath("/pkg:package/pkg:manifest/pkg:item[@id='cover-image']/@href", namespaces=ns)
|
||||
coverfile = None
|
||||
if len(coversection) > 0:
|
||||
coverfile = extractCover(zip, coversection[0], coverpath, tmp_file_path)
|
||||
else:
|
||||
coversection = tree.xpath("/pkg:package/pkg:manifest/pkg:item[@id='cover']/@href", namespaces=ns)
|
||||
if len(coversection) > 0:
|
||||
coverfile = extractCover(zip, coversection[0], coverpath, tmp_file_path)
|
||||
else:
|
||||
coverfile = None
|
||||
|
||||
meta_cover = tree.xpath("/pkg:package/pkg:metadata/pkg:meta[@name='cover']/@content", namespaces=ns)
|
||||
if len(meta_cover) > 0:
|
||||
coversection = tree.xpath("/pkg:package/pkg:manifest/pkg:item[@id='"+meta_cover[0]+"']/@href", namespaces=ns)
|
||||
if len(coversection) > 0:
|
||||
filetype = coversection[0].rsplit('.',1)[-1]
|
||||
if filetype == "xhtml" or filetype == "html": #if cover is (x)html format
|
||||
markup = zip.read(os.path.join(coverpath,coversection[0]))
|
||||
markupTree = etree.fromstring(markup)
|
||||
#no matter xhtml or html with no namespace
|
||||
imgsrc = markupTree.xpath("//*[local-name() = 'img']/@src")
|
||||
#imgsrc maybe startwith "../"" so fullpath join then relpath to cwd
|
||||
filename = os.path.relpath(os.path.join(os.path.dirname(os.path.join(coverpath, coversection[0])), imgsrc[0]))
|
||||
coverfile = extractCover(zip, filename, "", tmp_file_path)
|
||||
else:
|
||||
coverfile = extractCover(zip, coversection[0], coverpath, tmp_file_path)
|
||||
|
||||
if epub_metadata['title'] is None:
|
||||
title = original_file_name
|
||||
else:
|
||||
|
@ -72,4 +102,5 @@ def get_epub_info(tmp_file_path, original_file_name, original_file_extension):
|
|||
description=epub_metadata['description'],
|
||||
tags="",
|
||||
series="",
|
||||
series_id="")
|
||||
series_id="",
|
||||
languages=epub_metadata['language'])
|
||||
|
|
15
cps/fb2.py
15
cps/fb2.py
|
@ -4,8 +4,10 @@
|
|||
from lxml import etree
|
||||
import os
|
||||
import uploader
|
||||
import StringIO
|
||||
|
||||
try:
|
||||
from io import StringIO
|
||||
except ImportError as e:
|
||||
import StringIO
|
||||
|
||||
def get_fb2_info(tmp_file_path, original_file_extension):
|
||||
|
||||
|
@ -37,16 +39,16 @@ def get_fb2_info(tmp_file_path, original_file_extension):
|
|||
first_name = u''
|
||||
return first_name + ' ' + middle_name + ' ' + last_name
|
||||
|
||||
author = unicode(", ".join(map(get_author, authors)))
|
||||
author = str(", ".join(map(get_author, authors)))
|
||||
|
||||
title = tree.xpath('/fb:FictionBook/fb:description/fb:title-info/fb:book-title/text()', namespaces=ns)
|
||||
if len(title):
|
||||
title = unicode(title[0])
|
||||
title = str(title[0])
|
||||
else:
|
||||
title = u''
|
||||
description = tree.xpath('/fb:FictionBook/fb:description/fb:publish-info/fb:book-name/text()', namespaces=ns)
|
||||
if len(description):
|
||||
description = unicode(description[0])
|
||||
description = str(description[0])
|
||||
else:
|
||||
description = u''
|
||||
|
||||
|
@ -59,4 +61,5 @@ def get_fb2_info(tmp_file_path, original_file_extension):
|
|||
description=description,
|
||||
tags="",
|
||||
series="",
|
||||
series_id="")
|
||||
series_id="",
|
||||
languages="")
|
||||
|
|
370
cps/gdriveutils.py
Normal file
370
cps/gdriveutils.py
Normal file
|
@ -0,0 +1,370 @@
|
|||
try:
|
||||
from pydrive.auth import GoogleAuth
|
||||
from pydrive.drive import GoogleDrive
|
||||
from apiclient import errors
|
||||
except ImportError:
|
||||
pass
|
||||
import os, time
|
||||
|
||||
from ub import config
|
||||
|
||||
from sqlalchemy import *
|
||||
from sqlalchemy import exc
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import *
|
||||
|
||||
|
||||
import web
|
||||
|
||||
|
||||
dbpath = os.path.join(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + os.sep + ".." + os.sep), "gdrive.db")
|
||||
engine = create_engine('sqlite:///{0}'.format(dbpath), echo=False)
|
||||
Base = declarative_base()
|
||||
|
||||
# Open session for database connection
|
||||
Session = sessionmaker()
|
||||
Session.configure(bind=engine)
|
||||
session = scoped_session(Session)
|
||||
|
||||
class GdriveId(Base):
|
||||
__tablename__='gdrive_ids'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
gdrive_id = Column(Integer, unique=True)
|
||||
path = Column(String)
|
||||
__table_args__ = (UniqueConstraint('gdrive_id', 'path', name='_gdrive_path_uc'),)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.path)
|
||||
|
||||
class PermissionAdded(Base):
|
||||
__tablename__='permissions_added'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
gdrive_id = Column(Integer, unique=True)
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.gdrive_id)
|
||||
|
||||
def migrate():
|
||||
if not engine.dialect.has_table(engine.connect(), "permissions_added"):
|
||||
PermissionAdded.__table__.create(bind = engine)
|
||||
for sql in session.execute("select sql from sqlite_master where type='table'"):
|
||||
if 'CREATE TABLE gdrive_ids' in sql[0]:
|
||||
currUniqueConstraint='UNIQUE (gdrive_id)'
|
||||
if currUniqueConstraint in sql[0]:
|
||||
sql=sql[0].replace(currUniqueConstraint, 'UNIQUE (gdrive_id, path)')
|
||||
sql=sql.replace(GdriveId.__tablename__, GdriveId.__tablename__+ '2')
|
||||
session.execute(sql)
|
||||
session.execute('INSERT INTO gdrive_ids2 (id, gdrive_id, path) SELECT id, gdrive_id, path FROM gdrive_ids;')
|
||||
session.commit()
|
||||
session.execute('DROP TABLE %s' % 'gdrive_ids')
|
||||
session.execute('ALTER TABLE gdrive_ids2 RENAME to gdrive_ids')
|
||||
break
|
||||
|
||||
if not os.path.exists(dbpath):
|
||||
try:
|
||||
Base.metadata.create_all(engine)
|
||||
except Exception:
|
||||
raise
|
||||
|
||||
migrate()
|
||||
|
||||
def getDrive(gauth=None):
|
||||
if not gauth:
|
||||
gauth=GoogleAuth(settings_file='settings.yaml')
|
||||
# Try to load saved client credentials
|
||||
gauth.LoadCredentialsFile("gdrive_credentials")
|
||||
if gauth.access_token_expired:
|
||||
# Refresh them if expired
|
||||
gauth.Refresh()
|
||||
else:
|
||||
# Initialize the saved creds
|
||||
gauth.Authorize()
|
||||
# Save the current credentials to a file
|
||||
return GoogleDrive(gauth)
|
||||
|
||||
def getEbooksFolder(drive=None):
|
||||
if not drive:
|
||||
drive = getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
ebooksFolder= "title = '%s' and 'root' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" % config.config_google_drive_folder
|
||||
|
||||
fileList = drive.ListFile({'q': ebooksFolder}).GetList()
|
||||
return fileList[0]
|
||||
|
||||
def getEbooksFolderId(drive=None):
|
||||
storedPathName=session.query(GdriveId).filter(GdriveId.path == '/').first()
|
||||
if storedPathName:
|
||||
return storedPathName.gdrive_id
|
||||
else:
|
||||
gDriveId=GdriveId()
|
||||
gDriveId.gdrive_id=getEbooksFolder(drive)['id']
|
||||
gDriveId.path='/'
|
||||
session.merge(gDriveId)
|
||||
session.commit()
|
||||
return
|
||||
|
||||
def getFolderInFolder(parentId, folderName, drive=None):
|
||||
if not drive:
|
||||
drive = getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
folder= "title = '%s' and '%s' in parents and mimeType = 'application/vnd.google-apps.folder' and trashed = false" % (folderName.replace("'", "\\'"), parentId)
|
||||
fileList = drive.ListFile({'q': folder}).GetList()
|
||||
return fileList[0]
|
||||
|
||||
def getFile(pathId, fileName, drive=None):
|
||||
if not drive:
|
||||
drive = getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
metaDataFile="'%s' in parents and trashed = false and title = '%s'" % (pathId, fileName.replace("'", "\\'"))
|
||||
|
||||
fileList = drive.ListFile({'q': metaDataFile}).GetList()
|
||||
return fileList[0]
|
||||
|
||||
def getFolderId(path, drive=None):
|
||||
if not drive:
|
||||
drive=getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
currentFolderId=getEbooksFolderId(drive)
|
||||
sqlCheckPath=path if path[-1] =='/' else path + '/'
|
||||
storedPathName=session.query(GdriveId).filter(GdriveId.path == sqlCheckPath).first()
|
||||
|
||||
if not storedPathName:
|
||||
dbChange=False
|
||||
s=path.split('/')
|
||||
for i, x in enumerate(s):
|
||||
if len(x) > 0:
|
||||
currentPath="/".join(s[:i+1])
|
||||
if currentPath[-1] != '/':
|
||||
currentPath = currentPath + '/'
|
||||
storedPathName=session.query(GdriveId).filter(GdriveId.path == currentPath).first()
|
||||
if storedPathName:
|
||||
currentFolderId=storedPathName.gdrive_id
|
||||
else:
|
||||
currentFolderId=getFolderInFolder(currentFolderId, x, drive)['id']
|
||||
gDriveId=GdriveId()
|
||||
gDriveId.gdrive_id=currentFolderId
|
||||
gDriveId.path=currentPath
|
||||
session.merge(gDriveId)
|
||||
dbChange=True
|
||||
if dbChange:
|
||||
session.commit()
|
||||
else:
|
||||
currentFolderId=storedPathName.gdrive_id
|
||||
return currentFolderId
|
||||
|
||||
|
||||
def getFileFromEbooksFolder(drive, path, fileName):
|
||||
if not drive:
|
||||
drive=getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
if path:
|
||||
sqlCheckPath=path if path[-1] =='/' else path + '/'
|
||||
folderId=getFolderId(path, drive)
|
||||
else:
|
||||
folderId=getEbooksFolderId(drive)
|
||||
|
||||
return getFile(folderId, fileName, drive)
|
||||
|
||||
def copyDriveFileRemote(drive, origin_file_id, copy_title):
|
||||
if not drive:
|
||||
drive=getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
copied_file = {'title': copy_title}
|
||||
try:
|
||||
file_data = drive.auth.service.files().copy(
|
||||
fileId=origin_file_id, body=copied_file).execute()
|
||||
return drive.CreateFile({'id': file_data['id']})
|
||||
except errors.HttpError as error:
|
||||
print ('An error occurred: %s' % error)
|
||||
return None
|
||||
|
||||
def downloadFile(drive, path, filename, output):
|
||||
if not drive:
|
||||
drive=getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
f=getFileFromEbooksFolder(drive, path, filename)
|
||||
f.GetContentFile(output)
|
||||
|
||||
def backupCalibreDbAndOptionalDownload(drive, f=None):
|
||||
pass
|
||||
if not drive:
|
||||
drive=getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
metaDataFile="'%s' in parents and title = 'metadata.db' and trashed = false" % getEbooksFolderId()
|
||||
|
||||
fileList = drive.ListFile({'q': metaDataFile}).GetList()
|
||||
|
||||
databaseFile=fileList[0]
|
||||
|
||||
if f:
|
||||
databaseFile.GetContentFile(f)
|
||||
|
||||
def copyToDrive(drive, uploadFile, createRoot, replaceFiles,
|
||||
ignoreFiles=[],
|
||||
parent=None, prevDir=''):
|
||||
if not drive:
|
||||
drive=getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
isInitial=not bool(parent)
|
||||
if not parent:
|
||||
parent=getEbooksFolder(drive)
|
||||
if os.path.isdir(os.path.join(prevDir,uploadFile)):
|
||||
existingFolder=drive.ListFile({'q' : "title = '%s' and '%s' in parents and trashed = false" % (os.path.basename(uploadFile), parent['id'])}).GetList()
|
||||
if len(existingFolder) == 0 and (not isInitial or createRoot):
|
||||
parent = drive.CreateFile({'title': os.path.basename(uploadFile), 'parents' : [{"kind": "drive#fileLink", 'id' : parent['id']}],
|
||||
"mimeType": "application/vnd.google-apps.folder" })
|
||||
parent.Upload()
|
||||
else:
|
||||
if (not isInitial or createRoot) and len(existingFolder) > 0:
|
||||
parent=existingFolder[0]
|
||||
for f in os.listdir(os.path.join(prevDir,uploadFile)):
|
||||
if f not in ignoreFiles:
|
||||
copyToDrive(drive, f, True, replaceFiles, ignoreFiles, parent, os.path.join(prevDir,uploadFile))
|
||||
else:
|
||||
if os.path.basename(uploadFile) not in ignoreFiles:
|
||||
existingFiles=drive.ListFile({'q' : "title = '%s' and '%s' in parents and trashed = false" % (os.path.basename(uploadFile), parent['id'])}).GetList()
|
||||
if len(existingFiles) > 0:
|
||||
driveFile=existingFiles[0]
|
||||
else:
|
||||
driveFile = drive.CreateFile({'title': os.path.basename(uploadFile), 'parents' : [{"kind": "drive#fileLink", 'id' : parent['id']}], })
|
||||
driveFile.SetContentFile(os.path.join(prevDir,uploadFile))
|
||||
driveFile.Upload()
|
||||
|
||||
def uploadFileToEbooksFolder(drive, destFile, f):
|
||||
if not drive:
|
||||
drive=getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
parent=getEbooksFolder(drive)
|
||||
splitDir=destFile.split('/')
|
||||
for i, x in enumerate(splitDir):
|
||||
if i == len(splitDir)-1:
|
||||
existingFiles=drive.ListFile({'q' : "title = '%s' and '%s' in parents and trashed = false" % (x, parent['id'])}).GetList()
|
||||
if len(existingFiles) > 0:
|
||||
driveFile=existingFiles[0]
|
||||
else:
|
||||
driveFile = drive.CreateFile({'title': x, 'parents' : [{"kind": "drive#fileLink", 'id' : parent['id']}], })
|
||||
driveFile.SetContentFile(f)
|
||||
driveFile.Upload()
|
||||
else:
|
||||
existingFolder=drive.ListFile({'q' : "title = '%s' and '%s' in parents and trashed = false" % (x, parent['id'])}).GetList()
|
||||
if len(existingFolder) == 0:
|
||||
parent = drive.CreateFile({'title': x, 'parents' : [{"kind": "drive#fileLink", 'id' : parent['id']}],
|
||||
"mimeType": "application/vnd.google-apps.folder" })
|
||||
parent.Upload()
|
||||
else:
|
||||
parent=existingFolder[0]
|
||||
|
||||
|
||||
def watchChange(drive, channel_id, channel_type, channel_address,
|
||||
channel_token=None, expiration=None):
|
||||
if not drive:
|
||||
drive=getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
"""Watch for all changes to a user's Drive.
|
||||
Args:
|
||||
service: Drive API service instance.
|
||||
channel_id: Unique string that identifies this channel.
|
||||
channel_type: Type of delivery mechanism used for this channel.
|
||||
channel_address: Address where notifications are delivered.
|
||||
channel_token: An arbitrary string delivered to the target address with
|
||||
each notification delivered over this channel. Optional.
|
||||
channel_address: Address where notifications are delivered. Optional.
|
||||
Returns:
|
||||
The created channel if successful
|
||||
Raises:
|
||||
apiclient.errors.HttpError: if http request to create channel fails.
|
||||
"""
|
||||
body = {
|
||||
'id': channel_id,
|
||||
'type': channel_type,
|
||||
'address': channel_address
|
||||
}
|
||||
if channel_token:
|
||||
body['token'] = channel_token
|
||||
if expiration:
|
||||
body['expiration'] = expiration
|
||||
return drive.auth.service.changes().watch(body=body).execute()
|
||||
|
||||
def watchFile(drive, file_id, channel_id, channel_type, channel_address,
|
||||
channel_token=None, expiration=None):
|
||||
"""Watch for any changes to a specific file.
|
||||
Args:
|
||||
service: Drive API service instance.
|
||||
file_id: ID of the file to watch.
|
||||
channel_id: Unique string that identifies this channel.
|
||||
channel_type: Type of delivery mechanism used for this channel.
|
||||
channel_address: Address where notifications are delivered.
|
||||
channel_token: An arbitrary string delivered to the target address with
|
||||
each notification delivered over this channel. Optional.
|
||||
channel_address: Address where notifications are delivered. Optional.
|
||||
Returns:
|
||||
The created channel if successful
|
||||
Raises:
|
||||
apiclient.errors.HttpError: if http request to create channel fails.
|
||||
"""
|
||||
if not drive:
|
||||
drive=getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
|
||||
body = {
|
||||
'id': channel_id,
|
||||
'type': channel_type,
|
||||
'address': channel_address
|
||||
}
|
||||
if channel_token:
|
||||
body['token'] = channel_token
|
||||
if expiration:
|
||||
body['expiration'] = expiration
|
||||
return drive.auth.service.files().watch(fileId=file_id, body=body).execute()
|
||||
|
||||
def stopChannel(drive, channel_id, resource_id):
|
||||
"""Stop watching to a specific channel.
|
||||
Args:
|
||||
service: Drive API service instance.
|
||||
channel_id: ID of the channel to stop.
|
||||
resource_id: Resource ID of the channel to stop.
|
||||
Raises:
|
||||
apiclient.errors.HttpError: if http request to create channel fails.
|
||||
"""
|
||||
if not drive:
|
||||
drive=getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
service=drive.auth.service
|
||||
body = {
|
||||
'id': channel_id,
|
||||
'resourceId': resource_id
|
||||
}
|
||||
return drive.auth.service.channels().stop(body=body).execute()
|
||||
|
||||
def getChangeById (drive, change_id):
|
||||
if not drive:
|
||||
drive=getDrive()
|
||||
if drive.auth.access_token_expired:
|
||||
drive.auth.Refresh()
|
||||
"""Print a single Change resource information.
|
||||
|
||||
Args:
|
||||
service: Drive API service instance.
|
||||
change_id: ID of the Change resource to retrieve.
|
||||
"""
|
||||
try:
|
||||
change = drive.auth.service.changes().get(changeId=change_id).execute()
|
||||
return change
|
||||
except errors.HttpError, error:
|
||||
web.app.logger.exception(error)
|
||||
return None
|
|
@ -13,11 +13,18 @@ import os
|
|||
import traceback
|
||||
import re
|
||||
import unicodedata
|
||||
from StringIO import StringIO
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
from email.MIMEBase import MIMEBase
|
||||
from email.MIMEMultipart import MIMEMultipart
|
||||
from email.MIMEText import MIMEText
|
||||
except ImportError as e:
|
||||
from io import StringIO
|
||||
from email.mime.base import MIMEBase
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
from email import encoders
|
||||
from email.MIMEBase import MIMEBase
|
||||
from email.MIMEMultipart import MIMEMultipart
|
||||
from email.MIMEText import MIMEText
|
||||
from email.generator import Generator
|
||||
from email.utils import formatdate
|
||||
from email.utils import make_msgid
|
||||
|
@ -28,11 +35,16 @@ import shutil
|
|||
import requests
|
||||
import zipfile
|
||||
from tornado.ioloop import IOLoop
|
||||
try:
|
||||
import gdriveutils as gd
|
||||
except ImportError:
|
||||
pass
|
||||
import web
|
||||
|
||||
try:
|
||||
import unidecode
|
||||
use_unidecode=True
|
||||
except:
|
||||
except Exception as e:
|
||||
use_unidecode=False
|
||||
|
||||
# Global variables
|
||||
|
@ -147,7 +159,7 @@ def send_raw_email(kindle_mail, msg):
|
|||
|
||||
smtplib.stderr = org_stderr
|
||||
|
||||
except (socket.error, smtplib.SMTPRecipientsRefused, smtplib.SMTPException), e:
|
||||
except (socket.error, smtplib.SMTPRecipientsRefused, smtplib.SMTPException) as e:
|
||||
app.logger.error(traceback.print_exc())
|
||||
return _("Failed to send mail: %s" % str(e))
|
||||
|
||||
|
@ -239,7 +251,10 @@ def get_valid_filename(value, replace_whitespace=True):
|
|||
value=value.replace(u'ß',u'ss')
|
||||
value = unicodedata.normalize('NFKD', value)
|
||||
re_slugify = re.compile('[\W\s-]', re.UNICODE)
|
||||
value = unicode(re_slugify.sub('', value).strip())
|
||||
if type(value) is str: #Python3 str, Python2 unicode
|
||||
value = re_slugify.sub('', value).strip()
|
||||
else:
|
||||
value = unicode(re_slugify.sub('', value).strip())
|
||||
if replace_whitespace:
|
||||
#*+:\"/<>? werden durch _ ersetzt
|
||||
value = re.sub('[\*\+:\\\"/<>\?]+', u'_', value, flags=re.U)
|
||||
|
@ -280,6 +295,30 @@ def update_dir_stucture(book_id, calibrepath):
|
|||
book.path = new_authordir + '/' + book.path.split('/')[1]
|
||||
db.session.commit()
|
||||
|
||||
def update_dir_structure_gdrive(book_id):
|
||||
db.session.connection().connection.connection.create_function("title_sort", 1, db.title_sort)
|
||||
book = db.session.query(db.Books).filter(db.Books.id == book_id).first()
|
||||
|
||||
authordir = book.path.split('/')[0]
|
||||
new_authordir = get_valid_filename(book.authors[0].name)
|
||||
titledir = book.path.split('/')[1]
|
||||
new_titledir = get_valid_filename(book.title) + " (" + str(book_id) + ")"
|
||||
|
||||
if titledir != new_titledir:
|
||||
print (titledir)
|
||||
gFile=gd.getFileFromEbooksFolder(web.Gdrive.Instance().drive,os.path.dirname(book.path),titledir)
|
||||
gFile['title']= new_titledir
|
||||
gFile.Upload()
|
||||
book.path = book.path.split('/')[0] + '/' + new_titledir
|
||||
|
||||
if authordir != new_authordir:
|
||||
gFile=gd.getFileFromEbooksFolder(web.Gdrive.Instance().drive,None,authordir)
|
||||
gFile['title']= new_authordir
|
||||
gFile.Upload()
|
||||
book.path = new_authordir + '/' + book.path.split('/')[1]
|
||||
|
||||
db.session.commit()
|
||||
|
||||
class Updater(threading.Thread):
|
||||
|
||||
def __init__(self):
|
||||
|
@ -305,9 +344,13 @@ class Updater(threading.Thread):
|
|||
ub.session.close()
|
||||
ub.engine.dispose()
|
||||
self.status=6
|
||||
# stop tornado server
|
||||
server = IOLoop.instance()
|
||||
server.add_callback(server.stop)
|
||||
|
||||
if web.gevent_server:
|
||||
web.gevent_server.stop()
|
||||
else:
|
||||
# stop tornado server
|
||||
server = IOLoop.instance()
|
||||
server.add_callback(server.stop)
|
||||
self.status=7
|
||||
|
||||
def get_update_status(self):
|
||||
|
@ -379,7 +422,7 @@ class Updater(threading.Thread):
|
|||
try:
|
||||
os.chown(dst_file, permission.st_uid, permission.st_uid)
|
||||
# print('Permissions: User '+str(new_permissions.st_uid)+' Group '+str(new_permissions.st_uid))
|
||||
except:
|
||||
except Exception as e:
|
||||
e = sys.exc_info()
|
||||
logging.getLogger('cps.web').debug('Fail '+str(dst_file)+' error: '+str(e))
|
||||
return
|
||||
|
@ -421,7 +464,7 @@ class Updater(threading.Thread):
|
|||
logging.getLogger('cps.web').debug("Delete file " + item_path)
|
||||
log_from_thread("Delete file " + item_path)
|
||||
os.remove(item_path)
|
||||
except:
|
||||
except Exception as e:
|
||||
logging.getLogger('cps.web').debug("Could not remove:" + item_path)
|
||||
shutil.rmtree(source, ignore_errors=True)
|
||||
|
||||
|
|
180
cps/static/js/get_meta.js
Normal file
180
cps/static/js/get_meta.js
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Get Metadata from Douban Books api and Google Books api
|
||||
* Created by idalin<dalin.lin@gmail.com>
|
||||
* Google Books api document: https://developers.google.com/books/docs/v1/using
|
||||
* Douban Books api document: https://developers.douban.com/wiki/?title=book_v2 (Chinese Only)
|
||||
*/
|
||||
|
||||
$(document).ready(function () {
|
||||
var msg = i18n_msg;
|
||||
var douban = 'https://api.douban.com';
|
||||
var db_search = '/v2/book/search';
|
||||
var db_get_info = '/v2/book/';
|
||||
var db_get_info_by_isbn = '/v2/book/isbn/ ';
|
||||
var db_done = false;
|
||||
|
||||
var google = 'https://www.googleapis.com/';
|
||||
var gg_search = '/books/v1/volumes';
|
||||
var gg_get_info = '/books/v1/volumes/';
|
||||
var gg_done = false;
|
||||
|
||||
var db_results = [];
|
||||
var gg_results = [];
|
||||
var show_flag = 0;
|
||||
String.prototype.replaceAll = function (s1, s2) {
|
||||
return this.replace(new RegExp(s1, "gm"), s2);
|
||||
}
|
||||
|
||||
gg_search_book = function (title) {
|
||||
title = title.replaceAll(/\s+/, '+');
|
||||
var url = google + gg_search + '?q=' + title;
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: "GET",
|
||||
dataType: "jsonp",
|
||||
jsonp: 'callback',
|
||||
success: function (data) {
|
||||
gg_results = data.items;
|
||||
},
|
||||
complete: function () {
|
||||
gg_done = true;
|
||||
show_result();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get_meta = function (source, id) {
|
||||
var meta;
|
||||
if (source == 'google') {;
|
||||
meta = gg_results[id];
|
||||
$('#description').val(meta.volumeInfo.description);
|
||||
$('#bookAuthor').val(meta.volumeInfo.authors.join(' & '));
|
||||
$('#book_title').val(meta.volumeInfo.title);
|
||||
if (meta.volumeInfo.categories) {
|
||||
var tags = meta.volumeInfo.categories.join(',');
|
||||
$('#tags').val(tags);
|
||||
}
|
||||
if (meta.volumeInfo.averageRating) {
|
||||
$('#rating').val(Math.round(meta.volumeInfo.averageRating));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (source == 'douban') {
|
||||
meta = db_results[id];
|
||||
$('#description').val(meta.summary);
|
||||
$('#bookAuthor').val(meta.author.join(' & '));
|
||||
$('#book_title').val(meta.title);
|
||||
var tags = '';
|
||||
for (var i = 0; i < meta.tags.length; i++) {
|
||||
tags = tags + meta.tags[i].title + ',';
|
||||
}
|
||||
$('#tags').val(tags);
|
||||
$('#rating').val(Math.round(meta.rating.average / 2));
|
||||
return;
|
||||
}
|
||||
}
|
||||
do_search = function (keyword) {
|
||||
show_flag = 0;
|
||||
$('#meta-info').text(msg.loading);
|
||||
var keyword = $('#keyword').val();
|
||||
if (keyword) {
|
||||
db_search_book(keyword);
|
||||
gg_search_book(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
db_search_book = function (title) {
|
||||
var url = douban + db_search + '?q=' + title + '&fields=all&count=10';
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: "GET",
|
||||
dataType: "jsonp",
|
||||
jsonp: 'callback',
|
||||
success: function (data) {
|
||||
db_results = data.books;
|
||||
},
|
||||
error: function () {
|
||||
$('#meta-info').html('<p class="text-danger">'+ msg.search_error+'!</p>');
|
||||
},
|
||||
complete: function () {
|
||||
db_done = true;
|
||||
show_result();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
show_result = function () {
|
||||
show_flag++;
|
||||
if (show_flag == 1) {
|
||||
$('#meta-info').html('<ul id="book-list" class="media-list"></ul>');
|
||||
}
|
||||
if (gg_done && db_done) {
|
||||
if (!gg_results && !db_results) {
|
||||
$('#meta-info').html('<p class="text-danger">'+ msg.no_result +'</p>');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (gg_done && gg_results.length > 0) {
|
||||
for (var i = 0; i < gg_results.length; i++) {
|
||||
var book = gg_results[i];
|
||||
var book_cover;
|
||||
if (book.volumeInfo.imageLinks) {
|
||||
book_cover = book.volumeInfo.imageLinks.thumbnail;
|
||||
} else {
|
||||
book_cover = '/static/generic_cover.jpg';
|
||||
}
|
||||
var book_html = '<li class="media">' +
|
||||
'<img class="pull-left img-responsive" data-toggle="modal" data-target="#metaModal" src="' +
|
||||
book_cover + '" alt="Cover" style="width:100px;height:150px" onclick=\'javascript:get_meta("google",' +
|
||||
i + ')\'>' +
|
||||
'<div class="media-body">' +
|
||||
'<h4 class="media-heading"><a href="https://books.google.com/books?id=' +
|
||||
book.id + '" target="_blank">' + book.volumeInfo.title + '</a></h4>' +
|
||||
'<p>'+ msg.author +':' + book.volumeInfo.authors + '</p>' +
|
||||
'<p>'+ msg.publisher + ':' + book.volumeInfo.publisher + '</p>' +
|
||||
'<p>'+ msg.description + ':' + book.volumeInfo.description + '</p>' +
|
||||
'<p>'+ msg.source + ':<a href="https://books.google.com" target="_blank">Google Books</a></p>' +
|
||||
'</div>' +
|
||||
'</li>';
|
||||
$("#book-list").append(book_html);
|
||||
}
|
||||
gg_done = false;
|
||||
}
|
||||
if (db_done && db_results.length > 0) {
|
||||
for (var i = 0; i < db_results.length; i++) {
|
||||
var book = db_results[i];
|
||||
var book_html = '<li class="media">' +
|
||||
'<img class="pull-left img-responsive" data-toggle="modal" data-target="#metaModal" src="' +
|
||||
book.image + '" alt="Cover" style="width:100px;height: 150px" onclick=\'javascript:get_meta("douban",' +
|
||||
i + ')\'>' +
|
||||
'<div class="media-body">' +
|
||||
'<h4 class="media-heading"><a href="https://book.douban.com/subject/' +
|
||||
book.id + '" target="_blank">' + book.title + '</a></h4>' +
|
||||
'<p>' + msg.author + ':' + book.author + '</p>' +
|
||||
'<p>' + msg.publisher + ':' + book.publisher + '</p>' +
|
||||
'<p>' + msg.description + ':' + book.summary + '</p>' +
|
||||
'<p>' + msg.source + ':<a href="https://book.douban.com" target="_blank">Douban Books</a></p>' +
|
||||
'</div>' +
|
||||
'</li>';
|
||||
$("#book-list").append(book_html);
|
||||
}
|
||||
db_done = false;
|
||||
}
|
||||
}
|
||||
|
||||
$('#do-search').click(function () {
|
||||
var keyword = $('#keyword').val();
|
||||
if (keyword) {
|
||||
do_search(keyword);
|
||||
}
|
||||
});
|
||||
|
||||
$('#get_meta').click(function () {
|
||||
var book_title = $('#book_title').val();
|
||||
if (book_title) {
|
||||
$('#keyword').val(book_title);
|
||||
do_search(book_title);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
1
cps/static/js/libs/bootstrap-rating-input.min.js
vendored
Normal file
1
cps/static/js/libs/bootstrap-rating-input.min.js
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
!function(a){"use strict";function b(a){return"[data-value"+(a?"="+a:"")+"]"}function c(a,b,c){var d=c.activeIcon,e=c.inactiveIcon;a.removeClass(b?e:d).addClass(b?d:e)}function d(b,c){var d=a.extend({},i,b.data(),c);return d.inline=""===d.inline||d.inline,d.readonly=""===d.readonly||d.readonly,d.clearable===!1?d.clearableLabel="":d.clearableLabel=d.clearable,d.clearable=""===d.clearable||d.clearable,d}function e(b,c){if(c.inline)var d=a('<span class="rating-input"></span>');else var d=a('<div class="rating-input"></div>');d.addClass(b.attr("class")),d.removeClass("rating");for(var e=c.min;e<=c.max;e++)d.append('<i class="'+c.iconLib+'" data-value="'+e+'"></i>');return c.clearable&&!c.readonly&&d.append(" ").append('<a class="'+f+'"><i class="'+c.iconLib+" "+c.clearableIcon+'"/>'+c.clearableLabel+"</a>"),d}var f="rating-clear",g="."+f,h="hidden",i={min:1,max:5,"empty-value":0,iconLib:"glyphicon",activeIcon:"glyphicon-star",inactiveIcon:"glyphicon-star-empty",clearable:!1,clearableIcon:"glyphicon-remove",clearableRemain:!1,inline:!1,readonly:!1},j=function(a,b){var c=this.$input=a;this.options=d(c,b);var f=this.$el=e(c,this.options);c.addClass(h).before(f),c.attr("type","hidden"),this.highlight(c.val())};j.VERSION="0.4.0",j.DEFAULTS=i,j.prototype={clear:function(){this.setValue(this.options["empty-value"])},setValue:function(a){this.highlight(a),this.updateInput(a)},highlight:function(a,d){var e=this.options,f=this.$el;if(a>=this.options.min&&a<=this.options.max){var i=f.find(b(a));c(i.prevAll("i").andSelf(),!0,e),c(i.nextAll("i"),!1,e)}else c(f.find(b()),!1,e);d||(this.options.clearableRemain?f.find(g).removeClass(h):a&&a!=this.options["empty-value"]?f.find(g).removeClass(h):f.find(g).addClass(h))},updateInput:function(a){var b=this.$input;b.val()!=a&&b.val(a).change()}};var k=a.fn.rating=function(c){return this.filter("input[type=number]").each(function(){var d=a(this),e="object"==typeof c&&c||{},f=new j(d,e);f.options.readonly||f.$el.on("mouseenter",b(),function(){f.highlight(a(this).data("value"),!0)}).on("mouseleave",b(),function(){f.highlight(d.val(),!0)}).on("click",b(),function(){f.setValue(a(this).data("value"))}).on("click",g,function(){f.clear()})})};k.Constructor=j,a(function(){a("input.rating[type=number]").each(function(){a(this).rating()})})}(jQuery);
|
1277
cps/static/js/libs/jquery.form.js
vendored
Normal file
1277
cps/static/js/libs/jquery.form.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -65,6 +65,13 @@ $(function() {
|
|||
}
|
||||
});
|
||||
});
|
||||
$("#restart_database").click(function() {
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
url: window.location.pathname+"/../../shutdown",
|
||||
data: {"parameter":2}
|
||||
});
|
||||
});
|
||||
$("#perform_update").click(function() {
|
||||
$('#spinner2').show();
|
||||
$.ajax({
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
<div>{{_('Current commit timestamp')}}: <span>{{commit}} </span></div>
|
||||
<div class="hidden" id="update_info">{{_('Newest commit timestamp')}}: <span></span></div>
|
||||
<p></p>
|
||||
<div class="btn btn-default" id="restart_database">{{_('Reconnect to Calibre DB')}}</div>
|
||||
<div class="btn btn-default" data-toggle="modal" data-target="#RestartDialog">{{_('Restart Calibre-web')}}</div>
|
||||
<div class="btn btn-default" data-toggle="modal" data-target="#ShutdownDialog">{{_('Stop Calibre-web')}}</div>
|
||||
<div class="btn btn-default" id="check_for_update">{{_('Check for update')}}</div>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<label for="rating">{{_('Rating')}}</label>
|
||||
<input type="number" min="0" max="5" step="1" class="form-control" name="rating" id="rating" value="{% if book.ratings %}{{book.ratings[0].rating / 2}}{% endif %}">
|
||||
<input type="number" name="rating" id="rating" class="rating input-lg" data-clearable="" value="{% if book.ratings %}{{(book.ratings[0].rating / 2)|int}}{% endif %}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="cover_url">{{_('Cover URL (jpg)')}}</label>
|
||||
|
@ -104,16 +104,54 @@
|
|||
<input name="detail_view" type="checkbox" checked> {{_('view book after edit')}}
|
||||
</label>
|
||||
</div>
|
||||
<a href="#" id="get_meta" class="btn btn-default" data-toggle="modal" data-target="#metaModal">{{_('Get metadata')}}</a>
|
||||
<button type="submit" class="btn btn-default">{{_('Submit')}}</button>
|
||||
<a href="{{ url_for('show_book',id=book.id) }}" class="btn btn-default">{{_('Back')}}</a>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="modal fade" id="metaModal" tabindex="-1" role="dialog" aria-labelledby="metaModalLabel">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="metaModalLabel">{{_('Get metadata')}}</h4>
|
||||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<label class="sr-only" for="keyword">{{_('Keyword')}}</label>
|
||||
<input type="text" class="form-control" id="keyword" placeholder="{{_(" Search keyword ")}}">
|
||||
</div>
|
||||
<button type="button" class="btn btn-default" id="do-search">{{_("Go!")}}</button>
|
||||
<span>{{_('Click the cover to load metadata to the form')}}</span>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-body" id="meta-info">
|
||||
{{_("Loading...")}}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{_('Close')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
var i18n_msg = {
|
||||
'loading': {{_('Loading...')|safe|tojson}},
|
||||
'search_error': {{_('Search error!')|safe|tojson}},
|
||||
'no_result': {{_('No Result! Please try anonther keyword.')|safe|tojson}},
|
||||
'author': {{_('Author')|safe|tojson}},
|
||||
'publisher': {{_('Publisher')|safe|tojson}},
|
||||
'description': {{_('Description')|safe|tojson}},
|
||||
'source': {{_('Source')|safe|tojson}},
|
||||
};
|
||||
</script>
|
||||
<script src="{{ url_for('static', filename='js/libs/typeahead.bundle.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/edit_books.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/libs/bootstrap-rating-input.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/get_meta.js') }}"></script>
|
||||
{% endblock %}
|
||||
{% block header %}
|
||||
<link href="{{ url_for('static', filename='css/libs/typeahead.css') }}" rel="stylesheet" media="screen">
|
||||
|
|
|
@ -7,6 +7,47 @@
|
|||
<label for="config_calibre_dir">{{_('Location of Calibre database')}}</label>
|
||||
<input type="text" class="form-control" name="config_calibre_dir" id="config_calibre_dir" value="{% if content.config_calibre_dir != None %}{{ content.config_calibre_dir }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
{% if gdrive %}
|
||||
<div class="form-group required">
|
||||
<input type="checkbox" id="config_use_google_drive" name="config_use_google_drive" {% if content.config_use_google_drive %}checked{% endif %} >
|
||||
<label for="config_use_google_drive">{{_('Use google drive?')}}</label>
|
||||
</div>
|
||||
<div id="gdrive_settings">
|
||||
<div class="form-group required">
|
||||
<label for="config_google_drive_client_id">{{_('Client id')}}</label>
|
||||
<input type="text" class="form-control" name="config_google_drive_client_id" id="config_google_drive_client_id" value="{% if content.config_google_drive_client_id %}{{content.config_google_drive_client_id}}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group required">
|
||||
<label for="config_google_drive_client_secret">{{_('Client secret')}}</label>
|
||||
<input type="text" class="form-control" name="config_google_drive_client_secret" id="config_google_drive_client_secret" value="{% if content.config_google_drive_client_secret %}{{content.config_google_drive_client_secret}}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group required">
|
||||
<label for="config_google_drive_calibre_url_base">{{_('Calibre Base URL')}}</label>
|
||||
<input type="text" class="form-control" name="config_google_drive_calibre_url_base" id="config_google_drive_calibre_url_base" value="{% if content.config_google_drive_calibre_url_base %}{{content.config_google_drive_calibre_url_base}}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group required">
|
||||
<label for="config_google_drive_folder">{{_('Google drive Calibre folder')}}</label>
|
||||
<input type="text" class="form-control" name="config_google_drive_folder" id="config_google_drive_folder" value="{% if content.config_google_drive_folder %}{{content.config_google_drive_folder}}{% endif %}" autocomplete="off" required>
|
||||
</div>
|
||||
{% if show_authenticate_google_drive %}
|
||||
<div class="form-group required">
|
||||
<a href="{{ url_for('authenticate_google_drive') }}" class="btn btn-primary">Authenticate Google Drive</a>
|
||||
</div>
|
||||
{% else %}
|
||||
{% if content.config_google_drive_watch_changes_response %}
|
||||
<label for="config_google_drive_watch_changes_response">{{_('Metadata Watch Channel ID')}}</label>
|
||||
<div class="form-group input-group required">
|
||||
<input type="text" class="form-control" name="config_google_drive_watch_changes_response" id="config_google_drive_watch_changes_response" value="{{ content.config_google_drive_watch_changes_response['id'] }} expires on {{ content.config_google_drive_watch_changes_response['expiration'] | strftime }}" autocomplete="off" disabled="">
|
||||
<span class="input-group-btn">
|
||||
<a href="{{ url_for('revoke_watch_gdrive') }}" class="btn btn-primary">Revoke</a>
|
||||
</span>
|
||||
{% else %}
|
||||
<a href="{{ url_for('watch_gdrive') }}" class="btn btn-primary">Enable watch of metadata.db</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group">
|
||||
<label for="config_port">{{_('Server Port')}}</label>
|
||||
<input type="number" min="1" max="65535" class="form-control" name="config_port" id="config_port" value="{% if content.config_port != None %}{{ content.config_port }}{% endif %}" autocomplete="off" required>
|
||||
|
@ -23,7 +64,10 @@
|
|||
<label for="config_random_books">{{_('No. of random books to show')}}</label>
|
||||
<input type="number" min="1" max="30" class="form-control" name="config_random_books" id="config_random_books" value="{% if content.config_random_books != None %}{{ content.config_random_books }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="config_columns_to_ignore">{{_('Regular expression for ignoring columns')}}</label>
|
||||
<input type="text" class="form-control" name="config_columns_to_ignore" id="config_columns_to_ignore" value="{% if content.config_columns_to_ignore != None %}{{ content.config_columns_to_ignore }}{% endif %}" autocomplete="off">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="config_title_regex">{{_('Regular expression for title sorting')}}</label>
|
||||
<input type="text" class="form-control" name="config_title_regex" id="config_title_regex" value="{% if content.config_title_regex != None %}{{ content.config_title_regex }}{% endif %}" autocomplete="off">
|
||||
|
@ -70,6 +114,10 @@
|
|||
<input type="checkbox" name="passwd_role" id="passwd_role" {% if content.role_passwd() %}checked{% endif %}>
|
||||
<label for="passwd_role">{{_('Allow Changing Password')}}</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" name="edit_shelf_role" id="edit_shelf_role" {% if content.role_edit_shelfs() %}checked{% endif %}>
|
||||
<label for="passwd_role">{{_('Allow Editing Public Shelfs')}}</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default">{{_('Submit')}}</button>
|
||||
{% if not origin %}
|
||||
<a href="{{ url_for('admin') }}" class="btn btn-default">{{_('Back')}}</a>
|
||||
|
@ -80,3 +128,22 @@
|
|||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block js %}
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#config_use_google_drive').trigger("change");
|
||||
});
|
||||
$('#config_use_google_drive').change(function(){
|
||||
formInputs=$("#gdrive_settings :input");
|
||||
isChecked=this.checked;
|
||||
formInputs.each(function(formInput) {
|
||||
$(this).prop('required',isChecked);
|
||||
});
|
||||
if (this.checked) {
|
||||
$('#gdrive_settings').show();
|
||||
} else {
|
||||
$('#gdrive_settings').hide();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{% block body %}
|
||||
<div class="single">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-lg-3 col-xs-12">
|
||||
<div class="col-sm-3 col-lg-3 col-xs-5">
|
||||
<div class="cover">
|
||||
{% if entry.has_cover %}
|
||||
<img src="{{ url_for('get_cover', cover_path=entry.path.replace('\\','/')) }}" />
|
||||
|
@ -107,6 +107,16 @@
|
|||
</div>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if not g.user.is_anonymous() %}
|
||||
<p>
|
||||
<div class="custom_columns" id="have_read_container">
|
||||
<form id="have_read_form" action="{{ url_for('toggle_read', id=entry.id)}}" method="POST") >
|
||||
<input id="have_read_cb" type="checkbox" {% if have_read %}checked{% endif %} >
|
||||
<label for="have_read_cb">{{_('Read')}}</label>
|
||||
</form>
|
||||
</div>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if entry.comments|length > 0 and entry.comments[0].text|length > 0%}
|
||||
|
@ -126,7 +136,7 @@
|
|||
</button>
|
||||
<ul class="dropdown-menu" aria-labelledby="btnGroupDrop1">
|
||||
{% for format in entry.data %}
|
||||
<li><a href="{{ url_for('get_download_link', book_id=entry.id, format=format.format|lower) }}">{{format.format}}</a></li>
|
||||
<li><a href="{{ url_for('get_download_link_ext', book_id=entry.id, format=format.format|lower, anyname=entry.id|string+'.'+format.format) }}">{{format.format}}</a></li>
|
||||
{%endfor%}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -208,3 +218,34 @@
|
|||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script src="{{ url_for('static', filename='js/libs/jquery.form.js') }}"></script>
|
||||
<script>
|
||||
$( document ).ready(function() {
|
||||
var haveReadForm = $('#have_read_form');
|
||||
haveReadForm.ajaxForm();
|
||||
});
|
||||
$("#have_read_container").attr('unselectable','on')
|
||||
.css({'-moz-user-select':'-moz-none',
|
||||
'-moz-user-select':'none',
|
||||
'-o-user-select':'none',
|
||||
'-khtml-user-select':'none', /* you could also put this in a class */
|
||||
'-webkit-user-select':'none',/* and add the CSS class here instead */
|
||||
'-ms-user-select':'none',
|
||||
'user-select':'none'
|
||||
}).bind('selectstart', function(){ return false; });
|
||||
$("#have_read_container").click(function() {
|
||||
var haveReadForm = $('#have_read_form');
|
||||
if ($("#have_read").find('span').hasClass('glyphicon-ok')) {
|
||||
$("#have_read").find('span').removeClass('glyphicon-ok');
|
||||
$("#have_read").find('span').addClass('glyphicon-remove');
|
||||
} else {
|
||||
$("#have_read").find('span').removeClass('glyphicon-remove');
|
||||
$("#have_read").find('span').addClass('glyphicon-ok');
|
||||
}
|
||||
haveReadForm.submit()
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
<uri>https://github.com/janeczku/calibre-web</uri>
|
||||
</author>
|
||||
|
||||
{% if entries and entries[0] %}
|
||||
{% for entry in entries %}
|
||||
<entry>
|
||||
<title>{{entry.title}}</title>
|
||||
|
@ -60,6 +61,7 @@
|
|||
{% endfor %}
|
||||
</entry>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% for entry in listelements %}
|
||||
<entry>
|
||||
<title>{{entry.name}}</title>
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
<div class="discover load-more">
|
||||
<h2>{{title}}</h2>
|
||||
<div class="row">
|
||||
{% if entries[0] %}
|
||||
{% for entry in entries %}
|
||||
<div id="books" class="col-sm-3 col-lg-2 col-xs-6 book">
|
||||
<div class="cover">
|
||||
|
@ -76,6 +77,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -39,6 +39,20 @@
|
|||
<id>{{url_for('feed_discover')}}</id>
|
||||
<content type="text">{{_('Show Random Books')}}</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>{{_('Read Books')}}</title>
|
||||
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_read_books')}}" />
|
||||
<link rel="subsection" href="{{url_for('feed_read_books')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition"/>
|
||||
<id>{{url_for('feed_read_books')}}</id>
|
||||
<content type="text">{{_('Read Books')}}</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>{{_('Unread Books')}}</title>
|
||||
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_unread_books')}}" />
|
||||
<link rel="subsection" href="{{url_for('feed_unread_books')}}" type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition"/>
|
||||
<id>{{url_for('feed_unread_books')}}</id>
|
||||
<content type="text">{{_('Unread Books')}}</content>
|
||||
</entry>
|
||||
<entry>
|
||||
<title>{{_('Authors')}}</title>
|
||||
<link type="application/atom+xml;profile=opds-catalog;type=feed;kind=acquisition" href="{{url_for('feed_authorindex')}}"/>
|
||||
|
|
|
@ -128,6 +128,10 @@
|
|||
{% if g.user.show_best_rated_books() %}
|
||||
<li><a href="{{url_for('best_rated_books')}}"><span class="glyphicon glyphicon-star"></span> {{_('Best rated Books')}}</a></li>
|
||||
{%endif%}
|
||||
{% if g.user.show_read_and_unread() %}
|
||||
<li><a href="{{url_for('read_books')}}"><span class="glyphicon glyphicon-eye-open"></span> {{_('Read Books')}}</a></li>
|
||||
<li><a href="{{url_for('unread_books')}}"><span class="glyphicon glyphicon-eye-close"></span> {{_('Unread Books')}}</a></li>
|
||||
{%endif%}
|
||||
{% if g.user.show_random_books() %}
|
||||
<li id="nav_rand"><a href="{{url_for('discover')}}"><span class="glyphicon glyphicon-random"></span> {{_('Discover')}}</a></li>
|
||||
{%endif%}
|
||||
|
|
|
@ -3,10 +3,11 @@
|
|||
<div class="discover">
|
||||
<h2>{{title}}</h2>
|
||||
{% if g.user.is_authenticated %}
|
||||
<a href="{{ url_for('delete_shelf', shelf_id=shelf.id) }}" class="btn btn-danger">{{ _('Delete this Shelf') }} </a>
|
||||
<a href="{{ url_for('edit_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Edit Shelf name') }} </a>
|
||||
<a href="{{ url_for('order_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Change order') }} </a>
|
||||
|
||||
{% if (g.user.role_edit_shelfs() and shelf.is_public ) or not shelf.is_public %}
|
||||
<div data-toggle="modal" data-target="#DeleteShelfDialog" class="btn btn-danger">{{ _('Delete this Shelf') }} </div>
|
||||
<a href="{{ url_for('edit_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Edit Shelf name') }} </a>
|
||||
<a href="{{ url_for('order_shelf', shelf_id=shelf.id) }}" class="btn btn-primary">{{ _('Change order') }} </a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
|
||||
|
@ -39,4 +40,20 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="DeleteShelfDialog" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-center">
|
||||
<span>{{_('Do you really want to delete the shelf?')}}</span>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<span>{{_('Shelf will be lost for everybody and forever!')}}</span>
|
||||
<p></p>
|
||||
<a href="{{ url_for('delete_shelf', shelf_id=shelf.id) }}" class="btn btn-danger">{{_('Ok')}}</a>
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{_('Back')}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -7,11 +7,13 @@
|
|||
<label for="title">{{_('Title')}}</label>
|
||||
<input type="text" class="form-control" name="title" id="title" value="{{ shelf.name if shelf.name != None }}">
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="is_public" {% if shelf.is_public == 1 %}checked{% endif %}> {{_('should the shelf be public?')}}
|
||||
</label>
|
||||
</div>
|
||||
{% if g.user.role_edit_shelfs() %}
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="is_public" {% if shelf.is_public == 1 %}checked{% endif %}> {{_('should the shelf be public?')}}
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
<button type="submit" class="btn btn-default">{{_('Submit')}}</button>
|
||||
{% if shelf.id != None %}
|
||||
<a href="{{ url_for('show_shelf', shelf_id=shelf.id) }}" class="btn btn-default">{{_('Back')}}</a>
|
||||
|
|
|
@ -70,6 +70,10 @@
|
|||
<input type="checkbox" name="show_author" id="show_author" {% if content.show_author() %}checked{% endif %}>
|
||||
<label for="show_author">{{_('Show author selection')}}</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" name="show_read_and_unread" id="show_read_and_unread" {% if content.show_read_and_unread() %}checked{% endif %}>
|
||||
<label for="show_read_and_unread">{{_('Show read and unread')}}</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" name="show_detail_random" id="show_detail_random" {% if content.show_detail_random() %}checked{% endif %}>
|
||||
<label for="show_detail_random">{{_('Show random books in detail view')}}</label>
|
||||
|
@ -100,6 +104,10 @@
|
|||
<input type="checkbox" name="passwd_role" id="passwd_role" {% if content.role_passwd() %}checked{% endif %}>
|
||||
<label for="passwd_role">{{_('Allow Changing Password')}}</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" name="edit_shelf_role" id="edit_shelf_role" {% if content.role_edit_shelfs() %}checked{% endif %}>
|
||||
<label for="passwd_role">{{_('Allow Editing Public Shelfs')}}</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if g.user and g.user.role_admin() and not profile and not new_user and not content.role_anonymous() %}
|
||||
|
|
Binary file not shown.
|
@ -21,7 +21,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Calibre-web\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/janeczku/calibre-web\n"
|
||||
"POT-Creation-Date: 2017-02-20 19:47+0100\n"
|
||||
"POT-Creation-Date: 2017-03-19 19:20+0100\n"
|
||||
"PO-Revision-Date: 2016-07-12 19:54+0200\n"
|
||||
"Last-Translator: Ozzie Isaacs\n"
|
||||
"Language: de\n"
|
||||
|
@ -32,347 +32,360 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.3.4\n"
|
||||
|
||||
#: cps/book_formats.py:111 cps/book_formats.py:115 cps/web.py:1030
|
||||
#: cps/book_formats.py:113 cps/book_formats.py:117 cps/web.py:1244
|
||||
msgid "not installed"
|
||||
msgstr "Nicht installiert"
|
||||
|
||||
#: cps/helper.py:150
|
||||
#: cps/helper.py:164
|
||||
#, python-format
|
||||
msgid "Failed to send mail: %s"
|
||||
msgstr "E-Mail: %s konnte nicht gesendet werden"
|
||||
|
||||
#: cps/helper.py:157
|
||||
#: cps/helper.py:171
|
||||
msgid "Calibre-web test email"
|
||||
msgstr "Calibre-web Test E-Mail"
|
||||
|
||||
#: cps/helper.py:158 cps/helper.py:168
|
||||
#: cps/helper.py:172 cps/helper.py:184
|
||||
msgid "This email has been sent via calibre web."
|
||||
msgstr "Die E-Mail wurde via calibre-web versendet"
|
||||
|
||||
#: cps/helper.py:167 cps/templates/detail.html:130
|
||||
#: cps/helper.py:181 cps/templates/detail.html:146
|
||||
msgid "Send to Kindle"
|
||||
msgstr "An Kindle senden"
|
||||
|
||||
#: cps/helper.py:185 cps/helper.py:200
|
||||
#: cps/helper.py:201 cps/helper.py:216
|
||||
msgid "Could not find any formats suitable for sending by email"
|
||||
msgstr ""
|
||||
"Konnte keine Formate finden welche für das versenden per E-Mail geeignet "
|
||||
"sind"
|
||||
|
||||
#: cps/helper.py:194
|
||||
#: cps/helper.py:210
|
||||
msgid "Could not convert epub to mobi"
|
||||
msgstr "Konnte .epub nicht nach .mobi konvertieren"
|
||||
|
||||
#: cps/ub.py:434
|
||||
#: cps/ub.py:488
|
||||
msgid "Guest"
|
||||
msgstr "Gast"
|
||||
|
||||
#: cps/web.py:734
|
||||
#: cps/web.py:904
|
||||
msgid "Requesting update package"
|
||||
msgstr "Frage Update Paket an"
|
||||
|
||||
#: cps/web.py:735
|
||||
#: cps/web.py:905
|
||||
msgid "Downloading update package"
|
||||
msgstr "Lade Update Paket herunter"
|
||||
|
||||
#: cps/web.py:736
|
||||
#: cps/web.py:906
|
||||
msgid "Unzipping update package"
|
||||
msgstr "Entpacke Update Paket"
|
||||
|
||||
#: cps/web.py:737
|
||||
#: cps/web.py:907
|
||||
msgid "Files are replaced"
|
||||
msgstr "Ersetze Dateien"
|
||||
|
||||
#: cps/web.py:738
|
||||
#: cps/web.py:908
|
||||
msgid "Database connections are closed"
|
||||
msgstr "Schließe Datenbankverbindungen"
|
||||
|
||||
#: cps/web.py:739
|
||||
#: cps/web.py:909
|
||||
msgid "Server is stopped"
|
||||
msgstr "Stoppe Server"
|
||||
|
||||
#: cps/web.py:740
|
||||
#: cps/web.py:910
|
||||
msgid "Update finished, please press okay and reload page"
|
||||
msgstr "Update abgeschlossen, bitte okay drücken und Seite neu laden"
|
||||
|
||||
#: cps/web.py:810
|
||||
#: cps/web.py:983
|
||||
msgid "Latest Books"
|
||||
msgstr "Letzte Bücher"
|
||||
|
||||
#: cps/web.py:835
|
||||
#: cps/web.py:1014
|
||||
msgid "Hot Books (most downloaded)"
|
||||
msgstr "Beliebte Bücher (die meisten Downloads)"
|
||||
|
||||
#: cps/web.py:845
|
||||
#: cps/web.py:1024
|
||||
msgid "Best rated books"
|
||||
msgstr "Best bewertete Bücher"
|
||||
|
||||
#: cps/templates/index.xml:36 cps/web.py:854
|
||||
#: cps/templates/index.xml:36 cps/web.py:1033
|
||||
msgid "Random Books"
|
||||
msgstr "Zufällige Bücher"
|
||||
|
||||
#: cps/web.py:867
|
||||
#: cps/web.py:1046
|
||||
msgid "Author list"
|
||||
msgstr "Autorenliste"
|
||||
|
||||
#: cps/web.py:878
|
||||
#: cps/web.py:1057
|
||||
#, python-format
|
||||
msgid "Author: %(name)s"
|
||||
msgstr "Autor: %(name)s"
|
||||
|
||||
#: cps/web.py:880 cps/web.py:908 cps/web.py:1007 cps/web.py:1235
|
||||
#: cps/web.py:2115
|
||||
#: cps/web.py:1059 cps/web.py:1087 cps/web.py:1221 cps/web.py:1626
|
||||
#: cps/web.py:2579
|
||||
msgid "Error opening eBook. File does not exist or file is not accessible:"
|
||||
msgstr ""
|
||||
"Buch öffnen fehlgeschlagen. Datei existiert nicht, oder ist nicht "
|
||||
"zugänglich."
|
||||
|
||||
#: cps/templates/index.xml:57 cps/web.py:894
|
||||
#: cps/templates/index.xml:71 cps/web.py:1073
|
||||
msgid "Series list"
|
||||
msgstr "Liste Serien"
|
||||
|
||||
#: cps/web.py:906
|
||||
#: cps/web.py:1085
|
||||
#, python-format
|
||||
msgid "Series: %(serie)s"
|
||||
msgstr "Serie: %(serie)s"
|
||||
|
||||
#: cps/web.py:939
|
||||
#: cps/web.py:1118
|
||||
msgid "Available languages"
|
||||
msgstr "Verfügbare Sprachen"
|
||||
|
||||
#: cps/web.py:954
|
||||
#: cps/web.py:1133
|
||||
#, python-format
|
||||
msgid "Language: %(name)s"
|
||||
msgstr "Sprache: %(name)s"
|
||||
|
||||
#: cps/templates/index.xml:50 cps/web.py:967
|
||||
#: cps/templates/index.xml:64 cps/web.py:1146
|
||||
msgid "Category list"
|
||||
msgstr "Kategorieliste"
|
||||
|
||||
#: cps/web.py:979
|
||||
#: cps/web.py:1158
|
||||
#, python-format
|
||||
msgid "Category: %(name)s"
|
||||
msgstr "Kategorie: %(name)s"
|
||||
|
||||
#: cps/web.py:1040
|
||||
#: cps/web.py:1267
|
||||
msgid "Statistics"
|
||||
msgstr "Statistiken"
|
||||
|
||||
#: cps/web.py:1061
|
||||
#: cps/web.py:1375
|
||||
msgid "Server restarted, please reload page"
|
||||
msgstr "Server neu gestartet,bitte Seite neu laden"
|
||||
|
||||
#: cps/web.py:1063
|
||||
#: cps/web.py:1377
|
||||
msgid "Performing shutdown of server, please close window"
|
||||
msgstr "Server wird runtergefahren, bitte Fenster schließen"
|
||||
|
||||
#: cps/web.py:1073
|
||||
#: cps/web.py:1392
|
||||
msgid "Update done"
|
||||
msgstr "Update durchgeführt"
|
||||
|
||||
#: cps/web.py:1147 cps/web.py:1160
|
||||
#: cps/web.py:1470 cps/web.py:1483
|
||||
msgid "search"
|
||||
msgstr "Suche"
|
||||
|
||||
#: cps/web.py:1211 cps/web.py:1218 cps/web.py:1225 cps/web.py:1232
|
||||
#: cps/web.py:1602 cps/web.py:1609 cps/web.py:1616 cps/web.py:1623
|
||||
msgid "Read a Book"
|
||||
msgstr "Lese ein Buch"
|
||||
|
||||
#: cps/web.py:1276 cps/web.py:1713
|
||||
#: cps/web.py:1676 cps/web.py:2152
|
||||
msgid "Please fill out all fields!"
|
||||
msgstr "Bitte alle Felder ausfüllen!"
|
||||
|
||||
#: cps/web.py:1277 cps/web.py:1293 cps/web.py:1298 cps/web.py:1300
|
||||
#: cps/web.py:1677 cps/web.py:1693 cps/web.py:1698 cps/web.py:1700
|
||||
msgid "register"
|
||||
msgstr "Registieren"
|
||||
|
||||
#: cps/web.py:1292
|
||||
#: cps/web.py:1692
|
||||
msgid "An unknown error occured. Please try again later."
|
||||
msgstr "Es ist ein unbekannter Fehler aufgetreten. Bitte später erneut versuchen."
|
||||
|
||||
#: cps/web.py:1297
|
||||
#: cps/web.py:1697
|
||||
msgid "This username or email address is already in use."
|
||||
msgstr "Der Benutzername oder die E-Mailadresse ist in bereits in Benutzung."
|
||||
|
||||
#: cps/web.py:1315
|
||||
#: cps/web.py:1715
|
||||
#, python-format
|
||||
msgid "you are now logged in as: '%(nickname)s'"
|
||||
msgstr "Du bist nun eingeloggt als '%(nickname)s'"
|
||||
|
||||
#: cps/web.py:1320
|
||||
#: cps/web.py:1720
|
||||
msgid "Wrong Username or Password"
|
||||
msgstr "Falscher Benutzername oder Passwort"
|
||||
|
||||
#: cps/web.py:1322
|
||||
#: cps/web.py:1722
|
||||
msgid "login"
|
||||
msgstr "Login"
|
||||
|
||||
#: cps/web.py:1339
|
||||
#: cps/web.py:1739
|
||||
msgid "Please configure the SMTP mail settings first..."
|
||||
msgstr "Bitte zuerst die SMTP Mail Einstellung konfigurieren ..."
|
||||
|
||||
#: cps/web.py:1343
|
||||
#: cps/web.py:1743
|
||||
#, python-format
|
||||
msgid "Book successfully send to %(kindlemail)s"
|
||||
msgstr "Buch erfolgreich versandt an %(kindlemail)s"
|
||||
|
||||
#: cps/web.py:1347
|
||||
#: cps/web.py:1747
|
||||
#, python-format
|
||||
msgid "There was an error sending this book: %(res)s"
|
||||
msgstr "Beim Senden des Buchs trat ein Fehler auf: %(res)s"
|
||||
|
||||
#: cps/web.py:1349
|
||||
#: cps/web.py:1749 cps/web.py:2232
|
||||
msgid "Please configure your kindle email address first..."
|
||||
msgstr "Bitte die Kindle E-Mail Adresse zuuerst konfigurieren..."
|
||||
|
||||
#: cps/web.py:1369
|
||||
#: cps/web.py:1774
|
||||
#, python-format
|
||||
msgid "Book has been added to shelf: %(sname)s"
|
||||
msgstr "Das Buch wurde dem Bücherregal: %(sname)s hinzugefügt"
|
||||
|
||||
#: cps/web.py:1390
|
||||
#: cps/web.py:1793
|
||||
#, python-format
|
||||
msgid "Book has been removed from shelf: %(sname)s"
|
||||
msgstr "Das Buch wurde aus dem Bücherregal: %(sname)s entfernt"
|
||||
|
||||
#: cps/web.py:1409 cps/web.py:1433
|
||||
#: cps/web.py:1812 cps/web.py:1836
|
||||
#, python-format
|
||||
msgid "A shelf with the name '%(title)s' already exists."
|
||||
msgstr "Es existiert bereits ein Bücheregal mit dem Titel '%(title)s'"
|
||||
|
||||
#: cps/web.py:1414
|
||||
#: cps/web.py:1817
|
||||
#, python-format
|
||||
msgid "Shelf %(title)s created"
|
||||
msgstr "Bücherregal %(title)s erzeugt"
|
||||
|
||||
#: cps/web.py:1416 cps/web.py:1444
|
||||
#: cps/web.py:1819 cps/web.py:1847
|
||||
msgid "There was an error"
|
||||
msgstr "Es trat ein Fehler auf"
|
||||
|
||||
#: cps/web.py:1417 cps/web.py:1419
|
||||
#: cps/web.py:1820 cps/web.py:1822
|
||||
msgid "create a shelf"
|
||||
msgstr "Bücherregal erzeugen"
|
||||
|
||||
#: cps/web.py:1442
|
||||
#: cps/web.py:1845
|
||||
#, python-format
|
||||
msgid "Shelf %(title)s changed"
|
||||
msgstr "Bücherregal %(title)s verändert"
|
||||
|
||||
#: cps/web.py:1445 cps/web.py:1447
|
||||
#: cps/web.py:1848 cps/web.py:1850
|
||||
msgid "Edit a shelf"
|
||||
msgstr "Bücherregal editieren"
|
||||
|
||||
#: cps/web.py:1465
|
||||
#: cps/web.py:1868
|
||||
#, python-format
|
||||
msgid "successfully deleted shelf %(name)s"
|
||||
msgstr "Bücherregal %(name)s erfolgreich gelöscht"
|
||||
|
||||
#: cps/web.py:1487
|
||||
#: cps/web.py:1890
|
||||
#, python-format
|
||||
msgid "Shelf: '%(name)s'"
|
||||
msgstr "Bücherregal: '%(name)s'"
|
||||
|
||||
#: cps/web.py:1518
|
||||
#: cps/web.py:1921
|
||||
#, python-format
|
||||
msgid "Change order of Shelf: '%(name)s'"
|
||||
msgstr "Reihenfolge in Bücherregal '%(name)s' verändern"
|
||||
|
||||
#: cps/web.py:1580
|
||||
#: cps/web.py:1985
|
||||
msgid "Found an existing account for this email address."
|
||||
msgstr "Es existiert ein Benutzerkonto für diese E-Mailadresse"
|
||||
|
||||
#: cps/web.py:1582 cps/web.py:1586
|
||||
#: cps/web.py:1987 cps/web.py:1991
|
||||
#, python-format
|
||||
msgid "%(name)s's profile"
|
||||
msgstr "%(name)s's Profil"
|
||||
|
||||
#: cps/web.py:1583
|
||||
#: cps/web.py:1988
|
||||
msgid "Profile updated"
|
||||
msgstr "Profil aktualisiert"
|
||||
|
||||
#: cps/web.py:1597
|
||||
#: cps/web.py:2002
|
||||
msgid "Admin page"
|
||||
msgstr "Admin Seite"
|
||||
|
||||
#: cps/web.py:1668
|
||||
#: cps/web.py:2106
|
||||
msgid "Calibre-web configuration updated"
|
||||
msgstr "Calibre-web Konfiguration wurde aktualisiert"
|
||||
|
||||
#: cps/web.py:1675 cps/web.py:1681 cps/web.py:1694
|
||||
#: cps/web.py:2113 cps/web.py:2119 cps/web.py:2133
|
||||
msgid "Basic Configuration"
|
||||
msgstr "Basis Konfiguration"
|
||||
|
||||
#: cps/web.py:1679
|
||||
#: cps/web.py:2117
|
||||
msgid "DB location is not valid, please enter correct path"
|
||||
msgstr "DB Speicherort ist ungültig, bitte Pfad korrigieren"
|
||||
|
||||
#: cps/templates/admin.html:34 cps/web.py:1715 cps/web.py:1761
|
||||
#: cps/templates/admin.html:34 cps/web.py:2154 cps/web.py:2202
|
||||
msgid "Add new user"
|
||||
msgstr "Neuen Benutzer hinzufügen"
|
||||
|
||||
#: cps/web.py:1753
|
||||
#: cps/web.py:2194
|
||||
#, python-format
|
||||
msgid "User '%(user)s' created"
|
||||
msgstr "Benutzer '%(user)s' angelegt"
|
||||
|
||||
#: cps/web.py:1757
|
||||
#: cps/web.py:2198
|
||||
msgid "Found an existing account for this email address or nickname."
|
||||
msgstr ""
|
||||
"Es existiert ein Benutzerkonto für diese Emailadresse oder den "
|
||||
"Benutzernamen."
|
||||
|
||||
#: cps/web.py:1779
|
||||
#: cps/web.py:2220
|
||||
msgid "Mail settings updated"
|
||||
msgstr "E-Mail Einstellungen aktualisiert"
|
||||
|
||||
#: cps/web.py:1785
|
||||
#: cps/web.py:2227
|
||||
#, python-format
|
||||
msgid "Test E-Mail successfully send to %(kindlemail)s"
|
||||
msgstr "Test E-Mail erfolgreich an %(kindlemail)s versendet"
|
||||
|
||||
#: cps/web.py:1788
|
||||
#: cps/web.py:2230
|
||||
#, python-format
|
||||
msgid "There was an error sending the Test E-Mail: %(res)s"
|
||||
msgstr "Fehler beim versenden der Test E-Mail: %(res)s"
|
||||
|
||||
#: cps/web.py:1789
|
||||
#: cps/web.py:2234
|
||||
msgid "E-Mail settings updated"
|
||||
msgstr "E-Mail Einstellungen wurde aktualisiert"
|
||||
|
||||
#: cps/web.py:2235
|
||||
msgid "Edit mail settings"
|
||||
msgstr "E-Mail Einstellungen editieren"
|
||||
|
||||
#: cps/web.py:1817
|
||||
#: cps/web.py:2263
|
||||
#, python-format
|
||||
msgid "User '%(nick)s' deleted"
|
||||
msgstr "Benutzer '%(nick)s' gelöscht"
|
||||
|
||||
#: cps/web.py:1898
|
||||
#: cps/web.py:2349
|
||||
#, python-format
|
||||
msgid "User '%(nick)s' updated"
|
||||
msgstr "Benutzer '%(nick)s' aktualisiert"
|
||||
|
||||
#: cps/web.py:1901
|
||||
#: cps/web.py:2352
|
||||
msgid "An unknown error occured."
|
||||
msgstr "Es ist ein unbekanter Fehler aufgetreten"
|
||||
|
||||
#: cps/web.py:1904
|
||||
#: cps/web.py:2355
|
||||
#, python-format
|
||||
msgid "Edit User %(nick)s"
|
||||
msgstr "Benutzer %(nick)s bearbeiten"
|
||||
|
||||
#: cps/web.py:2110 cps/web.py:2113 cps/web.py:2188
|
||||
#: cps/web.py:2574 cps/web.py:2577 cps/web.py:2689
|
||||
msgid "edit metadata"
|
||||
msgstr "Metadaten editieren"
|
||||
|
||||
#: cps/web.py:2145
|
||||
#: cps/web.py:2598
|
||||
#, python-format
|
||||
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
|
||||
msgstr "Die Dateiendung \"%s\" kann nicht auf diesen Server hochgeladen werden"
|
||||
|
||||
#: cps/web.py:2604
|
||||
msgid "File to be uploaded must have an extension"
|
||||
msgstr "Datei müssen eine Erweiterung haben, um hochgeladen zu werden"
|
||||
|
||||
#: cps/web.py:2621
|
||||
#, python-format
|
||||
msgid "Failed to create path %s (Permission denied)."
|
||||
msgstr "Fehler beim Erzeugen des Pfads %s (Zugriff verweigert)"
|
||||
|
||||
#: cps/web.py:2150
|
||||
#: cps/web.py:2626
|
||||
#, python-format
|
||||
msgid "Failed to store file %s (Permission denied)."
|
||||
msgstr "Fehler beim speichern der Datei %s (Zugriff verweigert)"
|
||||
|
||||
#: cps/web.py:2155
|
||||
#: cps/web.py:2631
|
||||
#, python-format
|
||||
msgid "Failed to delete file %s (Permission denied)."
|
||||
msgstr "Fehler beim Löschen von Datei %s (Zugriff verweigert)"
|
||||
|
@ -401,7 +414,7 @@ msgstr "DLS"
|
|||
msgid "Admin"
|
||||
msgstr "Admin"
|
||||
|
||||
#: cps/templates/admin.html:13 cps/templates/detail.html:117
|
||||
#: cps/templates/admin.html:13 cps/templates/detail.html:134
|
||||
msgid "Download"
|
||||
msgstr "Download"
|
||||
|
||||
|
@ -457,7 +470,7 @@ msgstr "Konfiguration"
|
|||
msgid "Calibre DB dir"
|
||||
msgstr "Calibre DB Pfad"
|
||||
|
||||
#: cps/templates/admin.html:61 cps/templates/config_edit.html:32
|
||||
#: cps/templates/admin.html:61 cps/templates/config_edit.html:76
|
||||
msgid "Log Level"
|
||||
msgstr "Log Level"
|
||||
|
||||
|
@ -465,7 +478,7 @@ msgstr "Log Level"
|
|||
msgid "Port"
|
||||
msgstr "Port"
|
||||
|
||||
#: cps/templates/admin.html:63 cps/templates/config_edit.html:19
|
||||
#: cps/templates/admin.html:63 cps/templates/config_edit.html:60
|
||||
msgid "Books per page"
|
||||
msgstr "Bücher pro Seite"
|
||||
|
||||
|
@ -494,42 +507,46 @@ msgid "Newest commit timestamp"
|
|||
msgstr "Neuestes Commit Datum"
|
||||
|
||||
#: cps/templates/admin.html:83
|
||||
msgid "Reconnect to Calibre DB"
|
||||
msgstr "Calibre-DB neu verbinden"
|
||||
|
||||
#: cps/templates/admin.html:84
|
||||
msgid "Restart Calibre-web"
|
||||
msgstr "Calibre-web Neustarten"
|
||||
|
||||
#: cps/templates/admin.html:84
|
||||
#: cps/templates/admin.html:85
|
||||
msgid "Stop Calibre-web"
|
||||
msgstr "Stoppe Calibre-web"
|
||||
|
||||
#: cps/templates/admin.html:85
|
||||
#: cps/templates/admin.html:86
|
||||
msgid "Check for update"
|
||||
msgstr "Suche nach Update"
|
||||
|
||||
#: cps/templates/admin.html:86
|
||||
#: cps/templates/admin.html:87
|
||||
msgid "Perform Update"
|
||||
msgstr "Update durchführen"
|
||||
|
||||
#: cps/templates/admin.html:96
|
||||
#: cps/templates/admin.html:97
|
||||
msgid "Do you really want to restart Calibre-web?"
|
||||
msgstr "Calibre-web wirklich neustarten?"
|
||||
|
||||
#: cps/templates/admin.html:101 cps/templates/admin.html:115
|
||||
#: cps/templates/admin.html:136
|
||||
#: cps/templates/admin.html:102 cps/templates/admin.html:116
|
||||
#: cps/templates/admin.html:137
|
||||
msgid "Ok"
|
||||
msgstr "Ok"
|
||||
|
||||
#: cps/templates/admin.html:102 cps/templates/admin.html:116
|
||||
#: cps/templates/book_edit.html:108 cps/templates/config_edit.html:75
|
||||
#: cps/templates/admin.html:103 cps/templates/admin.html:117
|
||||
#: cps/templates/book_edit.html:109 cps/templates/config_edit.html:119
|
||||
#: cps/templates/email_edit.html:36 cps/templates/shelf_edit.html:17
|
||||
#: cps/templates/shelf_order.html:12 cps/templates/user_edit.html:111
|
||||
#: cps/templates/shelf_order.html:12 cps/templates/user_edit.html:120
|
||||
msgid "Back"
|
||||
msgstr "Zurück"
|
||||
|
||||
#: cps/templates/admin.html:114
|
||||
#: cps/templates/admin.html:115
|
||||
msgid "Do you really want to stop Calibre-web?"
|
||||
msgstr "Calibre-web wirklich stoppen"
|
||||
|
||||
#: cps/templates/admin.html:127
|
||||
#: cps/templates/admin.html:128
|
||||
msgid "Updating, please do not reload page"
|
||||
msgstr "Updatevorgang, bitte Seite nicht neu laden"
|
||||
|
||||
|
@ -537,20 +554,21 @@ msgstr "Updatevorgang, bitte Seite nicht neu laden"
|
|||
msgid "Book Title"
|
||||
msgstr "Buchtitel"
|
||||
|
||||
#: cps/templates/book_edit.html:20 cps/templates/search_form.html:10
|
||||
#: cps/templates/book_edit.html:20 cps/templates/book_edit.html:145
|
||||
#: cps/templates/search_form.html:10
|
||||
msgid "Author"
|
||||
msgstr "Autor"
|
||||
|
||||
#: cps/templates/book_edit.html:24
|
||||
#: cps/templates/book_edit.html:24 cps/templates/book_edit.html:147
|
||||
msgid "Description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: cps/templates/book_edit.html:28 cps/templates/search_form.html:13
|
||||
#: cps/templates/book_edit.html:28 cps/templates/search_form.html:17
|
||||
msgid "Tags"
|
||||
msgstr "Tags"
|
||||
|
||||
#: cps/templates/book_edit.html:33 cps/templates/layout.html:138
|
||||
#: cps/templates/search_form.html:33
|
||||
#: cps/templates/book_edit.html:33 cps/templates/layout.html:142
|
||||
#: cps/templates/search_form.html:37
|
||||
msgid "Series"
|
||||
msgstr "Serien"
|
||||
|
||||
|
@ -582,69 +600,142 @@ msgstr "Nein"
|
|||
msgid "view book after edit"
|
||||
msgstr "Buch nach Bearbeitung ansehen"
|
||||
|
||||
#: cps/templates/book_edit.html:107 cps/templates/config_edit.html:73
|
||||
#: cps/templates/login.html:19 cps/templates/search_form.html:75
|
||||
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:109
|
||||
#: cps/templates/book_edit.html:107 cps/templates/book_edit.html:118
|
||||
msgid "Get metadata"
|
||||
msgstr "Metadaten laden"
|
||||
|
||||
#: cps/templates/book_edit.html:108 cps/templates/config_edit.html:117
|
||||
#: cps/templates/login.html:19 cps/templates/search_form.html:79
|
||||
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:118
|
||||
msgid "Submit"
|
||||
msgstr "Abschicken"
|
||||
|
||||
#: cps/templates/book_edit.html:121
|
||||
msgid "Keyword"
|
||||
msgstr "Suchbegriff"
|
||||
|
||||
#: cps/templates/book_edit.html:122
|
||||
msgid " Search keyword "
|
||||
msgstr "Suchbegriff"
|
||||
|
||||
#: cps/templates/book_edit.html:124 cps/templates/layout.html:60
|
||||
msgid "Go!"
|
||||
msgstr "Los!"
|
||||
|
||||
#: cps/templates/book_edit.html:125
|
||||
msgid "Click the cover to load metadata to the form"
|
||||
msgstr "Klicke auf das Bild um die Metadaten zu übertragen"
|
||||
|
||||
#: cps/templates/book_edit.html:129 cps/templates/book_edit.html:142
|
||||
msgid "Loading..."
|
||||
msgstr "Lade..."
|
||||
|
||||
#: cps/templates/book_edit.html:132
|
||||
msgid "Close"
|
||||
msgstr "Schließen"
|
||||
|
||||
#: cps/templates/book_edit.html:143
|
||||
msgid "Search error!"
|
||||
msgstr "Fehler bei Suche!"
|
||||
|
||||
#: cps/templates/book_edit.html:144
|
||||
msgid "No Result! Please try anonther keyword."
|
||||
msgstr "Kein Ergebniss! Bitte anderen Begriff versuchen"
|
||||
|
||||
#: cps/templates/book_edit.html:146 cps/templates/detail.html:76
|
||||
#: cps/templates/search_form.html:14
|
||||
msgid "Publisher"
|
||||
msgstr "Herausgeber"
|
||||
|
||||
#: cps/templates/book_edit.html:148
|
||||
msgid "Source"
|
||||
msgstr "Quelle"
|
||||
|
||||
#: cps/templates/config_edit.html:7
|
||||
msgid "Location of Calibre database"
|
||||
msgstr "Speicherort der Calibre Datenbank"
|
||||
|
||||
#: cps/templates/config_edit.html:11
|
||||
#: cps/templates/config_edit.html:13
|
||||
msgid "Use google drive?"
|
||||
msgstr "Google Drive benutzen"
|
||||
|
||||
#: cps/templates/config_edit.html:17
|
||||
msgid "Client id"
|
||||
msgstr "Benutzer Id"
|
||||
|
||||
#: cps/templates/config_edit.html:21
|
||||
msgid "Client secret"
|
||||
msgstr "Benutzer Secret"
|
||||
|
||||
#: cps/templates/config_edit.html:25
|
||||
msgid "Calibre Base URL"
|
||||
msgstr "Calibnre Basis URL"
|
||||
|
||||
#: cps/templates/config_edit.html:29
|
||||
msgid "Google drive Calibre folder"
|
||||
msgstr "Google Drive Calibre Ordner"
|
||||
|
||||
#: cps/templates/config_edit.html:38
|
||||
msgid "Metadata Watch Channel ID"
|
||||
msgstr "Matadata Überwachungs-ID"
|
||||
|
||||
#: cps/templates/config_edit.html:52
|
||||
msgid "Server Port"
|
||||
msgstr "Server Port"
|
||||
|
||||
#: cps/templates/config_edit.html:15 cps/templates/shelf_edit.html:7
|
||||
#: cps/templates/config_edit.html:56 cps/templates/shelf_edit.html:7
|
||||
msgid "Title"
|
||||
msgstr "Titel"
|
||||
|
||||
#: cps/templates/config_edit.html:23
|
||||
#: cps/templates/config_edit.html:64
|
||||
msgid "No. of random books to show"
|
||||
msgstr "Anzahl Anzeige zufällige Bücher"
|
||||
|
||||
#: cps/templates/config_edit.html:28
|
||||
#: cps/templates/config_edit.html:68
|
||||
msgid "Regular expression for ignoring columns"
|
||||
msgstr "Regulärer Ausdruck um Spalten zu ignorien"
|
||||
|
||||
#: cps/templates/config_edit.html:72
|
||||
msgid "Regular expression for title sorting"
|
||||
msgstr "Regulärer Ausdruck für Titelsortierung"
|
||||
|
||||
#: cps/templates/config_edit.html:42
|
||||
#: cps/templates/config_edit.html:86
|
||||
msgid "Enable uploading"
|
||||
msgstr "Hochladen aktivieren"
|
||||
|
||||
#: cps/templates/config_edit.html:46
|
||||
#: cps/templates/config_edit.html:90
|
||||
msgid "Enable anonymous browsing"
|
||||
msgstr "Anonymes Browsen aktivieren"
|
||||
|
||||
#: cps/templates/config_edit.html:50
|
||||
#: cps/templates/config_edit.html:94
|
||||
msgid "Enable public registration"
|
||||
msgstr "Öffentliche Registrierung aktivieren"
|
||||
|
||||
#: cps/templates/config_edit.html:52
|
||||
#: cps/templates/config_edit.html:96
|
||||
msgid "Default Settings for new users"
|
||||
msgstr "Default Einstellungen für neue Benutzer"
|
||||
|
||||
#: cps/templates/config_edit.html:55 cps/templates/user_edit.html:80
|
||||
#: cps/templates/config_edit.html:99 cps/templates/user_edit.html:87
|
||||
msgid "Admin user"
|
||||
msgstr "Admin Benutzer"
|
||||
|
||||
#: cps/templates/config_edit.html:59 cps/templates/user_edit.html:85
|
||||
#: cps/templates/config_edit.html:103 cps/templates/user_edit.html:92
|
||||
msgid "Allow Downloads"
|
||||
msgstr "Downloads erlauben"
|
||||
|
||||
#: cps/templates/config_edit.html:63 cps/templates/user_edit.html:89
|
||||
#: cps/templates/config_edit.html:107 cps/templates/user_edit.html:96
|
||||
msgid "Allow Uploads"
|
||||
msgstr "Uploads erlauben"
|
||||
|
||||
#: cps/templates/config_edit.html:67 cps/templates/user_edit.html:93
|
||||
#: cps/templates/config_edit.html:111 cps/templates/user_edit.html:100
|
||||
msgid "Allow Edit"
|
||||
msgstr "Bearbeiten erlauben"
|
||||
|
||||
#: cps/templates/config_edit.html:71 cps/templates/user_edit.html:98
|
||||
#: cps/templates/config_edit.html:115 cps/templates/user_edit.html:105
|
||||
msgid "Allow Changing Password"
|
||||
msgstr "Passwort ändern erlauben"
|
||||
|
||||
#: cps/templates/config_edit.html:78 cps/templates/layout.html:93
|
||||
#: cps/templates/config_edit.html:122 cps/templates/layout.html:93
|
||||
#: cps/templates/login.html:4
|
||||
msgid "Login"
|
||||
msgstr "Login"
|
||||
|
@ -661,23 +752,27 @@ msgstr "von"
|
|||
msgid "language"
|
||||
msgstr "Sprache"
|
||||
|
||||
#: cps/templates/detail.html:74
|
||||
#: cps/templates/detail.html:81
|
||||
msgid "Publishing date"
|
||||
msgstr "Herausgabedatum"
|
||||
|
||||
#: cps/templates/detail.html:106
|
||||
#: cps/templates/detail.html:115
|
||||
msgid "Read"
|
||||
msgstr "Gelesen"
|
||||
|
||||
#: cps/templates/detail.html:123
|
||||
msgid "Description:"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: cps/templates/detail.html:134
|
||||
#: cps/templates/detail.html:151
|
||||
msgid "Read in browser"
|
||||
msgstr "Im Browser lesen"
|
||||
|
||||
#: cps/templates/detail.html:154
|
||||
#: cps/templates/detail.html:171
|
||||
msgid "Add to shelf"
|
||||
msgstr "Zu Bücherregal hinzufügen"
|
||||
|
||||
#: cps/templates/detail.html:194
|
||||
#: cps/templates/detail.html:211
|
||||
msgid "Edit metadata"
|
||||
msgstr "Metadaten bearbeiten"
|
||||
|
||||
|
@ -759,19 +854,29 @@ msgstr "Die neuesten Bücher"
|
|||
msgid "Show Random Books"
|
||||
msgstr "Zeige zufällige Bücher"
|
||||
|
||||
#: cps/templates/index.xml:43 cps/templates/layout.html:140
|
||||
#: cps/templates/index.xml:43 cps/templates/index.xml:47
|
||||
#: cps/templates/layout.html:132
|
||||
msgid "Read Books"
|
||||
msgstr "Gelesene Bücher"
|
||||
|
||||
#: cps/templates/index.xml:50 cps/templates/index.xml:54
|
||||
#: cps/templates/layout.html:133
|
||||
msgid "Unread Books"
|
||||
msgstr "Ungelesene Bücher"
|
||||
|
||||
#: cps/templates/index.xml:57 cps/templates/layout.html:144
|
||||
msgid "Authors"
|
||||
msgstr "Autoren"
|
||||
|
||||
#: cps/templates/index.xml:47
|
||||
#: cps/templates/index.xml:61
|
||||
msgid "Books ordered by Author"
|
||||
msgstr "Bücher nach Autoren sortiert"
|
||||
|
||||
#: cps/templates/index.xml:54
|
||||
#: cps/templates/index.xml:68
|
||||
msgid "Books ordered by category"
|
||||
msgstr "Bücher nach Kategorien sortiert"
|
||||
|
||||
#: cps/templates/index.xml:61
|
||||
#: cps/templates/index.xml:75
|
||||
msgid "Books ordered by series"
|
||||
msgstr "Bücher nach Reihen geordnet"
|
||||
|
||||
|
@ -779,10 +884,6 @@ msgstr "Bücher nach Reihen geordnet"
|
|||
msgid "Toggle navigation"
|
||||
msgstr "Nagivation umschalten"
|
||||
|
||||
#: cps/templates/layout.html:60
|
||||
msgid "Go!"
|
||||
msgstr "Los!"
|
||||
|
||||
#: cps/templates/layout.html:68
|
||||
msgid "Advanced Search"
|
||||
msgstr "Erweiterte Suche"
|
||||
|
@ -799,31 +900,31 @@ msgstr "Registrieren"
|
|||
msgid "Browse"
|
||||
msgstr "Browsen"
|
||||
|
||||
#: cps/templates/layout.html:132
|
||||
#: cps/templates/layout.html:136
|
||||
msgid "Discover"
|
||||
msgstr "Entdecke"
|
||||
|
||||
#: cps/templates/layout.html:135
|
||||
#: cps/templates/layout.html:139
|
||||
msgid "Categories"
|
||||
msgstr "Kategorien"
|
||||
|
||||
#: cps/templates/layout.html:142 cps/templates/search_form.html:54
|
||||
#: cps/templates/layout.html:146 cps/templates/search_form.html:58
|
||||
msgid "Languages"
|
||||
msgstr "Sprachen"
|
||||
|
||||
#: cps/templates/layout.html:145
|
||||
#: cps/templates/layout.html:149
|
||||
msgid "Public Shelves"
|
||||
msgstr "Öffentiche Bücherregale"
|
||||
|
||||
#: cps/templates/layout.html:149
|
||||
#: cps/templates/layout.html:153
|
||||
msgid "Your Shelves"
|
||||
msgstr "Deine Bücherregale"
|
||||
|
||||
#: cps/templates/layout.html:154
|
||||
#: cps/templates/layout.html:158
|
||||
msgid "Create a Shelf"
|
||||
msgstr "Bücherregal erzeugen"
|
||||
|
||||
#: cps/templates/layout.html:155
|
||||
#: cps/templates/layout.html:159
|
||||
msgid "About"
|
||||
msgstr "Über"
|
||||
|
||||
|
@ -889,15 +990,15 @@ msgstr "Versuche eine andere Suche"
|
|||
msgid "Results for:"
|
||||
msgstr "Ergebnisse für:"
|
||||
|
||||
#: cps/templates/search_form.html:23
|
||||
#: cps/templates/search_form.html:27
|
||||
msgid "Exclude Tags"
|
||||
msgstr "Tags ausschließen"
|
||||
|
||||
#: cps/templates/search_form.html:43
|
||||
#: cps/templates/search_form.html:47
|
||||
msgid "Exclude Series"
|
||||
msgstr "Serie ausschließen"
|
||||
|
||||
#: cps/templates/search_form.html:64
|
||||
#: cps/templates/search_form.html:68
|
||||
msgid "Exclude Languages"
|
||||
msgstr "Sprache ausschließen"
|
||||
|
||||
|
@ -922,37 +1023,37 @@ msgid "Drag 'n drop to rearrange order"
|
|||
msgstr "Drag 'n drop um Reihenfolge zu ändern"
|
||||
|
||||
#: cps/templates/stats.html:3
|
||||
msgid "Linked libraries"
|
||||
msgstr "Dynamische Bibliotheken"
|
||||
|
||||
#: cps/templates/stats.html:8
|
||||
msgid "Program library"
|
||||
msgstr "Programm Bibliotheken"
|
||||
|
||||
#: cps/templates/stats.html:9
|
||||
msgid "Installed Version"
|
||||
msgstr "Installierte Version"
|
||||
|
||||
#: cps/templates/stats.html:32
|
||||
msgid "Calibre library statistics"
|
||||
msgstr "Calibre Bibliothek Statistiken"
|
||||
|
||||
#: cps/templates/stats.html:37
|
||||
#: cps/templates/stats.html:8
|
||||
msgid "Books in this Library"
|
||||
msgstr "Bücher in dieser Bibliothek"
|
||||
|
||||
#: cps/templates/stats.html:41
|
||||
#: cps/templates/stats.html:12
|
||||
msgid "Authors in this Library"
|
||||
msgstr "Autoren in dieser Bibliothek"
|
||||
|
||||
#: cps/templates/stats.html:45
|
||||
#: cps/templates/stats.html:16
|
||||
msgid "Categories in this Library"
|
||||
msgstr "Kategorien in dieser Bibliothek"
|
||||
|
||||
#: cps/templates/stats.html:49
|
||||
#: cps/templates/stats.html:20
|
||||
msgid "Series in this Library"
|
||||
msgstr "Serien in dieser Bibliothek"
|
||||
|
||||
#: cps/templates/stats.html:24
|
||||
msgid "Linked libraries"
|
||||
msgstr "Dynamische Bibliotheken"
|
||||
|
||||
#: cps/templates/stats.html:28
|
||||
msgid "Program library"
|
||||
msgstr "Programm Bibliotheken"
|
||||
|
||||
#: cps/templates/stats.html:29
|
||||
msgid "Installed Version"
|
||||
msgstr "Installierte Version"
|
||||
|
||||
#: cps/templates/user_edit.html:23
|
||||
msgid "Kindle E-Mail"
|
||||
msgstr "Kindle E-Mail"
|
||||
|
@ -965,43 +1066,47 @@ msgstr "Zeige nur Bücher mit dieser Sprache"
|
|||
msgid "Show all"
|
||||
msgstr "Zeige alle"
|
||||
|
||||
#: cps/templates/user_edit.html:45
|
||||
#: cps/templates/user_edit.html:47
|
||||
msgid "Show random books"
|
||||
msgstr "Zeige Zufällige Bücher"
|
||||
|
||||
#: cps/templates/user_edit.html:49
|
||||
#: cps/templates/user_edit.html:51
|
||||
msgid "Show hot books"
|
||||
msgstr "Zeige Auswahl Beliebte Bücher"
|
||||
|
||||
#: cps/templates/user_edit.html:53
|
||||
#: cps/templates/user_edit.html:55
|
||||
msgid "Show best rated books"
|
||||
msgstr "Zeige am besten bewertete Bücher"
|
||||
|
||||
#: cps/templates/user_edit.html:57
|
||||
#: cps/templates/user_edit.html:59
|
||||
msgid "Show language selection"
|
||||
msgstr "Zeige Sprachauswahl"
|
||||
|
||||
#: cps/templates/user_edit.html:61
|
||||
#: cps/templates/user_edit.html:63
|
||||
msgid "Show series selection"
|
||||
msgstr "Zeige Serienauswahl"
|
||||
|
||||
#: cps/templates/user_edit.html:65
|
||||
#: cps/templates/user_edit.html:67
|
||||
msgid "Show category selection"
|
||||
msgstr "Zeige Kategorienauswahl"
|
||||
|
||||
#: cps/templates/user_edit.html:69
|
||||
#: cps/templates/user_edit.html:71
|
||||
msgid "Show author selection"
|
||||
msgstr "Zeige Autorenauswahl"
|
||||
|
||||
#: cps/templates/user_edit.html:73
|
||||
#: cps/templates/user_edit.html:75
|
||||
msgid "Show read and unread"
|
||||
msgstr "Zeige Gelesen/Ungelesen Auswahl"
|
||||
|
||||
#: cps/templates/user_edit.html:79
|
||||
msgid "Show random books in detail view"
|
||||
msgstr "Zeige zufällige Bücher in der Detailansicht"
|
||||
|
||||
#: cps/templates/user_edit.html:105
|
||||
#: cps/templates/user_edit.html:112
|
||||
msgid "Delete this user"
|
||||
msgstr "Benutzer löschen"
|
||||
|
||||
#: cps/templates/user_edit.html:116
|
||||
#: cps/templates/user_edit.html:127
|
||||
msgid "Recent Downloads"
|
||||
msgstr "Letzte Downloads"
|
||||
|
||||
|
|
Binary file not shown.
|
@ -14,7 +14,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Calibre-web\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/janeczku/calibre-web\n"
|
||||
"POT-Creation-Date: 2017-02-20 19:47+0100\n"
|
||||
"POT-Creation-Date: 2017-03-19 19:20+0100\n"
|
||||
"PO-Revision-Date: 2016-11-13 18:35+0100\n"
|
||||
"Last-Translator: Juan F. Villa <juan.villa@paisdelconocimiento.org>\n"
|
||||
"Language: es\n"
|
||||
|
@ -25,341 +25,354 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.3.4\n"
|
||||
|
||||
#: cps/book_formats.py:111 cps/book_formats.py:115 cps/web.py:1030
|
||||
#: cps/book_formats.py:113 cps/book_formats.py:117 cps/web.py:1244
|
||||
msgid "not installed"
|
||||
msgstr "No instalado"
|
||||
|
||||
#: cps/helper.py:150
|
||||
#: cps/helper.py:164
|
||||
#, python-format
|
||||
msgid "Failed to send mail: %s"
|
||||
msgstr "Fallo al enviar el correo : %s"
|
||||
|
||||
#: cps/helper.py:157
|
||||
#: cps/helper.py:171
|
||||
msgid "Calibre-web test email"
|
||||
msgstr "Prueba de Correo Calibre-web"
|
||||
|
||||
#: cps/helper.py:158 cps/helper.py:168
|
||||
#: cps/helper.py:172 cps/helper.py:184
|
||||
msgid "This email has been sent via calibre web."
|
||||
msgstr "Este mensaje ha sido enviado via Calibre Web."
|
||||
|
||||
#: cps/helper.py:167 cps/templates/detail.html:130
|
||||
#: cps/helper.py:181 cps/templates/detail.html:146
|
||||
msgid "Send to Kindle"
|
||||
msgstr "Enviar a Kindle"
|
||||
|
||||
#: cps/helper.py:185 cps/helper.py:200
|
||||
#: cps/helper.py:201 cps/helper.py:216
|
||||
msgid "Could not find any formats suitable for sending by email"
|
||||
msgstr "Formato no compatible para enviar por correo electronico"
|
||||
|
||||
#: cps/helper.py:194
|
||||
#: cps/helper.py:210
|
||||
msgid "Could not convert epub to mobi"
|
||||
msgstr "No fue posible convertir de epub a mobi"
|
||||
|
||||
#: cps/ub.py:434
|
||||
#: cps/ub.py:488
|
||||
msgid "Guest"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:734
|
||||
#: cps/web.py:904
|
||||
msgid "Requesting update package"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:735
|
||||
#: cps/web.py:905
|
||||
msgid "Downloading update package"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:736
|
||||
#: cps/web.py:906
|
||||
msgid "Unzipping update package"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:737
|
||||
#: cps/web.py:907
|
||||
msgid "Files are replaced"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:738
|
||||
#: cps/web.py:908
|
||||
msgid "Database connections are closed"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:739
|
||||
#: cps/web.py:909
|
||||
msgid "Server is stopped"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:740
|
||||
#: cps/web.py:910
|
||||
msgid "Update finished, please press okay and reload page"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:810
|
||||
#: cps/web.py:983
|
||||
msgid "Latest Books"
|
||||
msgstr "Libros recientes"
|
||||
|
||||
#: cps/web.py:835
|
||||
#: cps/web.py:1014
|
||||
msgid "Hot Books (most downloaded)"
|
||||
msgstr "Libros Populares (los mas descargados)"
|
||||
|
||||
#: cps/web.py:845
|
||||
#: cps/web.py:1024
|
||||
msgid "Best rated books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:36 cps/web.py:854
|
||||
#: cps/templates/index.xml:36 cps/web.py:1033
|
||||
msgid "Random Books"
|
||||
msgstr "Libros al Azar"
|
||||
|
||||
#: cps/web.py:867
|
||||
#: cps/web.py:1046
|
||||
msgid "Author list"
|
||||
msgstr "Lista de Autores"
|
||||
|
||||
#: cps/web.py:878
|
||||
#: cps/web.py:1057
|
||||
#, python-format
|
||||
msgid "Author: %(name)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:880 cps/web.py:908 cps/web.py:1007 cps/web.py:1235
|
||||
#: cps/web.py:2115
|
||||
#: cps/web.py:1059 cps/web.py:1087 cps/web.py:1221 cps/web.py:1626
|
||||
#: cps/web.py:2579
|
||||
msgid "Error opening eBook. File does not exist or file is not accessible:"
|
||||
msgstr "Error en apertura del Objeto. El archivo no existe o no es accesible"
|
||||
|
||||
#: cps/templates/index.xml:57 cps/web.py:894
|
||||
#: cps/templates/index.xml:71 cps/web.py:1073
|
||||
msgid "Series list"
|
||||
msgstr "lista de Series"
|
||||
|
||||
#: cps/web.py:906
|
||||
#: cps/web.py:1085
|
||||
#, python-format
|
||||
msgid "Series: %(serie)s"
|
||||
msgstr "Series : %(serie)s"
|
||||
|
||||
#: cps/web.py:939
|
||||
#: cps/web.py:1118
|
||||
msgid "Available languages"
|
||||
msgstr "Lenguajes disponibles"
|
||||
|
||||
#: cps/web.py:954
|
||||
#: cps/web.py:1133
|
||||
#, python-format
|
||||
msgid "Language: %(name)s"
|
||||
msgstr "Lenguaje: %(name)s"
|
||||
|
||||
#: cps/templates/index.xml:50 cps/web.py:967
|
||||
#: cps/templates/index.xml:64 cps/web.py:1146
|
||||
msgid "Category list"
|
||||
msgstr "Lista de Categorias"
|
||||
|
||||
#: cps/web.py:979
|
||||
#: cps/web.py:1158
|
||||
#, python-format
|
||||
msgid "Category: %(name)s"
|
||||
msgstr "Categoria : %(name)s"
|
||||
|
||||
#: cps/web.py:1040
|
||||
#: cps/web.py:1267
|
||||
msgid "Statistics"
|
||||
msgstr "Estadisticas"
|
||||
|
||||
#: cps/web.py:1061
|
||||
#: cps/web.py:1375
|
||||
msgid "Server restarted, please reload page"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1063
|
||||
#: cps/web.py:1377
|
||||
msgid "Performing shutdown of server, please close window"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1073
|
||||
#: cps/web.py:1392
|
||||
msgid "Update done"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1147 cps/web.py:1160
|
||||
#: cps/web.py:1470 cps/web.py:1483
|
||||
msgid "search"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1211 cps/web.py:1218 cps/web.py:1225 cps/web.py:1232
|
||||
#: cps/web.py:1602 cps/web.py:1609 cps/web.py:1616 cps/web.py:1623
|
||||
msgid "Read a Book"
|
||||
msgstr "Leer un Libro"
|
||||
|
||||
#: cps/web.py:1276 cps/web.py:1713
|
||||
#: cps/web.py:1676 cps/web.py:2152
|
||||
msgid "Please fill out all fields!"
|
||||
msgstr "Por favor llenar todos los campos!"
|
||||
|
||||
#: cps/web.py:1277 cps/web.py:1293 cps/web.py:1298 cps/web.py:1300
|
||||
#: cps/web.py:1677 cps/web.py:1693 cps/web.py:1698 cps/web.py:1700
|
||||
msgid "register"
|
||||
msgstr "Registrarse"
|
||||
|
||||
#: cps/web.py:1292
|
||||
#: cps/web.py:1692
|
||||
msgid "An unknown error occured. Please try again later."
|
||||
msgstr "Ocurrio un error. Intentar de nuevo mas tarde."
|
||||
|
||||
#: cps/web.py:1297
|
||||
#: cps/web.py:1697
|
||||
msgid "This username or email address is already in use."
|
||||
msgstr "Usuario o direccion de correo en uso."
|
||||
|
||||
#: cps/web.py:1315
|
||||
#: cps/web.py:1715
|
||||
#, python-format
|
||||
msgid "you are now logged in as: '%(nickname)s'"
|
||||
msgstr "Sesion iniciada como : '%(nickname)s'"
|
||||
|
||||
#: cps/web.py:1320
|
||||
#: cps/web.py:1720
|
||||
msgid "Wrong Username or Password"
|
||||
msgstr "Usuario o contraseña invalido"
|
||||
|
||||
#: cps/web.py:1322
|
||||
#: cps/web.py:1722
|
||||
msgid "login"
|
||||
msgstr "Iniciar Sesion"
|
||||
|
||||
#: cps/web.py:1339
|
||||
#: cps/web.py:1739
|
||||
msgid "Please configure the SMTP mail settings first..."
|
||||
msgstr "Configurar primero los parametros SMTP por favor..."
|
||||
|
||||
#: cps/web.py:1343
|
||||
#: cps/web.py:1743
|
||||
#, python-format
|
||||
msgid "Book successfully send to %(kindlemail)s"
|
||||
msgstr "Envio de Libro a %(kindlemail)s correctamente"
|
||||
|
||||
#: cps/web.py:1347
|
||||
#: cps/web.py:1747
|
||||
#, python-format
|
||||
msgid "There was an error sending this book: %(res)s"
|
||||
msgstr "Ha sucedido un error en el envio del Libro: %(res)s"
|
||||
|
||||
#: cps/web.py:1349
|
||||
#: cps/web.py:1749 cps/web.py:2232
|
||||
msgid "Please configure your kindle email address first..."
|
||||
msgstr "Configurar primero la dirección de correo Kindle por favor..."
|
||||
|
||||
#: cps/web.py:1369
|
||||
#: cps/web.py:1774
|
||||
#, python-format
|
||||
msgid "Book has been added to shelf: %(sname)s"
|
||||
msgstr "El libro fue agregado a el estante: %(sname)s"
|
||||
|
||||
#: cps/web.py:1390
|
||||
#: cps/web.py:1793
|
||||
#, python-format
|
||||
msgid "Book has been removed from shelf: %(sname)s"
|
||||
msgstr "El libro fue removido del estante: %(sname)s"
|
||||
|
||||
#: cps/web.py:1409 cps/web.py:1433
|
||||
#: cps/web.py:1812 cps/web.py:1836
|
||||
#, python-format
|
||||
msgid "A shelf with the name '%(title)s' already exists."
|
||||
msgstr "Une étagère de ce nom '%(title)s' existe déjà."
|
||||
|
||||
#: cps/web.py:1414
|
||||
#: cps/web.py:1817
|
||||
#, python-format
|
||||
msgid "Shelf %(title)s created"
|
||||
msgstr "Estante %(title)s creado"
|
||||
|
||||
#: cps/web.py:1416 cps/web.py:1444
|
||||
#: cps/web.py:1819 cps/web.py:1847
|
||||
msgid "There was an error"
|
||||
msgstr "Hemos tenido un error"
|
||||
|
||||
#: cps/web.py:1417 cps/web.py:1419
|
||||
#: cps/web.py:1820 cps/web.py:1822
|
||||
msgid "create a shelf"
|
||||
msgstr "Crear un Estante"
|
||||
|
||||
#: cps/web.py:1442
|
||||
#: cps/web.py:1845
|
||||
#, python-format
|
||||
msgid "Shelf %(title)s changed"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1445 cps/web.py:1447
|
||||
#: cps/web.py:1848 cps/web.py:1850
|
||||
msgid "Edit a shelf"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1465
|
||||
#: cps/web.py:1868
|
||||
#, python-format
|
||||
msgid "successfully deleted shelf %(name)s"
|
||||
msgstr "Estante %(name)s fue borrado correctamente"
|
||||
|
||||
#: cps/web.py:1487
|
||||
#: cps/web.py:1890
|
||||
#, python-format
|
||||
msgid "Shelf: '%(name)s'"
|
||||
msgstr "Estante: '%(name)s'"
|
||||
|
||||
#: cps/web.py:1518
|
||||
#: cps/web.py:1921
|
||||
#, python-format
|
||||
msgid "Change order of Shelf: '%(name)s'"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1580
|
||||
#: cps/web.py:1985
|
||||
msgid "Found an existing account for this email address."
|
||||
msgstr "Existe una cuenta vinculada a esta cuenta de correo."
|
||||
|
||||
#: cps/web.py:1582 cps/web.py:1586
|
||||
#: cps/web.py:1987 cps/web.py:1991
|
||||
#, python-format
|
||||
msgid "%(name)s's profile"
|
||||
msgstr "Perfil de %(name)s"
|
||||
|
||||
#: cps/web.py:1583
|
||||
#: cps/web.py:1988
|
||||
msgid "Profile updated"
|
||||
msgstr "Perfil actualizado"
|
||||
|
||||
#: cps/web.py:1597
|
||||
#: cps/web.py:2002
|
||||
msgid "Admin page"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1668
|
||||
#: cps/web.py:2106
|
||||
msgid "Calibre-web configuration updated"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1675 cps/web.py:1681 cps/web.py:1694
|
||||
#: cps/web.py:2113 cps/web.py:2119 cps/web.py:2133
|
||||
msgid "Basic Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1679
|
||||
#: cps/web.py:2117
|
||||
msgid "DB location is not valid, please enter correct path"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:34 cps/web.py:1715 cps/web.py:1761
|
||||
#: cps/templates/admin.html:34 cps/web.py:2154 cps/web.py:2202
|
||||
msgid "Add new user"
|
||||
msgstr "Agregar un nuevo usuario"
|
||||
|
||||
#: cps/web.py:1753
|
||||
#: cps/web.py:2194
|
||||
#, python-format
|
||||
msgid "User '%(user)s' created"
|
||||
msgstr "Usuario '%(user)s' creado"
|
||||
|
||||
#: cps/web.py:1757
|
||||
#: cps/web.py:2198
|
||||
msgid "Found an existing account for this email address or nickname."
|
||||
msgstr "Se ha encontrado una cuenta vinculada a esta cuenta de correo o usuario."
|
||||
|
||||
#: cps/web.py:1779
|
||||
#: cps/web.py:2220
|
||||
msgid "Mail settings updated"
|
||||
msgstr "Parametros de correo actualizados"
|
||||
|
||||
#: cps/web.py:1785
|
||||
#: cps/web.py:2227
|
||||
#, python-format
|
||||
msgid "Test E-Mail successfully send to %(kindlemail)s"
|
||||
msgstr "Exito al realizar envio de prueba a %(kindlemail)s"
|
||||
|
||||
#: cps/web.py:1788
|
||||
#: cps/web.py:2230
|
||||
#, python-format
|
||||
msgid "There was an error sending the Test E-Mail: %(res)s"
|
||||
msgstr "Error al realizar envio de prueba a E-Mail: %(res)s"
|
||||
|
||||
#: cps/web.py:1789
|
||||
#: cps/web.py:2234
|
||||
msgid "E-Mail settings updated"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2235
|
||||
msgid "Edit mail settings"
|
||||
msgstr "Editar parametros de correo"
|
||||
|
||||
#: cps/web.py:1817
|
||||
#: cps/web.py:2263
|
||||
#, python-format
|
||||
msgid "User '%(nick)s' deleted"
|
||||
msgstr "Usuario '%(nick)s' borrado"
|
||||
|
||||
#: cps/web.py:1898
|
||||
#: cps/web.py:2349
|
||||
#, python-format
|
||||
msgid "User '%(nick)s' updated"
|
||||
msgstr "Usuario '%(nick)s' Actualizado"
|
||||
|
||||
#: cps/web.py:1901
|
||||
#: cps/web.py:2352
|
||||
msgid "An unknown error occured."
|
||||
msgstr "Oups ! Error inesperado."
|
||||
|
||||
#: cps/web.py:1904
|
||||
#: cps/web.py:2355
|
||||
#, python-format
|
||||
msgid "Edit User %(nick)s"
|
||||
msgstr "Editar Usuario %(nick)s"
|
||||
|
||||
#: cps/web.py:2110 cps/web.py:2113 cps/web.py:2188
|
||||
#: cps/web.py:2574 cps/web.py:2577 cps/web.py:2689
|
||||
msgid "edit metadata"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2145
|
||||
#: cps/web.py:2598
|
||||
#, python-format
|
||||
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2604
|
||||
msgid "File to be uploaded must have an extension"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2621
|
||||
#, python-format
|
||||
msgid "Failed to create path %s (Permission denied)."
|
||||
msgstr "Fallo al crear la ruta %s (permiso negado)"
|
||||
|
||||
#: cps/web.py:2150
|
||||
#: cps/web.py:2626
|
||||
#, python-format
|
||||
msgid "Failed to store file %s (Permission denied)."
|
||||
msgstr "Fallo al almacenar el archivo %s (permiso negado)"
|
||||
|
||||
#: cps/web.py:2155
|
||||
#: cps/web.py:2631
|
||||
#, python-format
|
||||
msgid "Failed to delete file %s (Permission denied)."
|
||||
msgstr "Fallo al borrar el archivo %s (permiso negado)"
|
||||
|
@ -388,7 +401,7 @@ msgstr "DLS"
|
|||
msgid "Admin"
|
||||
msgstr "Administracion"
|
||||
|
||||
#: cps/templates/admin.html:13 cps/templates/detail.html:117
|
||||
#: cps/templates/admin.html:13 cps/templates/detail.html:134
|
||||
msgid "Download"
|
||||
msgstr "Descarga"
|
||||
|
||||
|
@ -444,7 +457,7 @@ msgstr ""
|
|||
msgid "Calibre DB dir"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:61 cps/templates/config_edit.html:32
|
||||
#: cps/templates/admin.html:61 cps/templates/config_edit.html:76
|
||||
msgid "Log Level"
|
||||
msgstr ""
|
||||
|
||||
|
@ -452,7 +465,7 @@ msgstr ""
|
|||
msgid "Port"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:63 cps/templates/config_edit.html:19
|
||||
#: cps/templates/admin.html:63 cps/templates/config_edit.html:60
|
||||
msgid "Books per page"
|
||||
msgstr ""
|
||||
|
||||
|
@ -481,42 +494,46 @@ msgid "Newest commit timestamp"
|
|||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:83
|
||||
msgid "Restart Calibre-web"
|
||||
msgid "Reconnect to Calibre DB"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:84
|
||||
msgid "Stop Calibre-web"
|
||||
msgid "Restart Calibre-web"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:85
|
||||
msgid "Check for update"
|
||||
msgid "Stop Calibre-web"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:86
|
||||
msgid "Check for update"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:87
|
||||
msgid "Perform Update"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:96
|
||||
#: cps/templates/admin.html:97
|
||||
msgid "Do you really want to restart Calibre-web?"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:101 cps/templates/admin.html:115
|
||||
#: cps/templates/admin.html:136
|
||||
#: cps/templates/admin.html:102 cps/templates/admin.html:116
|
||||
#: cps/templates/admin.html:137
|
||||
msgid "Ok"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:102 cps/templates/admin.html:116
|
||||
#: cps/templates/book_edit.html:108 cps/templates/config_edit.html:75
|
||||
#: cps/templates/admin.html:103 cps/templates/admin.html:117
|
||||
#: cps/templates/book_edit.html:109 cps/templates/config_edit.html:119
|
||||
#: cps/templates/email_edit.html:36 cps/templates/shelf_edit.html:17
|
||||
#: cps/templates/shelf_order.html:12 cps/templates/user_edit.html:111
|
||||
#: cps/templates/shelf_order.html:12 cps/templates/user_edit.html:120
|
||||
msgid "Back"
|
||||
msgstr "Regresar"
|
||||
|
||||
#: cps/templates/admin.html:114
|
||||
#: cps/templates/admin.html:115
|
||||
msgid "Do you really want to stop Calibre-web?"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:127
|
||||
#: cps/templates/admin.html:128
|
||||
msgid "Updating, please do not reload page"
|
||||
msgstr ""
|
||||
|
||||
|
@ -524,20 +541,21 @@ msgstr ""
|
|||
msgid "Book Title"
|
||||
msgstr "Titulo del Libro"
|
||||
|
||||
#: cps/templates/book_edit.html:20 cps/templates/search_form.html:10
|
||||
#: cps/templates/book_edit.html:20 cps/templates/book_edit.html:145
|
||||
#: cps/templates/search_form.html:10
|
||||
msgid "Author"
|
||||
msgstr "Autor"
|
||||
|
||||
#: cps/templates/book_edit.html:24
|
||||
#: cps/templates/book_edit.html:24 cps/templates/book_edit.html:147
|
||||
msgid "Description"
|
||||
msgstr "Descripcion"
|
||||
|
||||
#: cps/templates/book_edit.html:28 cps/templates/search_form.html:13
|
||||
#: cps/templates/book_edit.html:28 cps/templates/search_form.html:17
|
||||
msgid "Tags"
|
||||
msgstr "Etiqueta"
|
||||
|
||||
#: cps/templates/book_edit.html:33 cps/templates/layout.html:138
|
||||
#: cps/templates/search_form.html:33
|
||||
#: cps/templates/book_edit.html:33 cps/templates/layout.html:142
|
||||
#: cps/templates/search_form.html:37
|
||||
msgid "Series"
|
||||
msgstr "Series"
|
||||
|
||||
|
@ -569,69 +587,142 @@ msgstr "No"
|
|||
msgid "view book after edit"
|
||||
msgstr "Ver libro tras la edicion"
|
||||
|
||||
#: cps/templates/book_edit.html:107 cps/templates/config_edit.html:73
|
||||
#: cps/templates/login.html:19 cps/templates/search_form.html:75
|
||||
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:109
|
||||
#: cps/templates/book_edit.html:107 cps/templates/book_edit.html:118
|
||||
msgid "Get metadata"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:108 cps/templates/config_edit.html:117
|
||||
#: cps/templates/login.html:19 cps/templates/search_form.html:79
|
||||
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:118
|
||||
msgid "Submit"
|
||||
msgstr "Enviar"
|
||||
|
||||
#: cps/templates/book_edit.html:121
|
||||
msgid "Keyword"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:122
|
||||
msgid " Search keyword "
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:124 cps/templates/layout.html:60
|
||||
msgid "Go!"
|
||||
msgstr "Vamos!"
|
||||
|
||||
#: cps/templates/book_edit.html:125
|
||||
msgid "Click the cover to load metadata to the form"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:129 cps/templates/book_edit.html:142
|
||||
msgid "Loading..."
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:132
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:143
|
||||
msgid "Search error!"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:144
|
||||
msgid "No Result! Please try anonther keyword."
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:146 cps/templates/detail.html:76
|
||||
#: cps/templates/search_form.html:14
|
||||
msgid "Publisher"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:148
|
||||
msgid "Source"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:7
|
||||
msgid "Location of Calibre database"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:11
|
||||
msgid "Server Port"
|
||||
#: cps/templates/config_edit.html:13
|
||||
msgid "Use google drive?"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:15 cps/templates/shelf_edit.html:7
|
||||
msgid "Title"
|
||||
msgstr "Titulo"
|
||||
|
||||
#: cps/templates/config_edit.html:23
|
||||
msgid "No. of random books to show"
|
||||
#: cps/templates/config_edit.html:17
|
||||
msgid "Client id"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:28
|
||||
msgid "Regular expression for title sorting"
|
||||
#: cps/templates/config_edit.html:21
|
||||
msgid "Client secret"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:42
|
||||
msgid "Enable uploading"
|
||||
#: cps/templates/config_edit.html:25
|
||||
msgid "Calibre Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:46
|
||||
msgid "Enable anonymous browsing"
|
||||
#: cps/templates/config_edit.html:29
|
||||
msgid "Google drive Calibre folder"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:50
|
||||
msgid "Enable public registration"
|
||||
#: cps/templates/config_edit.html:38
|
||||
msgid "Metadata Watch Channel ID"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:52
|
||||
msgid "Server Port"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:56 cps/templates/shelf_edit.html:7
|
||||
msgid "Title"
|
||||
msgstr "Titulo"
|
||||
|
||||
#: cps/templates/config_edit.html:64
|
||||
msgid "No. of random books to show"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:68
|
||||
msgid "Regular expression for ignoring columns"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:72
|
||||
msgid "Regular expression for title sorting"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:86
|
||||
msgid "Enable uploading"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:90
|
||||
msgid "Enable anonymous browsing"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:94
|
||||
msgid "Enable public registration"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:96
|
||||
msgid "Default Settings for new users"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:55 cps/templates/user_edit.html:80
|
||||
#: cps/templates/config_edit.html:99 cps/templates/user_edit.html:87
|
||||
msgid "Admin user"
|
||||
msgstr "Usuario Administrador"
|
||||
|
||||
#: cps/templates/config_edit.html:59 cps/templates/user_edit.html:85
|
||||
#: cps/templates/config_edit.html:103 cps/templates/user_edit.html:92
|
||||
msgid "Allow Downloads"
|
||||
msgstr "Permitir descargas"
|
||||
|
||||
#: cps/templates/config_edit.html:63 cps/templates/user_edit.html:89
|
||||
#: cps/templates/config_edit.html:107 cps/templates/user_edit.html:96
|
||||
msgid "Allow Uploads"
|
||||
msgstr "Permitir subidas de archivos"
|
||||
|
||||
#: cps/templates/config_edit.html:67 cps/templates/user_edit.html:93
|
||||
#: cps/templates/config_edit.html:111 cps/templates/user_edit.html:100
|
||||
msgid "Allow Edit"
|
||||
msgstr "Permitir editar"
|
||||
|
||||
#: cps/templates/config_edit.html:71 cps/templates/user_edit.html:98
|
||||
#: cps/templates/config_edit.html:115 cps/templates/user_edit.html:105
|
||||
msgid "Allow Changing Password"
|
||||
msgstr "Permitir cambiar la clave"
|
||||
|
||||
#: cps/templates/config_edit.html:78 cps/templates/layout.html:93
|
||||
#: cps/templates/config_edit.html:122 cps/templates/layout.html:93
|
||||
#: cps/templates/login.html:4
|
||||
msgid "Login"
|
||||
msgstr "Inicio de Sesion"
|
||||
|
@ -648,23 +739,27 @@ msgstr "de"
|
|||
msgid "language"
|
||||
msgstr "Lenguaje"
|
||||
|
||||
#: cps/templates/detail.html:74
|
||||
#: cps/templates/detail.html:81
|
||||
msgid "Publishing date"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/detail.html:106
|
||||
#: cps/templates/detail.html:115
|
||||
msgid "Read"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/detail.html:123
|
||||
msgid "Description:"
|
||||
msgstr "Descripcion :"
|
||||
|
||||
#: cps/templates/detail.html:134
|
||||
#: cps/templates/detail.html:151
|
||||
msgid "Read in browser"
|
||||
msgstr "Ver en el navegador"
|
||||
|
||||
#: cps/templates/detail.html:154
|
||||
#: cps/templates/detail.html:171
|
||||
msgid "Add to shelf"
|
||||
msgstr "Agregar al estante"
|
||||
|
||||
#: cps/templates/detail.html:194
|
||||
#: cps/templates/detail.html:211
|
||||
msgid "Edit metadata"
|
||||
msgstr "Editar la metadata"
|
||||
|
||||
|
@ -744,19 +839,29 @@ msgstr "Libros Recientes"
|
|||
msgid "Show Random Books"
|
||||
msgstr "Mostrar libros al azar"
|
||||
|
||||
#: cps/templates/index.xml:43 cps/templates/layout.html:140
|
||||
#: cps/templates/index.xml:43 cps/templates/index.xml:47
|
||||
#: cps/templates/layout.html:132
|
||||
msgid "Read Books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:50 cps/templates/index.xml:54
|
||||
#: cps/templates/layout.html:133
|
||||
msgid "Unread Books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:57 cps/templates/layout.html:144
|
||||
msgid "Authors"
|
||||
msgstr "Autores"
|
||||
|
||||
#: cps/templates/index.xml:47
|
||||
#: cps/templates/index.xml:61
|
||||
msgid "Books ordered by Author"
|
||||
msgstr "Libros ordenados por Autor"
|
||||
|
||||
#: cps/templates/index.xml:54
|
||||
#: cps/templates/index.xml:68
|
||||
msgid "Books ordered by category"
|
||||
msgstr "Libros ordenados por Categorias"
|
||||
|
||||
#: cps/templates/index.xml:61
|
||||
#: cps/templates/index.xml:75
|
||||
msgid "Books ordered by series"
|
||||
msgstr "Libros ordenados por Series"
|
||||
|
||||
|
@ -764,10 +869,6 @@ msgstr "Libros ordenados por Series"
|
|||
msgid "Toggle navigation"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/layout.html:60
|
||||
msgid "Go!"
|
||||
msgstr "Vamos!"
|
||||
|
||||
#: cps/templates/layout.html:68
|
||||
msgid "Advanced Search"
|
||||
msgstr "Busqueda avanzada"
|
||||
|
@ -784,31 +885,31 @@ msgstr "Registro"
|
|||
msgid "Browse"
|
||||
msgstr "Explorar"
|
||||
|
||||
#: cps/templates/layout.html:132
|
||||
#: cps/templates/layout.html:136
|
||||
msgid "Discover"
|
||||
msgstr "Descubrir"
|
||||
|
||||
#: cps/templates/layout.html:135
|
||||
#: cps/templates/layout.html:139
|
||||
msgid "Categories"
|
||||
msgstr "Categoria"
|
||||
|
||||
#: cps/templates/layout.html:142 cps/templates/search_form.html:54
|
||||
#: cps/templates/layout.html:146 cps/templates/search_form.html:58
|
||||
msgid "Languages"
|
||||
msgstr "Lenguaje"
|
||||
|
||||
#: cps/templates/layout.html:145
|
||||
#: cps/templates/layout.html:149
|
||||
msgid "Public Shelves"
|
||||
msgstr "Estantes Publicos"
|
||||
|
||||
#: cps/templates/layout.html:149
|
||||
#: cps/templates/layout.html:153
|
||||
msgid "Your Shelves"
|
||||
msgstr "Sus Estantes"
|
||||
|
||||
#: cps/templates/layout.html:154
|
||||
#: cps/templates/layout.html:158
|
||||
msgid "Create a Shelf"
|
||||
msgstr "Crear un estante"
|
||||
|
||||
#: cps/templates/layout.html:155
|
||||
#: cps/templates/layout.html:159
|
||||
msgid "About"
|
||||
msgstr "Acerca de"
|
||||
|
||||
|
@ -874,15 +975,15 @@ msgstr "Intente una busqueda diferente"
|
|||
msgid "Results for:"
|
||||
msgstr "Resultados para:"
|
||||
|
||||
#: cps/templates/search_form.html:23
|
||||
#: cps/templates/search_form.html:27
|
||||
msgid "Exclude Tags"
|
||||
msgstr "Excluir etiquetas"
|
||||
|
||||
#: cps/templates/search_form.html:43
|
||||
#: cps/templates/search_form.html:47
|
||||
msgid "Exclude Series"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/search_form.html:64
|
||||
#: cps/templates/search_form.html:68
|
||||
msgid "Exclude Languages"
|
||||
msgstr ""
|
||||
|
||||
|
@ -907,37 +1008,37 @@ msgid "Drag 'n drop to rearrange order"
|
|||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:3
|
||||
msgid "Linked libraries"
|
||||
msgstr "Librerias vinculadas"
|
||||
|
||||
#: cps/templates/stats.html:8
|
||||
msgid "Program library"
|
||||
msgstr "Librerias del programa"
|
||||
|
||||
#: cps/templates/stats.html:9
|
||||
msgid "Installed Version"
|
||||
msgstr "Version instalada"
|
||||
|
||||
#: cps/templates/stats.html:32
|
||||
msgid "Calibre library statistics"
|
||||
msgstr "Estadisticas de la Biblioteca"
|
||||
|
||||
#: cps/templates/stats.html:37
|
||||
#: cps/templates/stats.html:8
|
||||
msgid "Books in this Library"
|
||||
msgstr "Libros en esta Biblioteca"
|
||||
|
||||
#: cps/templates/stats.html:41
|
||||
#: cps/templates/stats.html:12
|
||||
msgid "Authors in this Library"
|
||||
msgstr "Autores en esta Biblioteca"
|
||||
|
||||
#: cps/templates/stats.html:45
|
||||
#: cps/templates/stats.html:16
|
||||
msgid "Categories in this Library"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:49
|
||||
#: cps/templates/stats.html:20
|
||||
msgid "Series in this Library"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:24
|
||||
msgid "Linked libraries"
|
||||
msgstr "Librerias vinculadas"
|
||||
|
||||
#: cps/templates/stats.html:28
|
||||
msgid "Program library"
|
||||
msgstr "Librerias del programa"
|
||||
|
||||
#: cps/templates/stats.html:29
|
||||
msgid "Installed Version"
|
||||
msgstr "Version instalada"
|
||||
|
||||
#: cps/templates/user_edit.html:23
|
||||
msgid "Kindle E-Mail"
|
||||
msgstr "Correo del Kindle"
|
||||
|
@ -950,43 +1051,47 @@ msgstr "Mostrar lenguaje de los libros"
|
|||
msgid "Show all"
|
||||
msgstr "Mostrar Todo"
|
||||
|
||||
#: cps/templates/user_edit.html:45
|
||||
#: cps/templates/user_edit.html:47
|
||||
msgid "Show random books"
|
||||
msgstr "Mostrar libros al azar"
|
||||
|
||||
#: cps/templates/user_edit.html:49
|
||||
#: cps/templates/user_edit.html:51
|
||||
msgid "Show hot books"
|
||||
msgstr "Mostrar libros populares"
|
||||
|
||||
#: cps/templates/user_edit.html:53
|
||||
#: cps/templates/user_edit.html:55
|
||||
msgid "Show best rated books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:57
|
||||
#: cps/templates/user_edit.html:59
|
||||
msgid "Show language selection"
|
||||
msgstr "Mostrar lenguaje seleccionado"
|
||||
|
||||
#: cps/templates/user_edit.html:61
|
||||
#: cps/templates/user_edit.html:63
|
||||
msgid "Show series selection"
|
||||
msgstr "Mostrar series seleccionadas"
|
||||
|
||||
#: cps/templates/user_edit.html:65
|
||||
#: cps/templates/user_edit.html:67
|
||||
msgid "Show category selection"
|
||||
msgstr "Mostrar categorias elegidas"
|
||||
|
||||
#: cps/templates/user_edit.html:69
|
||||
#: cps/templates/user_edit.html:71
|
||||
msgid "Show author selection"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:73
|
||||
#: cps/templates/user_edit.html:75
|
||||
msgid "Show read and unread"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:79
|
||||
msgid "Show random books in detail view"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:105
|
||||
#: cps/templates/user_edit.html:112
|
||||
msgid "Delete this user"
|
||||
msgstr "Borrar este usuario"
|
||||
|
||||
#: cps/templates/user_edit.html:116
|
||||
#: cps/templates/user_edit.html:127
|
||||
msgid "Recent Downloads"
|
||||
msgstr "Descargas Recientes"
|
||||
|
||||
|
|
Binary file not shown.
|
@ -20,7 +20,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Calibre-web\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/janeczku/calibre-web\n"
|
||||
"POT-Creation-Date: 2017-02-20 19:47+0100\n"
|
||||
"POT-Creation-Date: 2017-03-19 19:20+0100\n"
|
||||
"PO-Revision-Date: 2016-11-13 18:35+0100\n"
|
||||
"Last-Translator: Nicolas Roudninski <nicoroud@gmail.com>\n"
|
||||
"Language: fr\n"
|
||||
|
@ -31,343 +31,356 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.3.4\n"
|
||||
|
||||
#: cps/book_formats.py:111 cps/book_formats.py:115 cps/web.py:1030
|
||||
#: cps/book_formats.py:113 cps/book_formats.py:117 cps/web.py:1244
|
||||
msgid "not installed"
|
||||
msgstr ""
|
||||
|
||||
#: cps/helper.py:150
|
||||
#: cps/helper.py:164
|
||||
#, python-format
|
||||
msgid "Failed to send mail: %s"
|
||||
msgstr "Impossible d'envoyer le courriel : %s"
|
||||
|
||||
#: cps/helper.py:157
|
||||
#: cps/helper.py:171
|
||||
msgid "Calibre-web test email"
|
||||
msgstr ""
|
||||
|
||||
#: cps/helper.py:158 cps/helper.py:168
|
||||
#: cps/helper.py:172 cps/helper.py:184
|
||||
msgid "This email has been sent via calibre web."
|
||||
msgstr "Ce message a été envoyé depuis calibre web."
|
||||
|
||||
#: cps/helper.py:167 cps/templates/detail.html:130
|
||||
#: cps/helper.py:181 cps/templates/detail.html:146
|
||||
msgid "Send to Kindle"
|
||||
msgstr "Envoyer ver Kindle"
|
||||
|
||||
#: cps/helper.py:185 cps/helper.py:200
|
||||
#: cps/helper.py:201 cps/helper.py:216
|
||||
msgid "Could not find any formats suitable for sending by email"
|
||||
msgstr "Impossible de trouver un format adapté à envoyer par courriel"
|
||||
|
||||
#: cps/helper.py:194
|
||||
#: cps/helper.py:210
|
||||
msgid "Could not convert epub to mobi"
|
||||
msgstr "Impossible de convertir epub vers mobi"
|
||||
|
||||
#: cps/ub.py:434
|
||||
#: cps/ub.py:488
|
||||
msgid "Guest"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:734
|
||||
#: cps/web.py:904
|
||||
msgid "Requesting update package"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:735
|
||||
#: cps/web.py:905
|
||||
msgid "Downloading update package"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:736
|
||||
#: cps/web.py:906
|
||||
msgid "Unzipping update package"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:737
|
||||
#: cps/web.py:907
|
||||
msgid "Files are replaced"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:738
|
||||
#: cps/web.py:908
|
||||
msgid "Database connections are closed"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:739
|
||||
#: cps/web.py:909
|
||||
msgid "Server is stopped"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:740
|
||||
#: cps/web.py:910
|
||||
msgid "Update finished, please press okay and reload page"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:810
|
||||
#: cps/web.py:983
|
||||
msgid "Latest Books"
|
||||
msgstr "Derniers livres"
|
||||
|
||||
#: cps/web.py:835
|
||||
#: cps/web.py:1014
|
||||
msgid "Hot Books (most downloaded)"
|
||||
msgstr "Livres populaires (les plus téléchargés)"
|
||||
|
||||
#: cps/web.py:845
|
||||
#: cps/web.py:1024
|
||||
msgid "Best rated books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:36 cps/web.py:854
|
||||
#: cps/templates/index.xml:36 cps/web.py:1033
|
||||
msgid "Random Books"
|
||||
msgstr "Livres au hasard"
|
||||
|
||||
#: cps/web.py:867
|
||||
#: cps/web.py:1046
|
||||
msgid "Author list"
|
||||
msgstr "Liste des auteurs"
|
||||
|
||||
#: cps/web.py:878
|
||||
#: cps/web.py:1057
|
||||
#, python-format
|
||||
msgid "Author: %(name)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:880 cps/web.py:908 cps/web.py:1007 cps/web.py:1235
|
||||
#: cps/web.py:2115
|
||||
#: cps/web.py:1059 cps/web.py:1087 cps/web.py:1221 cps/web.py:1626
|
||||
#: cps/web.py:2579
|
||||
msgid "Error opening eBook. File does not exist or file is not accessible:"
|
||||
msgstr ""
|
||||
"Erreur d'ouverture du livre numérique. Le fichier n'existe pas ou n'est "
|
||||
"pas accessible :"
|
||||
|
||||
#: cps/templates/index.xml:57 cps/web.py:894
|
||||
#: cps/templates/index.xml:71 cps/web.py:1073
|
||||
msgid "Series list"
|
||||
msgstr "Liste des séries"
|
||||
|
||||
#: cps/web.py:906
|
||||
#: cps/web.py:1085
|
||||
#, python-format
|
||||
msgid "Series: %(serie)s"
|
||||
msgstr "Séries : %(serie)s"
|
||||
|
||||
#: cps/web.py:939
|
||||
#: cps/web.py:1118
|
||||
msgid "Available languages"
|
||||
msgstr "Langues disponibles"
|
||||
|
||||
#: cps/web.py:954
|
||||
#: cps/web.py:1133
|
||||
#, python-format
|
||||
msgid "Language: %(name)s"
|
||||
msgstr "Langue : %(name)s"
|
||||
|
||||
#: cps/templates/index.xml:50 cps/web.py:967
|
||||
#: cps/templates/index.xml:64 cps/web.py:1146
|
||||
msgid "Category list"
|
||||
msgstr "Liste des catégories"
|
||||
|
||||
#: cps/web.py:979
|
||||
#: cps/web.py:1158
|
||||
#, python-format
|
||||
msgid "Category: %(name)s"
|
||||
msgstr "Catégorie : %(name)s"
|
||||
|
||||
#: cps/web.py:1040
|
||||
#: cps/web.py:1267
|
||||
msgid "Statistics"
|
||||
msgstr "Statistiques"
|
||||
|
||||
#: cps/web.py:1061
|
||||
#: cps/web.py:1375
|
||||
msgid "Server restarted, please reload page"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1063
|
||||
#: cps/web.py:1377
|
||||
msgid "Performing shutdown of server, please close window"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1073
|
||||
#: cps/web.py:1392
|
||||
msgid "Update done"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1147 cps/web.py:1160
|
||||
#: cps/web.py:1470 cps/web.py:1483
|
||||
msgid "search"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1211 cps/web.py:1218 cps/web.py:1225 cps/web.py:1232
|
||||
#: cps/web.py:1602 cps/web.py:1609 cps/web.py:1616 cps/web.py:1623
|
||||
msgid "Read a Book"
|
||||
msgstr "Lire un livre"
|
||||
|
||||
#: cps/web.py:1276 cps/web.py:1713
|
||||
#: cps/web.py:1676 cps/web.py:2152
|
||||
msgid "Please fill out all fields!"
|
||||
msgstr "SVP, complétez tous les champs !"
|
||||
|
||||
#: cps/web.py:1277 cps/web.py:1293 cps/web.py:1298 cps/web.py:1300
|
||||
#: cps/web.py:1677 cps/web.py:1693 cps/web.py:1698 cps/web.py:1700
|
||||
msgid "register"
|
||||
msgstr "S'enregistrer"
|
||||
|
||||
#: cps/web.py:1292
|
||||
#: cps/web.py:1692
|
||||
msgid "An unknown error occured. Please try again later."
|
||||
msgstr "Une erreur a eu lieu. Merci de réessayez plus tard."
|
||||
|
||||
#: cps/web.py:1297
|
||||
#: cps/web.py:1697
|
||||
msgid "This username or email address is already in use."
|
||||
msgstr "Ce nom d'utilisateur ou cette adresse de courriel est déjà utilisée."
|
||||
|
||||
#: cps/web.py:1315
|
||||
#: cps/web.py:1715
|
||||
#, python-format
|
||||
msgid "you are now logged in as: '%(nickname)s'"
|
||||
msgstr "Vous êtes maintenant connecté sous : '%(nickname)s'"
|
||||
|
||||
#: cps/web.py:1320
|
||||
#: cps/web.py:1720
|
||||
msgid "Wrong Username or Password"
|
||||
msgstr "Mauvais nom d'utilisateur ou mot de passe"
|
||||
|
||||
#: cps/web.py:1322
|
||||
#: cps/web.py:1722
|
||||
msgid "login"
|
||||
msgstr "Connexion"
|
||||
|
||||
#: cps/web.py:1339
|
||||
#: cps/web.py:1739
|
||||
msgid "Please configure the SMTP mail settings first..."
|
||||
msgstr "Veillez configurer les paramètres smtp d'abord..."
|
||||
|
||||
#: cps/web.py:1343
|
||||
#: cps/web.py:1743
|
||||
#, python-format
|
||||
msgid "Book successfully send to %(kindlemail)s"
|
||||
msgstr "Livres envoyés à %(kindlemail)s avec succès"
|
||||
|
||||
#: cps/web.py:1347
|
||||
#: cps/web.py:1747
|
||||
#, python-format
|
||||
msgid "There was an error sending this book: %(res)s"
|
||||
msgstr "Il y a eu une erreur en envoyant ce livre : %(res)s"
|
||||
|
||||
#: cps/web.py:1349
|
||||
#: cps/web.py:1749 cps/web.py:2232
|
||||
msgid "Please configure your kindle email address first..."
|
||||
msgstr "Veuillez configurer votre adresse kindle d'abord..."
|
||||
|
||||
#: cps/web.py:1369
|
||||
#: cps/web.py:1774
|
||||
#, python-format
|
||||
msgid "Book has been added to shelf: %(sname)s"
|
||||
msgstr "Le livre a bien été ajouté à l'étagère : %(sname)s"
|
||||
|
||||
#: cps/web.py:1390
|
||||
#: cps/web.py:1793
|
||||
#, python-format
|
||||
msgid "Book has been removed from shelf: %(sname)s"
|
||||
msgstr "Le livre a été supprimé de l'étagère %(sname)s"
|
||||
|
||||
#: cps/web.py:1409 cps/web.py:1433
|
||||
#: cps/web.py:1812 cps/web.py:1836
|
||||
#, python-format
|
||||
msgid "A shelf with the name '%(title)s' already exists."
|
||||
msgstr "Une étagère de ce nom '%(title)s' existe déjà."
|
||||
|
||||
#: cps/web.py:1414
|
||||
#: cps/web.py:1817
|
||||
#, python-format
|
||||
msgid "Shelf %(title)s created"
|
||||
msgstr "Étagère %(title)s créée"
|
||||
|
||||
#: cps/web.py:1416 cps/web.py:1444
|
||||
#: cps/web.py:1819 cps/web.py:1847
|
||||
msgid "There was an error"
|
||||
msgstr "Il y a eu une erreur"
|
||||
|
||||
#: cps/web.py:1417 cps/web.py:1419
|
||||
#: cps/web.py:1820 cps/web.py:1822
|
||||
msgid "create a shelf"
|
||||
msgstr "Créer une étagère"
|
||||
|
||||
#: cps/web.py:1442
|
||||
#: cps/web.py:1845
|
||||
#, python-format
|
||||
msgid "Shelf %(title)s changed"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1445 cps/web.py:1447
|
||||
#: cps/web.py:1848 cps/web.py:1850
|
||||
msgid "Edit a shelf"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1465
|
||||
#: cps/web.py:1868
|
||||
#, python-format
|
||||
msgid "successfully deleted shelf %(name)s"
|
||||
msgstr "L'étagère %(name)s a été supprimé avec succès"
|
||||
|
||||
#: cps/web.py:1487
|
||||
#: cps/web.py:1890
|
||||
#, python-format
|
||||
msgid "Shelf: '%(name)s'"
|
||||
msgstr "Étagère : '%(name)s'"
|
||||
|
||||
#: cps/web.py:1518
|
||||
#: cps/web.py:1921
|
||||
#, python-format
|
||||
msgid "Change order of Shelf: '%(name)s'"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1580
|
||||
#: cps/web.py:1985
|
||||
msgid "Found an existing account for this email address."
|
||||
msgstr "Un compte avec cette adresse de courriel existe déjà."
|
||||
|
||||
#: cps/web.py:1582 cps/web.py:1586
|
||||
#: cps/web.py:1987 cps/web.py:1991
|
||||
#, python-format
|
||||
msgid "%(name)s's profile"
|
||||
msgstr "Profil de %(name)s"
|
||||
|
||||
#: cps/web.py:1583
|
||||
#: cps/web.py:1988
|
||||
msgid "Profile updated"
|
||||
msgstr "Profil mis à jour"
|
||||
|
||||
#: cps/web.py:1597
|
||||
#: cps/web.py:2002
|
||||
msgid "Admin page"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1668
|
||||
#: cps/web.py:2106
|
||||
msgid "Calibre-web configuration updated"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1675 cps/web.py:1681 cps/web.py:1694
|
||||
#: cps/web.py:2113 cps/web.py:2119 cps/web.py:2133
|
||||
msgid "Basic Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1679
|
||||
#: cps/web.py:2117
|
||||
msgid "DB location is not valid, please enter correct path"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:34 cps/web.py:1715 cps/web.py:1761
|
||||
#: cps/templates/admin.html:34 cps/web.py:2154 cps/web.py:2202
|
||||
msgid "Add new user"
|
||||
msgstr "Ajouter un nouvel utilisateur"
|
||||
|
||||
#: cps/web.py:1753
|
||||
#: cps/web.py:2194
|
||||
#, python-format
|
||||
msgid "User '%(user)s' created"
|
||||
msgstr "Utilisateur '%(user)s' créé"
|
||||
|
||||
#: cps/web.py:1757
|
||||
#: cps/web.py:2198
|
||||
msgid "Found an existing account for this email address or nickname."
|
||||
msgstr "Un compte avec cette adresse de courriel ou ce surnom existe déjà."
|
||||
|
||||
#: cps/web.py:1779
|
||||
#: cps/web.py:2220
|
||||
msgid "Mail settings updated"
|
||||
msgstr "Paramètres de courriel mis à jour"
|
||||
|
||||
#: cps/web.py:1785
|
||||
#: cps/web.py:2227
|
||||
#, python-format
|
||||
msgid "Test E-Mail successfully send to %(kindlemail)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1788
|
||||
#: cps/web.py:2230
|
||||
#, python-format
|
||||
msgid "There was an error sending the Test E-Mail: %(res)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1789
|
||||
#: cps/web.py:2234
|
||||
msgid "E-Mail settings updated"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2235
|
||||
msgid "Edit mail settings"
|
||||
msgstr "Éditer les paramètres de courriel"
|
||||
|
||||
#: cps/web.py:1817
|
||||
#: cps/web.py:2263
|
||||
#, python-format
|
||||
msgid "User '%(nick)s' deleted"
|
||||
msgstr "Utilisateur '%(nick)s' supprimé"
|
||||
|
||||
#: cps/web.py:1898
|
||||
#: cps/web.py:2349
|
||||
#, python-format
|
||||
msgid "User '%(nick)s' updated"
|
||||
msgstr "Utilisateur '%(nick)s' mis à jour"
|
||||
|
||||
#: cps/web.py:1901
|
||||
#: cps/web.py:2352
|
||||
msgid "An unknown error occured."
|
||||
msgstr "Oups ! Une erreur inconnue a eu lieu."
|
||||
|
||||
#: cps/web.py:1904
|
||||
#: cps/web.py:2355
|
||||
#, python-format
|
||||
msgid "Edit User %(nick)s"
|
||||
msgstr "Éditer l'utilisateur %(nick)s"
|
||||
|
||||
#: cps/web.py:2110 cps/web.py:2113 cps/web.py:2188
|
||||
#: cps/web.py:2574 cps/web.py:2577 cps/web.py:2689
|
||||
msgid "edit metadata"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2145
|
||||
#: cps/web.py:2598
|
||||
#, python-format
|
||||
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2604
|
||||
msgid "File to be uploaded must have an extension"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2621
|
||||
#, python-format
|
||||
msgid "Failed to create path %s (Permission denied)."
|
||||
msgstr "Impossible de créer le chemin %s (permission refusée)"
|
||||
|
||||
#: cps/web.py:2150
|
||||
#: cps/web.py:2626
|
||||
#, python-format
|
||||
msgid "Failed to store file %s (Permission denied)."
|
||||
msgstr "Impossible d'enregistrer le fichier %s (permission refusée)"
|
||||
|
||||
#: cps/web.py:2155
|
||||
#: cps/web.py:2631
|
||||
#, python-format
|
||||
msgid "Failed to delete file %s (Permission denied)."
|
||||
msgstr "Impossible de supprimer le fichier %s (permission refusée)"
|
||||
|
@ -396,7 +409,7 @@ msgstr "DLS"
|
|||
msgid "Admin"
|
||||
msgstr "Administration"
|
||||
|
||||
#: cps/templates/admin.html:13 cps/templates/detail.html:117
|
||||
#: cps/templates/admin.html:13 cps/templates/detail.html:134
|
||||
msgid "Download"
|
||||
msgstr "Télécharger"
|
||||
|
||||
|
@ -452,7 +465,7 @@ msgstr ""
|
|||
msgid "Calibre DB dir"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:61 cps/templates/config_edit.html:32
|
||||
#: cps/templates/admin.html:61 cps/templates/config_edit.html:76
|
||||
msgid "Log Level"
|
||||
msgstr ""
|
||||
|
||||
|
@ -460,7 +473,7 @@ msgstr ""
|
|||
msgid "Port"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:63 cps/templates/config_edit.html:19
|
||||
#: cps/templates/admin.html:63 cps/templates/config_edit.html:60
|
||||
msgid "Books per page"
|
||||
msgstr ""
|
||||
|
||||
|
@ -489,42 +502,46 @@ msgid "Newest commit timestamp"
|
|||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:83
|
||||
msgid "Restart Calibre-web"
|
||||
msgid "Reconnect to Calibre DB"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:84
|
||||
msgid "Stop Calibre-web"
|
||||
msgid "Restart Calibre-web"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:85
|
||||
msgid "Check for update"
|
||||
msgid "Stop Calibre-web"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:86
|
||||
msgid "Check for update"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:87
|
||||
msgid "Perform Update"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:96
|
||||
#: cps/templates/admin.html:97
|
||||
msgid "Do you really want to restart Calibre-web?"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:101 cps/templates/admin.html:115
|
||||
#: cps/templates/admin.html:136
|
||||
#: cps/templates/admin.html:102 cps/templates/admin.html:116
|
||||
#: cps/templates/admin.html:137
|
||||
msgid "Ok"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:102 cps/templates/admin.html:116
|
||||
#: cps/templates/book_edit.html:108 cps/templates/config_edit.html:75
|
||||
#: cps/templates/admin.html:103 cps/templates/admin.html:117
|
||||
#: cps/templates/book_edit.html:109 cps/templates/config_edit.html:119
|
||||
#: cps/templates/email_edit.html:36 cps/templates/shelf_edit.html:17
|
||||
#: cps/templates/shelf_order.html:12 cps/templates/user_edit.html:111
|
||||
#: cps/templates/shelf_order.html:12 cps/templates/user_edit.html:120
|
||||
msgid "Back"
|
||||
msgstr "Retour"
|
||||
|
||||
#: cps/templates/admin.html:114
|
||||
#: cps/templates/admin.html:115
|
||||
msgid "Do you really want to stop Calibre-web?"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:127
|
||||
#: cps/templates/admin.html:128
|
||||
msgid "Updating, please do not reload page"
|
||||
msgstr ""
|
||||
|
||||
|
@ -532,20 +549,21 @@ msgstr ""
|
|||
msgid "Book Title"
|
||||
msgstr "Titre du livre"
|
||||
|
||||
#: cps/templates/book_edit.html:20 cps/templates/search_form.html:10
|
||||
#: cps/templates/book_edit.html:20 cps/templates/book_edit.html:145
|
||||
#: cps/templates/search_form.html:10
|
||||
msgid "Author"
|
||||
msgstr "Auteur"
|
||||
|
||||
#: cps/templates/book_edit.html:24
|
||||
#: cps/templates/book_edit.html:24 cps/templates/book_edit.html:147
|
||||
msgid "Description"
|
||||
msgstr "Description"
|
||||
|
||||
#: cps/templates/book_edit.html:28 cps/templates/search_form.html:13
|
||||
#: cps/templates/book_edit.html:28 cps/templates/search_form.html:17
|
||||
msgid "Tags"
|
||||
msgstr "Étiquette"
|
||||
|
||||
#: cps/templates/book_edit.html:33 cps/templates/layout.html:138
|
||||
#: cps/templates/search_form.html:33
|
||||
#: cps/templates/book_edit.html:33 cps/templates/layout.html:142
|
||||
#: cps/templates/search_form.html:37
|
||||
msgid "Series"
|
||||
msgstr "Séries"
|
||||
|
||||
|
@ -577,69 +595,142 @@ msgstr "Non"
|
|||
msgid "view book after edit"
|
||||
msgstr "Voir le livre après l'édition"
|
||||
|
||||
#: cps/templates/book_edit.html:107 cps/templates/config_edit.html:73
|
||||
#: cps/templates/login.html:19 cps/templates/search_form.html:75
|
||||
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:109
|
||||
#: cps/templates/book_edit.html:107 cps/templates/book_edit.html:118
|
||||
msgid "Get metadata"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:108 cps/templates/config_edit.html:117
|
||||
#: cps/templates/login.html:19 cps/templates/search_form.html:79
|
||||
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:118
|
||||
msgid "Submit"
|
||||
msgstr "Soumettre"
|
||||
|
||||
#: cps/templates/book_edit.html:121
|
||||
msgid "Keyword"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:122
|
||||
msgid " Search keyword "
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:124 cps/templates/layout.html:60
|
||||
msgid "Go!"
|
||||
msgstr "Allez !"
|
||||
|
||||
#: cps/templates/book_edit.html:125
|
||||
msgid "Click the cover to load metadata to the form"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:129 cps/templates/book_edit.html:142
|
||||
msgid "Loading..."
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:132
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:143
|
||||
msgid "Search error!"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:144
|
||||
msgid "No Result! Please try anonther keyword."
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:146 cps/templates/detail.html:76
|
||||
#: cps/templates/search_form.html:14
|
||||
msgid "Publisher"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:148
|
||||
msgid "Source"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:7
|
||||
msgid "Location of Calibre database"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:11
|
||||
msgid "Server Port"
|
||||
#: cps/templates/config_edit.html:13
|
||||
msgid "Use google drive?"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:15 cps/templates/shelf_edit.html:7
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
|
||||
#: cps/templates/config_edit.html:23
|
||||
msgid "No. of random books to show"
|
||||
#: cps/templates/config_edit.html:17
|
||||
msgid "Client id"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:28
|
||||
msgid "Regular expression for title sorting"
|
||||
#: cps/templates/config_edit.html:21
|
||||
msgid "Client secret"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:42
|
||||
msgid "Enable uploading"
|
||||
#: cps/templates/config_edit.html:25
|
||||
msgid "Calibre Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:46
|
||||
msgid "Enable anonymous browsing"
|
||||
#: cps/templates/config_edit.html:29
|
||||
msgid "Google drive Calibre folder"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:50
|
||||
msgid "Enable public registration"
|
||||
#: cps/templates/config_edit.html:38
|
||||
msgid "Metadata Watch Channel ID"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:52
|
||||
msgid "Server Port"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:56 cps/templates/shelf_edit.html:7
|
||||
msgid "Title"
|
||||
msgstr "Titre"
|
||||
|
||||
#: cps/templates/config_edit.html:64
|
||||
msgid "No. of random books to show"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:68
|
||||
msgid "Regular expression for ignoring columns"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:72
|
||||
msgid "Regular expression for title sorting"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:86
|
||||
msgid "Enable uploading"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:90
|
||||
msgid "Enable anonymous browsing"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:94
|
||||
msgid "Enable public registration"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:96
|
||||
msgid "Default Settings for new users"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:55 cps/templates/user_edit.html:80
|
||||
#: cps/templates/config_edit.html:99 cps/templates/user_edit.html:87
|
||||
msgid "Admin user"
|
||||
msgstr "Utilisateur admin"
|
||||
|
||||
#: cps/templates/config_edit.html:59 cps/templates/user_edit.html:85
|
||||
#: cps/templates/config_edit.html:103 cps/templates/user_edit.html:92
|
||||
msgid "Allow Downloads"
|
||||
msgstr "Permettre les téléchargements"
|
||||
|
||||
#: cps/templates/config_edit.html:63 cps/templates/user_edit.html:89
|
||||
#: cps/templates/config_edit.html:107 cps/templates/user_edit.html:96
|
||||
msgid "Allow Uploads"
|
||||
msgstr "Permettre les téléversements"
|
||||
|
||||
#: cps/templates/config_edit.html:67 cps/templates/user_edit.html:93
|
||||
#: cps/templates/config_edit.html:111 cps/templates/user_edit.html:100
|
||||
msgid "Allow Edit"
|
||||
msgstr "Permettre l'édition"
|
||||
|
||||
#: cps/templates/config_edit.html:71 cps/templates/user_edit.html:98
|
||||
#: cps/templates/config_edit.html:115 cps/templates/user_edit.html:105
|
||||
msgid "Allow Changing Password"
|
||||
msgstr "Permettre le changement de mot de passe"
|
||||
|
||||
#: cps/templates/config_edit.html:78 cps/templates/layout.html:93
|
||||
#: cps/templates/config_edit.html:122 cps/templates/layout.html:93
|
||||
#: cps/templates/login.html:4
|
||||
msgid "Login"
|
||||
msgstr "Connexion"
|
||||
|
@ -656,23 +747,27 @@ msgstr ""
|
|||
msgid "language"
|
||||
msgstr "Langue"
|
||||
|
||||
#: cps/templates/detail.html:74
|
||||
#: cps/templates/detail.html:81
|
||||
msgid "Publishing date"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/detail.html:106
|
||||
#: cps/templates/detail.html:115
|
||||
msgid "Read"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/detail.html:123
|
||||
msgid "Description:"
|
||||
msgstr "Description :"
|
||||
|
||||
#: cps/templates/detail.html:134
|
||||
#: cps/templates/detail.html:151
|
||||
msgid "Read in browser"
|
||||
msgstr "Lire dans le navigateur"
|
||||
|
||||
#: cps/templates/detail.html:154
|
||||
#: cps/templates/detail.html:171
|
||||
msgid "Add to shelf"
|
||||
msgstr "Ajouter à l'étagère"
|
||||
|
||||
#: cps/templates/detail.html:194
|
||||
#: cps/templates/detail.html:211
|
||||
msgid "Edit metadata"
|
||||
msgstr "Éditer les métadonnées"
|
||||
|
||||
|
@ -752,19 +847,29 @@ msgstr "Les derniers livres"
|
|||
msgid "Show Random Books"
|
||||
msgstr "Montrer des livres au hasard"
|
||||
|
||||
#: cps/templates/index.xml:43 cps/templates/layout.html:140
|
||||
#: cps/templates/index.xml:43 cps/templates/index.xml:47
|
||||
#: cps/templates/layout.html:132
|
||||
msgid "Read Books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:50 cps/templates/index.xml:54
|
||||
#: cps/templates/layout.html:133
|
||||
msgid "Unread Books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:57 cps/templates/layout.html:144
|
||||
msgid "Authors"
|
||||
msgstr "Auteurs"
|
||||
|
||||
#: cps/templates/index.xml:47
|
||||
#: cps/templates/index.xml:61
|
||||
msgid "Books ordered by Author"
|
||||
msgstr "Livres classés par auteur"
|
||||
|
||||
#: cps/templates/index.xml:54
|
||||
#: cps/templates/index.xml:68
|
||||
msgid "Books ordered by category"
|
||||
msgstr "Livres classés par catégorie"
|
||||
|
||||
#: cps/templates/index.xml:61
|
||||
#: cps/templates/index.xml:75
|
||||
msgid "Books ordered by series"
|
||||
msgstr "Livres classés par série"
|
||||
|
||||
|
@ -772,10 +877,6 @@ msgstr "Livres classés par série"
|
|||
msgid "Toggle navigation"
|
||||
msgstr "Basculer la navigation"
|
||||
|
||||
#: cps/templates/layout.html:60
|
||||
msgid "Go!"
|
||||
msgstr "Allez !"
|
||||
|
||||
#: cps/templates/layout.html:68
|
||||
msgid "Advanced Search"
|
||||
msgstr "Recherche avancée"
|
||||
|
@ -792,31 +893,31 @@ msgstr "S'enregistrer"
|
|||
msgid "Browse"
|
||||
msgstr "Explorer"
|
||||
|
||||
#: cps/templates/layout.html:132
|
||||
#: cps/templates/layout.html:136
|
||||
msgid "Discover"
|
||||
msgstr "Découvrir"
|
||||
|
||||
#: cps/templates/layout.html:135
|
||||
#: cps/templates/layout.html:139
|
||||
msgid "Categories"
|
||||
msgstr "Catégories"
|
||||
|
||||
#: cps/templates/layout.html:142 cps/templates/search_form.html:54
|
||||
#: cps/templates/layout.html:146 cps/templates/search_form.html:58
|
||||
msgid "Languages"
|
||||
msgstr "Langues"
|
||||
|
||||
#: cps/templates/layout.html:145
|
||||
#: cps/templates/layout.html:149
|
||||
msgid "Public Shelves"
|
||||
msgstr "Étagères publiques"
|
||||
|
||||
#: cps/templates/layout.html:149
|
||||
#: cps/templates/layout.html:153
|
||||
msgid "Your Shelves"
|
||||
msgstr "Vos étagères"
|
||||
|
||||
#: cps/templates/layout.html:154
|
||||
#: cps/templates/layout.html:158
|
||||
msgid "Create a Shelf"
|
||||
msgstr "Créer une étagère"
|
||||
|
||||
#: cps/templates/layout.html:155
|
||||
#: cps/templates/layout.html:159
|
||||
msgid "About"
|
||||
msgstr "À popos"
|
||||
|
||||
|
@ -882,15 +983,15 @@ msgstr "Essayer une recherche différente"
|
|||
msgid "Results for:"
|
||||
msgstr "Résultats pour :"
|
||||
|
||||
#: cps/templates/search_form.html:23
|
||||
#: cps/templates/search_form.html:27
|
||||
msgid "Exclude Tags"
|
||||
msgstr "Exclure des étiquettes"
|
||||
|
||||
#: cps/templates/search_form.html:43
|
||||
#: cps/templates/search_form.html:47
|
||||
msgid "Exclude Series"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/search_form.html:64
|
||||
#: cps/templates/search_form.html:68
|
||||
msgid "Exclude Languages"
|
||||
msgstr ""
|
||||
|
||||
|
@ -915,37 +1016,37 @@ msgid "Drag 'n drop to rearrange order"
|
|||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:3
|
||||
msgid "Linked libraries"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:8
|
||||
msgid "Program library"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:9
|
||||
msgid "Installed Version"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:32
|
||||
msgid "Calibre library statistics"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:37
|
||||
#: cps/templates/stats.html:8
|
||||
msgid "Books in this Library"
|
||||
msgstr "Livres dans la bibiothèque"
|
||||
|
||||
#: cps/templates/stats.html:41
|
||||
#: cps/templates/stats.html:12
|
||||
msgid "Authors in this Library"
|
||||
msgstr "Auteurs dans la bibliothèque"
|
||||
|
||||
#: cps/templates/stats.html:45
|
||||
#: cps/templates/stats.html:16
|
||||
msgid "Categories in this Library"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:49
|
||||
#: cps/templates/stats.html:20
|
||||
msgid "Series in this Library"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:24
|
||||
msgid "Linked libraries"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:28
|
||||
msgid "Program library"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:29
|
||||
msgid "Installed Version"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:23
|
||||
msgid "Kindle E-Mail"
|
||||
msgstr "Courriel Kindle"
|
||||
|
@ -958,43 +1059,47 @@ msgstr "Montrer les livres dans la langue"
|
|||
msgid "Show all"
|
||||
msgstr "Montrer tout"
|
||||
|
||||
#: cps/templates/user_edit.html:45
|
||||
#: cps/templates/user_edit.html:47
|
||||
msgid "Show random books"
|
||||
msgstr "Montrer des livres au hasard"
|
||||
|
||||
#: cps/templates/user_edit.html:49
|
||||
#: cps/templates/user_edit.html:51
|
||||
msgid "Show hot books"
|
||||
msgstr "Montrer les livres populaires"
|
||||
|
||||
#: cps/templates/user_edit.html:53
|
||||
#: cps/templates/user_edit.html:55
|
||||
msgid "Show best rated books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:57
|
||||
#: cps/templates/user_edit.html:59
|
||||
msgid "Show language selection"
|
||||
msgstr "Montrer la sélection de la langue"
|
||||
|
||||
#: cps/templates/user_edit.html:61
|
||||
#: cps/templates/user_edit.html:63
|
||||
msgid "Show series selection"
|
||||
msgstr "Montrer la sélection des séries"
|
||||
|
||||
#: cps/templates/user_edit.html:65
|
||||
#: cps/templates/user_edit.html:67
|
||||
msgid "Show category selection"
|
||||
msgstr "Montrer la sélection des catégories"
|
||||
|
||||
#: cps/templates/user_edit.html:69
|
||||
#: cps/templates/user_edit.html:71
|
||||
msgid "Show author selection"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:73
|
||||
#: cps/templates/user_edit.html:75
|
||||
msgid "Show read and unread"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:79
|
||||
msgid "Show random books in detail view"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:105
|
||||
#: cps/templates/user_edit.html:112
|
||||
msgid "Delete this user"
|
||||
msgstr "Supprimer cet utilisateur"
|
||||
|
||||
#: cps/templates/user_edit.html:116
|
||||
#: cps/templates/user_edit.html:127
|
||||
msgid "Recent Downloads"
|
||||
msgstr "Téléchargements récents"
|
||||
|
||||
|
|
Binary file not shown.
|
@ -15,7 +15,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: Calibre-web\n"
|
||||
"Report-Msgid-Bugs-To: https://github.com/janeczku/calibre-web\n"
|
||||
"POT-Creation-Date: 2017-02-20 19:47+0100\n"
|
||||
"POT-Creation-Date: 2017-03-19 19:20+0100\n"
|
||||
"PO-Revision-Date: 2017-01-06 17:00+0000\n"
|
||||
"Last-Translator: dalin <dalin.lin@gmail.com>\n"
|
||||
"Language: zh_Hans_CN\n"
|
||||
|
@ -26,341 +26,354 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.3.4\n"
|
||||
|
||||
#: cps/book_formats.py:111 cps/book_formats.py:115 cps/web.py:1030
|
||||
#: cps/book_formats.py:113 cps/book_formats.py:117 cps/web.py:1244
|
||||
msgid "not installed"
|
||||
msgstr "未安装"
|
||||
|
||||
#: cps/helper.py:150
|
||||
#: cps/helper.py:164
|
||||
#, python-format
|
||||
msgid "Failed to send mail: %s"
|
||||
msgstr "发送邮件失败: %s"
|
||||
|
||||
#: cps/helper.py:157
|
||||
#: cps/helper.py:171
|
||||
msgid "Calibre-web test email"
|
||||
msgstr "Calibre-web 测试邮件"
|
||||
|
||||
#: cps/helper.py:158 cps/helper.py:168
|
||||
#: cps/helper.py:172 cps/helper.py:184
|
||||
msgid "This email has been sent via calibre web."
|
||||
msgstr "此邮件由calibre web发送"
|
||||
|
||||
#: cps/helper.py:167 cps/templates/detail.html:130
|
||||
#: cps/helper.py:181 cps/templates/detail.html:146
|
||||
msgid "Send to Kindle"
|
||||
msgstr "发送到Kindle"
|
||||
|
||||
#: cps/helper.py:185 cps/helper.py:200
|
||||
#: cps/helper.py:201 cps/helper.py:216
|
||||
msgid "Could not find any formats suitable for sending by email"
|
||||
msgstr "无法找到适合邮件发送的格式"
|
||||
|
||||
#: cps/helper.py:194
|
||||
#: cps/helper.py:210
|
||||
msgid "Could not convert epub to mobi"
|
||||
msgstr "无法转换epub到mobi"
|
||||
|
||||
#: cps/ub.py:434
|
||||
#: cps/ub.py:488
|
||||
msgid "Guest"
|
||||
msgstr "游客"
|
||||
|
||||
#: cps/web.py:734
|
||||
#: cps/web.py:904
|
||||
msgid "Requesting update package"
|
||||
msgstr "正在请求更新包"
|
||||
|
||||
#: cps/web.py:735
|
||||
#: cps/web.py:905
|
||||
msgid "Downloading update package"
|
||||
msgstr "正在下载更新包"
|
||||
|
||||
#: cps/web.py:736
|
||||
#: cps/web.py:906
|
||||
msgid "Unzipping update package"
|
||||
msgstr "正在解压更新包"
|
||||
|
||||
#: cps/web.py:737
|
||||
#: cps/web.py:907
|
||||
msgid "Files are replaced"
|
||||
msgstr "文件已替换"
|
||||
|
||||
#: cps/web.py:738
|
||||
#: cps/web.py:908
|
||||
msgid "Database connections are closed"
|
||||
msgstr "数据库连接已关闭"
|
||||
|
||||
#: cps/web.py:739
|
||||
#: cps/web.py:909
|
||||
msgid "Server is stopped"
|
||||
msgstr "服务器已停止"
|
||||
|
||||
#: cps/web.py:740
|
||||
#: cps/web.py:910
|
||||
msgid "Update finished, please press okay and reload page"
|
||||
msgstr "更新完成,请按确定并刷新页面"
|
||||
|
||||
#: cps/web.py:810
|
||||
#: cps/web.py:983
|
||||
msgid "Latest Books"
|
||||
msgstr "最新书籍"
|
||||
|
||||
#: cps/web.py:835
|
||||
#: cps/web.py:1014
|
||||
msgid "Hot Books (most downloaded)"
|
||||
msgstr "热门书籍(最多下载)"
|
||||
|
||||
#: cps/web.py:845
|
||||
#: cps/web.py:1024
|
||||
msgid "Best rated books"
|
||||
msgstr "最高评分书籍"
|
||||
|
||||
#: cps/templates/index.xml:36 cps/web.py:854
|
||||
#: cps/templates/index.xml:36 cps/web.py:1033
|
||||
msgid "Random Books"
|
||||
msgstr "随机书籍"
|
||||
|
||||
#: cps/web.py:867
|
||||
#: cps/web.py:1046
|
||||
msgid "Author list"
|
||||
msgstr "作者列表"
|
||||
|
||||
#: cps/web.py:878
|
||||
#, python-forma
|
||||
#: cps/web.py:1057
|
||||
#, python-forma, python-format
|
||||
msgid "Author: %(name)s"
|
||||
msgstr "作者: %(name)s"
|
||||
|
||||
#: cps/web.py:880 cps/web.py:908 cps/web.py:1007 cps/web.py:1235
|
||||
#: cps/web.py:2115
|
||||
#: cps/web.py:1059 cps/web.py:1087 cps/web.py:1221 cps/web.py:1626
|
||||
#: cps/web.py:2579
|
||||
msgid "Error opening eBook. File does not exist or file is not accessible:"
|
||||
msgstr "无法打开电子书。 文件不存在或者文件不可访问:"
|
||||
|
||||
#: cps/templates/index.xml:57 cps/web.py:894
|
||||
#: cps/templates/index.xml:71 cps/web.py:1073
|
||||
msgid "Series list"
|
||||
msgstr "丛书列表"
|
||||
|
||||
#: cps/web.py:906
|
||||
#: cps/web.py:1085
|
||||
#, python-format
|
||||
msgid "Series: %(serie)s"
|
||||
msgstr "丛书: %(serie)s"
|
||||
|
||||
#: cps/web.py:939
|
||||
#: cps/web.py:1118
|
||||
msgid "Available languages"
|
||||
msgstr "可用语言"
|
||||
|
||||
#: cps/web.py:954
|
||||
#: cps/web.py:1133
|
||||
#, python-format
|
||||
msgid "Language: %(name)s"
|
||||
msgstr "语言: %(name)s"
|
||||
|
||||
#: cps/templates/index.xml:50 cps/web.py:967
|
||||
#: cps/templates/index.xml:64 cps/web.py:1146
|
||||
msgid "Category list"
|
||||
msgstr "分类列表"
|
||||
|
||||
#: cps/web.py:979
|
||||
#: cps/web.py:1158
|
||||
#, python-format
|
||||
msgid "Category: %(name)s"
|
||||
msgstr "分类: %(name)s"
|
||||
|
||||
#: cps/web.py:1040
|
||||
#: cps/web.py:1267
|
||||
msgid "Statistics"
|
||||
msgstr "统计"
|
||||
|
||||
#: cps/web.py:1061
|
||||
#: cps/web.py:1375
|
||||
msgid "Server restarted, please reload page"
|
||||
msgstr "服务器已重启,请刷新页面"
|
||||
|
||||
#: cps/web.py:1063
|
||||
#: cps/web.py:1377
|
||||
msgid "Performing shutdown of server, please close window"
|
||||
msgstr "正在关闭服务器,请关闭窗口"
|
||||
|
||||
#: cps/web.py:1073
|
||||
#: cps/web.py:1392
|
||||
msgid "Update done"
|
||||
msgstr "更新完成"
|
||||
|
||||
#: cps/web.py:1147 cps/web.py:1160
|
||||
#: cps/web.py:1470 cps/web.py:1483
|
||||
msgid "search"
|
||||
msgstr "搜索"
|
||||
|
||||
#: cps/web.py:1211 cps/web.py:1218 cps/web.py:1225 cps/web.py:1232
|
||||
#: cps/web.py:1602 cps/web.py:1609 cps/web.py:1616 cps/web.py:1623
|
||||
msgid "Read a Book"
|
||||
msgstr "阅读一本书"
|
||||
|
||||
#: cps/web.py:1276 cps/web.py:1713
|
||||
#: cps/web.py:1676 cps/web.py:2152
|
||||
msgid "Please fill out all fields!"
|
||||
msgstr "请填写所有字段"
|
||||
|
||||
#: cps/web.py:1277 cps/web.py:1293 cps/web.py:1298 cps/web.py:1300
|
||||
#: cps/web.py:1677 cps/web.py:1693 cps/web.py:1698 cps/web.py:1700
|
||||
msgid "register"
|
||||
msgstr "注册"
|
||||
|
||||
#: cps/web.py:1292
|
||||
#: cps/web.py:1692
|
||||
msgid "An unknown error occured. Please try again later."
|
||||
msgstr "发生一个未知错误。请稍后再试。"
|
||||
|
||||
#: cps/web.py:1297
|
||||
#: cps/web.py:1697
|
||||
msgid "This username or email address is already in use."
|
||||
msgstr "此用户名或邮箱已被使用。"
|
||||
|
||||
#: cps/web.py:1315
|
||||
#: cps/web.py:1715
|
||||
#, python-format
|
||||
msgid "you are now logged in as: '%(nickname)s'"
|
||||
msgstr "您现在已以'%(nickname)s'身份登录"
|
||||
|
||||
#: cps/web.py:1320
|
||||
#: cps/web.py:1720
|
||||
msgid "Wrong Username or Password"
|
||||
msgstr "用户名或密码错误"
|
||||
|
||||
#: cps/web.py:1322
|
||||
#: cps/web.py:1722
|
||||
msgid "login"
|
||||
msgstr "登录"
|
||||
|
||||
#: cps/web.py:1339
|
||||
#: cps/web.py:1739
|
||||
msgid "Please configure the SMTP mail settings first..."
|
||||
msgstr "请先配置SMTP邮箱..."
|
||||
|
||||
#: cps/web.py:1343
|
||||
#: cps/web.py:1743
|
||||
#, python-format
|
||||
msgid "Book successfully send to %(kindlemail)s"
|
||||
msgstr "此书已被成功发给 %(kindlemail)s"
|
||||
|
||||
#: cps/web.py:1347
|
||||
#: cps/web.py:1747
|
||||
#, python-format
|
||||
msgid "There was an error sending this book: %(res)s"
|
||||
msgstr "发送这本书的时候出现错误: %(res)s"
|
||||
|
||||
#: cps/web.py:1349
|
||||
#: cps/web.py:1749 cps/web.py:2232
|
||||
msgid "Please configure your kindle email address first..."
|
||||
msgstr "请先配置您的kindle电子邮箱地址..."
|
||||
|
||||
#: cps/web.py:1369
|
||||
#: cps/web.py:1774
|
||||
#, python-format
|
||||
msgid "Book has been added to shelf: %(sname)s"
|
||||
msgstr "此书已被添加到书架: %(sname)s"
|
||||
|
||||
#: cps/web.py:1390
|
||||
#: cps/web.py:1793
|
||||
#, python-format
|
||||
msgid "Book has been removed from shelf: %(sname)s"
|
||||
msgstr "此书已从书架 %(sname)s 中删除"
|
||||
|
||||
#: cps/web.py:1409 cps/web.py:1433
|
||||
#: cps/web.py:1812 cps/web.py:1836
|
||||
#, python-format
|
||||
msgid "A shelf with the name '%(title)s' already exists."
|
||||
msgstr "已存在书架 '%(title)s'。"
|
||||
|
||||
#: cps/web.py:1414
|
||||
#: cps/web.py:1817
|
||||
#, python-format
|
||||
msgid "Shelf %(title)s created"
|
||||
msgstr "书架 %(title)s 已被创建"
|
||||
|
||||
#: cps/web.py:1416 cps/web.py:1444
|
||||
#: cps/web.py:1819 cps/web.py:1847
|
||||
msgid "There was an error"
|
||||
msgstr "发生错误"
|
||||
|
||||
#: cps/web.py:1417 cps/web.py:1419
|
||||
#: cps/web.py:1820 cps/web.py:1822
|
||||
msgid "create a shelf"
|
||||
msgstr "创建书架"
|
||||
|
||||
#: cps/web.py:1442
|
||||
#: cps/web.py:1845
|
||||
#, python-format
|
||||
msgid "Shelf %(title)s changed"
|
||||
msgstr "书架 %(title)s 已被修改"
|
||||
|
||||
#: cps/web.py:1445 cps/web.py:1447
|
||||
#: cps/web.py:1848 cps/web.py:1850
|
||||
msgid "Edit a shelf"
|
||||
msgstr "编辑书架"
|
||||
|
||||
#: cps/web.py:1465
|
||||
#: cps/web.py:1868
|
||||
#, python-format
|
||||
msgid "successfully deleted shelf %(name)s"
|
||||
msgstr "成功删除书架 %(name)s"
|
||||
|
||||
#: cps/web.py:1487
|
||||
#: cps/web.py:1890
|
||||
#, python-format
|
||||
msgid "Shelf: '%(name)s'"
|
||||
msgstr "书架: '%(name)s'"
|
||||
|
||||
#: cps/web.py:1518
|
||||
#: cps/web.py:1921
|
||||
#, python-format
|
||||
msgid "Change order of Shelf: '%(name)s'"
|
||||
msgstr "修改书架 '%(name)s' 顺序"
|
||||
|
||||
#: cps/web.py:1580
|
||||
#: cps/web.py:1985
|
||||
msgid "Found an existing account for this email address."
|
||||
msgstr "找到已使用此邮箱的账号。"
|
||||
|
||||
#: cps/web.py:1582 cps/web.py:1586
|
||||
#: cps/web.py:1987 cps/web.py:1991
|
||||
#, python-format
|
||||
msgid "%(name)s's profile"
|
||||
msgstr "%(name)s 的资料"
|
||||
|
||||
#: cps/web.py:1583
|
||||
#: cps/web.py:1988
|
||||
msgid "Profile updated"
|
||||
msgstr "资料已更新"
|
||||
|
||||
#: cps/web.py:1597
|
||||
#: cps/web.py:2002
|
||||
msgid "Admin page"
|
||||
msgstr "管理页"
|
||||
|
||||
#: cps/web.py:1668
|
||||
#: cps/web.py:2106
|
||||
msgid "Calibre-web configuration updated"
|
||||
msgstr "Calibre-web配置已更新"
|
||||
|
||||
#: cps/web.py:1675 cps/web.py:1681 cps/web.py:1694
|
||||
#: cps/web.py:2113 cps/web.py:2119 cps/web.py:2133
|
||||
msgid "Basic Configuration"
|
||||
msgstr "基本配置"
|
||||
|
||||
#: cps/web.py:1679
|
||||
#: cps/web.py:2117
|
||||
msgid "DB location is not valid, please enter correct path"
|
||||
msgstr "DB位置无效,请输入正确路径"
|
||||
|
||||
#: cps/templates/admin.html:34 cps/web.py:1715 cps/web.py:1761
|
||||
#: cps/templates/admin.html:34 cps/web.py:2154 cps/web.py:2202
|
||||
msgid "Add new user"
|
||||
msgstr "添加新用户"
|
||||
|
||||
#: cps/web.py:1753
|
||||
#: cps/web.py:2194
|
||||
#, python-format
|
||||
msgid "User '%(user)s' created"
|
||||
msgstr "用户 '%(user)s' 已被创建"
|
||||
|
||||
#: cps/web.py:1757
|
||||
#: cps/web.py:2198
|
||||
msgid "Found an existing account for this email address or nickname."
|
||||
msgstr "已找到使用此邮箱或昵称的账号。"
|
||||
|
||||
#: cps/web.py:1779
|
||||
#: cps/web.py:2220
|
||||
msgid "Mail settings updated"
|
||||
msgstr "邮箱设置已更新"
|
||||
|
||||
#: cps/web.py:1785
|
||||
#: cps/web.py:2227
|
||||
#, python-format
|
||||
msgid "Test E-Mail successfully send to %(kindlemail)s"
|
||||
msgstr "测试邮件已成功发送到 %(kindlemail)s"
|
||||
|
||||
#: cps/web.py:1788
|
||||
#: cps/web.py:2230
|
||||
#, python-format
|
||||
msgid "There was an error sending the Test E-Mail: %(res)s"
|
||||
msgstr "发送测试邮件时发生错误: %(res)s"
|
||||
|
||||
#: cps/web.py:1789
|
||||
#: cps/web.py:2234
|
||||
msgid "E-Mail settings updated"
|
||||
msgstr "E-Mail 设置已更新"
|
||||
|
||||
#: cps/web.py:2235
|
||||
msgid "Edit mail settings"
|
||||
msgstr "编辑邮箱设置"
|
||||
|
||||
#: cps/web.py:1817
|
||||
#: cps/web.py:2263
|
||||
#, python-format
|
||||
msgid "User '%(nick)s' deleted"
|
||||
msgstr "用户 '%(nick)s' 已被删除"
|
||||
|
||||
#: cps/web.py:1898
|
||||
#: cps/web.py:2349
|
||||
#, python-format
|
||||
msgid "User '%(nick)s' updated"
|
||||
msgstr "用户 '%(nick)s' 已被更新"
|
||||
|
||||
#: cps/web.py:1901
|
||||
#: cps/web.py:2352
|
||||
msgid "An unknown error occured."
|
||||
msgstr "发生未知错误。"
|
||||
|
||||
#: cps/web.py:1904
|
||||
#: cps/web.py:2355
|
||||
#, python-format
|
||||
msgid "Edit User %(nick)s"
|
||||
msgstr "编辑用户 %(nick)s"
|
||||
|
||||
#: cps/web.py:2110 cps/web.py:2113 cps/web.py:2188
|
||||
#: cps/web.py:2574 cps/web.py:2577 cps/web.py:2689
|
||||
msgid "edit metadata"
|
||||
msgstr "编辑元数据"
|
||||
|
||||
#: cps/web.py:2145
|
||||
#: cps/web.py:2598
|
||||
#, python-format
|
||||
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
|
||||
msgstr "不能上传后缀为 \"%s\" 的文件到此服务器"
|
||||
|
||||
#: cps/web.py:2604
|
||||
msgid "File to be uploaded must have an extension"
|
||||
msgstr "要上传的文件必须有一个后缀"
|
||||
|
||||
#: cps/web.py:2621
|
||||
#, python-format
|
||||
msgid "Failed to create path %s (Permission denied)."
|
||||
msgstr "创建路径 %s 失败(权限拒绝)。"
|
||||
|
||||
#: cps/web.py:2150
|
||||
#: cps/web.py:2626
|
||||
#, python-format
|
||||
msgid "Failed to store file %s (Permission denied)."
|
||||
msgstr "存储文件 %s 失败(权限拒绝)。"
|
||||
|
||||
#: cps/web.py:2155
|
||||
#: cps/web.py:2631
|
||||
#, python-format
|
||||
msgid "Failed to delete file %s (Permission denied)."
|
||||
msgstr "删除文件 %s 失败(权限拒绝)。"
|
||||
|
@ -389,7 +402,7 @@ msgstr ""
|
|||
msgid "Admin"
|
||||
msgstr "管理"
|
||||
|
||||
#: cps/templates/admin.html:13 cps/templates/detail.html:117
|
||||
#: cps/templates/admin.html:13 cps/templates/detail.html:134
|
||||
msgid "Download"
|
||||
msgstr "下载"
|
||||
|
||||
|
@ -445,7 +458,7 @@ msgstr "配置"
|
|||
msgid "Calibre DB dir"
|
||||
msgstr "Calibre DB目录"
|
||||
|
||||
#: cps/templates/admin.html:61 cps/templates/config_edit.html:32
|
||||
#: cps/templates/admin.html:61 cps/templates/config_edit.html:76
|
||||
msgid "Log Level"
|
||||
msgstr "日志级别"
|
||||
|
||||
|
@ -453,7 +466,7 @@ msgstr "日志级别"
|
|||
msgid "Port"
|
||||
msgstr "端口"
|
||||
|
||||
#: cps/templates/admin.html:63 cps/templates/config_edit.html:19
|
||||
#: cps/templates/admin.html:63 cps/templates/config_edit.html:60
|
||||
msgid "Books per page"
|
||||
msgstr "每页书籍数"
|
||||
|
||||
|
@ -482,42 +495,46 @@ msgid "Newest commit timestamp"
|
|||
msgstr "最新提交时间戳"
|
||||
|
||||
#: cps/templates/admin.html:83
|
||||
msgid "Reconnect to Calibre DB"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:84
|
||||
msgid "Restart Calibre-web"
|
||||
msgstr "重启 Calibre-web"
|
||||
|
||||
#: cps/templates/admin.html:84
|
||||
#: cps/templates/admin.html:85
|
||||
msgid "Stop Calibre-web"
|
||||
msgstr "停止 Calibre-web"
|
||||
|
||||
#: cps/templates/admin.html:85
|
||||
#: cps/templates/admin.html:86
|
||||
msgid "Check for update"
|
||||
msgstr "检查更新"
|
||||
|
||||
#: cps/templates/admin.html:86
|
||||
#: cps/templates/admin.html:87
|
||||
msgid "Perform Update"
|
||||
msgstr "执行更新"
|
||||
|
||||
#: cps/templates/admin.html:96
|
||||
#: cps/templates/admin.html:97
|
||||
msgid "Do you really want to restart Calibre-web?"
|
||||
msgstr "您确定要重启 Calibre-web 吗?"
|
||||
|
||||
#: cps/templates/admin.html:101 cps/templates/admin.html:115
|
||||
#: cps/templates/admin.html:136
|
||||
#: cps/templates/admin.html:102 cps/templates/admin.html:116
|
||||
#: cps/templates/admin.html:137
|
||||
msgid "Ok"
|
||||
msgstr "确定"
|
||||
|
||||
#: cps/templates/admin.html:102 cps/templates/admin.html:116
|
||||
#: cps/templates/book_edit.html:108 cps/templates/config_edit.html:75
|
||||
#: cps/templates/admin.html:103 cps/templates/admin.html:117
|
||||
#: cps/templates/book_edit.html:109 cps/templates/config_edit.html:119
|
||||
#: cps/templates/email_edit.html:36 cps/templates/shelf_edit.html:17
|
||||
#: cps/templates/shelf_order.html:12 cps/templates/user_edit.html:111
|
||||
#: cps/templates/shelf_order.html:12 cps/templates/user_edit.html:120
|
||||
msgid "Back"
|
||||
msgstr "后退"
|
||||
|
||||
#: cps/templates/admin.html:114
|
||||
#: cps/templates/admin.html:115
|
||||
msgid "Do you really want to stop Calibre-web?"
|
||||
msgstr "您确定要关闭 Calibre-web 吗?"
|
||||
|
||||
#: cps/templates/admin.html:127
|
||||
#: cps/templates/admin.html:128
|
||||
msgid "Updating, please do not reload page"
|
||||
msgstr "正在更新,请不要刷新页面"
|
||||
|
||||
|
@ -525,20 +542,21 @@ msgstr "正在更新,请不要刷新页面"
|
|||
msgid "Book Title"
|
||||
msgstr "书名"
|
||||
|
||||
#: cps/templates/book_edit.html:20 cps/templates/search_form.html:10
|
||||
#: cps/templates/book_edit.html:20 cps/templates/book_edit.html:145
|
||||
#: cps/templates/search_form.html:10
|
||||
msgid "Author"
|
||||
msgstr "作者"
|
||||
|
||||
#: cps/templates/book_edit.html:24
|
||||
#: cps/templates/book_edit.html:24 cps/templates/book_edit.html:147
|
||||
msgid "Description"
|
||||
msgstr "简介"
|
||||
|
||||
#: cps/templates/book_edit.html:28 cps/templates/search_form.html:13
|
||||
#: cps/templates/book_edit.html:28 cps/templates/search_form.html:17
|
||||
msgid "Tags"
|
||||
msgstr "标签"
|
||||
|
||||
#: cps/templates/book_edit.html:33 cps/templates/layout.html:138
|
||||
#: cps/templates/search_form.html:33
|
||||
#: cps/templates/book_edit.html:33 cps/templates/layout.html:142
|
||||
#: cps/templates/search_form.html:37
|
||||
msgid "Series"
|
||||
msgstr "丛书"
|
||||
|
||||
|
@ -570,69 +588,142 @@ msgstr ""
|
|||
msgid "view book after edit"
|
||||
msgstr "编辑后查看书籍"
|
||||
|
||||
#: cps/templates/book_edit.html:107 cps/templates/config_edit.html:73
|
||||
#: cps/templates/login.html:19 cps/templates/search_form.html:75
|
||||
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:109
|
||||
#: cps/templates/book_edit.html:107 cps/templates/book_edit.html:118
|
||||
msgid "Get metadata"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:108 cps/templates/config_edit.html:117
|
||||
#: cps/templates/login.html:19 cps/templates/search_form.html:79
|
||||
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:118
|
||||
msgid "Submit"
|
||||
msgstr "提交"
|
||||
|
||||
#: cps/templates/book_edit.html:121
|
||||
msgid "Keyword"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:122
|
||||
msgid " Search keyword "
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:124 cps/templates/layout.html:60
|
||||
msgid "Go!"
|
||||
msgstr "走起!"
|
||||
|
||||
#: cps/templates/book_edit.html:125
|
||||
msgid "Click the cover to load metadata to the form"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:129 cps/templates/book_edit.html:142
|
||||
msgid "Loading..."
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:132
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:143
|
||||
msgid "Search error!"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:144
|
||||
msgid "No Result! Please try anonther keyword."
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:146 cps/templates/detail.html:76
|
||||
#: cps/templates/search_form.html:14
|
||||
msgid "Publisher"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:148
|
||||
msgid "Source"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:7
|
||||
msgid "Location of Calibre database"
|
||||
msgstr "Calibre 数据库位置"
|
||||
|
||||
#: cps/templates/config_edit.html:11
|
||||
#: cps/templates/config_edit.html:13
|
||||
msgid "Use google drive?"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:17
|
||||
msgid "Client id"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:21
|
||||
msgid "Client secret"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:25
|
||||
msgid "Calibre Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:29
|
||||
msgid "Google drive Calibre folder"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:38
|
||||
msgid "Metadata Watch Channel ID"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:52
|
||||
msgid "Server Port"
|
||||
msgstr "服务器端口"
|
||||
|
||||
#: cps/templates/config_edit.html:15 cps/templates/shelf_edit.html:7
|
||||
#: cps/templates/config_edit.html:56 cps/templates/shelf_edit.html:7
|
||||
msgid "Title"
|
||||
msgstr "标题"
|
||||
|
||||
#: cps/templates/config_edit.html:23
|
||||
#: cps/templates/config_edit.html:64
|
||||
msgid "No. of random books to show"
|
||||
msgstr "随机书籍显示数量"
|
||||
|
||||
#: cps/templates/config_edit.html:28
|
||||
#: cps/templates/config_edit.html:68
|
||||
msgid "Regular expression for ignoring columns"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:72
|
||||
msgid "Regular expression for title sorting"
|
||||
msgstr "标题排序的正则表达式"
|
||||
|
||||
#: cps/templates/config_edit.html:42
|
||||
#: cps/templates/config_edit.html:86
|
||||
msgid "Enable uploading"
|
||||
msgstr "启用上传"
|
||||
|
||||
#: cps/templates/config_edit.html:46
|
||||
#: cps/templates/config_edit.html:90
|
||||
msgid "Enable anonymous browsing"
|
||||
msgstr "启用匿名浏览"
|
||||
|
||||
#: cps/templates/config_edit.html:50
|
||||
#: cps/templates/config_edit.html:94
|
||||
msgid "Enable public registration"
|
||||
msgstr "启用注册"
|
||||
|
||||
#: cps/templates/config_edit.html:52
|
||||
#: cps/templates/config_edit.html:96
|
||||
msgid "Default Settings for new users"
|
||||
msgstr "新用户默认设置"
|
||||
|
||||
#: cps/templates/config_edit.html:55 cps/templates/user_edit.html:80
|
||||
#: cps/templates/config_edit.html:99 cps/templates/user_edit.html:87
|
||||
msgid "Admin user"
|
||||
msgstr "管理用户"
|
||||
|
||||
#: cps/templates/config_edit.html:59 cps/templates/user_edit.html:85
|
||||
#: cps/templates/config_edit.html:103 cps/templates/user_edit.html:92
|
||||
msgid "Allow Downloads"
|
||||
msgstr "允许下载"
|
||||
|
||||
#: cps/templates/config_edit.html:63 cps/templates/user_edit.html:89
|
||||
#: cps/templates/config_edit.html:107 cps/templates/user_edit.html:96
|
||||
msgid "Allow Uploads"
|
||||
msgstr "允许上传"
|
||||
|
||||
#: cps/templates/config_edit.html:67 cps/templates/user_edit.html:93
|
||||
#: cps/templates/config_edit.html:111 cps/templates/user_edit.html:100
|
||||
msgid "Allow Edit"
|
||||
msgstr "允许编辑"
|
||||
|
||||
#: cps/templates/config_edit.html:71 cps/templates/user_edit.html:98
|
||||
#: cps/templates/config_edit.html:115 cps/templates/user_edit.html:105
|
||||
msgid "Allow Changing Password"
|
||||
msgstr "允许修改密码"
|
||||
|
||||
#: cps/templates/config_edit.html:78 cps/templates/layout.html:93
|
||||
#: cps/templates/config_edit.html:122 cps/templates/layout.html:93
|
||||
#: cps/templates/login.html:4
|
||||
msgid "Login"
|
||||
msgstr "登录"
|
||||
|
@ -649,23 +740,27 @@ msgstr ""
|
|||
msgid "language"
|
||||
msgstr "语言"
|
||||
|
||||
#: cps/templates/detail.html:74
|
||||
#: cps/templates/detail.html:81
|
||||
msgid "Publishing date"
|
||||
msgstr "出版日期"
|
||||
|
||||
#: cps/templates/detail.html:106
|
||||
#: cps/templates/detail.html:115
|
||||
msgid "Read"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/detail.html:123
|
||||
msgid "Description:"
|
||||
msgstr "简介:"
|
||||
|
||||
#: cps/templates/detail.html:134
|
||||
#: cps/templates/detail.html:151
|
||||
msgid "Read in browser"
|
||||
msgstr "在浏览器中阅读"
|
||||
|
||||
#: cps/templates/detail.html:154
|
||||
#: cps/templates/detail.html:171
|
||||
msgid "Add to shelf"
|
||||
msgstr "添加到书架"
|
||||
|
||||
#: cps/templates/detail.html:194
|
||||
#: cps/templates/detail.html:211
|
||||
msgid "Edit metadata"
|
||||
msgstr "编辑元数据"
|
||||
|
||||
|
@ -745,19 +840,29 @@ msgstr "最新书籍"
|
|||
msgid "Show Random Books"
|
||||
msgstr "显示随机书籍"
|
||||
|
||||
#: cps/templates/index.xml:43 cps/templates/layout.html:140
|
||||
#: cps/templates/index.xml:43 cps/templates/index.xml:47
|
||||
#: cps/templates/layout.html:132
|
||||
msgid "Read Books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:50 cps/templates/index.xml:54
|
||||
#: cps/templates/layout.html:133
|
||||
msgid "Unread Books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:57 cps/templates/layout.html:144
|
||||
msgid "Authors"
|
||||
msgstr "作者"
|
||||
|
||||
#: cps/templates/index.xml:47
|
||||
#: cps/templates/index.xml:61
|
||||
msgid "Books ordered by Author"
|
||||
msgstr "书籍按作者排序"
|
||||
|
||||
#: cps/templates/index.xml:54
|
||||
#: cps/templates/index.xml:68
|
||||
msgid "Books ordered by category"
|
||||
msgstr "书籍按分类排序"
|
||||
|
||||
#: cps/templates/index.xml:61
|
||||
#: cps/templates/index.xml:75
|
||||
msgid "Books ordered by series"
|
||||
msgstr "书籍按丛书排序"
|
||||
|
||||
|
@ -765,10 +870,6 @@ msgstr "书籍按丛书排序"
|
|||
msgid "Toggle navigation"
|
||||
msgstr "切换导航"
|
||||
|
||||
#: cps/templates/layout.html:60
|
||||
msgid "Go!"
|
||||
msgstr "走起!"
|
||||
|
||||
#: cps/templates/layout.html:68
|
||||
msgid "Advanced Search"
|
||||
msgstr "高级搜索"
|
||||
|
@ -785,31 +886,31 @@ msgstr "注册"
|
|||
msgid "Browse"
|
||||
msgstr "浏览"
|
||||
|
||||
#: cps/templates/layout.html:132
|
||||
#: cps/templates/layout.html:136
|
||||
msgid "Discover"
|
||||
msgstr "发现"
|
||||
|
||||
#: cps/templates/layout.html:135
|
||||
#: cps/templates/layout.html:139
|
||||
msgid "Categories"
|
||||
msgstr "分类"
|
||||
|
||||
#: cps/templates/layout.html:142 cps/templates/search_form.html:54
|
||||
#: cps/templates/layout.html:146 cps/templates/search_form.html:58
|
||||
msgid "Languages"
|
||||
msgstr "语言"
|
||||
|
||||
#: cps/templates/layout.html:145
|
||||
#: cps/templates/layout.html:149
|
||||
msgid "Public Shelves"
|
||||
msgstr "公开书架"
|
||||
|
||||
#: cps/templates/layout.html:149
|
||||
#: cps/templates/layout.html:153
|
||||
msgid "Your Shelves"
|
||||
msgstr "您的书架"
|
||||
|
||||
#: cps/templates/layout.html:154
|
||||
#: cps/templates/layout.html:158
|
||||
msgid "Create a Shelf"
|
||||
msgstr "创建书架"
|
||||
|
||||
#: cps/templates/layout.html:155
|
||||
#: cps/templates/layout.html:159
|
||||
msgid "About"
|
||||
msgstr "关于"
|
||||
|
||||
|
@ -875,15 +976,15 @@ msgstr "请尝试别的关键字"
|
|||
msgid "Results for:"
|
||||
msgstr "结果:"
|
||||
|
||||
#: cps/templates/search_form.html:23
|
||||
#: cps/templates/search_form.html:27
|
||||
msgid "Exclude Tags"
|
||||
msgstr "排除标签"
|
||||
|
||||
#: cps/templates/search_form.html:43
|
||||
#: cps/templates/search_form.html:47
|
||||
msgid "Exclude Series"
|
||||
msgstr "排除丛书"
|
||||
|
||||
#: cps/templates/search_form.html:64
|
||||
#: cps/templates/search_form.html:68
|
||||
msgid "Exclude Languages"
|
||||
msgstr "排除语言"
|
||||
|
||||
|
@ -908,37 +1009,37 @@ msgid "Drag 'n drop to rearrange order"
|
|||
msgstr "拖拽以重新排序"
|
||||
|
||||
#: cps/templates/stats.html:3
|
||||
msgid "Linked libraries"
|
||||
msgstr "链接库"
|
||||
|
||||
#: cps/templates/stats.html:8
|
||||
msgid "Program library"
|
||||
msgstr "程序库"
|
||||
|
||||
#: cps/templates/stats.html:9
|
||||
msgid "Installed Version"
|
||||
msgstr "已安装版本"
|
||||
|
||||
#: cps/templates/stats.html:32
|
||||
msgid "Calibre library statistics"
|
||||
msgstr "Calibre书库统计"
|
||||
|
||||
#: cps/templates/stats.html:37
|
||||
#: cps/templates/stats.html:8
|
||||
msgid "Books in this Library"
|
||||
msgstr "本书在此书库"
|
||||
|
||||
#: cps/templates/stats.html:41
|
||||
#: cps/templates/stats.html:12
|
||||
msgid "Authors in this Library"
|
||||
msgstr "个作者在此书库"
|
||||
|
||||
#: cps/templates/stats.html:45
|
||||
#: cps/templates/stats.html:16
|
||||
msgid "Categories in this Library"
|
||||
msgstr "个分类在此书库"
|
||||
|
||||
#: cps/templates/stats.html:49
|
||||
#: cps/templates/stats.html:20
|
||||
msgid "Series in this Library"
|
||||
msgstr "个丛书在此书库"
|
||||
|
||||
#: cps/templates/stats.html:24
|
||||
msgid "Linked libraries"
|
||||
msgstr "链接库"
|
||||
|
||||
#: cps/templates/stats.html:28
|
||||
msgid "Program library"
|
||||
msgstr "程序库"
|
||||
|
||||
#: cps/templates/stats.html:29
|
||||
msgid "Installed Version"
|
||||
msgstr "已安装版本"
|
||||
|
||||
#: cps/templates/user_edit.html:23
|
||||
msgid "Kindle E-Mail"
|
||||
msgstr ""
|
||||
|
@ -951,43 +1052,47 @@ msgstr "按语言显示书籍"
|
|||
msgid "Show all"
|
||||
msgstr "显示全部"
|
||||
|
||||
#: cps/templates/user_edit.html:45
|
||||
#: cps/templates/user_edit.html:47
|
||||
msgid "Show random books"
|
||||
msgstr "显示随机书籍"
|
||||
|
||||
#: cps/templates/user_edit.html:49
|
||||
#: cps/templates/user_edit.html:51
|
||||
msgid "Show hot books"
|
||||
msgstr "显示热门书籍"
|
||||
|
||||
#: cps/templates/user_edit.html:53
|
||||
#: cps/templates/user_edit.html:55
|
||||
msgid "Show best rated books"
|
||||
msgstr "显示最高评分书籍"
|
||||
|
||||
#: cps/templates/user_edit.html:57
|
||||
#: cps/templates/user_edit.html:59
|
||||
msgid "Show language selection"
|
||||
msgstr "显示语言选择"
|
||||
|
||||
#: cps/templates/user_edit.html:61
|
||||
#: cps/templates/user_edit.html:63
|
||||
msgid "Show series selection"
|
||||
msgstr "显示丛书选择"
|
||||
|
||||
#: cps/templates/user_edit.html:65
|
||||
#: cps/templates/user_edit.html:67
|
||||
msgid "Show category selection"
|
||||
msgstr "显示分类选择"
|
||||
|
||||
#: cps/templates/user_edit.html:69
|
||||
#: cps/templates/user_edit.html:71
|
||||
msgid "Show author selection"
|
||||
msgstr "显示作者选择"
|
||||
|
||||
#: cps/templates/user_edit.html:73
|
||||
#: cps/templates/user_edit.html:75
|
||||
msgid "Show read and unread"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:79
|
||||
msgid "Show random books in detail view"
|
||||
msgstr "在详情页显示随机书籍"
|
||||
|
||||
#: cps/templates/user_edit.html:105
|
||||
#: cps/templates/user_edit.html:112
|
||||
msgid "Delete this user"
|
||||
msgstr "删除此用户"
|
||||
|
||||
#: cps/templates/user_edit.html:116
|
||||
#: cps/templates/user_edit.html:127
|
||||
msgid "Recent Downloads"
|
||||
msgstr "最近下载"
|
||||
|
||||
|
|
81
cps/ub.py
81
cps/ub.py
|
@ -10,6 +10,8 @@ import os
|
|||
import logging
|
||||
from werkzeug.security import generate_password_hash
|
||||
from flask_babel import gettext as _
|
||||
import json
|
||||
#from builtins import str
|
||||
|
||||
dbpath = os.path.join(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + os.sep + ".." + os.sep), "app.db")
|
||||
engine = create_engine('sqlite:///{0}'.format(dbpath), echo=False)
|
||||
|
@ -22,6 +24,7 @@ ROLE_UPLOAD = 4
|
|||
ROLE_EDIT = 8
|
||||
ROLE_PASSWD = 16
|
||||
ROLE_ANONYMOUS = 32
|
||||
ROLE_EDIT_SHELFS = 64
|
||||
|
||||
DETAIL_RANDOM = 1
|
||||
SIDEBAR_LANGUAGE = 2
|
||||
|
@ -31,6 +34,7 @@ SIDEBAR_HOT = 16
|
|||
SIDEBAR_RANDOM = 32
|
||||
SIDEBAR_AUTHOR = 64
|
||||
SIDEBAR_BEST_RATED = 128
|
||||
SIDEBAR_READ_AND_UNREAD = 256
|
||||
|
||||
DEFAULT_PASS = "admin123"
|
||||
DEFAULT_PORT = int(os.environ.get("CALIBRE_PORT", 8083))
|
||||
|
@ -83,6 +87,12 @@ class UserBase:
|
|||
else:
|
||||
return False
|
||||
|
||||
def role_edit_shelfs(self):
|
||||
if self.role is not None:
|
||||
return True if self.role & ROLE_EDIT_SHELFS == ROLE_EDIT_SHELFS else False
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_active(self):
|
||||
return True
|
||||
|
||||
|
@ -90,7 +100,7 @@ class UserBase:
|
|||
return False
|
||||
|
||||
def get_id(self):
|
||||
return unicode(self.id)
|
||||
return str(self.id)
|
||||
|
||||
def filter_language(self):
|
||||
return self.default_language
|
||||
|
@ -137,6 +147,12 @@ class UserBase:
|
|||
else:
|
||||
return False
|
||||
|
||||
def show_read_and_unread(self):
|
||||
if self.sidebar_view is not None:
|
||||
return True if self.sidebar_view & SIDEBAR_READ_AND_UNREAD == SIDEBAR_READ_AND_UNREAD else False
|
||||
else:
|
||||
return False
|
||||
|
||||
def show_detail_random(self):
|
||||
if self.sidebar_view is not None:
|
||||
return True if self.sidebar_view & DETAIL_RANDOM == DETAIL_RANDOM else False
|
||||
|
@ -178,7 +194,6 @@ class Anonymous(AnonymousUserMixin, UserBase):
|
|||
self.role = data.role
|
||||
self.sidebar_view = data.sidebar_view
|
||||
self.default_language = data.default_language
|
||||
self.default_language = data.default_language
|
||||
self.locale = data.locale
|
||||
self.anon_browse = settings.config_anonbrowse
|
||||
|
||||
|
@ -217,6 +232,14 @@ class BookShelf(Base):
|
|||
def __repr__(self):
|
||||
return '<Book %r>' % self.id
|
||||
|
||||
class ReadBook(Base):
|
||||
__tablename__ = 'book_read_link'
|
||||
|
||||
id=Column(Integer, primary_key=True)
|
||||
book_id = Column(Integer, unique=False)
|
||||
user_id =Column(Integer, ForeignKey('user.id'), unique=False)
|
||||
is_read = Column(Boolean, unique=False)
|
||||
|
||||
|
||||
# Baseclass representing Downloads from calibre-web in app.db
|
||||
class Downloads(Base):
|
||||
|
@ -253,6 +276,14 @@ class Settings(Base):
|
|||
config_anonbrowse = Column(SmallInteger, default=0)
|
||||
config_public_reg = Column(SmallInteger, default=0)
|
||||
config_default_role = Column(SmallInteger, default=0)
|
||||
config_columns_to_ignore = Column(String)
|
||||
config_use_google_drive = Column(Boolean)
|
||||
config_google_drive_client_id = Column(String)
|
||||
config_google_drive_client_secret = Column(String)
|
||||
config_google_drive_folder = Column(String)
|
||||
config_google_drive_calibre_url_base = Column(String)
|
||||
config_google_drive_watch_changes_response = Column(String)
|
||||
config_columns_to_ignore = Column(String)
|
||||
|
||||
def __repr__(self):
|
||||
pass
|
||||
|
@ -279,7 +310,18 @@ class Config:
|
|||
self.config_anonbrowse = data.config_anonbrowse
|
||||
self.config_public_reg = data.config_public_reg
|
||||
self.config_default_role = data.config_default_role
|
||||
if self.config_calibre_dir is not None:
|
||||
self.config_columns_to_ignore = data.config_columns_to_ignore
|
||||
self.config_use_google_drive = data.config_use_google_drive
|
||||
self.config_google_drive_client_id = data.config_google_drive_client_id
|
||||
self.config_google_drive_client_secret = data.config_google_drive_client_secret
|
||||
self.config_google_drive_calibre_url_base = data.config_google_drive_calibre_url_base
|
||||
self.config_google_drive_folder = data.config_google_drive_folder
|
||||
if data.config_google_drive_watch_changes_response:
|
||||
self.config_google_drive_watch_changes_response = json.loads(data.config_google_drive_watch_changes_response)
|
||||
else:
|
||||
self.config_google_drive_watch_changes_response=None
|
||||
self.config_columns_to_ignore = data.config_columns_to_ignore
|
||||
if self.config_calibre_dir is not None and (not self.config_use_google_drive or os.path.exists(self.config_calibre_dir + '/metadata.db')):
|
||||
self.db_configured = True
|
||||
else:
|
||||
self.db_configured = False
|
||||
|
@ -318,6 +360,12 @@ class Config:
|
|||
else:
|
||||
return False
|
||||
|
||||
def role_edit_shelfs(self):
|
||||
if self.config_default_role is not None:
|
||||
return True if self.config_default_role & ROLE_EDIT_SHELFS == ROLE_EDIT_SHELFS else False
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_Log_Level(self):
|
||||
ret_value=""
|
||||
if self.config_log_level == logging.INFO:
|
||||
|
@ -335,6 +383,9 @@ class Config:
|
|||
# everywhere to curent should work. Migration is done by checking if relevant coloums are existing, and than adding
|
||||
# rows with SQL commands
|
||||
def migrate_Database():
|
||||
if not engine.dialect.has_table(engine.connect(), "book_read_link"):
|
||||
ReadBook.__table__.create(bind = engine)
|
||||
|
||||
try:
|
||||
session.query(exists().where(User.locale)).scalar()
|
||||
session.commit()
|
||||
|
@ -360,6 +411,23 @@ def migrate_Database():
|
|||
conn.execute("ALTER TABLE Settings ADD column `config_anonbrowse` SmallInteger DEFAULT 0")
|
||||
conn.execute("ALTER TABLE Settings ADD column `config_public_reg` SmallInteger DEFAULT 0")
|
||||
session.commit()
|
||||
|
||||
try:
|
||||
session.query(exists().where(Settings.config_use_google_drive)).scalar()
|
||||
except exc.OperationalError:
|
||||
conn = engine.connect()
|
||||
conn.execute("ALTER TABLE Settings ADD column `config_use_google_drive` INTEGER DEFAULT 0")
|
||||
conn.execute("ALTER TABLE Settings ADD column `config_google_drive_client_id` String DEFAULT ''")
|
||||
conn.execute("ALTER TABLE Settings ADD column `config_google_drive_client_secret` String DEFAULT ''")
|
||||
conn.execute("ALTER TABLE Settings ADD column `config_google_drive_calibre_url_base` INTEGER DEFAULT 0")
|
||||
conn.execute("ALTER TABLE Settings ADD column `config_google_drive_folder` String DEFAULT ''")
|
||||
conn.execute("ALTER TABLE Settings ADD column `config_google_drive_watch_changes_response` String DEFAULT ''")
|
||||
try:
|
||||
session.query(exists().where(Settings.config_columns_to_ignore)).scalar()
|
||||
except exc.OperationalError:
|
||||
conn = engine.connect()
|
||||
conn.execute("ALTER TABLE Settings ADD column `config_columns_to_ignore` String DEFAULT ''")
|
||||
session.commit()
|
||||
try:
|
||||
session.query(exists().where(Settings.config_default_role)).scalar()
|
||||
session.commit()
|
||||
|
@ -438,7 +506,7 @@ def create_anonymous_user():
|
|||
session.add(user)
|
||||
try:
|
||||
session.commit()
|
||||
except:
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
pass
|
||||
|
||||
|
@ -449,14 +517,15 @@ def create_admin_user():
|
|||
user.nickname = "admin"
|
||||
user.role = ROLE_USER + ROLE_ADMIN + ROLE_DOWNLOAD + ROLE_UPLOAD + ROLE_EDIT + ROLE_PASSWD
|
||||
user.sidebar_view = DETAIL_RANDOM + SIDEBAR_LANGUAGE + SIDEBAR_SERIES + SIDEBAR_CATEGORY + SIDEBAR_HOT + \
|
||||
SIDEBAR_RANDOM + SIDEBAR_AUTHOR + SIDEBAR_BEST_RATED
|
||||
SIDEBAR_RANDOM + SIDEBAR_AUTHOR + SIDEBAR_BEST_RATED + SIDEBAR_READ_AND_UNREAD
|
||||
|
||||
|
||||
user.password = generate_password_hash(DEFAULT_PASS)
|
||||
|
||||
session.add(user)
|
||||
try:
|
||||
session.commit()
|
||||
except:
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
pass
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import hashlib
|
|||
from collections import namedtuple
|
||||
import book_formats
|
||||
|
||||
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, series_id')
|
||||
BookMeta = namedtuple('BookMeta', 'file_path, extension, title, author, cover, description, tags, series, series_id, languages')
|
||||
|
||||
"""
|
||||
:rtype: BookMeta
|
||||
|
|
639
cps/web.py
639
cps/web.py
File diff suppressed because it is too large
Load Diff
14
gdrive_template.yaml
Normal file
14
gdrive_template.yaml
Normal file
|
@ -0,0 +1,14 @@
|
|||
client_config_backend: settings
|
||||
client_config:
|
||||
client_id: %(client_id)s
|
||||
client_secret: %(client_secret)s
|
||||
redirect_uri: %(redirect_uri)s
|
||||
|
||||
save_credentials: True
|
||||
save_credentials_backend: file
|
||||
save_credentials_file: gdrive_credentials
|
||||
|
||||
get_refresh_token: True
|
||||
|
||||
oauth_scope:
|
||||
- https://www.googleapis.com/auth/drive
|
3
getVendor.sh
Normal file
3
getVendor.sh
Normal file
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
pip install --target ./vendor -r requirements.txt
|
451
messages.pot
451
messages.pot
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PROJECT VERSION\n"
|
||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||
"POT-Creation-Date: 2017-02-20 19:47+0100\n"
|
||||
"POT-Creation-Date: 2017-03-19 19:20+0100\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -17,341 +17,354 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.3.4\n"
|
||||
|
||||
#: cps/book_formats.py:111 cps/book_formats.py:115 cps/web.py:1030
|
||||
#: cps/book_formats.py:113 cps/book_formats.py:117 cps/web.py:1244
|
||||
msgid "not installed"
|
||||
msgstr ""
|
||||
|
||||
#: cps/helper.py:150
|
||||
#: cps/helper.py:164
|
||||
#, python-format
|
||||
msgid "Failed to send mail: %s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/helper.py:157
|
||||
#: cps/helper.py:171
|
||||
msgid "Calibre-web test email"
|
||||
msgstr ""
|
||||
|
||||
#: cps/helper.py:158 cps/helper.py:168
|
||||
#: cps/helper.py:172 cps/helper.py:184
|
||||
msgid "This email has been sent via calibre web."
|
||||
msgstr ""
|
||||
|
||||
#: cps/helper.py:167 cps/templates/detail.html:130
|
||||
#: cps/helper.py:181 cps/templates/detail.html:146
|
||||
msgid "Send to Kindle"
|
||||
msgstr ""
|
||||
|
||||
#: cps/helper.py:185 cps/helper.py:200
|
||||
#: cps/helper.py:201 cps/helper.py:216
|
||||
msgid "Could not find any formats suitable for sending by email"
|
||||
msgstr ""
|
||||
|
||||
#: cps/helper.py:194
|
||||
#: cps/helper.py:210
|
||||
msgid "Could not convert epub to mobi"
|
||||
msgstr ""
|
||||
|
||||
#: cps/ub.py:434
|
||||
#: cps/ub.py:488
|
||||
msgid "Guest"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:734
|
||||
#: cps/web.py:904
|
||||
msgid "Requesting update package"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:735
|
||||
#: cps/web.py:905
|
||||
msgid "Downloading update package"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:736
|
||||
#: cps/web.py:906
|
||||
msgid "Unzipping update package"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:737
|
||||
#: cps/web.py:907
|
||||
msgid "Files are replaced"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:738
|
||||
#: cps/web.py:908
|
||||
msgid "Database connections are closed"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:739
|
||||
#: cps/web.py:909
|
||||
msgid "Server is stopped"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:740
|
||||
#: cps/web.py:910
|
||||
msgid "Update finished, please press okay and reload page"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:810
|
||||
#: cps/web.py:983
|
||||
msgid "Latest Books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:835
|
||||
#: cps/web.py:1014
|
||||
msgid "Hot Books (most downloaded)"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:845
|
||||
#: cps/web.py:1024
|
||||
msgid "Best rated books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:36 cps/web.py:854
|
||||
#: cps/templates/index.xml:36 cps/web.py:1033
|
||||
msgid "Random Books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:867
|
||||
#: cps/web.py:1046
|
||||
msgid "Author list"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:878
|
||||
#: cps/web.py:1057
|
||||
#, python-format
|
||||
msgid "Author: %(name)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:880 cps/web.py:908 cps/web.py:1007 cps/web.py:1235
|
||||
#: cps/web.py:2115
|
||||
#: cps/web.py:1059 cps/web.py:1087 cps/web.py:1221 cps/web.py:1626
|
||||
#: cps/web.py:2579
|
||||
msgid "Error opening eBook. File does not exist or file is not accessible:"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:57 cps/web.py:894
|
||||
#: cps/templates/index.xml:71 cps/web.py:1073
|
||||
msgid "Series list"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:906
|
||||
#: cps/web.py:1085
|
||||
#, python-format
|
||||
msgid "Series: %(serie)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:939
|
||||
#: cps/web.py:1118
|
||||
msgid "Available languages"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:954
|
||||
#: cps/web.py:1133
|
||||
#, python-format
|
||||
msgid "Language: %(name)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:50 cps/web.py:967
|
||||
#: cps/templates/index.xml:64 cps/web.py:1146
|
||||
msgid "Category list"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:979
|
||||
#: cps/web.py:1158
|
||||
#, python-format
|
||||
msgid "Category: %(name)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1040
|
||||
#: cps/web.py:1267
|
||||
msgid "Statistics"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1061
|
||||
#: cps/web.py:1375
|
||||
msgid "Server restarted, please reload page"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1063
|
||||
#: cps/web.py:1377
|
||||
msgid "Performing shutdown of server, please close window"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1073
|
||||
#: cps/web.py:1392
|
||||
msgid "Update done"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1147 cps/web.py:1160
|
||||
#: cps/web.py:1470 cps/web.py:1483
|
||||
msgid "search"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1211 cps/web.py:1218 cps/web.py:1225 cps/web.py:1232
|
||||
#: cps/web.py:1602 cps/web.py:1609 cps/web.py:1616 cps/web.py:1623
|
||||
msgid "Read a Book"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1276 cps/web.py:1713
|
||||
#: cps/web.py:1676 cps/web.py:2152
|
||||
msgid "Please fill out all fields!"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1277 cps/web.py:1293 cps/web.py:1298 cps/web.py:1300
|
||||
#: cps/web.py:1677 cps/web.py:1693 cps/web.py:1698 cps/web.py:1700
|
||||
msgid "register"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1292
|
||||
#: cps/web.py:1692
|
||||
msgid "An unknown error occured. Please try again later."
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1297
|
||||
#: cps/web.py:1697
|
||||
msgid "This username or email address is already in use."
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1315
|
||||
#: cps/web.py:1715
|
||||
#, python-format
|
||||
msgid "you are now logged in as: '%(nickname)s'"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1320
|
||||
#: cps/web.py:1720
|
||||
msgid "Wrong Username or Password"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1322
|
||||
#: cps/web.py:1722
|
||||
msgid "login"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1339
|
||||
#: cps/web.py:1739
|
||||
msgid "Please configure the SMTP mail settings first..."
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1343
|
||||
#: cps/web.py:1743
|
||||
#, python-format
|
||||
msgid "Book successfully send to %(kindlemail)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1347
|
||||
#: cps/web.py:1747
|
||||
#, python-format
|
||||
msgid "There was an error sending this book: %(res)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1349
|
||||
#: cps/web.py:1749 cps/web.py:2232
|
||||
msgid "Please configure your kindle email address first..."
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1369
|
||||
#: cps/web.py:1774
|
||||
#, python-format
|
||||
msgid "Book has been added to shelf: %(sname)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1390
|
||||
#: cps/web.py:1793
|
||||
#, python-format
|
||||
msgid "Book has been removed from shelf: %(sname)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1409 cps/web.py:1433
|
||||
#: cps/web.py:1812 cps/web.py:1836
|
||||
#, python-format
|
||||
msgid "A shelf with the name '%(title)s' already exists."
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1414
|
||||
#: cps/web.py:1817
|
||||
#, python-format
|
||||
msgid "Shelf %(title)s created"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1416 cps/web.py:1444
|
||||
#: cps/web.py:1819 cps/web.py:1847
|
||||
msgid "There was an error"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1417 cps/web.py:1419
|
||||
#: cps/web.py:1820 cps/web.py:1822
|
||||
msgid "create a shelf"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1442
|
||||
#: cps/web.py:1845
|
||||
#, python-format
|
||||
msgid "Shelf %(title)s changed"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1445 cps/web.py:1447
|
||||
#: cps/web.py:1848 cps/web.py:1850
|
||||
msgid "Edit a shelf"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1465
|
||||
#: cps/web.py:1868
|
||||
#, python-format
|
||||
msgid "successfully deleted shelf %(name)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1487
|
||||
#: cps/web.py:1890
|
||||
#, python-format
|
||||
msgid "Shelf: '%(name)s'"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1518
|
||||
#: cps/web.py:1921
|
||||
#, python-format
|
||||
msgid "Change order of Shelf: '%(name)s'"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1580
|
||||
#: cps/web.py:1985
|
||||
msgid "Found an existing account for this email address."
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1582 cps/web.py:1586
|
||||
#: cps/web.py:1987 cps/web.py:1991
|
||||
#, python-format
|
||||
msgid "%(name)s's profile"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1583
|
||||
#: cps/web.py:1988
|
||||
msgid "Profile updated"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1597
|
||||
#: cps/web.py:2002
|
||||
msgid "Admin page"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1668
|
||||
#: cps/web.py:2106
|
||||
msgid "Calibre-web configuration updated"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1675 cps/web.py:1681 cps/web.py:1694
|
||||
#: cps/web.py:2113 cps/web.py:2119 cps/web.py:2133
|
||||
msgid "Basic Configuration"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1679
|
||||
#: cps/web.py:2117
|
||||
msgid "DB location is not valid, please enter correct path"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:34 cps/web.py:1715 cps/web.py:1761
|
||||
#: cps/templates/admin.html:34 cps/web.py:2154 cps/web.py:2202
|
||||
msgid "Add new user"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1753
|
||||
#: cps/web.py:2194
|
||||
#, python-format
|
||||
msgid "User '%(user)s' created"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1757
|
||||
#: cps/web.py:2198
|
||||
msgid "Found an existing account for this email address or nickname."
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1779
|
||||
#: cps/web.py:2220
|
||||
msgid "Mail settings updated"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1785
|
||||
#: cps/web.py:2227
|
||||
#, python-format
|
||||
msgid "Test E-Mail successfully send to %(kindlemail)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1788
|
||||
#: cps/web.py:2230
|
||||
#, python-format
|
||||
msgid "There was an error sending the Test E-Mail: %(res)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1789
|
||||
#: cps/web.py:2234
|
||||
msgid "E-Mail settings updated"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2235
|
||||
msgid "Edit mail settings"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1817
|
||||
#: cps/web.py:2263
|
||||
#, python-format
|
||||
msgid "User '%(nick)s' deleted"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1898
|
||||
#: cps/web.py:2349
|
||||
#, python-format
|
||||
msgid "User '%(nick)s' updated"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1901
|
||||
#: cps/web.py:2352
|
||||
msgid "An unknown error occured."
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:1904
|
||||
#: cps/web.py:2355
|
||||
#, python-format
|
||||
msgid "Edit User %(nick)s"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2110 cps/web.py:2113 cps/web.py:2188
|
||||
#: cps/web.py:2574 cps/web.py:2577 cps/web.py:2689
|
||||
msgid "edit metadata"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2145
|
||||
#: cps/web.py:2598
|
||||
#, python-format
|
||||
msgid "File extension \"%s\" is not allowed to be uploaded to this server"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2604
|
||||
msgid "File to be uploaded must have an extension"
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2621
|
||||
#, python-format
|
||||
msgid "Failed to create path %s (Permission denied)."
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2150
|
||||
#: cps/web.py:2626
|
||||
#, python-format
|
||||
msgid "Failed to store file %s (Permission denied)."
|
||||
msgstr ""
|
||||
|
||||
#: cps/web.py:2155
|
||||
#: cps/web.py:2631
|
||||
#, python-format
|
||||
msgid "Failed to delete file %s (Permission denied)."
|
||||
msgstr ""
|
||||
|
@ -380,7 +393,7 @@ msgstr ""
|
|||
msgid "Admin"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:13 cps/templates/detail.html:117
|
||||
#: cps/templates/admin.html:13 cps/templates/detail.html:134
|
||||
msgid "Download"
|
||||
msgstr ""
|
||||
|
||||
|
@ -436,7 +449,7 @@ msgstr ""
|
|||
msgid "Calibre DB dir"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:61 cps/templates/config_edit.html:32
|
||||
#: cps/templates/admin.html:61 cps/templates/config_edit.html:76
|
||||
msgid "Log Level"
|
||||
msgstr ""
|
||||
|
||||
|
@ -444,7 +457,7 @@ msgstr ""
|
|||
msgid "Port"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:63 cps/templates/config_edit.html:19
|
||||
#: cps/templates/admin.html:63 cps/templates/config_edit.html:60
|
||||
msgid "Books per page"
|
||||
msgstr ""
|
||||
|
||||
|
@ -473,42 +486,46 @@ msgid "Newest commit timestamp"
|
|||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:83
|
||||
msgid "Restart Calibre-web"
|
||||
msgid "Reconnect to Calibre DB"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:84
|
||||
msgid "Stop Calibre-web"
|
||||
msgid "Restart Calibre-web"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:85
|
||||
msgid "Check for update"
|
||||
msgid "Stop Calibre-web"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:86
|
||||
msgid "Check for update"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:87
|
||||
msgid "Perform Update"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:96
|
||||
#: cps/templates/admin.html:97
|
||||
msgid "Do you really want to restart Calibre-web?"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:101 cps/templates/admin.html:115
|
||||
#: cps/templates/admin.html:136
|
||||
#: cps/templates/admin.html:102 cps/templates/admin.html:116
|
||||
#: cps/templates/admin.html:137
|
||||
msgid "Ok"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:102 cps/templates/admin.html:116
|
||||
#: cps/templates/book_edit.html:108 cps/templates/config_edit.html:75
|
||||
#: cps/templates/admin.html:103 cps/templates/admin.html:117
|
||||
#: cps/templates/book_edit.html:109 cps/templates/config_edit.html:119
|
||||
#: cps/templates/email_edit.html:36 cps/templates/shelf_edit.html:17
|
||||
#: cps/templates/shelf_order.html:12 cps/templates/user_edit.html:111
|
||||
#: cps/templates/shelf_order.html:12 cps/templates/user_edit.html:120
|
||||
msgid "Back"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:114
|
||||
#: cps/templates/admin.html:115
|
||||
msgid "Do you really want to stop Calibre-web?"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/admin.html:127
|
||||
#: cps/templates/admin.html:128
|
||||
msgid "Updating, please do not reload page"
|
||||
msgstr ""
|
||||
|
||||
|
@ -516,20 +533,21 @@ msgstr ""
|
|||
msgid "Book Title"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:20 cps/templates/search_form.html:10
|
||||
#: cps/templates/book_edit.html:20 cps/templates/book_edit.html:145
|
||||
#: cps/templates/search_form.html:10
|
||||
msgid "Author"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:24
|
||||
#: cps/templates/book_edit.html:24 cps/templates/book_edit.html:147
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:28 cps/templates/search_form.html:13
|
||||
#: cps/templates/book_edit.html:28 cps/templates/search_form.html:17
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:33 cps/templates/layout.html:138
|
||||
#: cps/templates/search_form.html:33
|
||||
#: cps/templates/book_edit.html:33 cps/templates/layout.html:142
|
||||
#: cps/templates/search_form.html:37
|
||||
msgid "Series"
|
||||
msgstr ""
|
||||
|
||||
|
@ -561,69 +579,142 @@ msgstr ""
|
|||
msgid "view book after edit"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:107 cps/templates/config_edit.html:73
|
||||
#: cps/templates/login.html:19 cps/templates/search_form.html:75
|
||||
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:109
|
||||
#: cps/templates/book_edit.html:107 cps/templates/book_edit.html:118
|
||||
msgid "Get metadata"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:108 cps/templates/config_edit.html:117
|
||||
#: cps/templates/login.html:19 cps/templates/search_form.html:79
|
||||
#: cps/templates/shelf_edit.html:15 cps/templates/user_edit.html:118
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:121
|
||||
msgid "Keyword"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:122
|
||||
msgid " Search keyword "
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:124 cps/templates/layout.html:60
|
||||
msgid "Go!"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:125
|
||||
msgid "Click the cover to load metadata to the form"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:129 cps/templates/book_edit.html:142
|
||||
msgid "Loading..."
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:132
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:143
|
||||
msgid "Search error!"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:144
|
||||
msgid "No Result! Please try anonther keyword."
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:146 cps/templates/detail.html:76
|
||||
#: cps/templates/search_form.html:14
|
||||
msgid "Publisher"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/book_edit.html:148
|
||||
msgid "Source"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:7
|
||||
msgid "Location of Calibre database"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:11
|
||||
msgid "Server Port"
|
||||
#: cps/templates/config_edit.html:13
|
||||
msgid "Use google drive?"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:15 cps/templates/shelf_edit.html:7
|
||||
msgid "Title"
|
||||
#: cps/templates/config_edit.html:17
|
||||
msgid "Client id"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:23
|
||||
msgid "No. of random books to show"
|
||||
#: cps/templates/config_edit.html:21
|
||||
msgid "Client secret"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:28
|
||||
msgid "Regular expression for title sorting"
|
||||
#: cps/templates/config_edit.html:25
|
||||
msgid "Calibre Base URL"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:42
|
||||
msgid "Enable uploading"
|
||||
#: cps/templates/config_edit.html:29
|
||||
msgid "Google drive Calibre folder"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:46
|
||||
msgid "Enable anonymous browsing"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:50
|
||||
msgid "Enable public registration"
|
||||
#: cps/templates/config_edit.html:38
|
||||
msgid "Metadata Watch Channel ID"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:52
|
||||
msgid "Server Port"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:56 cps/templates/shelf_edit.html:7
|
||||
msgid "Title"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:64
|
||||
msgid "No. of random books to show"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:68
|
||||
msgid "Regular expression for ignoring columns"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:72
|
||||
msgid "Regular expression for title sorting"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:86
|
||||
msgid "Enable uploading"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:90
|
||||
msgid "Enable anonymous browsing"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:94
|
||||
msgid "Enable public registration"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:96
|
||||
msgid "Default Settings for new users"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:55 cps/templates/user_edit.html:80
|
||||
#: cps/templates/config_edit.html:99 cps/templates/user_edit.html:87
|
||||
msgid "Admin user"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:59 cps/templates/user_edit.html:85
|
||||
#: cps/templates/config_edit.html:103 cps/templates/user_edit.html:92
|
||||
msgid "Allow Downloads"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:63 cps/templates/user_edit.html:89
|
||||
#: cps/templates/config_edit.html:107 cps/templates/user_edit.html:96
|
||||
msgid "Allow Uploads"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:67 cps/templates/user_edit.html:93
|
||||
#: cps/templates/config_edit.html:111 cps/templates/user_edit.html:100
|
||||
msgid "Allow Edit"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:71 cps/templates/user_edit.html:98
|
||||
#: cps/templates/config_edit.html:115 cps/templates/user_edit.html:105
|
||||
msgid "Allow Changing Password"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/config_edit.html:78 cps/templates/layout.html:93
|
||||
#: cps/templates/config_edit.html:122 cps/templates/layout.html:93
|
||||
#: cps/templates/login.html:4
|
||||
msgid "Login"
|
||||
msgstr ""
|
||||
|
@ -640,23 +731,27 @@ msgstr ""
|
|||
msgid "language"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/detail.html:74
|
||||
#: cps/templates/detail.html:81
|
||||
msgid "Publishing date"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/detail.html:106
|
||||
#: cps/templates/detail.html:115
|
||||
msgid "Read"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/detail.html:123
|
||||
msgid "Description:"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/detail.html:134
|
||||
#: cps/templates/detail.html:151
|
||||
msgid "Read in browser"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/detail.html:154
|
||||
#: cps/templates/detail.html:171
|
||||
msgid "Add to shelf"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/detail.html:194
|
||||
#: cps/templates/detail.html:211
|
||||
msgid "Edit metadata"
|
||||
msgstr ""
|
||||
|
||||
|
@ -736,19 +831,29 @@ msgstr ""
|
|||
msgid "Show Random Books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:43 cps/templates/layout.html:140
|
||||
#: cps/templates/index.xml:43 cps/templates/index.xml:47
|
||||
#: cps/templates/layout.html:132
|
||||
msgid "Read Books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:50 cps/templates/index.xml:54
|
||||
#: cps/templates/layout.html:133
|
||||
msgid "Unread Books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:57 cps/templates/layout.html:144
|
||||
msgid "Authors"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:47
|
||||
#: cps/templates/index.xml:61
|
||||
msgid "Books ordered by Author"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:54
|
||||
#: cps/templates/index.xml:68
|
||||
msgid "Books ordered by category"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/index.xml:61
|
||||
#: cps/templates/index.xml:75
|
||||
msgid "Books ordered by series"
|
||||
msgstr ""
|
||||
|
||||
|
@ -756,10 +861,6 @@ msgstr ""
|
|||
msgid "Toggle navigation"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/layout.html:60
|
||||
msgid "Go!"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/layout.html:68
|
||||
msgid "Advanced Search"
|
||||
msgstr ""
|
||||
|
@ -776,31 +877,31 @@ msgstr ""
|
|||
msgid "Browse"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/layout.html:132
|
||||
#: cps/templates/layout.html:136
|
||||
msgid "Discover"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/layout.html:135
|
||||
#: cps/templates/layout.html:139
|
||||
msgid "Categories"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/layout.html:142 cps/templates/search_form.html:54
|
||||
#: cps/templates/layout.html:146 cps/templates/search_form.html:58
|
||||
msgid "Languages"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/layout.html:145
|
||||
#: cps/templates/layout.html:149
|
||||
msgid "Public Shelves"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/layout.html:149
|
||||
#: cps/templates/layout.html:153
|
||||
msgid "Your Shelves"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/layout.html:154
|
||||
#: cps/templates/layout.html:158
|
||||
msgid "Create a Shelf"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/layout.html:155
|
||||
#: cps/templates/layout.html:159
|
||||
msgid "About"
|
||||
msgstr ""
|
||||
|
||||
|
@ -866,15 +967,15 @@ msgstr ""
|
|||
msgid "Results for:"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/search_form.html:23
|
||||
#: cps/templates/search_form.html:27
|
||||
msgid "Exclude Tags"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/search_form.html:43
|
||||
#: cps/templates/search_form.html:47
|
||||
msgid "Exclude Series"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/search_form.html:64
|
||||
#: cps/templates/search_form.html:68
|
||||
msgid "Exclude Languages"
|
||||
msgstr ""
|
||||
|
||||
|
@ -899,37 +1000,37 @@ msgid "Drag 'n drop to rearrange order"
|
|||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:3
|
||||
msgid "Linked libraries"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:8
|
||||
msgid "Program library"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:9
|
||||
msgid "Installed Version"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:32
|
||||
msgid "Calibre library statistics"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:37
|
||||
#: cps/templates/stats.html:8
|
||||
msgid "Books in this Library"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:41
|
||||
#: cps/templates/stats.html:12
|
||||
msgid "Authors in this Library"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:45
|
||||
#: cps/templates/stats.html:16
|
||||
msgid "Categories in this Library"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:49
|
||||
#: cps/templates/stats.html:20
|
||||
msgid "Series in this Library"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:24
|
||||
msgid "Linked libraries"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:28
|
||||
msgid "Program library"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/stats.html:29
|
||||
msgid "Installed Version"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:23
|
||||
msgid "Kindle E-Mail"
|
||||
msgstr ""
|
||||
|
@ -942,43 +1043,47 @@ msgstr ""
|
|||
msgid "Show all"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:45
|
||||
#: cps/templates/user_edit.html:47
|
||||
msgid "Show random books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:49
|
||||
#: cps/templates/user_edit.html:51
|
||||
msgid "Show hot books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:53
|
||||
#: cps/templates/user_edit.html:55
|
||||
msgid "Show best rated books"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:57
|
||||
#: cps/templates/user_edit.html:59
|
||||
msgid "Show language selection"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:61
|
||||
#: cps/templates/user_edit.html:63
|
||||
msgid "Show series selection"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:65
|
||||
#: cps/templates/user_edit.html:67
|
||||
msgid "Show category selection"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:69
|
||||
#: cps/templates/user_edit.html:71
|
||||
msgid "Show author selection"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:73
|
||||
#: cps/templates/user_edit.html:75
|
||||
msgid "Show read and unread"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:79
|
||||
msgid "Show random books in detail view"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:105
|
||||
#: cps/templates/user_edit.html:112
|
||||
msgid "Delete this user"
|
||||
msgstr ""
|
||||
|
||||
#: cps/templates/user_edit.html:116
|
||||
#: cps/templates/user_edit.html:127
|
||||
msgid "Recent Downloads"
|
||||
msgstr ""
|
||||
|
||||
|
|
13
optional-requirements.txt
Normal file
13
optional-requirements.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
gevent==1.2.1
|
||||
google-api-python-client==1.6.1
|
||||
greenlet==0.4.12
|
||||
httplib2==0.9.2
|
||||
lxml==3.7.2
|
||||
oauth2client==4.0.0
|
||||
pyasn1-modules==0.0.8
|
||||
pyasn1==0.1.9
|
||||
PyDrive==1.3.1
|
||||
PyYAML==3.12
|
||||
rsa==3.4.2
|
||||
six==1.10.0
|
||||
uritemplate==3.0.0
|
60
readme.md
60
readme.md
|
@ -1,4 +1,4 @@
|
|||
##About
|
||||
# About
|
||||
|
||||
Calibre Web is a web app providing a clean interface for browsing, reading and downloading eBooks using an existing [Calibre](https://calibre-ebook.com) database.
|
||||
|
||||
|
@ -6,7 +6,8 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
|
|||
|
||||
![screenshot](https://raw.githubusercontent.com/janeczku/docker-calibre-web/master/screenshot.png)
|
||||
|
||||
##Features
|
||||
## Features
|
||||
|
||||
- Bootstrap 3 HTML5 interface
|
||||
- full graphical setup
|
||||
- User management
|
||||
|
@ -28,13 +29,14 @@ Calibre Web is a web app providing a clean interface for browsing, reading and d
|
|||
|
||||
## Quick start
|
||||
|
||||
1. Execute the command: `python cps.py`
|
||||
2. Point your browser to `http://localhost:8083` or `http://localhost:8083/opds` for the OPDS catalog
|
||||
3. Set `Location of Calibre database` to the path of the folder where your Calibre library (metadata.db) lives, push "submit" button
|
||||
4. Go to Login page
|
||||
1. Install required dependencies by executing `pip install -r requirements.txt`
|
||||
2. Execute the command: `python cps.py` (or `nohup python cps.py` - recommended if you want to exit the terminal window)
|
||||
3. Point your browser to `http://localhost:8083` or `http://localhost:8083/opds` for the OPDS catalog
|
||||
4. Set `Location of Calibre database` to the path of the folder where your Calibre library (metadata.db) lives, push "submit" button
|
||||
5. Go to Login page
|
||||
|
||||
**Default admin login:**
|
||||
*Username:* admin
|
||||
**Default admin login:**
|
||||
*Username:* admin
|
||||
*Password:* admin123
|
||||
|
||||
## Runtime Configuration Options
|
||||
|
@ -56,10 +58,44 @@ Tick to enable uploading of PDF, epub, FB2. This requires the imagemagick librar
|
|||
## Requirements
|
||||
|
||||
Python 2.7+
|
||||
|
||||
Optionally, to enable on-the-fly conversion from EPUB to MOBI when using the send-to-kindle feature:
|
||||
|
||||
[Download](http://www.amazon.com/gp/feature.html?docId=1000765211) Amazon's KindleGen tool for your platform and place the binary named as `kindlegen` in the `vendor` folder.
|
||||
Optionally, to enable on-the-fly conversion from EPUB to MOBI when using the send-to-kindle feature:
|
||||
|
||||
[Download](http://www.amazon.com/gp/feature.html?docId=1000765211) Amazon's KindleGen tool for your platform and place the binary named as `kindlegen` in the `vendor` folder.
|
||||
|
||||
## Using Google Drive integration
|
||||
|
||||
Additional optional dependencys are necessary to get this work. Please install all optional requirements by executing `pip install -r optional-requirements.txt`
|
||||
|
||||
To use google drive integration, you have to use the google developer console to create a new app. https://console.developers.google.com
|
||||
|
||||
Once a project has been created, we need to create a client ID and a client secret that will be used to enable the OAuth request with google, and enable the Drive API. To do this, follow the steps below: -
|
||||
|
||||
1. Open project in developer console
|
||||
2. Click Enable API, and enable google drive
|
||||
3. Now on the sidebar, click Credentials
|
||||
4. Click Create Credentials and OAuth Client ID
|
||||
5. Select Web Application and then next
|
||||
6. Give the Credentials a name and enter your callback, which will be CALIBRE_WEB_URL/gdrive/callback
|
||||
7. Finally click save
|
||||
|
||||
The Drive API should now be setup and ready to use, so we need to integrate it into Calibre Web. This is done as below: -
|
||||
|
||||
1. Open config page
|
||||
2. Enter the location that will be used to store the metadata.db file, and to temporary store uploaded books and other temporary files for upload
|
||||
2. Tick Use Google Drive
|
||||
3. Enter Client Secret and Client Key as provided via previous steps
|
||||
4. Enter the folder that is the root of your calibre library
|
||||
5. Enter base URL for calibre (used for google callbacks)
|
||||
6 Now select Authenticate Google Drive
|
||||
7. This should redirect you to google to allow it top use your Drive, and then redirect you back to the config page
|
||||
8. Google Drive should now be connected and be used to get images and download Epubs. The metadata.db is stored in the calibre library location
|
||||
|
||||
### Optional
|
||||
If your calibre web is using https, it is possible to add a "watch" to the drive. This will inform us if the metadata.db file is updated and allow us to update our calibre library accordingly.
|
||||
|
||||
9. Click enable watch of metadata.db
|
||||
9. Note that this expires after a week, so will need to be manually refresh
|
||||
|
||||
## Docker image
|
||||
|
||||
|
@ -131,4 +167,4 @@ Replace the user and ExecStart with your user and foldernames.
|
|||
|
||||
`sudo systemctl enable cps.service`
|
||||
|
||||
enables the service.
|
||||
enables the service.
|
||||
|
|
12
requirements.txt
Normal file
12
requirements.txt
Normal file
|
@ -0,0 +1,12 @@
|
|||
Babel>=1.3
|
||||
Flask-Babel==0.11.1
|
||||
Flask-Login>=0.3.2
|
||||
Flask-Principal>=0.3.2
|
||||
Flask>=0.11
|
||||
iso-639>=0.4.5
|
||||
PyPDF2==1.26.0
|
||||
pytz>=2016.10
|
||||
requests>=2.11.1
|
||||
SQLAlchemy>=0.8.4
|
||||
tornado>=4.1
|
||||
Wand>=0.4.4
|
22
vendor/LICENSE_flask_login
vendored
22
vendor/LICENSE_flask_login
vendored
|
@ -1,22 +0,0 @@
|
|||
Copyright (c) 2011 Matthew Frazier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
22
vendor/LICENSE_flask_principal
vendored
22
vendor/LICENSE_flask_principal
vendored
|
@ -1,22 +0,0 @@
|
|||
Copyright (c) 2012 Ali Afshar
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
31
vendor/LICENSE_itsdangerous
vendored
31
vendor/LICENSE_itsdangerous
vendored
|
@ -1,31 +0,0 @@
|
|||
Copyright (c) 2011 by Armin Ronacher and the Django Software Foundation.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
5
vendor/PyPDF2/__init__.py
vendored
5
vendor/PyPDF2/__init__.py
vendored
|
@ -1,5 +0,0 @@
|
|||
from .pdf import PdfFileReader, PdfFileWriter
|
||||
from .merger import PdfFileMerger
|
||||
from .pagerange import PageRange, parse_filename_page_ranges
|
||||
from ._version import __version__
|
||||
__all__ = ["pdf", "PdfFileMerger"]
|
1
vendor/PyPDF2/_version.py
vendored
1
vendor/PyPDF2/_version.py
vendored
|
@ -1 +0,0 @@
|
|||
__version__ = '1.26.0'
|
362
vendor/PyPDF2/filters.py
vendored
362
vendor/PyPDF2/filters.py
vendored
|
@ -1,362 +0,0 @@
|
|||
# vim: sw=4:expandtab:foldmethod=marker
|
||||
#
|
||||
# Copyright (c) 2006, Mathieu Fenniak
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
"""
|
||||
Implementation of stream filters for PDF.
|
||||
"""
|
||||
__author__ = "Mathieu Fenniak"
|
||||
__author_email__ = "biziqe@mathieu.fenniak.net"
|
||||
|
||||
from .utils import PdfReadError, ord_, chr_
|
||||
from sys import version_info
|
||||
if version_info < ( 3, 0 ):
|
||||
from cStringIO import StringIO
|
||||
else:
|
||||
from io import StringIO
|
||||
import struct
|
||||
|
||||
try:
|
||||
import zlib
|
||||
|
||||
def decompress(data):
|
||||
return zlib.decompress(data)
|
||||
|
||||
def compress(data):
|
||||
return zlib.compress(data)
|
||||
|
||||
except ImportError:
|
||||
# Unable to import zlib. Attempt to use the System.IO.Compression
|
||||
# library from the .NET framework. (IronPython only)
|
||||
import System
|
||||
from System import IO, Collections, Array
|
||||
|
||||
def _string_to_bytearr(buf):
|
||||
retval = Array.CreateInstance(System.Byte, len(buf))
|
||||
for i in range(len(buf)):
|
||||
retval[i] = ord(buf[i])
|
||||
return retval
|
||||
|
||||
def _bytearr_to_string(bytes):
|
||||
retval = ""
|
||||
for i in range(bytes.Length):
|
||||
retval += chr(bytes[i])
|
||||
return retval
|
||||
|
||||
def _read_bytes(stream):
|
||||
ms = IO.MemoryStream()
|
||||
buf = Array.CreateInstance(System.Byte, 2048)
|
||||
while True:
|
||||
bytes = stream.Read(buf, 0, buf.Length)
|
||||
if bytes == 0:
|
||||
break
|
||||
else:
|
||||
ms.Write(buf, 0, bytes)
|
||||
retval = ms.ToArray()
|
||||
ms.Close()
|
||||
return retval
|
||||
|
||||
def decompress(data):
|
||||
bytes = _string_to_bytearr(data)
|
||||
ms = IO.MemoryStream()
|
||||
ms.Write(bytes, 0, bytes.Length)
|
||||
ms.Position = 0 # fseek 0
|
||||
gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Decompress)
|
||||
bytes = _read_bytes(gz)
|
||||
retval = _bytearr_to_string(bytes)
|
||||
gz.Close()
|
||||
return retval
|
||||
|
||||
def compress(data):
|
||||
bytes = _string_to_bytearr(data)
|
||||
ms = IO.MemoryStream()
|
||||
gz = IO.Compression.DeflateStream(ms, IO.Compression.CompressionMode.Compress, True)
|
||||
gz.Write(bytes, 0, bytes.Length)
|
||||
gz.Close()
|
||||
ms.Position = 0 # fseek 0
|
||||
bytes = ms.ToArray()
|
||||
retval = _bytearr_to_string(bytes)
|
||||
ms.Close()
|
||||
return retval
|
||||
|
||||
|
||||
class FlateDecode(object):
|
||||
def decode(data, decodeParms):
|
||||
data = decompress(data)
|
||||
predictor = 1
|
||||
if decodeParms:
|
||||
try:
|
||||
predictor = decodeParms.get("/Predictor", 1)
|
||||
except AttributeError:
|
||||
pass # usually an array with a null object was read
|
||||
|
||||
# predictor 1 == no predictor
|
||||
if predictor != 1:
|
||||
columns = decodeParms["/Columns"]
|
||||
# PNG prediction:
|
||||
if predictor >= 10 and predictor <= 15:
|
||||
output = StringIO()
|
||||
# PNG prediction can vary from row to row
|
||||
rowlength = columns + 1
|
||||
assert len(data) % rowlength == 0
|
||||
prev_rowdata = (0,) * rowlength
|
||||
for row in range(len(data) // rowlength):
|
||||
rowdata = [ord_(x) for x in data[(row*rowlength):((row+1)*rowlength)]]
|
||||
filterByte = rowdata[0]
|
||||
if filterByte == 0:
|
||||
pass
|
||||
elif filterByte == 1:
|
||||
for i in range(2, rowlength):
|
||||
rowdata[i] = (rowdata[i] + rowdata[i-1]) % 256
|
||||
elif filterByte == 2:
|
||||
for i in range(1, rowlength):
|
||||
rowdata[i] = (rowdata[i] + prev_rowdata[i]) % 256
|
||||
else:
|
||||
# unsupported PNG filter
|
||||
raise PdfReadError("Unsupported PNG filter %r" % filterByte)
|
||||
prev_rowdata = rowdata
|
||||
output.write(''.join([chr(x) for x in rowdata[1:]]))
|
||||
data = output.getvalue()
|
||||
else:
|
||||
# unsupported predictor
|
||||
raise PdfReadError("Unsupported flatedecode predictor %r" % predictor)
|
||||
return data
|
||||
decode = staticmethod(decode)
|
||||
|
||||
def encode(data):
|
||||
return compress(data)
|
||||
encode = staticmethod(encode)
|
||||
|
||||
|
||||
class ASCIIHexDecode(object):
|
||||
def decode(data, decodeParms=None):
|
||||
retval = ""
|
||||
char = ""
|
||||
x = 0
|
||||
while True:
|
||||
c = data[x]
|
||||
if c == ">":
|
||||
break
|
||||
elif c.isspace():
|
||||
x += 1
|
||||
continue
|
||||
char += c
|
||||
if len(char) == 2:
|
||||
retval += chr(int(char, base=16))
|
||||
char = ""
|
||||
x += 1
|
||||
assert char == ""
|
||||
return retval
|
||||
decode = staticmethod(decode)
|
||||
|
||||
|
||||
class LZWDecode(object):
|
||||
"""Taken from:
|
||||
http://www.java2s.com/Open-Source/Java-Document/PDF/PDF-Renderer/com/sun/pdfview/decode/LZWDecode.java.htm
|
||||
"""
|
||||
class decoder(object):
|
||||
def __init__(self, data):
|
||||
self.STOP=257
|
||||
self.CLEARDICT=256
|
||||
self.data=data
|
||||
self.bytepos=0
|
||||
self.bitpos=0
|
||||
self.dict=[""]*4096
|
||||
for i in range(256):
|
||||
self.dict[i]=chr(i)
|
||||
self.resetDict()
|
||||
|
||||
def resetDict(self):
|
||||
self.dictlen=258
|
||||
self.bitspercode=9
|
||||
|
||||
def nextCode(self):
|
||||
fillbits=self.bitspercode
|
||||
value=0
|
||||
while fillbits>0 :
|
||||
if self.bytepos >= len(self.data):
|
||||
return -1
|
||||
nextbits=ord(self.data[self.bytepos])
|
||||
bitsfromhere=8-self.bitpos
|
||||
if bitsfromhere>fillbits:
|
||||
bitsfromhere=fillbits
|
||||
value |= (((nextbits >> (8-self.bitpos-bitsfromhere)) &
|
||||
(0xff >> (8-bitsfromhere))) <<
|
||||
(fillbits-bitsfromhere))
|
||||
fillbits -= bitsfromhere
|
||||
self.bitpos += bitsfromhere
|
||||
if self.bitpos >=8:
|
||||
self.bitpos=0
|
||||
self.bytepos = self.bytepos+1
|
||||
return value
|
||||
|
||||
def decode(self):
|
||||
""" algorithm derived from:
|
||||
http://www.rasip.fer.hr/research/compress/algorithms/fund/lz/lzw.html
|
||||
and the PDFReference
|
||||
"""
|
||||
cW = self.CLEARDICT;
|
||||
baos=""
|
||||
while True:
|
||||
pW = cW;
|
||||
cW = self.nextCode();
|
||||
if cW == -1:
|
||||
raise PdfReadError("Missed the stop code in LZWDecode!")
|
||||
if cW == self.STOP:
|
||||
break;
|
||||
elif cW == self.CLEARDICT:
|
||||
self.resetDict();
|
||||
elif pW == self.CLEARDICT:
|
||||
baos+=self.dict[cW]
|
||||
else:
|
||||
if cW < self.dictlen:
|
||||
baos += self.dict[cW]
|
||||
p=self.dict[pW]+self.dict[cW][0]
|
||||
self.dict[self.dictlen]=p
|
||||
self.dictlen+=1
|
||||
else:
|
||||
p=self.dict[pW]+self.dict[pW][0]
|
||||
baos+=p
|
||||
self.dict[self.dictlen] = p;
|
||||
self.dictlen+=1
|
||||
if (self.dictlen >= (1 << self.bitspercode) - 1 and
|
||||
self.bitspercode < 12):
|
||||
self.bitspercode+=1
|
||||
return baos
|
||||
|
||||
@staticmethod
|
||||
def decode(data,decodeParams=None):
|
||||
return LZWDecode.decoder(data).decode()
|
||||
|
||||
|
||||
class ASCII85Decode(object):
|
||||
def decode(data, decodeParms=None):
|
||||
if version_info < ( 3, 0 ):
|
||||
retval = ""
|
||||
group = []
|
||||
x = 0
|
||||
hitEod = False
|
||||
# remove all whitespace from data
|
||||
data = [y for y in data if not (y in ' \n\r\t')]
|
||||
while not hitEod:
|
||||
c = data[x]
|
||||
if len(retval) == 0 and c == "<" and data[x+1] == "~":
|
||||
x += 2
|
||||
continue
|
||||
#elif c.isspace():
|
||||
# x += 1
|
||||
# continue
|
||||
elif c == 'z':
|
||||
assert len(group) == 0
|
||||
retval += '\x00\x00\x00\x00'
|
||||
x += 1
|
||||
continue
|
||||
elif c == "~" and data[x+1] == ">":
|
||||
if len(group) != 0:
|
||||
# cannot have a final group of just 1 char
|
||||
assert len(group) > 1
|
||||
cnt = len(group) - 1
|
||||
group += [ 85, 85, 85 ]
|
||||
hitEod = cnt
|
||||
else:
|
||||
break
|
||||
else:
|
||||
c = ord(c) - 33
|
||||
assert c >= 0 and c < 85
|
||||
group += [ c ]
|
||||
if len(group) >= 5:
|
||||
b = group[0] * (85**4) + \
|
||||
group[1] * (85**3) + \
|
||||
group[2] * (85**2) + \
|
||||
group[3] * 85 + \
|
||||
group[4]
|
||||
assert b < (2**32 - 1)
|
||||
c4 = chr((b >> 0) % 256)
|
||||
c3 = chr((b >> 8) % 256)
|
||||
c2 = chr((b >> 16) % 256)
|
||||
c1 = chr(b >> 24)
|
||||
retval += (c1 + c2 + c3 + c4)
|
||||
if hitEod:
|
||||
retval = retval[:-4+hitEod]
|
||||
group = []
|
||||
x += 1
|
||||
return retval
|
||||
else:
|
||||
if isinstance(data, str):
|
||||
data = data.encode('ascii')
|
||||
n = b = 0
|
||||
out = bytearray()
|
||||
for c in data:
|
||||
if ord('!') <= c and c <= ord('u'):
|
||||
n += 1
|
||||
b = b*85+(c-33)
|
||||
if n == 5:
|
||||
out += struct.pack(b'>L',b)
|
||||
n = b = 0
|
||||
elif c == ord('z'):
|
||||
assert n == 0
|
||||
out += b'\0\0\0\0'
|
||||
elif c == ord('~'):
|
||||
if n:
|
||||
for _ in range(5-n):
|
||||
b = b*85+84
|
||||
out += struct.pack(b'>L',b)[:n-1]
|
||||
break
|
||||
return bytes(out)
|
||||
decode = staticmethod(decode)
|
||||
|
||||
|
||||
def decodeStreamData(stream):
|
||||
from .generic import NameObject
|
||||
filters = stream.get("/Filter", ())
|
||||
if len(filters) and not isinstance(filters[0], NameObject):
|
||||
# we have a single filter instance
|
||||
filters = (filters,)
|
||||
data = stream._data
|
||||
# If there is not data to decode we should not try to decode the data.
|
||||
if data:
|
||||
for filterType in filters:
|
||||
if filterType == "/FlateDecode" or filterType == "/Fl":
|
||||
data = FlateDecode.decode(data, stream.get("/DecodeParms"))
|
||||
elif filterType == "/ASCIIHexDecode" or filterType == "/AHx":
|
||||
data = ASCIIHexDecode.decode(data)
|
||||
elif filterType == "/LZWDecode" or filterType == "/LZW":
|
||||
data = LZWDecode.decode(data, stream.get("/DecodeParms"))
|
||||
elif filterType == "/ASCII85Decode" or filterType == "/A85":
|
||||
data = ASCII85Decode.decode(data)
|
||||
elif filterType == "/Crypt":
|
||||
decodeParams = stream.get("/DecodeParams", {})
|
||||
if "/Name" not in decodeParams and "/Type" not in decodeParams:
|
||||
pass
|
||||
else:
|
||||
raise NotImplementedError("/Crypt filter with /Name or /Type not supported yet")
|
||||
else:
|
||||
# unsupported filter
|
||||
raise NotImplementedError("unsupported filter %s" % filterType)
|
||||
return data
|
1226
vendor/PyPDF2/generic.py
vendored
1226
vendor/PyPDF2/generic.py
vendored
File diff suppressed because it is too large
Load Diff
553
vendor/PyPDF2/merger.py
vendored
553
vendor/PyPDF2/merger.py
vendored
|
@ -1,553 +0,0 @@
|
|||
# vim: sw=4:expandtab:foldmethod=marker
|
||||
#
|
||||
# Copyright (c) 2006, Mathieu Fenniak
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from .generic import *
|
||||
from .utils import isString, str_
|
||||
from .pdf import PdfFileReader, PdfFileWriter
|
||||
from .pagerange import PageRange
|
||||
from sys import version_info
|
||||
if version_info < ( 3, 0 ):
|
||||
from cStringIO import StringIO
|
||||
StreamIO = StringIO
|
||||
else:
|
||||
from io import BytesIO
|
||||
from io import FileIO as file
|
||||
StreamIO = BytesIO
|
||||
|
||||
|
||||
class _MergedPage(object):
|
||||
"""
|
||||
_MergedPage is used internally by PdfFileMerger to collect necessary
|
||||
information on each page that is being merged.
|
||||
"""
|
||||
def __init__(self, pagedata, src, id):
|
||||
self.src = src
|
||||
self.pagedata = pagedata
|
||||
self.out_pagedata = None
|
||||
self.id = id
|
||||
|
||||
|
||||
class PdfFileMerger(object):
|
||||
"""
|
||||
Initializes a PdfFileMerger object. PdfFileMerger merges multiple PDFs
|
||||
into a single PDF. It can concatenate, slice, insert, or any combination
|
||||
of the above.
|
||||
|
||||
See the functions :meth:`merge()<merge>` (or :meth:`append()<append>`)
|
||||
and :meth:`write()<write>` for usage information.
|
||||
|
||||
:param bool strict: Determines whether user should be warned of all
|
||||
problems and also causes some correctable problems to be fatal.
|
||||
Defaults to ``True``.
|
||||
"""
|
||||
|
||||
def __init__(self, strict=True):
|
||||
self.inputs = []
|
||||
self.pages = []
|
||||
self.output = PdfFileWriter()
|
||||
self.bookmarks = []
|
||||
self.named_dests = []
|
||||
self.id_count = 0
|
||||
self.strict = strict
|
||||
|
||||
def merge(self, position, fileobj, bookmark=None, pages=None, import_bookmarks=True):
|
||||
"""
|
||||
Merges the pages from the given file into the output file at the
|
||||
specified page number.
|
||||
|
||||
:param int position: The *page number* to insert this file. File will
|
||||
be inserted after the given number.
|
||||
|
||||
:param fileobj: A File Object or an object that supports the standard read
|
||||
and seek methods similar to a File Object. Could also be a
|
||||
string representing a path to a PDF file.
|
||||
|
||||
:param str bookmark: Optionally, you may specify a bookmark to be applied at
|
||||
the beginning of the included file by supplying the text of the bookmark.
|
||||
|
||||
:param pages: can be a :ref:`Page Range <page-range>` or a ``(start, stop[, step])`` tuple
|
||||
to merge only the specified range of pages from the source
|
||||
document into the output document.
|
||||
|
||||
:param bool import_bookmarks: You may prevent the source document's bookmarks
|
||||
from being imported by specifying this as ``False``.
|
||||
"""
|
||||
|
||||
# This parameter is passed to self.inputs.append and means
|
||||
# that the stream used was created in this method.
|
||||
my_file = False
|
||||
|
||||
# If the fileobj parameter is a string, assume it is a path
|
||||
# and create a file object at that location. If it is a file,
|
||||
# copy the file's contents into a BytesIO (or StreamIO) stream object; if
|
||||
# it is a PdfFileReader, copy that reader's stream into a
|
||||
# BytesIO (or StreamIO) stream.
|
||||
# If fileobj is none of the above types, it is not modified
|
||||
decryption_key = None
|
||||
if isString(fileobj):
|
||||
fileobj = file(fileobj, 'rb')
|
||||
my_file = True
|
||||
elif isinstance(fileobj, file):
|
||||
fileobj.seek(0)
|
||||
filecontent = fileobj.read()
|
||||
fileobj = StreamIO(filecontent)
|
||||
my_file = True
|
||||
elif isinstance(fileobj, PdfFileReader):
|
||||
orig_tell = fileobj.stream.tell()
|
||||
fileobj.stream.seek(0)
|
||||
filecontent = StreamIO(fileobj.stream.read())
|
||||
fileobj.stream.seek(orig_tell) # reset the stream to its original location
|
||||
fileobj = filecontent
|
||||
if hasattr(fileobj, '_decryption_key'):
|
||||
decryption_key = fileobj._decryption_key
|
||||
my_file = True
|
||||
|
||||
# Create a new PdfFileReader instance using the stream
|
||||
# (either file or BytesIO or StringIO) created above
|
||||
pdfr = PdfFileReader(fileobj, strict=self.strict)
|
||||
if decryption_key is not None:
|
||||
pdfr._decryption_key = decryption_key
|
||||
|
||||
# Find the range of pages to merge.
|
||||
if pages == None:
|
||||
pages = (0, pdfr.getNumPages())
|
||||
elif isinstance(pages, PageRange):
|
||||
pages = pages.indices(pdfr.getNumPages())
|
||||
elif not isinstance(pages, tuple):
|
||||
raise TypeError('"pages" must be a tuple of (start, stop[, step])')
|
||||
|
||||
srcpages = []
|
||||
if bookmark:
|
||||
bookmark = Bookmark(TextStringObject(bookmark), NumberObject(self.id_count), NameObject('/Fit'))
|
||||
|
||||
outline = []
|
||||
if import_bookmarks:
|
||||
outline = pdfr.getOutlines()
|
||||
outline = self._trim_outline(pdfr, outline, pages)
|
||||
|
||||
if bookmark:
|
||||
self.bookmarks += [bookmark, outline]
|
||||
else:
|
||||
self.bookmarks += outline
|
||||
|
||||
dests = pdfr.namedDestinations
|
||||
dests = self._trim_dests(pdfr, dests, pages)
|
||||
self.named_dests += dests
|
||||
|
||||
# Gather all the pages that are going to be merged
|
||||
for i in range(*pages):
|
||||
pg = pdfr.getPage(i)
|
||||
|
||||
id = self.id_count
|
||||
self.id_count += 1
|
||||
|
||||
mp = _MergedPage(pg, pdfr, id)
|
||||
|
||||
srcpages.append(mp)
|
||||
|
||||
self._associate_dests_to_pages(srcpages)
|
||||
self._associate_bookmarks_to_pages(srcpages)
|
||||
|
||||
# Slice to insert the pages at the specified position
|
||||
self.pages[position:position] = srcpages
|
||||
|
||||
# Keep track of our input files so we can close them later
|
||||
self.inputs.append((fileobj, pdfr, my_file))
|
||||
|
||||
def append(self, fileobj, bookmark=None, pages=None, import_bookmarks=True):
|
||||
"""
|
||||
Identical to the :meth:`merge()<merge>` method, but assumes you want to concatenate
|
||||
all pages onto the end of the file instead of specifying a position.
|
||||
|
||||
:param fileobj: A File Object or an object that supports the standard read
|
||||
and seek methods similar to a File Object. Could also be a
|
||||
string representing a path to a PDF file.
|
||||
|
||||
:param str bookmark: Optionally, you may specify a bookmark to be applied at
|
||||
the beginning of the included file by supplying the text of the bookmark.
|
||||
|
||||
:param pages: can be a :ref:`Page Range <page-range>` or a ``(start, stop[, step])`` tuple
|
||||
to merge only the specified range of pages from the source
|
||||
document into the output document.
|
||||
|
||||
:param bool import_bookmarks: You may prevent the source document's bookmarks
|
||||
from being imported by specifying this as ``False``.
|
||||
"""
|
||||
|
||||
self.merge(len(self.pages), fileobj, bookmark, pages, import_bookmarks)
|
||||
|
||||
def write(self, fileobj):
|
||||
"""
|
||||
Writes all data that has been merged to the given output file.
|
||||
|
||||
:param fileobj: Output file. Can be a filename or any kind of
|
||||
file-like object.
|
||||
"""
|
||||
my_file = False
|
||||
if isString(fileobj):
|
||||
fileobj = file(fileobj, 'wb')
|
||||
my_file = True
|
||||
|
||||
# Add pages to the PdfFileWriter
|
||||
# The commented out line below was replaced with the two lines below it to allow PdfFileMerger to work with PyPdf 1.13
|
||||
for page in self.pages:
|
||||
self.output.addPage(page.pagedata)
|
||||
page.out_pagedata = self.output.getReference(self.output._pages.getObject()["/Kids"][-1].getObject())
|
||||
#idnum = self.output._objects.index(self.output._pages.getObject()["/Kids"][-1].getObject()) + 1
|
||||
#page.out_pagedata = IndirectObject(idnum, 0, self.output)
|
||||
|
||||
# Once all pages are added, create bookmarks to point at those pages
|
||||
self._write_dests()
|
||||
self._write_bookmarks()
|
||||
|
||||
# Write the output to the file
|
||||
self.output.write(fileobj)
|
||||
|
||||
if my_file:
|
||||
fileobj.close()
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Shuts all file descriptors (input and output) and clears all memory
|
||||
usage.
|
||||
"""
|
||||
self.pages = []
|
||||
for fo, pdfr, mine in self.inputs:
|
||||
if mine:
|
||||
fo.close()
|
||||
|
||||
self.inputs = []
|
||||
self.output = None
|
||||
|
||||
def addMetadata(self, infos):
|
||||
"""
|
||||
Add custom metadata to the output.
|
||||
|
||||
:param dict infos: a Python dictionary where each key is a field
|
||||
and each value is your new metadata.
|
||||
Example: ``{u'/Title': u'My title'}``
|
||||
"""
|
||||
self.output.addMetadata(infos)
|
||||
|
||||
def setPageLayout(self, layout):
|
||||
"""
|
||||
Set the page layout
|
||||
|
||||
:param str layout: The page layout to be used
|
||||
|
||||
Valid layouts are:
|
||||
/NoLayout Layout explicitly not specified
|
||||
/SinglePage Show one page at a time
|
||||
/OneColumn Show one column at a time
|
||||
/TwoColumnLeft Show pages in two columns, odd-numbered pages on the left
|
||||
/TwoColumnRight Show pages in two columns, odd-numbered pages on the right
|
||||
/TwoPageLeft Show two pages at a time, odd-numbered pages on the left
|
||||
/TwoPageRight Show two pages at a time, odd-numbered pages on the right
|
||||
"""
|
||||
self.output.setPageLayout(layout)
|
||||
|
||||
def setPageMode(self, mode):
|
||||
"""
|
||||
Set the page mode.
|
||||
|
||||
:param str mode: The page mode to use.
|
||||
|
||||
Valid modes are:
|
||||
/UseNone Do not show outlines or thumbnails panels
|
||||
/UseOutlines Show outlines (aka bookmarks) panel
|
||||
/UseThumbs Show page thumbnails panel
|
||||
/FullScreen Fullscreen view
|
||||
/UseOC Show Optional Content Group (OCG) panel
|
||||
/UseAttachments Show attachments panel
|
||||
"""
|
||||
self.output.setPageMode(mode)
|
||||
|
||||
def _trim_dests(self, pdf, dests, pages):
|
||||
"""
|
||||
Removes any named destinations that are not a part of the specified
|
||||
page set.
|
||||
"""
|
||||
new_dests = []
|
||||
prev_header_added = True
|
||||
for k, o in list(dests.items()):
|
||||
for j in range(*pages):
|
||||
if pdf.getPage(j).getObject() == o['/Page'].getObject():
|
||||
o[NameObject('/Page')] = o['/Page'].getObject()
|
||||
assert str_(k) == str_(o['/Title'])
|
||||
new_dests.append(o)
|
||||
break
|
||||
return new_dests
|
||||
|
||||
def _trim_outline(self, pdf, outline, pages):
|
||||
"""
|
||||
Removes any outline/bookmark entries that are not a part of the
|
||||
specified page set.
|
||||
"""
|
||||
new_outline = []
|
||||
prev_header_added = True
|
||||
for i, o in enumerate(outline):
|
||||
if isinstance(o, list):
|
||||
sub = self._trim_outline(pdf, o, pages)
|
||||
if sub:
|
||||
if not prev_header_added:
|
||||
new_outline.append(outline[i-1])
|
||||
new_outline.append(sub)
|
||||
else:
|
||||
prev_header_added = False
|
||||
for j in range(*pages):
|
||||
if pdf.getPage(j).getObject() == o['/Page'].getObject():
|
||||
o[NameObject('/Page')] = o['/Page'].getObject()
|
||||
new_outline.append(o)
|
||||
prev_header_added = True
|
||||
break
|
||||
return new_outline
|
||||
|
||||
def _write_dests(self):
|
||||
dests = self.named_dests
|
||||
|
||||
for v in dests:
|
||||
pageno = None
|
||||
pdf = None
|
||||
if '/Page' in v:
|
||||
for i, p in enumerate(self.pages):
|
||||
if p.id == v['/Page']:
|
||||
v[NameObject('/Page')] = p.out_pagedata
|
||||
pageno = i
|
||||
pdf = p.src
|
||||
break
|
||||
if pageno != None:
|
||||
self.output.addNamedDestinationObject(v)
|
||||
|
||||
def _write_bookmarks(self, bookmarks=None, parent=None):
|
||||
|
||||
if bookmarks == None:
|
||||
bookmarks = self.bookmarks
|
||||
|
||||
last_added = None
|
||||
for b in bookmarks:
|
||||
if isinstance(b, list):
|
||||
self._write_bookmarks(b, last_added)
|
||||
continue
|
||||
|
||||
pageno = None
|
||||
pdf = None
|
||||
if '/Page' in b:
|
||||
for i, p in enumerate(self.pages):
|
||||
if p.id == b['/Page']:
|
||||
#b[NameObject('/Page')] = p.out_pagedata
|
||||
args = [NumberObject(p.id), NameObject(b['/Type'])]
|
||||
#nothing more to add
|
||||
#if b['/Type'] == '/Fit' or b['/Type'] == '/FitB'
|
||||
if b['/Type'] == '/FitH' or b['/Type'] == '/FitBH':
|
||||
if '/Top' in b and not isinstance(b['/Top'], NullObject):
|
||||
args.append(FloatObject(b['/Top']))
|
||||
else:
|
||||
args.append(FloatObject(0))
|
||||
del b['/Top']
|
||||
elif b['/Type'] == '/FitV' or b['/Type'] == '/FitBV':
|
||||
if '/Left' in b and not isinstance(b['/Left'], NullObject):
|
||||
args.append(FloatObject(b['/Left']))
|
||||
else:
|
||||
args.append(FloatObject(0))
|
||||
del b['/Left']
|
||||
elif b['/Type'] == '/XYZ':
|
||||
if '/Left' in b and not isinstance(b['/Left'], NullObject):
|
||||
args.append(FloatObject(b['/Left']))
|
||||
else:
|
||||
args.append(FloatObject(0))
|
||||
if '/Top' in b and not isinstance(b['/Top'], NullObject):
|
||||
args.append(FloatObject(b['/Top']))
|
||||
else:
|
||||
args.append(FloatObject(0))
|
||||
if '/Zoom' in b and not isinstance(b['/Zoom'], NullObject):
|
||||
args.append(FloatObject(b['/Zoom']))
|
||||
else:
|
||||
args.append(FloatObject(0))
|
||||
del b['/Top'], b['/Zoom'], b['/Left']
|
||||
elif b['/Type'] == '/FitR':
|
||||
if '/Left' in b and not isinstance(b['/Left'], NullObject):
|
||||
args.append(FloatObject(b['/Left']))
|
||||
else:
|
||||
args.append(FloatObject(0))
|
||||
if '/Bottom' in b and not isinstance(b['/Bottom'], NullObject):
|
||||
args.append(FloatObject(b['/Bottom']))
|
||||
else:
|
||||
args.append(FloatObject(0))
|
||||
if '/Right' in b and not isinstance(b['/Right'], NullObject):
|
||||
args.append(FloatObject(b['/Right']))
|
||||
else:
|
||||
args.append(FloatObject(0))
|
||||
if '/Top' in b and not isinstance(b['/Top'], NullObject):
|
||||
args.append(FloatObject(b['/Top']))
|
||||
else:
|
||||
args.append(FloatObject(0))
|
||||
del b['/Left'], b['/Right'], b['/Bottom'], b['/Top']
|
||||
|
||||
b[NameObject('/A')] = DictionaryObject({NameObject('/S'): NameObject('/GoTo'), NameObject('/D'): ArrayObject(args)})
|
||||
|
||||
pageno = i
|
||||
pdf = p.src
|
||||
break
|
||||
if pageno != None:
|
||||
del b['/Page'], b['/Type']
|
||||
last_added = self.output.addBookmarkDict(b, parent)
|
||||
|
||||
def _associate_dests_to_pages(self, pages):
|
||||
for nd in self.named_dests:
|
||||
pageno = None
|
||||
np = nd['/Page']
|
||||
|
||||
if isinstance(np, NumberObject):
|
||||
continue
|
||||
|
||||
for p in pages:
|
||||
if np.getObject() == p.pagedata.getObject():
|
||||
pageno = p.id
|
||||
|
||||
if pageno != None:
|
||||
nd[NameObject('/Page')] = NumberObject(pageno)
|
||||
else:
|
||||
raise ValueError("Unresolved named destination '%s'" % (nd['/Title'],))
|
||||
|
||||
def _associate_bookmarks_to_pages(self, pages, bookmarks=None):
|
||||
if bookmarks == None:
|
||||
bookmarks = self.bookmarks
|
||||
|
||||
for b in bookmarks:
|
||||
if isinstance(b, list):
|
||||
self._associate_bookmarks_to_pages(pages, b)
|
||||
continue
|
||||
|
||||
pageno = None
|
||||
bp = b['/Page']
|
||||
|
||||
if isinstance(bp, NumberObject):
|
||||
continue
|
||||
|
||||
for p in pages:
|
||||
if bp.getObject() == p.pagedata.getObject():
|
||||
pageno = p.id
|
||||
|
||||
if pageno != None:
|
||||
b[NameObject('/Page')] = NumberObject(pageno)
|
||||
else:
|
||||
raise ValueError("Unresolved bookmark '%s'" % (b['/Title'],))
|
||||
|
||||
def findBookmark(self, bookmark, root=None):
|
||||
if root == None:
|
||||
root = self.bookmarks
|
||||
|
||||
for i, b in enumerate(root):
|
||||
if isinstance(b, list):
|
||||
res = self.findBookmark(bookmark, b)
|
||||
if res:
|
||||
return [i] + res
|
||||
elif b == bookmark or b['/Title'] == bookmark:
|
||||
return [i]
|
||||
|
||||
return None
|
||||
|
||||
def addBookmark(self, title, pagenum, parent=None):
|
||||
"""
|
||||
Add a bookmark to this PDF file.
|
||||
|
||||
:param str title: Title to use for this bookmark.
|
||||
:param int pagenum: Page number this bookmark will point to.
|
||||
:param parent: A reference to a parent bookmark to create nested
|
||||
bookmarks.
|
||||
"""
|
||||
if parent == None:
|
||||
iloc = [len(self.bookmarks)-1]
|
||||
elif isinstance(parent, list):
|
||||
iloc = parent
|
||||
else:
|
||||
iloc = self.findBookmark(parent)
|
||||
|
||||
dest = Bookmark(TextStringObject(title), NumberObject(pagenum), NameObject('/FitH'), NumberObject(826))
|
||||
|
||||
if parent == None:
|
||||
self.bookmarks.append(dest)
|
||||
else:
|
||||
bmparent = self.bookmarks
|
||||
for i in iloc[:-1]:
|
||||
bmparent = bmparent[i]
|
||||
npos = iloc[-1]+1
|
||||
if npos < len(bmparent) and isinstance(bmparent[npos], list):
|
||||
bmparent[npos].append(dest)
|
||||
else:
|
||||
bmparent.insert(npos, [dest])
|
||||
return dest
|
||||
|
||||
def addNamedDestination(self, title, pagenum):
|
||||
"""
|
||||
Add a destination to the output.
|
||||
|
||||
:param str title: Title to use
|
||||
:param int pagenum: Page number this destination points at.
|
||||
"""
|
||||
|
||||
dest = Destination(TextStringObject(title), NumberObject(pagenum), NameObject('/FitH'), NumberObject(826))
|
||||
self.named_dests.append(dest)
|
||||
|
||||
|
||||
class OutlinesObject(list):
|
||||
def __init__(self, pdf, tree, parent=None):
|
||||
list.__init__(self)
|
||||
self.tree = tree
|
||||
self.pdf = pdf
|
||||
self.parent = parent
|
||||
|
||||
def remove(self, index):
|
||||
obj = self[index]
|
||||
del self[index]
|
||||
self.tree.removeChild(obj)
|
||||
|
||||
def add(self, title, pagenum):
|
||||
pageRef = self.pdf.getObject(self.pdf._pages)['/Kids'][pagenum]
|
||||
action = DictionaryObject()
|
||||
action.update({
|
||||
NameObject('/D') : ArrayObject([pageRef, NameObject('/FitH'), NumberObject(826)]),
|
||||
NameObject('/S') : NameObject('/GoTo')
|
||||
})
|
||||
actionRef = self.pdf._addObject(action)
|
||||
bookmark = TreeObject()
|
||||
|
||||
bookmark.update({
|
||||
NameObject('/A'): actionRef,
|
||||
NameObject('/Title'): createStringObject(title),
|
||||
})
|
||||
|
||||
self.pdf._addObject(bookmark)
|
||||
|
||||
self.tree.addChild(bookmark)
|
||||
|
||||
def removeAll(self):
|
||||
for child in [x for x in self.tree.children()]:
|
||||
self.tree.removeChild(child)
|
||||
self.pop()
|
152
vendor/PyPDF2/pagerange.py
vendored
152
vendor/PyPDF2/pagerange.py
vendored
|
@ -1,152 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
"""
|
||||
Representation and utils for ranges of PDF file pages.
|
||||
|
||||
Copyright (c) 2014, Steve Witham <switham_github@mac-guyver.com>.
|
||||
All rights reserved. This software is available under a BSD license;
|
||||
see https://github.com/mstamy2/PyPDF2/blob/master/LICENSE
|
||||
"""
|
||||
|
||||
import re
|
||||
from .utils import isString
|
||||
|
||||
_INT_RE = r"(0|-?[1-9]\d*)" # A decimal int, don't allow "-0".
|
||||
PAGE_RANGE_RE = "^({int}|({int}?(:{int}?(:{int}?)?)))$".format(int=_INT_RE)
|
||||
# groups: 12 34 5 6 7 8
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
PAGE_RANGE_HELP = """Remember, page indices start with zero.
|
||||
Page range expression examples:
|
||||
: all pages. -1 last page.
|
||||
22 just the 23rd page. :-1 all but the last page.
|
||||
0:3 the first three pages. -2 second-to-last page.
|
||||
:3 the first three pages. -2: last two pages.
|
||||
5: from the sixth page onward. -3:-1 third & second to last.
|
||||
The third, "stride" or "step" number is also recognized.
|
||||
::2 0 2 4 ... to the end. 3:0:-1 3 2 1 but not 0.
|
||||
1:10:2 1 3 5 7 9 2::-1 2 1 0.
|
||||
::-1 all pages in reverse order.
|
||||
"""
|
||||
|
||||
|
||||
class PageRange(object):
|
||||
"""
|
||||
A slice-like representation of a range of page indices,
|
||||
i.e. page numbers, only starting at zero.
|
||||
The syntax is like what you would put between brackets [ ].
|
||||
The slice is one of the few Python types that can't be subclassed,
|
||||
but this class converts to and from slices, and allows similar use.
|
||||
o PageRange(str) parses a string representing a page range.
|
||||
o PageRange(slice) directly "imports" a slice.
|
||||
o to_slice() gives the equivalent slice.
|
||||
o str() and repr() allow printing.
|
||||
o indices(n) is like slice.indices(n).
|
||||
"""
|
||||
|
||||
def __init__(self, arg):
|
||||
"""
|
||||
Initialize with either a slice -- giving the equivalent page range,
|
||||
or a PageRange object -- making a copy,
|
||||
or a string like
|
||||
"int", "[int]:[int]" or "[int]:[int]:[int]",
|
||||
where the brackets indicate optional ints.
|
||||
{page_range_help}
|
||||
Note the difference between this notation and arguments to slice():
|
||||
slice(3) means the first three pages;
|
||||
PageRange("3") means the range of only the fourth page.
|
||||
However PageRange(slice(3)) means the first three pages.
|
||||
"""
|
||||
if isinstance(arg, slice):
|
||||
self._slice = arg
|
||||
return
|
||||
|
||||
if isinstance(arg, PageRange):
|
||||
self._slice = arg.to_slice()
|
||||
return
|
||||
|
||||
m = isString(arg) and re.match(PAGE_RANGE_RE, arg)
|
||||
if not m:
|
||||
raise ParseError(arg)
|
||||
elif m.group(2):
|
||||
# Special case: just an int means a range of one page.
|
||||
start = int(m.group(2))
|
||||
stop = start + 1 if start != -1 else None
|
||||
self._slice = slice(start, stop)
|
||||
else:
|
||||
self._slice = slice(*[int(g) if g else None
|
||||
for g in m.group(4, 6, 8)])
|
||||
|
||||
# Just formatting this when there is __doc__ for __init__
|
||||
if __init__.__doc__:
|
||||
__init__.__doc__ = __init__.__doc__.format(page_range_help=PAGE_RANGE_HELP)
|
||||
|
||||
@staticmethod
|
||||
def valid(input):
|
||||
""" True if input is a valid initializer for a PageRange. """
|
||||
return isinstance(input, slice) or \
|
||||
isinstance(input, PageRange) or \
|
||||
(isString(input)
|
||||
and bool(re.match(PAGE_RANGE_RE, input)))
|
||||
|
||||
def to_slice(self):
|
||||
""" Return the slice equivalent of this page range. """
|
||||
return self._slice
|
||||
|
||||
def __str__(self):
|
||||
""" A string like "1:2:3". """
|
||||
s = self._slice
|
||||
if s.step == None:
|
||||
if s.start != None and s.stop == s.start + 1:
|
||||
return str(s.start)
|
||||
|
||||
indices = s.start, s.stop
|
||||
else:
|
||||
indices = s.start, s.stop, s.step
|
||||
return ':'.join("" if i == None else str(i) for i in indices)
|
||||
|
||||
def __repr__(self):
|
||||
""" A string like "PageRange('1:2:3')". """
|
||||
return "PageRange(" + repr(str(self)) + ")"
|
||||
|
||||
def indices(self, n):
|
||||
"""
|
||||
n is the length of the list of pages to choose from.
|
||||
Returns arguments for range(). See help(slice.indices).
|
||||
"""
|
||||
return self._slice.indices(n)
|
||||
|
||||
|
||||
PAGE_RANGE_ALL = PageRange(":") # The range of all pages.
|
||||
|
||||
|
||||
def parse_filename_page_ranges(args):
|
||||
"""
|
||||
Given a list of filenames and page ranges, return a list of
|
||||
(filename, page_range) pairs.
|
||||
First arg must be a filename; other ags are filenames, page-range
|
||||
expressions, slice objects, or PageRange objects.
|
||||
A filename not followed by a page range indicates all pages of the file.
|
||||
"""
|
||||
pairs = []
|
||||
pdf_filename = None
|
||||
did_page_range = False
|
||||
for arg in args + [None]:
|
||||
if PageRange.valid(arg):
|
||||
if not pdf_filename:
|
||||
raise ValueError("The first argument must be a filename, " \
|
||||
"not a page range.")
|
||||
|
||||
pairs.append( (pdf_filename, PageRange(arg)) )
|
||||
did_page_range = True
|
||||
else:
|
||||
# New filename or end of list--do all of the previous file?
|
||||
if pdf_filename and not did_page_range:
|
||||
pairs.append( (pdf_filename, PAGE_RANGE_ALL) )
|
||||
|
||||
pdf_filename = arg
|
||||
did_page_range = False
|
||||
return pairs
|
3004
vendor/PyPDF2/pdf.py
vendored
3004
vendor/PyPDF2/pdf.py
vendored
File diff suppressed because it is too large
Load Diff
295
vendor/PyPDF2/utils.py
vendored
295
vendor/PyPDF2/utils.py
vendored
|
@ -1,295 +0,0 @@
|
|||
# Copyright (c) 2006, Mathieu Fenniak
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * The name of the author may not be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
Utility functions for PDF library.
|
||||
"""
|
||||
__author__ = "Mathieu Fenniak"
|
||||
__author_email__ = "biziqe@mathieu.fenniak.net"
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
try:
|
||||
import __builtin__ as builtins
|
||||
except ImportError: # Py3
|
||||
import builtins
|
||||
|
||||
|
||||
xrange_fn = getattr(builtins, "xrange", range)
|
||||
_basestring = getattr(builtins, "basestring", str)
|
||||
|
||||
bytes_type = type(bytes()) # Works the same in Python 2.X and 3.X
|
||||
string_type = getattr(builtins, "unicode", str)
|
||||
int_types = (int, long) if sys.version_info[0] < 3 else (int,)
|
||||
|
||||
|
||||
# Make basic type tests more consistent
|
||||
def isString(s):
|
||||
"""Test if arg is a string. Compatible with Python 2 and 3."""
|
||||
return isinstance(s, _basestring)
|
||||
|
||||
|
||||
def isInt(n):
|
||||
"""Test if arg is an int. Compatible with Python 2 and 3."""
|
||||
return isinstance(n, int_types)
|
||||
|
||||
|
||||
def isBytes(b):
|
||||
"""Test if arg is a bytes instance. Compatible with Python 2 and 3."""
|
||||
return isinstance(b, bytes_type)
|
||||
|
||||
|
||||
#custom implementation of warnings.formatwarning
|
||||
def formatWarning(message, category, filename, lineno, line=None):
|
||||
file = filename.replace("/", "\\").rsplit("\\", 1)[1] # find the file name
|
||||
return "%s: %s [%s:%s]\n" % (category.__name__, message, file, lineno)
|
||||
|
||||
|
||||
def readUntilWhitespace(stream, maxchars=None):
|
||||
"""
|
||||
Reads non-whitespace characters and returns them.
|
||||
Stops upon encountering whitespace or when maxchars is reached.
|
||||
"""
|
||||
txt = b_("")
|
||||
while True:
|
||||
tok = stream.read(1)
|
||||
if tok.isspace() or not tok:
|
||||
break
|
||||
txt += tok
|
||||
if len(txt) == maxchars:
|
||||
break
|
||||
return txt
|
||||
|
||||
|
||||
def readNonWhitespace(stream):
|
||||
"""
|
||||
Finds and reads the next non-whitespace character (ignores whitespace).
|
||||
"""
|
||||
tok = WHITESPACES[0]
|
||||
while tok in WHITESPACES:
|
||||
tok = stream.read(1)
|
||||
return tok
|
||||
|
||||
|
||||
def skipOverWhitespace(stream):
|
||||
"""
|
||||
Similar to readNonWhitespace, but returns a Boolean if more than
|
||||
one whitespace character was read.
|
||||
"""
|
||||
tok = WHITESPACES[0]
|
||||
cnt = 0;
|
||||
while tok in WHITESPACES:
|
||||
tok = stream.read(1)
|
||||
cnt+=1
|
||||
return (cnt > 1)
|
||||
|
||||
|
||||
def skipOverComment(stream):
|
||||
tok = stream.read(1)
|
||||
stream.seek(-1, 1)
|
||||
if tok == b_('%'):
|
||||
while tok not in (b_('\n'), b_('\r')):
|
||||
tok = stream.read(1)
|
||||
|
||||
|
||||
def readUntilRegex(stream, regex, ignore_eof=False):
|
||||
"""
|
||||
Reads until the regular expression pattern matched (ignore the match)
|
||||
Raise PdfStreamError on premature end-of-file.
|
||||
:param bool ignore_eof: If true, ignore end-of-line and return immediately
|
||||
"""
|
||||
name = b_('')
|
||||
while True:
|
||||
tok = stream.read(16)
|
||||
if not tok:
|
||||
# stream has truncated prematurely
|
||||
if ignore_eof == True:
|
||||
return name
|
||||
else:
|
||||
raise PdfStreamError("Stream has ended unexpectedly")
|
||||
m = regex.search(tok)
|
||||
if m is not None:
|
||||
name += tok[:m.start()]
|
||||
stream.seek(m.start()-len(tok), 1)
|
||||
break
|
||||
name += tok
|
||||
return name
|
||||
|
||||
|
||||
class ConvertFunctionsToVirtualList(object):
|
||||
def __init__(self, lengthFunction, getFunction):
|
||||
self.lengthFunction = lengthFunction
|
||||
self.getFunction = getFunction
|
||||
|
||||
def __len__(self):
|
||||
return self.lengthFunction()
|
||||
|
||||
def __getitem__(self, index):
|
||||
if isinstance(index, slice):
|
||||
indices = xrange_fn(*index.indices(len(self)))
|
||||
cls = type(self)
|
||||
return cls(indices.__len__, lambda idx: self[indices[idx]])
|
||||
if not isInt(index):
|
||||
raise TypeError("sequence indices must be integers")
|
||||
len_self = len(self)
|
||||
if index < 0:
|
||||
# support negative indexes
|
||||
index = len_self + index
|
||||
if index < 0 or index >= len_self:
|
||||
raise IndexError("sequence index out of range")
|
||||
return self.getFunction(index)
|
||||
|
||||
|
||||
def RC4_encrypt(key, plaintext):
|
||||
S = [i for i in range(256)]
|
||||
j = 0
|
||||
for i in range(256):
|
||||
j = (j + S[i] + ord_(key[i % len(key)])) % 256
|
||||
S[i], S[j] = S[j], S[i]
|
||||
i, j = 0, 0
|
||||
retval = b_("")
|
||||
for x in range(len(plaintext)):
|
||||
i = (i + 1) % 256
|
||||
j = (j + S[i]) % 256
|
||||
S[i], S[j] = S[j], S[i]
|
||||
t = S[(S[i] + S[j]) % 256]
|
||||
retval += b_(chr(ord_(plaintext[x]) ^ t))
|
||||
return retval
|
||||
|
||||
|
||||
def matrixMultiply(a, b):
|
||||
return [[sum([float(i)*float(j)
|
||||
for i, j in zip(row, col)]
|
||||
) for col in zip(*b)]
|
||||
for row in a]
|
||||
|
||||
|
||||
def markLocation(stream):
|
||||
"""Creates text file showing current location in context."""
|
||||
# Mainly for debugging
|
||||
RADIUS = 5000
|
||||
stream.seek(-RADIUS, 1)
|
||||
outputDoc = open('PyPDF2_pdfLocation.txt', 'w')
|
||||
outputDoc.write(stream.read(RADIUS))
|
||||
outputDoc.write('HERE')
|
||||
outputDoc.write(stream.read(RADIUS))
|
||||
outputDoc.close()
|
||||
stream.seek(-RADIUS, 1)
|
||||
|
||||
|
||||
class PyPdfError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PdfReadError(PyPdfError):
|
||||
pass
|
||||
|
||||
|
||||
class PageSizeNotDefinedError(PyPdfError):
|
||||
pass
|
||||
|
||||
|
||||
class PdfReadWarning(UserWarning):
|
||||
pass
|
||||
|
||||
|
||||
class PdfStreamError(PdfReadError):
|
||||
pass
|
||||
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
def b_(s):
|
||||
return s
|
||||
else:
|
||||
B_CACHE = {}
|
||||
|
||||
def b_(s):
|
||||
bc = B_CACHE
|
||||
if s in bc:
|
||||
return bc[s]
|
||||
if type(s) == bytes:
|
||||
return s
|
||||
else:
|
||||
r = s.encode('latin-1')
|
||||
if len(s) < 2:
|
||||
bc[s] = r
|
||||
return r
|
||||
|
||||
|
||||
def u_(s):
|
||||
if sys.version_info[0] < 3:
|
||||
return unicode(s, 'unicode_escape')
|
||||
else:
|
||||
return s
|
||||
|
||||
|
||||
def str_(b):
|
||||
if sys.version_info[0] < 3:
|
||||
return b
|
||||
else:
|
||||
if type(b) == bytes:
|
||||
return b.decode('latin-1')
|
||||
else:
|
||||
return b
|
||||
|
||||
|
||||
def ord_(b):
|
||||
if sys.version_info[0] < 3 or type(b) == str:
|
||||
return ord(b)
|
||||
else:
|
||||
return b
|
||||
|
||||
|
||||
def chr_(c):
|
||||
if sys.version_info[0] < 3:
|
||||
return c
|
||||
else:
|
||||
return chr(c)
|
||||
|
||||
|
||||
def barray(b):
|
||||
if sys.version_info[0] < 3:
|
||||
return b
|
||||
else:
|
||||
return bytearray(b)
|
||||
|
||||
|
||||
def hexencode(b):
|
||||
if sys.version_info[0] < 3:
|
||||
return b.encode('hex')
|
||||
else:
|
||||
import codecs
|
||||
coder = codecs.getencoder('hex_codec')
|
||||
return coder(b)[0]
|
||||
|
||||
|
||||
def hexStr(num):
|
||||
return hex(num).replace('L', '')
|
||||
|
||||
|
||||
WHITESPACES = [b_(x) for x in [' ', '\n', '\r', '\t', '\x00']]
|
358
vendor/PyPDF2/xmp.py
vendored
358
vendor/PyPDF2/xmp.py
vendored
|
@ -1,358 +0,0 @@
|
|||
import re
|
||||
import datetime
|
||||
import decimal
|
||||
from .generic import PdfObject
|
||||
from xml.dom import getDOMImplementation
|
||||
from xml.dom.minidom import parseString
|
||||
from .utils import u_
|
||||
|
||||
RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
DC_NAMESPACE = "http://purl.org/dc/elements/1.1/"
|
||||
XMP_NAMESPACE = "http://ns.adobe.com/xap/1.0/"
|
||||
PDF_NAMESPACE = "http://ns.adobe.com/pdf/1.3/"
|
||||
XMPMM_NAMESPACE = "http://ns.adobe.com/xap/1.0/mm/"
|
||||
|
||||
# What is the PDFX namespace, you might ask? I might ask that too. It's
|
||||
# a completely undocumented namespace used to place "custom metadata"
|
||||
# properties, which are arbitrary metadata properties with no semantic or
|
||||
# documented meaning. Elements in the namespace are key/value-style storage,
|
||||
# where the element name is the key and the content is the value. The keys
|
||||
# are transformed into valid XML identifiers by substituting an invalid
|
||||
# identifier character with \u2182 followed by the unicode hex ID of the
|
||||
# original character. A key like "my car" is therefore "my\u21820020car".
|
||||
#
|
||||
# \u2182, in case you're wondering, is the unicode character
|
||||
# \u{ROMAN NUMERAL TEN THOUSAND}, a straightforward and obvious choice for
|
||||
# escaping characters.
|
||||
#
|
||||
# Intentional users of the pdfx namespace should be shot on sight. A
|
||||
# custom data schema and sensical XML elements could be used instead, as is
|
||||
# suggested by Adobe's own documentation on XMP (under "Extensibility of
|
||||
# Schemas").
|
||||
#
|
||||
# Information presented here on the /pdfx/ schema is a result of limited
|
||||
# reverse engineering, and does not constitute a full specification.
|
||||
PDFX_NAMESPACE = "http://ns.adobe.com/pdfx/1.3/"
|
||||
|
||||
iso8601 = re.compile("""
|
||||
(?P<year>[0-9]{4})
|
||||
(-
|
||||
(?P<month>[0-9]{2})
|
||||
(-
|
||||
(?P<day>[0-9]+)
|
||||
(T
|
||||
(?P<hour>[0-9]{2}):
|
||||
(?P<minute>[0-9]{2})
|
||||
(:(?P<second>[0-9]{2}(.[0-9]+)?))?
|
||||
(?P<tzd>Z|[-+][0-9]{2}:[0-9]{2})
|
||||
)?
|
||||
)?
|
||||
)?
|
||||
""", re.VERBOSE)
|
||||
|
||||
|
||||
class XmpInformation(PdfObject):
|
||||
"""
|
||||
An object that represents Adobe XMP metadata.
|
||||
Usually accessed by :meth:`getXmpMetadata()<PyPDF2.PdfFileReader.getXmpMetadata>`
|
||||
"""
|
||||
|
||||
def __init__(self, stream):
|
||||
self.stream = stream
|
||||
docRoot = parseString(self.stream.getData())
|
||||
self.rdfRoot = docRoot.getElementsByTagNameNS(RDF_NAMESPACE, "RDF")[0]
|
||||
self.cache = {}
|
||||
|
||||
def writeToStream(self, stream, encryption_key):
|
||||
self.stream.writeToStream(stream, encryption_key)
|
||||
|
||||
def getElement(self, aboutUri, namespace, name):
|
||||
for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"):
|
||||
if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri:
|
||||
attr = desc.getAttributeNodeNS(namespace, name)
|
||||
if attr != None:
|
||||
yield attr
|
||||
for element in desc.getElementsByTagNameNS(namespace, name):
|
||||
yield element
|
||||
|
||||
def getNodesInNamespace(self, aboutUri, namespace):
|
||||
for desc in self.rdfRoot.getElementsByTagNameNS(RDF_NAMESPACE, "Description"):
|
||||
if desc.getAttributeNS(RDF_NAMESPACE, "about") == aboutUri:
|
||||
for i in range(desc.attributes.length):
|
||||
attr = desc.attributes.item(i)
|
||||
if attr.namespaceURI == namespace:
|
||||
yield attr
|
||||
for child in desc.childNodes:
|
||||
if child.namespaceURI == namespace:
|
||||
yield child
|
||||
|
||||
def _getText(self, element):
|
||||
text = ""
|
||||
for child in element.childNodes:
|
||||
if child.nodeType == child.TEXT_NODE:
|
||||
text += child.data
|
||||
return text
|
||||
|
||||
def _converter_string(value):
|
||||
return value
|
||||
|
||||
def _converter_date(value):
|
||||
m = iso8601.match(value)
|
||||
year = int(m.group("year"))
|
||||
month = int(m.group("month") or "1")
|
||||
day = int(m.group("day") or "1")
|
||||
hour = int(m.group("hour") or "0")
|
||||
minute = int(m.group("minute") or "0")
|
||||
second = decimal.Decimal(m.group("second") or "0")
|
||||
seconds = second.to_integral(decimal.ROUND_FLOOR)
|
||||
milliseconds = (second - seconds) * 1000000
|
||||
tzd = m.group("tzd") or "Z"
|
||||
dt = datetime.datetime(year, month, day, hour, minute, seconds, milliseconds)
|
||||
if tzd != "Z":
|
||||
tzd_hours, tzd_minutes = [int(x) for x in tzd.split(":")]
|
||||
tzd_hours *= -1
|
||||
if tzd_hours < 0:
|
||||
tzd_minutes *= -1
|
||||
dt = dt + datetime.timedelta(hours=tzd_hours, minutes=tzd_minutes)
|
||||
return dt
|
||||
_test_converter_date = staticmethod(_converter_date)
|
||||
|
||||
def _getter_bag(namespace, name, converter):
|
||||
def get(self):
|
||||
cached = self.cache.get(namespace, {}).get(name)
|
||||
if cached:
|
||||
return cached
|
||||
retval = []
|
||||
for element in self.getElement("", namespace, name):
|
||||
bags = element.getElementsByTagNameNS(RDF_NAMESPACE, "Bag")
|
||||
if len(bags):
|
||||
for bag in bags:
|
||||
for item in bag.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
|
||||
value = self._getText(item)
|
||||
value = converter(value)
|
||||
retval.append(value)
|
||||
ns_cache = self.cache.setdefault(namespace, {})
|
||||
ns_cache[name] = retval
|
||||
return retval
|
||||
return get
|
||||
|
||||
def _getter_seq(namespace, name, converter):
|
||||
def get(self):
|
||||
cached = self.cache.get(namespace, {}).get(name)
|
||||
if cached:
|
||||
return cached
|
||||
retval = []
|
||||
for element in self.getElement("", namespace, name):
|
||||
seqs = element.getElementsByTagNameNS(RDF_NAMESPACE, "Seq")
|
||||
if len(seqs):
|
||||
for seq in seqs:
|
||||
for item in seq.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
|
||||
value = self._getText(item)
|
||||
value = converter(value)
|
||||
retval.append(value)
|
||||
else:
|
||||
value = converter(self._getText(element))
|
||||
retval.append(value)
|
||||
ns_cache = self.cache.setdefault(namespace, {})
|
||||
ns_cache[name] = retval
|
||||
return retval
|
||||
return get
|
||||
|
||||
def _getter_langalt(namespace, name, converter):
|
||||
def get(self):
|
||||
cached = self.cache.get(namespace, {}).get(name)
|
||||
if cached:
|
||||
return cached
|
||||
retval = {}
|
||||
for element in self.getElement("", namespace, name):
|
||||
alts = element.getElementsByTagNameNS(RDF_NAMESPACE, "Alt")
|
||||
if len(alts):
|
||||
for alt in alts:
|
||||
for item in alt.getElementsByTagNameNS(RDF_NAMESPACE, "li"):
|
||||
value = self._getText(item)
|
||||
value = converter(value)
|
||||
retval[item.getAttribute("xml:lang")] = value
|
||||
else:
|
||||
retval["x-default"] = converter(self._getText(element))
|
||||
ns_cache = self.cache.setdefault(namespace, {})
|
||||
ns_cache[name] = retval
|
||||
return retval
|
||||
return get
|
||||
|
||||
def _getter_single(namespace, name, converter):
|
||||
def get(self):
|
||||
cached = self.cache.get(namespace, {}).get(name)
|
||||
if cached:
|
||||
return cached
|
||||
value = None
|
||||
for element in self.getElement("", namespace, name):
|
||||
if element.nodeType == element.ATTRIBUTE_NODE:
|
||||
value = element.nodeValue
|
||||
else:
|
||||
value = self._getText(element)
|
||||
break
|
||||
if value != None:
|
||||
value = converter(value)
|
||||
ns_cache = self.cache.setdefault(namespace, {})
|
||||
ns_cache[name] = value
|
||||
return value
|
||||
return get
|
||||
|
||||
dc_contributor = property(_getter_bag(DC_NAMESPACE, "contributor", _converter_string))
|
||||
"""
|
||||
Contributors to the resource (other than the authors). An unsorted
|
||||
array of names.
|
||||
"""
|
||||
|
||||
dc_coverage = property(_getter_single(DC_NAMESPACE, "coverage", _converter_string))
|
||||
"""
|
||||
Text describing the extent or scope of the resource.
|
||||
"""
|
||||
|
||||
dc_creator = property(_getter_seq(DC_NAMESPACE, "creator", _converter_string))
|
||||
"""
|
||||
A sorted array of names of the authors of the resource, listed in order
|
||||
of precedence.
|
||||
"""
|
||||
|
||||
dc_date = property(_getter_seq(DC_NAMESPACE, "date", _converter_date))
|
||||
"""
|
||||
A sorted array of dates (datetime.datetime instances) of signifigance to
|
||||
the resource. The dates and times are in UTC.
|
||||
"""
|
||||
|
||||
dc_description = property(_getter_langalt(DC_NAMESPACE, "description", _converter_string))
|
||||
"""
|
||||
A language-keyed dictionary of textual descriptions of the content of the
|
||||
resource.
|
||||
"""
|
||||
|
||||
dc_format = property(_getter_single(DC_NAMESPACE, "format", _converter_string))
|
||||
"""
|
||||
The mime-type of the resource.
|
||||
"""
|
||||
|
||||
dc_identifier = property(_getter_single(DC_NAMESPACE, "identifier", _converter_string))
|
||||
"""
|
||||
Unique identifier of the resource.
|
||||
"""
|
||||
|
||||
dc_language = property(_getter_bag(DC_NAMESPACE, "language", _converter_string))
|
||||
"""
|
||||
An unordered array specifying the languages used in the resource.
|
||||
"""
|
||||
|
||||
dc_publisher = property(_getter_bag(DC_NAMESPACE, "publisher", _converter_string))
|
||||
"""
|
||||
An unordered array of publisher names.
|
||||
"""
|
||||
|
||||
dc_relation = property(_getter_bag(DC_NAMESPACE, "relation", _converter_string))
|
||||
"""
|
||||
An unordered array of text descriptions of relationships to other
|
||||
documents.
|
||||
"""
|
||||
|
||||
dc_rights = property(_getter_langalt(DC_NAMESPACE, "rights", _converter_string))
|
||||
"""
|
||||
A language-keyed dictionary of textual descriptions of the rights the
|
||||
user has to this resource.
|
||||
"""
|
||||
|
||||
dc_source = property(_getter_single(DC_NAMESPACE, "source", _converter_string))
|
||||
"""
|
||||
Unique identifier of the work from which this resource was derived.
|
||||
"""
|
||||
|
||||
dc_subject = property(_getter_bag(DC_NAMESPACE, "subject", _converter_string))
|
||||
"""
|
||||
An unordered array of descriptive phrases or keywrods that specify the
|
||||
topic of the content of the resource.
|
||||
"""
|
||||
|
||||
dc_title = property(_getter_langalt(DC_NAMESPACE, "title", _converter_string))
|
||||
"""
|
||||
A language-keyed dictionary of the title of the resource.
|
||||
"""
|
||||
|
||||
dc_type = property(_getter_bag(DC_NAMESPACE, "type", _converter_string))
|
||||
"""
|
||||
An unordered array of textual descriptions of the document type.
|
||||
"""
|
||||
|
||||
pdf_keywords = property(_getter_single(PDF_NAMESPACE, "Keywords", _converter_string))
|
||||
"""
|
||||
An unformatted text string representing document keywords.
|
||||
"""
|
||||
|
||||
pdf_pdfversion = property(_getter_single(PDF_NAMESPACE, "PDFVersion", _converter_string))
|
||||
"""
|
||||
The PDF file version, for example 1.0, 1.3.
|
||||
"""
|
||||
|
||||
pdf_producer = property(_getter_single(PDF_NAMESPACE, "Producer", _converter_string))
|
||||
"""
|
||||
The name of the tool that created the PDF document.
|
||||
"""
|
||||
|
||||
xmp_createDate = property(_getter_single(XMP_NAMESPACE, "CreateDate", _converter_date))
|
||||
"""
|
||||
The date and time the resource was originally created. The date and
|
||||
time are returned as a UTC datetime.datetime object.
|
||||
"""
|
||||
|
||||
xmp_modifyDate = property(_getter_single(XMP_NAMESPACE, "ModifyDate", _converter_date))
|
||||
"""
|
||||
The date and time the resource was last modified. The date and time
|
||||
are returned as a UTC datetime.datetime object.
|
||||
"""
|
||||
|
||||
xmp_metadataDate = property(_getter_single(XMP_NAMESPACE, "MetadataDate", _converter_date))
|
||||
"""
|
||||
The date and time that any metadata for this resource was last
|
||||
changed. The date and time are returned as a UTC datetime.datetime
|
||||
object.
|
||||
"""
|
||||
|
||||
xmp_creatorTool = property(_getter_single(XMP_NAMESPACE, "CreatorTool", _converter_string))
|
||||
"""
|
||||
The name of the first known tool used to create the resource.
|
||||
"""
|
||||
|
||||
xmpmm_documentId = property(_getter_single(XMPMM_NAMESPACE, "DocumentID", _converter_string))
|
||||
"""
|
||||
The common identifier for all versions and renditions of this resource.
|
||||
"""
|
||||
|
||||
xmpmm_instanceId = property(_getter_single(XMPMM_NAMESPACE, "InstanceID", _converter_string))
|
||||
"""
|
||||
An identifier for a specific incarnation of a document, updated each
|
||||
time a file is saved.
|
||||
"""
|
||||
|
||||
def custom_properties(self):
|
||||
if not hasattr(self, "_custom_properties"):
|
||||
self._custom_properties = {}
|
||||
for node in self.getNodesInNamespace("", PDFX_NAMESPACE):
|
||||
key = node.localName
|
||||
while True:
|
||||
# see documentation about PDFX_NAMESPACE earlier in file
|
||||
idx = key.find(u_("\u2182"))
|
||||
if idx == -1:
|
||||
break
|
||||
key = key[:idx] + chr(int(key[idx+1:idx+5], base=16)) + key[idx+5:]
|
||||
if node.nodeType == node.ATTRIBUTE_NODE:
|
||||
value = node.nodeValue
|
||||
else:
|
||||
value = self._getText(node)
|
||||
self._custom_properties[key] = value
|
||||
return self._custom_properties
|
||||
|
||||
custom_properties = property(custom_properties)
|
||||
"""
|
||||
Retrieves custom metadata properties defined in the undocumented pdfx
|
||||
metadata schema.
|
||||
|
||||
:return: a dictionary of key/value items for custom metadata properties.
|
||||
:rtype: dict
|
||||
"""
|
1
vendor/_version.py
vendored
1
vendor/_version.py
vendored
|
@ -1 +0,0 @@
|
|||
__version__ = '5.0.6'
|
28
vendor/babel/AUTHORS
vendored
28
vendor/babel/AUTHORS
vendored
|
@ -1,28 +0,0 @@
|
|||
Babel is written and maintained by the Babel team and various contributors:
|
||||
|
||||
Maintainer and Current Project Lead:
|
||||
|
||||
- Armin Ronacher <armin.ronacher@active-4.com>
|
||||
|
||||
Contributors:
|
||||
|
||||
- Christopher Lenz <cmlenz@gmail.com>
|
||||
- Alex Morega <alex@grep.ro>
|
||||
- Felix Schwarz <felix.schwarz@oss.schwarz.eu>
|
||||
- Pedro Algarvio <pedro@algarvio.me>
|
||||
- Jeroen Ruigrok van der Werven <asmodai@in-nomine.org>
|
||||
- Philip Jenvey <pjenvey@underboss.org>
|
||||
- Tobias Bieniek <Tobias.Bieniek@gmx.de>
|
||||
- Jonas Borgström <jonas@edgewall.org>
|
||||
- Daniel Neuhäuser <dasdasich@gmail.com>
|
||||
- Nick Retallack <nick@bitcasa.com>
|
||||
- Thomas Waldmann <tw@waldmann-edv.de>
|
||||
- Lennart Regebro <regebro@gmail.com>
|
||||
|
||||
Babel was previously developed under the Copyright of Edgewall Software. The
|
||||
following copyright notice holds true for releases before 2013: "Copyright (c)
|
||||
2007 - 2011 by Edgewall Software"
|
||||
|
||||
In addition to the regular contributions Babel includes a fork of Lennart
|
||||
Regebro's tzlocal that originally was licensed under the CC0 license. The
|
||||
original copyright of that project is "Copyright 2013 by Lennart Regebro".
|
29
vendor/babel/LICENSE
vendored
29
vendor/babel/LICENSE
vendored
|
@ -1,29 +0,0 @@
|
|||
Copyright (C) 2013 by the Babel Team, see AUTHORS for more information.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
3. The name of the author may not be used to endorse or promote
|
||||
products derived from this software without specific prior
|
||||
written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS
|
||||
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
|
||||
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
24
vendor/babel/__init__.py
vendored
24
vendor/babel/__init__.py
vendored
|
@ -1,24 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
babel
|
||||
~~~~~
|
||||
|
||||
Integrated collection of utilities that assist in internationalizing and
|
||||
localizing applications.
|
||||
|
||||
This package is basically composed of two major parts:
|
||||
|
||||
* tools to build and work with ``gettext`` message catalogs
|
||||
* a Python interface to the CLDR (Common Locale Data Repository), providing
|
||||
access to various locale display names, localized number and date
|
||||
formatting, etc.
|
||||
|
||||
:copyright: (c) 2013 by the Babel Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from babel.core import UnknownLocaleError, Locale, default_locale, \
|
||||
negotiate_locale, parse_locale, get_locale_identifier
|
||||
|
||||
|
||||
__version__ = '1.3'
|
51
vendor/babel/_compat.py
vendored
51
vendor/babel/_compat.py
vendored
|
@ -1,51 +0,0 @@
|
|||
import sys
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
|
||||
_identity = lambda x: x
|
||||
|
||||
|
||||
if not PY2:
|
||||
text_type = str
|
||||
string_types = (str,)
|
||||
integer_types = (int, )
|
||||
unichr = chr
|
||||
|
||||
text_to_native = lambda s, enc: s
|
||||
|
||||
iterkeys = lambda d: iter(d.keys())
|
||||
itervalues = lambda d: iter(d.values())
|
||||
iteritems = lambda d: iter(d.items())
|
||||
|
||||
from io import StringIO, BytesIO
|
||||
import pickle
|
||||
|
||||
izip = zip
|
||||
imap = map
|
||||
range_type = range
|
||||
|
||||
cmp = lambda a, b: (a > b) - (a < b)
|
||||
|
||||
else:
|
||||
text_type = unicode
|
||||
string_types = (str, unicode)
|
||||
integer_types = (int, long)
|
||||
|
||||
text_to_native = lambda s, enc: s.encode(enc)
|
||||
unichr = unichr
|
||||
|
||||
iterkeys = lambda d: d.iterkeys()
|
||||
itervalues = lambda d: d.itervalues()
|
||||
iteritems = lambda d: d.iteritems()
|
||||
|
||||
from cStringIO import StringIO as BytesIO
|
||||
from StringIO import StringIO
|
||||
import cPickle as pickle
|
||||
|
||||
from itertools import izip, imap
|
||||
range_type = xrange
|
||||
|
||||
cmp = cmp
|
||||
|
||||
|
||||
number_types = integer_types + (float,)
|
941
vendor/babel/core.py
vendored
941
vendor/babel/core.py
vendored
|
@ -1,941 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
babel.core
|
||||
~~~~~~~~~~
|
||||
|
||||
Core locale representation and locale data access.
|
||||
|
||||
:copyright: (c) 2013 by the Babel Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from babel import localedata
|
||||
from babel._compat import pickle, string_types
|
||||
|
||||
__all__ = ['UnknownLocaleError', 'Locale', 'default_locale', 'negotiate_locale',
|
||||
'parse_locale']
|
||||
|
||||
|
||||
_global_data = None
|
||||
|
||||
|
||||
def _raise_no_data_error():
|
||||
raise RuntimeError('The babel data files are not available. '
|
||||
'This usually happens because you are using '
|
||||
'a source checkout from Babel and you did '
|
||||
'not build the data files. Just make sure '
|
||||
'to run "python setup.py import_cldr" before '
|
||||
'installing the library.')
|
||||
|
||||
|
||||
def get_global(key):
|
||||
"""Return the dictionary for the given key in the global data.
|
||||
|
||||
The global data is stored in the ``babel/global.dat`` file and contains
|
||||
information independent of individual locales.
|
||||
|
||||
>>> get_global('zone_aliases')['UTC']
|
||||
u'Etc/GMT'
|
||||
>>> get_global('zone_territories')['Europe/Berlin']
|
||||
u'DE'
|
||||
|
||||
.. versionadded:: 0.9
|
||||
|
||||
:param key: the data key
|
||||
"""
|
||||
global _global_data
|
||||
if _global_data is None:
|
||||
dirname = os.path.join(os.path.dirname(__file__))
|
||||
filename = os.path.join(dirname, 'global.dat')
|
||||
if not os.path.isfile(filename):
|
||||
_raise_no_data_error()
|
||||
fileobj = open(filename, 'rb')
|
||||
try:
|
||||
_global_data = pickle.load(fileobj)
|
||||
finally:
|
||||
fileobj.close()
|
||||
return _global_data.get(key, {})
|
||||
|
||||
|
||||
LOCALE_ALIASES = {
|
||||
'ar': 'ar_SY', 'bg': 'bg_BG', 'bs': 'bs_BA', 'ca': 'ca_ES', 'cs': 'cs_CZ',
|
||||
'da': 'da_DK', 'de': 'de_DE', 'el': 'el_GR', 'en': 'en_US', 'es': 'es_ES',
|
||||
'et': 'et_EE', 'fa': 'fa_IR', 'fi': 'fi_FI', 'fr': 'fr_FR', 'gl': 'gl_ES',
|
||||
'he': 'he_IL', 'hu': 'hu_HU', 'id': 'id_ID', 'is': 'is_IS', 'it': 'it_IT',
|
||||
'ja': 'ja_JP', 'km': 'km_KH', 'ko': 'ko_KR', 'lt': 'lt_LT', 'lv': 'lv_LV',
|
||||
'mk': 'mk_MK', 'nl': 'nl_NL', 'nn': 'nn_NO', 'no': 'nb_NO', 'pl': 'pl_PL',
|
||||
'pt': 'pt_PT', 'ro': 'ro_RO', 'ru': 'ru_RU', 'sk': 'sk_SK', 'sl': 'sl_SI',
|
||||
'sv': 'sv_SE', 'th': 'th_TH', 'tr': 'tr_TR', 'uk': 'uk_UA'
|
||||
}
|
||||
|
||||
|
||||
class UnknownLocaleError(Exception):
|
||||
"""Exception thrown when a locale is requested for which no locale data
|
||||
is available.
|
||||
"""
|
||||
|
||||
def __init__(self, identifier):
|
||||
"""Create the exception.
|
||||
|
||||
:param identifier: the identifier string of the unsupported locale
|
||||
"""
|
||||
Exception.__init__(self, 'unknown locale %r' % identifier)
|
||||
|
||||
#: The identifier of the locale that could not be found.
|
||||
self.identifier = identifier
|
||||
|
||||
|
||||
class Locale(object):
|
||||
"""Representation of a specific locale.
|
||||
|
||||
>>> locale = Locale('en', 'US')
|
||||
>>> repr(locale)
|
||||
"Locale('en', territory='US')"
|
||||
>>> locale.display_name
|
||||
u'English (United States)'
|
||||
|
||||
A `Locale` object can also be instantiated from a raw locale string:
|
||||
|
||||
>>> locale = Locale.parse('en-US', sep='-')
|
||||
>>> repr(locale)
|
||||
"Locale('en', territory='US')"
|
||||
|
||||
`Locale` objects provide access to a collection of locale data, such as
|
||||
territory and language names, number and date format patterns, and more:
|
||||
|
||||
>>> locale.number_symbols['decimal']
|
||||
u'.'
|
||||
|
||||
If a locale is requested for which no locale data is available, an
|
||||
`UnknownLocaleError` is raised:
|
||||
|
||||
>>> Locale.parse('en_DE')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
UnknownLocaleError: unknown locale 'en_DE'
|
||||
|
||||
For more information see :rfc:`3066`.
|
||||
"""
|
||||
|
||||
def __init__(self, language, territory=None, script=None, variant=None):
|
||||
"""Initialize the locale object from the given identifier components.
|
||||
|
||||
>>> locale = Locale('en', 'US')
|
||||
>>> locale.language
|
||||
'en'
|
||||
>>> locale.territory
|
||||
'US'
|
||||
|
||||
:param language: the language code
|
||||
:param territory: the territory (country or region) code
|
||||
:param script: the script code
|
||||
:param variant: the variant code
|
||||
:raise `UnknownLocaleError`: if no locale data is available for the
|
||||
requested locale
|
||||
"""
|
||||
#: the language code
|
||||
self.language = language
|
||||
#: the territory (country or region) code
|
||||
self.territory = territory
|
||||
#: the script code
|
||||
self.script = script
|
||||
#: the variant code
|
||||
self.variant = variant
|
||||
self.__data = None
|
||||
|
||||
identifier = str(self)
|
||||
if not localedata.exists(identifier):
|
||||
raise UnknownLocaleError(identifier)
|
||||
|
||||
@classmethod
|
||||
def default(cls, category=None, aliases=LOCALE_ALIASES):
|
||||
"""Return the system default locale for the specified category.
|
||||
|
||||
>>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']:
|
||||
... os.environ[name] = ''
|
||||
>>> os.environ['LANG'] = 'fr_FR.UTF-8'
|
||||
>>> Locale.default('LC_MESSAGES')
|
||||
Locale('fr', territory='FR')
|
||||
|
||||
The following fallbacks to the variable are always considered:
|
||||
|
||||
- ``LANGUAGE``
|
||||
- ``LC_ALL``
|
||||
- ``LC_CTYPE``
|
||||
- ``LANG``
|
||||
|
||||
:param category: one of the ``LC_XXX`` environment variable names
|
||||
:param aliases: a dictionary of aliases for locale identifiers
|
||||
"""
|
||||
# XXX: use likely subtag expansion here instead of the
|
||||
# aliases dictionary.
|
||||
locale_string = default_locale(category, aliases=aliases)
|
||||
return cls.parse(locale_string)
|
||||
|
||||
@classmethod
|
||||
def negotiate(cls, preferred, available, sep='_', aliases=LOCALE_ALIASES):
|
||||
"""Find the best match between available and requested locale strings.
|
||||
|
||||
>>> Locale.negotiate(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
|
||||
Locale('de', territory='DE')
|
||||
>>> Locale.negotiate(['de_DE', 'en_US'], ['en', 'de'])
|
||||
Locale('de')
|
||||
>>> Locale.negotiate(['de_DE', 'de'], ['en_US'])
|
||||
|
||||
You can specify the character used in the locale identifiers to separate
|
||||
the differnet components. This separator is applied to both lists. Also,
|
||||
case is ignored in the comparison:
|
||||
|
||||
>>> Locale.negotiate(['de-DE', 'de'], ['en-us', 'de-de'], sep='-')
|
||||
Locale('de', territory='DE')
|
||||
|
||||
:param preferred: the list of locale identifers preferred by the user
|
||||
:param available: the list of locale identifiers available
|
||||
:param aliases: a dictionary of aliases for locale identifiers
|
||||
"""
|
||||
identifier = negotiate_locale(preferred, available, sep=sep,
|
||||
aliases=aliases)
|
||||
if identifier:
|
||||
return Locale.parse(identifier, sep=sep)
|
||||
|
||||
@classmethod
|
||||
def parse(cls, identifier, sep='_', resolve_likely_subtags=True):
|
||||
"""Create a `Locale` instance for the given locale identifier.
|
||||
|
||||
>>> l = Locale.parse('de-DE', sep='-')
|
||||
>>> l.display_name
|
||||
u'Deutsch (Deutschland)'
|
||||
|
||||
If the `identifier` parameter is not a string, but actually a `Locale`
|
||||
object, that object is returned:
|
||||
|
||||
>>> Locale.parse(l)
|
||||
Locale('de', territory='DE')
|
||||
|
||||
This also can perform resolving of likely subtags which it does
|
||||
by default. This is for instance useful to figure out the most
|
||||
likely locale for a territory you can use ``'und'`` as the
|
||||
language tag:
|
||||
|
||||
>>> Locale.parse('und_AT')
|
||||
Locale('de', territory='AT')
|
||||
|
||||
:param identifier: the locale identifier string
|
||||
:param sep: optional component separator
|
||||
:param resolve_likely_subtags: if this is specified then a locale will
|
||||
have its likely subtag resolved if the
|
||||
locale otherwise does not exist. For
|
||||
instance ``zh_TW`` by itself is not a
|
||||
locale that exists but Babel can
|
||||
automatically expand it to the full
|
||||
form of ``zh_hant_TW``. Note that this
|
||||
expansion is only taking place if no
|
||||
locale exists otherwise. For instance
|
||||
there is a locale ``en`` that can exist
|
||||
by itself.
|
||||
:raise `ValueError`: if the string does not appear to be a valid locale
|
||||
identifier
|
||||
:raise `UnknownLocaleError`: if no locale data is available for the
|
||||
requested locale
|
||||
"""
|
||||
if identifier is None:
|
||||
return None
|
||||
elif isinstance(identifier, Locale):
|
||||
return identifier
|
||||
elif not isinstance(identifier, string_types):
|
||||
raise TypeError('Unxpected value for identifier: %r' % (identifier,))
|
||||
|
||||
parts = parse_locale(identifier, sep=sep)
|
||||
input_id = get_locale_identifier(parts)
|
||||
|
||||
def _try_load(parts):
|
||||
try:
|
||||
return cls(*parts)
|
||||
except UnknownLocaleError:
|
||||
return None
|
||||
|
||||
def _try_load_reducing(parts):
|
||||
# Success on first hit, return it.
|
||||
locale = _try_load(parts)
|
||||
if locale is not None:
|
||||
return locale
|
||||
|
||||
# Now try without script and variant
|
||||
locale = _try_load(parts[:2])
|
||||
if locale is not None:
|
||||
return locale
|
||||
|
||||
locale = _try_load(parts)
|
||||
if locale is not None:
|
||||
return locale
|
||||
if not resolve_likely_subtags:
|
||||
raise UnknownLocaleError(input_id)
|
||||
|
||||
# From here onwards is some very bad likely subtag resolving. This
|
||||
# whole logic is not entirely correct but good enough (tm) for the
|
||||
# time being. This has been added so that zh_TW does not cause
|
||||
# errors for people when they upgrade. Later we should properly
|
||||
# implement ICU like fuzzy locale objects and provide a way to
|
||||
# maximize and minimize locale tags.
|
||||
|
||||
language, territory, script, variant = parts
|
||||
language = get_global('language_aliases').get(language, language)
|
||||
territory = get_global('territory_aliases').get(territory, territory)
|
||||
script = get_global('script_aliases').get(script, script)
|
||||
variant = get_global('variant_aliases').get(variant, variant)
|
||||
|
||||
if territory == 'ZZ':
|
||||
territory = None
|
||||
if script == 'Zzzz':
|
||||
script = None
|
||||
|
||||
parts = language, territory, script, variant
|
||||
|
||||
# First match: try the whole identifier
|
||||
new_id = get_locale_identifier(parts)
|
||||
likely_subtag = get_global('likely_subtags').get(new_id)
|
||||
if likely_subtag is not None:
|
||||
locale = _try_load_reducing(parse_locale(likely_subtag))
|
||||
if locale is not None:
|
||||
return locale
|
||||
|
||||
# If we did not find anything so far, try again with a
|
||||
# simplified identifier that is just the language
|
||||
likely_subtag = get_global('likely_subtags').get(language)
|
||||
if likely_subtag is not None:
|
||||
language2, _, script2, variant2 = parse_locale(likely_subtag)
|
||||
locale = _try_load_reducing((language2, territory, script2, variant2))
|
||||
if locale is not None:
|
||||
return locale
|
||||
|
||||
raise UnknownLocaleError(input_id)
|
||||
|
||||
def __eq__(self, other):
|
||||
for key in ('language', 'territory', 'script', 'variant'):
|
||||
if not hasattr(other, key):
|
||||
return False
|
||||
return (self.language == other.language) and \
|
||||
(self.territory == other.territory) and \
|
||||
(self.script == other.script) and \
|
||||
(self.variant == other.variant)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __repr__(self):
|
||||
parameters = ['']
|
||||
for key in ('territory', 'script', 'variant'):
|
||||
value = getattr(self, key)
|
||||
if value is not None:
|
||||
parameters.append('%s=%r' % (key, value))
|
||||
parameter_string = '%r' % self.language + ', '.join(parameters)
|
||||
return 'Locale(%s)' % parameter_string
|
||||
|
||||
def __str__(self):
|
||||
return get_locale_identifier((self.language, self.territory,
|
||||
self.script, self.variant))
|
||||
|
||||
@property
|
||||
def _data(self):
|
||||
if self.__data is None:
|
||||
self.__data = localedata.LocaleDataDict(localedata.load(str(self)))
|
||||
return self.__data
|
||||
|
||||
def get_display_name(self, locale=None):
|
||||
"""Return the display name of the locale using the given locale.
|
||||
|
||||
The display name will include the language, territory, script, and
|
||||
variant, if those are specified.
|
||||
|
||||
>>> Locale('zh', 'CN', script='Hans').get_display_name('en')
|
||||
u'Chinese (Simplified, China)'
|
||||
|
||||
:param locale: the locale to use
|
||||
"""
|
||||
if locale is None:
|
||||
locale = self
|
||||
locale = Locale.parse(locale)
|
||||
retval = locale.languages.get(self.language)
|
||||
if self.territory or self.script or self.variant:
|
||||
details = []
|
||||
if self.script:
|
||||
details.append(locale.scripts.get(self.script))
|
||||
if self.territory:
|
||||
details.append(locale.territories.get(self.territory))
|
||||
if self.variant:
|
||||
details.append(locale.variants.get(self.variant))
|
||||
details = filter(None, details)
|
||||
if details:
|
||||
retval += ' (%s)' % u', '.join(details)
|
||||
return retval
|
||||
|
||||
display_name = property(get_display_name, doc="""\
|
||||
The localized display name of the locale.
|
||||
|
||||
>>> Locale('en').display_name
|
||||
u'English'
|
||||
>>> Locale('en', 'US').display_name
|
||||
u'English (United States)'
|
||||
>>> Locale('sv').display_name
|
||||
u'svenska'
|
||||
|
||||
:type: `unicode`
|
||||
""")
|
||||
|
||||
def get_language_name(self, locale=None):
|
||||
"""Return the language of this locale in the given locale.
|
||||
|
||||
>>> Locale('zh', 'CN', script='Hans').get_language_name('de')
|
||||
u'Chinesisch'
|
||||
|
||||
.. versionadded:: 1.0
|
||||
|
||||
:param locale: the locale to use
|
||||
"""
|
||||
if locale is None:
|
||||
locale = self
|
||||
locale = Locale.parse(locale)
|
||||
return locale.languages.get(self.language)
|
||||
|
||||
language_name = property(get_language_name, doc="""\
|
||||
The localized language name of the locale.
|
||||
|
||||
>>> Locale('en', 'US').language_name
|
||||
u'English'
|
||||
""")
|
||||
|
||||
def get_territory_name(self, locale=None):
|
||||
"""Return the territory name in the given locale."""
|
||||
if locale is None:
|
||||
locale = self
|
||||
locale = Locale.parse(locale)
|
||||
return locale.territories.get(self.territory)
|
||||
|
||||
territory_name = property(get_territory_name, doc="""\
|
||||
The localized territory name of the locale if available.
|
||||
|
||||
>>> Locale('de', 'DE').territory_name
|
||||
u'Deutschland'
|
||||
""")
|
||||
|
||||
def get_script_name(self, locale=None):
|
||||
"""Return the script name in the given locale."""
|
||||
if locale is None:
|
||||
locale = self
|
||||
locale = Locale.parse(locale)
|
||||
return locale.scripts.get(self.script)
|
||||
|
||||
script_name = property(get_script_name, doc="""\
|
||||
The localized script name of the locale if available.
|
||||
|
||||
>>> Locale('ms', 'SG', script='Latn').script_name
|
||||
u'Latin'
|
||||
""")
|
||||
|
||||
@property
|
||||
def english_name(self):
|
||||
"""The english display name of the locale.
|
||||
|
||||
>>> Locale('de').english_name
|
||||
u'German'
|
||||
>>> Locale('de', 'DE').english_name
|
||||
u'German (Germany)'
|
||||
|
||||
:type: `unicode`"""
|
||||
return self.get_display_name(Locale('en'))
|
||||
|
||||
#{ General Locale Display Names
|
||||
|
||||
@property
|
||||
def languages(self):
|
||||
"""Mapping of language codes to translated language names.
|
||||
|
||||
>>> Locale('de', 'DE').languages['ja']
|
||||
u'Japanisch'
|
||||
|
||||
See `ISO 639 <http://www.loc.gov/standards/iso639-2/>`_ for
|
||||
more information.
|
||||
"""
|
||||
return self._data['languages']
|
||||
|
||||
@property
|
||||
def scripts(self):
|
||||
"""Mapping of script codes to translated script names.
|
||||
|
||||
>>> Locale('en', 'US').scripts['Hira']
|
||||
u'Hiragana'
|
||||
|
||||
See `ISO 15924 <http://www.evertype.com/standards/iso15924/>`_
|
||||
for more information.
|
||||
"""
|
||||
return self._data['scripts']
|
||||
|
||||
@property
|
||||
def territories(self):
|
||||
"""Mapping of script codes to translated script names.
|
||||
|
||||
>>> Locale('es', 'CO').territories['DE']
|
||||
u'Alemania'
|
||||
|
||||
See `ISO 3166 <http://www.iso.org/iso/en/prods-services/iso3166ma/>`_
|
||||
for more information.
|
||||
"""
|
||||
return self._data['territories']
|
||||
|
||||
@property
|
||||
def variants(self):
|
||||
"""Mapping of script codes to translated script names.
|
||||
|
||||
>>> Locale('de', 'DE').variants['1901']
|
||||
u'Alte deutsche Rechtschreibung'
|
||||
"""
|
||||
return self._data['variants']
|
||||
|
||||
#{ Number Formatting
|
||||
|
||||
@property
|
||||
def currencies(self):
|
||||
"""Mapping of currency codes to translated currency names. This
|
||||
only returns the generic form of the currency name, not the count
|
||||
specific one. If an actual number is requested use the
|
||||
:func:`babel.numbers.get_currency_name` function.
|
||||
|
||||
>>> Locale('en').currencies['COP']
|
||||
u'Colombian Peso'
|
||||
>>> Locale('de', 'DE').currencies['COP']
|
||||
u'Kolumbianischer Peso'
|
||||
"""
|
||||
return self._data['currency_names']
|
||||
|
||||
@property
|
||||
def currency_symbols(self):
|
||||
"""Mapping of currency codes to symbols.
|
||||
|
||||
>>> Locale('en', 'US').currency_symbols['USD']
|
||||
u'$'
|
||||
>>> Locale('es', 'CO').currency_symbols['USD']
|
||||
u'US$'
|
||||
"""
|
||||
return self._data['currency_symbols']
|
||||
|
||||
@property
|
||||
def number_symbols(self):
|
||||
"""Symbols used in number formatting.
|
||||
|
||||
>>> Locale('fr', 'FR').number_symbols['decimal']
|
||||
u','
|
||||
"""
|
||||
return self._data['number_symbols']
|
||||
|
||||
@property
|
||||
def decimal_formats(self):
|
||||
"""Locale patterns for decimal number formatting.
|
||||
|
||||
>>> Locale('en', 'US').decimal_formats[None]
|
||||
<NumberPattern u'#,##0.###'>
|
||||
"""
|
||||
return self._data['decimal_formats']
|
||||
|
||||
@property
|
||||
def currency_formats(self):
|
||||
"""Locale patterns for currency number formatting.
|
||||
|
||||
>>> print Locale('en', 'US').currency_formats[None]
|
||||
<NumberPattern u'\\xa4#,##0.00'>
|
||||
"""
|
||||
return self._data['currency_formats']
|
||||
|
||||
@property
|
||||
def percent_formats(self):
|
||||
"""Locale patterns for percent number formatting.
|
||||
|
||||
>>> Locale('en', 'US').percent_formats[None]
|
||||
<NumberPattern u'#,##0%'>
|
||||
"""
|
||||
return self._data['percent_formats']
|
||||
|
||||
@property
|
||||
def scientific_formats(self):
|
||||
"""Locale patterns for scientific number formatting.
|
||||
|
||||
>>> Locale('en', 'US').scientific_formats[None]
|
||||
<NumberPattern u'#E0'>
|
||||
"""
|
||||
return self._data['scientific_formats']
|
||||
|
||||
#{ Calendar Information and Date Formatting
|
||||
|
||||
@property
|
||||
def periods(self):
|
||||
"""Locale display names for day periods (AM/PM).
|
||||
|
||||
>>> Locale('en', 'US').periods['am']
|
||||
u'AM'
|
||||
"""
|
||||
return self._data['periods']
|
||||
|
||||
@property
|
||||
def days(self):
|
||||
"""Locale display names for weekdays.
|
||||
|
||||
>>> Locale('de', 'DE').days['format']['wide'][3]
|
||||
u'Donnerstag'
|
||||
"""
|
||||
return self._data['days']
|
||||
|
||||
@property
|
||||
def months(self):
|
||||
"""Locale display names for months.
|
||||
|
||||
>>> Locale('de', 'DE').months['format']['wide'][10]
|
||||
u'Oktober'
|
||||
"""
|
||||
return self._data['months']
|
||||
|
||||
@property
|
||||
def quarters(self):
|
||||
"""Locale display names for quarters.
|
||||
|
||||
>>> Locale('de', 'DE').quarters['format']['wide'][1]
|
||||
u'1. Quartal'
|
||||
"""
|
||||
return self._data['quarters']
|
||||
|
||||
@property
|
||||
def eras(self):
|
||||
"""Locale display names for eras.
|
||||
|
||||
>>> Locale('en', 'US').eras['wide'][1]
|
||||
u'Anno Domini'
|
||||
>>> Locale('en', 'US').eras['abbreviated'][0]
|
||||
u'BC'
|
||||
"""
|
||||
return self._data['eras']
|
||||
|
||||
@property
|
||||
def time_zones(self):
|
||||
"""Locale display names for time zones.
|
||||
|
||||
>>> Locale('en', 'US').time_zones['Europe/London']['long']['daylight']
|
||||
u'British Summer Time'
|
||||
>>> Locale('en', 'US').time_zones['America/St_Johns']['city']
|
||||
u'St. John\u2019s'
|
||||
"""
|
||||
return self._data['time_zones']
|
||||
|
||||
@property
|
||||
def meta_zones(self):
|
||||
"""Locale display names for meta time zones.
|
||||
|
||||
Meta time zones are basically groups of different Olson time zones that
|
||||
have the same GMT offset and daylight savings time.
|
||||
|
||||
>>> Locale('en', 'US').meta_zones['Europe_Central']['long']['daylight']
|
||||
u'Central European Summer Time'
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
return self._data['meta_zones']
|
||||
|
||||
@property
|
||||
def zone_formats(self):
|
||||
"""Patterns related to the formatting of time zones.
|
||||
|
||||
>>> Locale('en', 'US').zone_formats['fallback']
|
||||
u'%(1)s (%(0)s)'
|
||||
>>> Locale('pt', 'BR').zone_formats['region']
|
||||
u'Hor\\xe1rio %s'
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
return self._data['zone_formats']
|
||||
|
||||
@property
|
||||
def first_week_day(self):
|
||||
"""The first day of a week, with 0 being Monday.
|
||||
|
||||
>>> Locale('de', 'DE').first_week_day
|
||||
0
|
||||
>>> Locale('en', 'US').first_week_day
|
||||
6
|
||||
"""
|
||||
return self._data['week_data']['first_day']
|
||||
|
||||
@property
|
||||
def weekend_start(self):
|
||||
"""The day the weekend starts, with 0 being Monday.
|
||||
|
||||
>>> Locale('de', 'DE').weekend_start
|
||||
5
|
||||
"""
|
||||
return self._data['week_data']['weekend_start']
|
||||
|
||||
@property
|
||||
def weekend_end(self):
|
||||
"""The day the weekend ends, with 0 being Monday.
|
||||
|
||||
>>> Locale('de', 'DE').weekend_end
|
||||
6
|
||||
"""
|
||||
return self._data['week_data']['weekend_end']
|
||||
|
||||
@property
|
||||
def min_week_days(self):
|
||||
"""The minimum number of days in a week so that the week is counted as
|
||||
the first week of a year or month.
|
||||
|
||||
>>> Locale('de', 'DE').min_week_days
|
||||
4
|
||||
"""
|
||||
return self._data['week_data']['min_days']
|
||||
|
||||
@property
|
||||
def date_formats(self):
|
||||
"""Locale patterns for date formatting.
|
||||
|
||||
>>> Locale('en', 'US').date_formats['short']
|
||||
<DateTimePattern u'M/d/yy'>
|
||||
>>> Locale('fr', 'FR').date_formats['long']
|
||||
<DateTimePattern u'd MMMM y'>
|
||||
"""
|
||||
return self._data['date_formats']
|
||||
|
||||
@property
|
||||
def time_formats(self):
|
||||
"""Locale patterns for time formatting.
|
||||
|
||||
>>> Locale('en', 'US').time_formats['short']
|
||||
<DateTimePattern u'h:mm a'>
|
||||
>>> Locale('fr', 'FR').time_formats['long']
|
||||
<DateTimePattern u'HH:mm:ss z'>
|
||||
"""
|
||||
return self._data['time_formats']
|
||||
|
||||
@property
|
||||
def datetime_formats(self):
|
||||
"""Locale patterns for datetime formatting.
|
||||
|
||||
>>> Locale('en').datetime_formats['full']
|
||||
u"{1} 'at' {0}"
|
||||
>>> Locale('th').datetime_formats['medium']
|
||||
u'{1}, {0}'
|
||||
"""
|
||||
return self._data['datetime_formats']
|
||||
|
||||
@property
|
||||
def plural_form(self):
|
||||
"""Plural rules for the locale.
|
||||
|
||||
>>> Locale('en').plural_form(1)
|
||||
'one'
|
||||
>>> Locale('en').plural_form(0)
|
||||
'other'
|
||||
>>> Locale('fr').plural_form(0)
|
||||
'one'
|
||||
>>> Locale('ru').plural_form(100)
|
||||
'many'
|
||||
"""
|
||||
return self._data['plural_form']
|
||||
|
||||
|
||||
def default_locale(category=None, aliases=LOCALE_ALIASES):
|
||||
"""Returns the system default locale for a given category, based on
|
||||
environment variables.
|
||||
|
||||
>>> for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE']:
|
||||
... os.environ[name] = ''
|
||||
>>> os.environ['LANG'] = 'fr_FR.UTF-8'
|
||||
>>> default_locale('LC_MESSAGES')
|
||||
'fr_FR'
|
||||
|
||||
The "C" or "POSIX" pseudo-locales are treated as aliases for the
|
||||
"en_US_POSIX" locale:
|
||||
|
||||
>>> os.environ['LC_MESSAGES'] = 'POSIX'
|
||||
>>> default_locale('LC_MESSAGES')
|
||||
'en_US_POSIX'
|
||||
|
||||
The following fallbacks to the variable are always considered:
|
||||
|
||||
- ``LANGUAGE``
|
||||
- ``LC_ALL``
|
||||
- ``LC_CTYPE``
|
||||
- ``LANG``
|
||||
|
||||
:param category: one of the ``LC_XXX`` environment variable names
|
||||
:param aliases: a dictionary of aliases for locale identifiers
|
||||
"""
|
||||
varnames = (category, 'LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LANG')
|
||||
for name in filter(None, varnames):
|
||||
locale = os.getenv(name)
|
||||
if locale:
|
||||
if name == 'LANGUAGE' and ':' in locale:
|
||||
# the LANGUAGE variable may contain a colon-separated list of
|
||||
# language codes; we just pick the language on the list
|
||||
locale = locale.split(':')[0]
|
||||
if locale in ('C', 'POSIX'):
|
||||
locale = 'en_US_POSIX'
|
||||
elif aliases and locale in aliases:
|
||||
locale = aliases[locale]
|
||||
try:
|
||||
return get_locale_identifier(parse_locale(locale))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
def negotiate_locale(preferred, available, sep='_', aliases=LOCALE_ALIASES):
|
||||
"""Find the best match between available and requested locale strings.
|
||||
|
||||
>>> negotiate_locale(['de_DE', 'en_US'], ['de_DE', 'de_AT'])
|
||||
'de_DE'
|
||||
>>> negotiate_locale(['de_DE', 'en_US'], ['en', 'de'])
|
||||
'de'
|
||||
|
||||
Case is ignored by the algorithm, the result uses the case of the preferred
|
||||
locale identifier:
|
||||
|
||||
>>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
|
||||
'de_DE'
|
||||
|
||||
>>> negotiate_locale(['de_DE', 'en_US'], ['de_de', 'de_at'])
|
||||
'de_DE'
|
||||
|
||||
By default, some web browsers unfortunately do not include the territory
|
||||
in the locale identifier for many locales, and some don't even allow the
|
||||
user to easily add the territory. So while you may prefer using qualified
|
||||
locale identifiers in your web-application, they would not normally match
|
||||
the language-only locale sent by such browsers. To workaround that, this
|
||||
function uses a default mapping of commonly used langauge-only locale
|
||||
identifiers to identifiers including the territory:
|
||||
|
||||
>>> negotiate_locale(['ja', 'en_US'], ['ja_JP', 'en_US'])
|
||||
'ja_JP'
|
||||
|
||||
Some browsers even use an incorrect or outdated language code, such as "no"
|
||||
for Norwegian, where the correct locale identifier would actually be "nb_NO"
|
||||
(Bokmål) or "nn_NO" (Nynorsk). The aliases are intended to take care of
|
||||
such cases, too:
|
||||
|
||||
>>> negotiate_locale(['no', 'sv'], ['nb_NO', 'sv_SE'])
|
||||
'nb_NO'
|
||||
|
||||
You can override this default mapping by passing a different `aliases`
|
||||
dictionary to this function, or you can bypass the behavior althogher by
|
||||
setting the `aliases` parameter to `None`.
|
||||
|
||||
:param preferred: the list of locale strings preferred by the user
|
||||
:param available: the list of locale strings available
|
||||
:param sep: character that separates the different parts of the locale
|
||||
strings
|
||||
:param aliases: a dictionary of aliases for locale identifiers
|
||||
"""
|
||||
available = [a.lower() for a in available if a]
|
||||
for locale in preferred:
|
||||
ll = locale.lower()
|
||||
if ll in available:
|
||||
return locale
|
||||
if aliases:
|
||||
alias = aliases.get(ll)
|
||||
if alias:
|
||||
alias = alias.replace('_', sep)
|
||||
if alias.lower() in available:
|
||||
return alias
|
||||
parts = locale.split(sep)
|
||||
if len(parts) > 1 and parts[0].lower() in available:
|
||||
return parts[0]
|
||||
return None
|
||||
|
||||
|
||||
def parse_locale(identifier, sep='_'):
|
||||
"""Parse a locale identifier into a tuple of the form ``(language,
|
||||
territory, script, variant)``.
|
||||
|
||||
>>> parse_locale('zh_CN')
|
||||
('zh', 'CN', None, None)
|
||||
>>> parse_locale('zh_Hans_CN')
|
||||
('zh', 'CN', 'Hans', None)
|
||||
|
||||
The default component separator is "_", but a different separator can be
|
||||
specified using the `sep` parameter:
|
||||
|
||||
>>> parse_locale('zh-CN', sep='-')
|
||||
('zh', 'CN', None, None)
|
||||
|
||||
If the identifier cannot be parsed into a locale, a `ValueError` exception
|
||||
is raised:
|
||||
|
||||
>>> parse_locale('not_a_LOCALE_String')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: 'not_a_LOCALE_String' is not a valid locale identifier
|
||||
|
||||
Encoding information and locale modifiers are removed from the identifier:
|
||||
|
||||
>>> parse_locale('it_IT@euro')
|
||||
('it', 'IT', None, None)
|
||||
>>> parse_locale('en_US.UTF-8')
|
||||
('en', 'US', None, None)
|
||||
>>> parse_locale('de_DE.iso885915@euro')
|
||||
('de', 'DE', None, None)
|
||||
|
||||
See :rfc:`4646` for more information.
|
||||
|
||||
:param identifier: the locale identifier string
|
||||
:param sep: character that separates the different components of the locale
|
||||
identifier
|
||||
:raise `ValueError`: if the string does not appear to be a valid locale
|
||||
identifier
|
||||
"""
|
||||
if '.' in identifier:
|
||||
# this is probably the charset/encoding, which we don't care about
|
||||
identifier = identifier.split('.', 1)[0]
|
||||
if '@' in identifier:
|
||||
# this is a locale modifier such as @euro, which we don't care about
|
||||
# either
|
||||
identifier = identifier.split('@', 1)[0]
|
||||
|
||||
parts = identifier.split(sep)
|
||||
lang = parts.pop(0).lower()
|
||||
if not lang.isalpha():
|
||||
raise ValueError('expected only letters, got %r' % lang)
|
||||
|
||||
script = territory = variant = None
|
||||
if parts:
|
||||
if len(parts[0]) == 4 and parts[0].isalpha():
|
||||
script = parts.pop(0).title()
|
||||
|
||||
if parts:
|
||||
if len(parts[0]) == 2 and parts[0].isalpha():
|
||||
territory = parts.pop(0).upper()
|
||||
elif len(parts[0]) == 3 and parts[0].isdigit():
|
||||
territory = parts.pop(0)
|
||||
|
||||
if parts:
|
||||
if len(parts[0]) == 4 and parts[0][0].isdigit() or \
|
||||
len(parts[0]) >= 5 and parts[0][0].isalpha():
|
||||
variant = parts.pop()
|
||||
|
||||
if parts:
|
||||
raise ValueError('%r is not a valid locale identifier' % identifier)
|
||||
|
||||
return lang, territory, script, variant
|
||||
|
||||
|
||||
def get_locale_identifier(tup, sep='_'):
|
||||
"""The reverse of :func:`parse_locale`. It creates a locale identifier out
|
||||
of a ``(language, territory, script, variant)`` tuple. Items can be set to
|
||||
``None`` and trailing ``None``\s can also be left out of the tuple.
|
||||
|
||||
>>> get_locale_identifier(('de', 'DE', None, '1999'))
|
||||
'de_DE_1999'
|
||||
|
||||
.. versionadded:: 1.0
|
||||
|
||||
:param tup: the tuple as returned by :func:`parse_locale`.
|
||||
:param sep: the separator for the identifier.
|
||||
"""
|
||||
tup = tuple(tup[:4])
|
||||
lang, territory, script, variant = tup + (None,) * (4 - len(tup))
|
||||
return sep.join(filter(None, (lang, script, territory, variant)))
|
1181
vendor/babel/dates.py
vendored
1181
vendor/babel/dates.py
vendored
File diff suppressed because it is too large
Load Diff
BIN
vendor/babel/global.dat
vendored
BIN
vendor/babel/global.dat
vendored
Binary file not shown.
209
vendor/babel/localedata.py
vendored
209
vendor/babel/localedata.py
vendored
|
@ -1,209 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
babel.localedata
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Low-level locale data access.
|
||||
|
||||
:note: The `Locale` class, which uses this module under the hood, provides a
|
||||
more convenient interface for accessing the locale data.
|
||||
|
||||
:copyright: (c) 2013 by the Babel Team.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
import os
|
||||
import threading
|
||||
from collections import MutableMapping
|
||||
|
||||
from babel._compat import pickle
|
||||
|
||||
|
||||
_cache = {}
|
||||
_cache_lock = threading.RLock()
|
||||
_dirname = os.path.join(os.path.dirname(__file__), 'localedata')
|
||||
|
||||
|
||||
def exists(name):
|
||||
"""Check whether locale data is available for the given locale. Ther
|
||||
return value is `True` if it exists, `False` otherwise.
|
||||
|
||||
:param name: the locale identifier string
|
||||
"""
|
||||
if name in _cache:
|
||||
return True
|
||||
return os.path.exists(os.path.join(_dirname, '%s.dat' % name))
|
||||
|
||||
|
||||
def locale_identifiers():
|
||||
"""Return a list of all locale identifiers for which locale data is
|
||||
available.
|
||||
|
||||
.. versionadded:: 0.8.1
|
||||
|
||||
:return: a list of locale identifiers (strings)
|
||||
"""
|
||||
return [stem for stem, extension in [
|
||||
os.path.splitext(filename) for filename in os.listdir(_dirname)
|
||||
] if extension == '.dat' and stem != 'root']
|
||||
|
||||
|
||||
def load(name, merge_inherited=True):
|
||||
"""Load the locale data for the given locale.
|
||||
|
||||
The locale data is a dictionary that contains much of the data defined by
|
||||
the Common Locale Data Repository (CLDR). This data is stored as a
|
||||
collection of pickle files inside the ``babel`` package.
|
||||
|
||||
>>> d = load('en_US')
|
||||
>>> d['languages']['sv']
|
||||
u'Swedish'
|
||||
|
||||
Note that the results are cached, and subsequent requests for the same
|
||||
locale return the same dictionary:
|
||||
|
||||
>>> d1 = load('en_US')
|
||||
>>> d2 = load('en_US')
|
||||
>>> d1 is d2
|
||||
True
|
||||
|
||||
:param name: the locale identifier string (or "root")
|
||||
:param merge_inherited: whether the inherited data should be merged into
|
||||
the data of the requested locale
|
||||
:raise `IOError`: if no locale data file is found for the given locale
|
||||
identifer, or one of the locales it inherits from
|
||||
"""
|
||||
_cache_lock.acquire()
|
||||
try:
|
||||
data = _cache.get(name)
|
||||
if not data:
|
||||
# Load inherited data
|
||||
if name == 'root' or not merge_inherited:
|
||||
data = {}
|
||||
else:
|
||||
parts = name.split('_')
|
||||
if len(parts) == 1:
|
||||
parent = 'root'
|
||||
else:
|
||||
parent = '_'.join(parts[:-1])
|
||||
data = load(parent).copy()
|
||||
filename = os.path.join(_dirname, '%s.dat' % name)
|
||||
fileobj = open(filename, 'rb')
|
||||
try:
|
||||
if name != 'root' and merge_inherited:
|
||||
merge(data, pickle.load(fileobj))
|
||||
else:
|
||||
data = pickle.load(fileobj)
|
||||
_cache[name] = data
|
||||
finally:
|
||||
fileobj.close()
|
||||
return data
|
||||
finally:
|
||||
_cache_lock.release()
|
||||
|
||||
|
||||
def merge(dict1, dict2):
|
||||
"""Merge the data from `dict2` into the `dict1` dictionary, making copies
|
||||
of nested dictionaries.
|
||||
|
||||
>>> d = {1: 'foo', 3: 'baz'}
|
||||
>>> merge(d, {1: 'Foo', 2: 'Bar'})
|
||||
>>> items = d.items(); items.sort(); items
|
||||
[(1, 'Foo'), (2, 'Bar'), (3, 'baz')]
|
||||
|
||||
:param dict1: the dictionary to merge into
|
||||
:param dict2: the dictionary containing the data that should be merged
|
||||
"""
|
||||
for key, val2 in dict2.items():
|
||||
if val2 is not None:
|
||||
val1 = dict1.get(key)
|
||||
if isinstance(val2, dict):
|
||||
if val1 is None:
|
||||
val1 = {}
|
||||
if isinstance(val1, Alias):
|
||||
val1 = (val1, val2)
|
||||
elif isinstance(val1, tuple):
|
||||
alias, others = val1
|
||||
others = others.copy()
|
||||
merge(others, val2)
|
||||
val1 = (alias, others)
|
||||
else:
|
||||
val1 = val1.copy()
|
||||
merge(val1, val2)
|
||||
else:
|
||||
val1 = val2
|
||||
dict1[key] = val1
|
||||
|
||||
|
||||
class Alias(object):
|
||||
"""Representation of an alias in the locale data.
|
||||
|
||||
An alias is a value that refers to some other part of the locale data,
|
||||
as specified by the `keys`.
|
||||
"""
|
||||
|
||||
def __init__(self, keys):
|
||||
self.keys = tuple(keys)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r>' % (type(self).__name__, self.keys)
|
||||
|
||||
def resolve(self, data):
|
||||
"""Resolve the alias based on the given data.
|
||||
|
||||
This is done recursively, so if one alias resolves to a second alias,
|
||||
that second alias will also be resolved.
|
||||
|
||||
:param data: the locale data
|
||||
:type data: `dict`
|
||||
"""
|
||||
base = data
|
||||
for key in self.keys:
|
||||
data = data[key]
|
||||
if isinstance(data, Alias):
|
||||
data = data.resolve(base)
|
||||
elif isinstance(data, tuple):
|
||||
alias, others = data
|
||||
data = alias.resolve(base)
|
||||
return data
|
||||
|
||||
|
||||
class LocaleDataDict(MutableMapping):
|
||||
"""Dictionary wrapper that automatically resolves aliases to the actual
|
||||
values.
|
||||
"""
|
||||
|
||||
def __init__(self, data, base=None):
|
||||
self._data = data
|
||||
if base is None:
|
||||
base = data
|
||||
self.base = base
|
||||
|
||||
def __len__(self):
|
||||
return len(self._data)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._data)
|
||||
|
||||
def __getitem__(self, key):
|
||||
orig = val = self._data[key]
|
||||
if isinstance(val, Alias): # resolve an alias
|
||||
val = val.resolve(self.base)
|
||||
if isinstance(val, tuple): # Merge a partial dict with an alias
|
||||
alias, others = val
|
||||
val = alias.resolve(self.base).copy()
|
||||
merge(val, others)
|
||||
if type(val) is dict: # Return a nested alias-resolving dict
|
||||
val = LocaleDataDict(val, base=self.base)
|
||||
if val is not orig:
|
||||
self._data[key] = val
|
||||
return val
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._data[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._data[key]
|
||||
|
||||
def copy(self):
|
||||
return LocaleDataDict(self._data.copy(), base=self.base)
|
BIN
vendor/babel/localedata/aa.dat
vendored
BIN
vendor/babel/localedata/aa.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/aa_DJ.dat
vendored
BIN
vendor/babel/localedata/aa_DJ.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/aa_ER.dat
vendored
BIN
vendor/babel/localedata/aa_ER.dat
vendored
Binary file not shown.
4
vendor/babel/localedata/aa_ET.dat
vendored
4
vendor/babel/localedata/aa_ET.dat
vendored
|
@ -1,4 +0,0 @@
|
|||
€}q(Ucurrency_symbolsq}qUscientific_formatsq}qUpercent_formatsq}qUnumber_symbolsq}q Ucurrency_names_pluralq
|
||||
}qU week_dataq}q
(Umin_daysqKU
weekend_startqKU first_dayqKUweekend_endqKuUzone_formatsq}qUcurrency_formatsq}qU_versionqM5 U languagesq}qUterritoriesq}U
|
||||
time_zonesq}qUscriptsq}qUdecimal_formatsq}qU
|
||||
meta_zonesq }q!Uvariantsq"}q#Ucurrency_namesq$}q%U
unit_patternsq&}q'u.
|
BIN
vendor/babel/localedata/af.dat
vendored
BIN
vendor/babel/localedata/af.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/af_NA.dat
vendored
BIN
vendor/babel/localedata/af_NA.dat
vendored
Binary file not shown.
4
vendor/babel/localedata/af_ZA.dat
vendored
4
vendor/babel/localedata/af_ZA.dat
vendored
|
@ -1,4 +0,0 @@
|
|||
€}q(Ucurrency_symbolsq}qUscientific_formatsq}qUpercent_formatsq}qUnumber_symbolsq}q Ucurrency_names_pluralq
|
||||
}qU week_dataq}q
(Umin_daysqKU
weekend_startqKU first_dayqKUweekend_endqKuUzone_formatsq}qUcurrency_formatsq}qU_versionqM5 U languagesq}qUterritoriesq}U
|
||||
time_zonesq}qUscriptsq}qUdecimal_formatsq}qU
|
||||
meta_zonesq }q!Uvariantsq"}q#Ucurrency_namesq$}q%U
unit_patternsq&}q'u.
|
BIN
vendor/babel/localedata/agq.dat
vendored
BIN
vendor/babel/localedata/agq.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/agq_CM.dat
vendored
BIN
vendor/babel/localedata/agq_CM.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/ak.dat
vendored
BIN
vendor/babel/localedata/ak.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/ak_GH.dat
vendored
BIN
vendor/babel/localedata/ak_GH.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/am.dat
vendored
BIN
vendor/babel/localedata/am.dat
vendored
Binary file not shown.
4
vendor/babel/localedata/am_ET.dat
vendored
4
vendor/babel/localedata/am_ET.dat
vendored
|
@ -1,4 +0,0 @@
|
|||
€}q(Ucurrency_symbolsq}qUscientific_formatsq}qUpercent_formatsq}qUnumber_symbolsq}q Ucurrency_names_pluralq
|
||||
}qU week_dataq}q
(Umin_daysqKU
weekend_startqKU first_dayqKUweekend_endqKuUzone_formatsq}qUcurrency_formatsq}qU_versionqM5 U languagesq}qUterritoriesq}U
|
||||
time_zonesq}qUscriptsq}qUdecimal_formatsq}qU
|
||||
meta_zonesq }q!Uvariantsq"}q#Ucurrency_namesq$}q%U
unit_patternsq&}q'u.
|
BIN
vendor/babel/localedata/ar.dat
vendored
BIN
vendor/babel/localedata/ar.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/ar_001.dat
vendored
BIN
vendor/babel/localedata/ar_001.dat
vendored
Binary file not shown.
4
vendor/babel/localedata/ar_AE.dat
vendored
4
vendor/babel/localedata/ar_AE.dat
vendored
|
@ -1,4 +0,0 @@
|
|||
€}q(Ucurrency_symbolsq}qUscientific_formatsq}qUpercent_formatsq}qUnumber_symbolsq}q Ucurrency_names_pluralq
|
||||
}qU week_dataq}q
(U
weekend_startqKU first_dayqKUweekend_endqKuUzone_formatsq}qUcurrency_formatsq}qU_versionqM5 U languagesq}qUterritoriesq}U
|
||||
time_zonesq}qUscriptsq}qUdecimal_formatsq}qU
|
||||
meta_zonesq}q Uvariantsq!}q"Ucurrency_namesq#}q$U
unit_patternsq%}q&u.
|
4
vendor/babel/localedata/ar_BH.dat
vendored
4
vendor/babel/localedata/ar_BH.dat
vendored
|
@ -1,4 +0,0 @@
|
|||
€}q(Ucurrency_symbolsq}qUscientific_formatsq}qUpercent_formatsq}qUnumber_symbolsq}q Ucurrency_names_pluralq
|
||||
}qU week_dataq}q
(U
weekend_startqKU first_dayqKUweekend_endqKuUzone_formatsq}qUcurrency_formatsq}qU_versionqM5 U languagesq}qUterritoriesq}U
|
||||
time_zonesq}qUscriptsq}qUdecimal_formatsq}qU
|
||||
meta_zonesq}q Uvariantsq!}q"Ucurrency_namesq#}q$U
unit_patternsq%}q&u.
|
BIN
vendor/babel/localedata/ar_DJ.dat
vendored
BIN
vendor/babel/localedata/ar_DJ.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/ar_DZ.dat
vendored
BIN
vendor/babel/localedata/ar_DZ.dat
vendored
Binary file not shown.
4
vendor/babel/localedata/ar_EG.dat
vendored
4
vendor/babel/localedata/ar_EG.dat
vendored
|
@ -1,4 +0,0 @@
|
|||
€}q(Ucurrency_symbolsq}qUscientific_formatsq}qUpercent_formatsq}qUnumber_symbolsq}q Ucurrency_names_pluralq
|
||||
}qU week_dataq}q
(Umin_daysqKU
weekend_startqKU first_dayqKUweekend_endqKuUzone_formatsq}qUcurrency_formatsq}qU_versionqM5 U languagesq}qUterritoriesq}U
|
||||
time_zonesq}qUscriptsq}qUdecimal_formatsq}qU
|
||||
meta_zonesq }q!Uvariantsq"}q#Ucurrency_namesq$}q%U
unit_patternsq&}q'u.
|
BIN
vendor/babel/localedata/ar_EH.dat
vendored
BIN
vendor/babel/localedata/ar_EH.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/ar_ER.dat
vendored
BIN
vendor/babel/localedata/ar_ER.dat
vendored
Binary file not shown.
4
vendor/babel/localedata/ar_IL.dat
vendored
4
vendor/babel/localedata/ar_IL.dat
vendored
|
@ -1,4 +0,0 @@
|
|||
€}q(Ucurrency_symbolsq}qUscientific_formatsq}qUpercent_formatsq}qUnumber_symbolsq}q Ucurrency_names_pluralq
|
||||
}qU week_dataq}q
(U
weekend_startqKU first_dayqKUweekend_endqKuUzone_formatsq}qUcurrency_formatsq}qU_versionqM5 U languagesq}qUterritoriesq}U
|
||||
time_zonesq}qUscriptsq}qUdecimal_formatsq}qU
|
||||
meta_zonesq}q Uvariantsq!}q"Ucurrency_namesq#}q$U
unit_patternsq%}q&u.
|
BIN
vendor/babel/localedata/ar_IQ.dat
vendored
BIN
vendor/babel/localedata/ar_IQ.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/ar_JO.dat
vendored
BIN
vendor/babel/localedata/ar_JO.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/ar_KM.dat
vendored
BIN
vendor/babel/localedata/ar_KM.dat
vendored
Binary file not shown.
4
vendor/babel/localedata/ar_KW.dat
vendored
4
vendor/babel/localedata/ar_KW.dat
vendored
|
@ -1,4 +0,0 @@
|
|||
€}q(Ucurrency_symbolsq}qUscientific_formatsq}qUpercent_formatsq}qUnumber_symbolsq}q Ucurrency_names_pluralq
|
||||
}qU week_dataq}q
(U
weekend_startqKU first_dayqKUweekend_endqKuUzone_formatsq}qUcurrency_formatsq}qU_versionqM5 U languagesq}qUterritoriesq}U
|
||||
time_zonesq}qUscriptsq}qUdecimal_formatsq}qU
|
||||
meta_zonesq}q Uvariantsq!}q"Ucurrency_namesq#}q$U
unit_patternsq%}q&u.
|
BIN
vendor/babel/localedata/ar_LB.dat
vendored
BIN
vendor/babel/localedata/ar_LB.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/ar_LY.dat
vendored
BIN
vendor/babel/localedata/ar_LY.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/ar_MA.dat
vendored
BIN
vendor/babel/localedata/ar_MA.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/ar_MR.dat
vendored
BIN
vendor/babel/localedata/ar_MR.dat
vendored
Binary file not shown.
4
vendor/babel/localedata/ar_OM.dat
vendored
4
vendor/babel/localedata/ar_OM.dat
vendored
|
@ -1,4 +0,0 @@
|
|||
€}q(Ucurrency_symbolsq}qUscientific_formatsq}qUpercent_formatsq}qUnumber_symbolsq}q Ucurrency_names_pluralq
|
||||
}qU week_dataq}q
(U
weekend_startqKU first_dayqKUweekend_endqKuUzone_formatsq}qUcurrency_formatsq}qU_versionqM5 U languagesq}qUterritoriesq}U
|
||||
time_zonesq}qUscriptsq}qUdecimal_formatsq}qU
|
||||
meta_zonesq}q Uvariantsq!}q"Ucurrency_namesq#}q$U
unit_patternsq%}q&u.
|
BIN
vendor/babel/localedata/ar_PS.dat
vendored
BIN
vendor/babel/localedata/ar_PS.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/ar_QA.dat
vendored
BIN
vendor/babel/localedata/ar_QA.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/ar_SA.dat
vendored
BIN
vendor/babel/localedata/ar_SA.dat
vendored
Binary file not shown.
4
vendor/babel/localedata/ar_SD.dat
vendored
4
vendor/babel/localedata/ar_SD.dat
vendored
|
@ -1,4 +0,0 @@
|
|||
€}q(Ucurrency_symbolsq}qUscientific_formatsq}qUpercent_formatsq}qUnumber_symbolsq}q Ucurrency_names_pluralq
|
||||
}qU week_dataq}q
(Umin_daysqKU
weekend_startqKU first_dayqKUweekend_endqKuUzone_formatsq}qUcurrency_formatsq}qU_versionqM5 U languagesq}qUterritoriesq}U
|
||||
time_zonesq}qUscriptsq}qUdecimal_formatsq}qU
|
||||
meta_zonesq }q!Uvariantsq"}q#Ucurrency_namesq$}q%U
unit_patternsq&}q'u.
|
BIN
vendor/babel/localedata/ar_SO.dat
vendored
BIN
vendor/babel/localedata/ar_SO.dat
vendored
Binary file not shown.
BIN
vendor/babel/localedata/ar_SY.dat
vendored
BIN
vendor/babel/localedata/ar_SY.dat
vendored
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user