Merge branch 'Chinese_tanslation' (#90)

Thanks @idalin user language should be detected from browser, or otherwise with enabled anonymous browsing it can be selected as admin (configure language for user guest). Annymous browsing can be deactivated afterwards
This commit is contained in:
OzzieIsaacs 2017-01-14 15:05:49 +01:00
commit 2335252b1f
9 changed files with 1541 additions and 12 deletions

1
.gitignore vendored
View File

@ -22,3 +22,4 @@ cps/static/[0-9]*
.idea/
*.bak
*.log.*
tags

147
cps/static/js/context.js Normal file
View File

@ -0,0 +1,147 @@
/*!
* context.js Library associated with > v0.9.6.2 of intention.js
* http://intentionjs.com/
*
* Copyright 2011, 2013 Dowjones and other contributors
* Released under the MIT license
*
*/
(function () {
'use strict';
var context = function($, Intention){
// create a brand spankin new intention object
var intent=new Intention(),
// placeholder for the horizontal axis
horizontal_axis,
orientation_axis;
// throttle funtion used for keeping calls to the resize responive
// callback to a minimum
function throttle(callback, interval){
var lastExec = new Date(),
timer = null;
return function(e){
var d = new Date();
if (d-lastExec < interval) {
if (timer) {
window.clearTimeout(timer);
}
var callbackWrapper = function(event){
return function(){
callback(event);
};
};
timer = window.setTimeout(callbackWrapper(e), interval);
return false;
}
callback(e);
lastExec = d;
};
}
// catchall
// =======================================================================
intent.responsive([{name:'base'}]).respond('base');
// width context?
// =======================================================================
horizontal_axis = intent.responsive({
ID:'width',
contexts: [
{name:'standard', min:840},
{name:'tablet', min:510},
{name:'mobile', min:0}],
// compare the return value of the callback to each context
// return true for a match
matcher: function(test, context){
if(typeof test === 'string'){
return test === context.name;
}
return test>=context.min;
},
// callback, return value is passed to matcher()
// to compare against current context
measure: function(arg){
if(typeof arg === 'string'){
return arg;
}
return $(window).width();
}});
// orientation context?
// =======================================================================
orientation_axis = intent.responsive({
ID:'orientation',
contexts: [{name:'portrait', rotation: 0},
{name:'landscape', rotation:90}],
matcher: function(measure, ctx){
return measure === ctx.rotation;
},
measure: function(){
var test = Math.abs(window.orientation);
if(test > 0) {
test = 180 - test;
}
return test;
}
});
// ONE TIME CHECK AXES:
// touch device?
// =======================================================================
intent.responsive({
ID:'touch',
contexts:[{name:'touch'}],
matcher: function() {
return "ontouchstart" in window;
}}).respond();
// retina display?
// =======================================================================
intent.responsive({
ID: 'highres',
// contexts
contexts:[{name:'highres'}],
// matching:
matcher: function(){
return window.devicePixelRatio > 1;
}}).respond();
// bind events to the window
$(window).on('resize', throttle(horizontal_axis.respond, 100))
.on('orientationchange', horizontal_axis.respond)
.on('orientationchange', orientation_axis.respond);
// register the current width and orientation without waiting for a window
// resize
horizontal_axis.respond();
orientation_axis.respond();
$(function(){
// at doc ready grab all of the elements in the doc
intent.elements(document);
});
// return the intention object so that it can be extended by other plugins
return intent;
};
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define('context', ['jquery', 'intention'], factory);
} else {
// Browser globals
root.intent = factory(root.jQuery, root.Intention);
}
}(this, function ($, Intention) {
return context($, Intention);
}));
}).call(this);

564
cps/static/js/intention.js Normal file
View File

@ -0,0 +1,564 @@
/*!
* intention.js Library v0.9.7.2
* http://intentionjs.com/
*
* Copyright 2011, 2013 Dowjones and other contributors
* Released under the MIT license
*
*/
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
define('intention', ['jquery', 'underscore'], factory);
} else {
root.Intention = factory(root.jQuery, root._);
}
}(this, function($, _) {
'use strict';
var Intention = function(params){
var intent = _.extend(this, params,
{_listeners:{}, contexts:[], elms:$(), axes:{}, priority:[]});
return intent;
};
Intention.prototype = {
// public methods
responsive:function responsive(contexts, options){
// for generating random ids for axis when not specified
var idChars = 'abcdefghijklmnopqrstuvwxyz0123456789',
id='', i;
// create a random id for the axis
for(i=0; i<5; i++){
id += idChars[Math.floor(Math.random() * idChars.length)];
}
var defaults = {
// if no matcher function is specified expect to compare a
// string to the ctx.name property
matcher: function(measure, ctx){
return measure === ctx.name;
},
// function takes one arg and returns it
measure: _.identity,
ID: id
};
if(_.isObject(options) === false) {
options = {};
}
if((_.isArray(contexts)) && (_.isArray(contexts[0].contexts))){
_.each(contexts, function(axis){
responsive.apply(this, axis);
}, this);
return;
}
if((_.isArray(contexts) === false) && _.isObject(contexts)){
options = contexts;
} else {
options.contexts = contexts;
}
// fill in the options
options = _.extend({}, defaults, options);
// bind an the respond function to the axis ID and prefix it
// with an underscore so that it does not get whomped accidentally
this.on('_' + options.ID + ':', _.bind(
function(e){
this.axes = this._contextualize(
options.ID, e.context, this.axes);
this._respond(this.axes, this.elms);
}, this));
var axis = {
ID:options.ID,
current:null,
contexts:options.contexts,
respond:_.bind(this._responder(options.ID, options.contexts,
options.matcher, options.measure), this)
};
this.axes[options.ID] = axis;
this.axes.__keys__ = this.priority;
this.priority.unshift(options.ID);
return axis;
},
elements: function(scope){
// find all responsive elms in a specific dom scope
if(!scope){
scope = document;
}
$('[data-intent],[intent],[data-in],[in]',
scope).each(_.bind(function(i, elm){
this.add($(elm));
}, this));
return this;
},
add: function(elms, options){
var spec;
if(!options) {
options = {};
}
// is expecting a jquery object
elms.each(_.bind(function(i, elm){
var exists = false;
this.elms.each(function(i, respElm){
if(elm === respElm) {
exists=true;
return false;
}
return true;
});
if(exists === false){
// create the elements responsive data
spec = this._fillSpec(
_.extend(options, this._attrsToSpec(elm.attributes, this.axes)));
// make any appropriate changes based on the current contexts
this._makeChanges($(elm), spec, this.axes);
this.elms.push({
elm: elm,
spec: spec
});
}
}, this));
return this;
},
remove: function(elms){
// is expecting a jquery object
var respElms = this.elms;
// elms to remove
elms.each(function(i, elm){
// elms to check against
respElms.each(function(i, candidate){
if(elm === candidate.elm){
respElms.splice(i, 1);
// found the match, break the loop
return false;
}
return true;
});
});
return this;
},
is: function(ctxName){
var axes = this.axes;
return _.some(axes.__keys__, function(key){
return ctxName === axes[key].current;
});
},
current: function(axisName){
if(this.axes.hasOwnProperty(axisName)){
return this.axes[axisName].current;
} else {
return false;
}
},
// code and concept taken from simple implementation of
// observer pattern outlined here:
// http://www.nczonline.net/blog/2010/03/09/custom-events-in-javascript/
on: function(type, listener){
var events = type.split(' '),
i=0;
for(i;i<events.length;i++){
if(this._listeners[events[i]] === undefined) {
this._listeners[events[i]]=[];
}
this._listeners[events[i]].push(listener);
}
return this;
},
off: function(type, listener){
if(_.isArray(this._listeners[type])){
var listeners = this._listeners[type],
i;
for(i=0;listeners.length; i++){
if(listeners[i] === listener){
listeners.splice(i,1);
break;
}
}
}
return this;
},
// privates
_responder: function(axisID, contexts, matcher, measure){
var currentContext;
// called to perform a check
return function(){
var measurement = measure.apply(this, arguments);
_.every(contexts, function(ctx){
if( matcher(measurement, ctx)) {
// first time, or different than last context
if( (currentContext===undefined) ||
(ctx.name !== currentContext.name)){
currentContext = ctx;
// event emitting!
// emit the private axis event
this._emitter(
{_type: '_' + axisID + ':', context:currentContext.name},
currentContext, this)
// emit the public axis event
._emitter({_type: axisID + ':', context:currentContext.name},
currentContext, this)
// attempt to trigger the axis to context pair
._emitter(_.extend({},
{_type: axisID + ':' + currentContext.name},
currentContext), currentContext, this)
// then emit the context event (second ensures the context
// changes happen after all dom manipulations)
._emitter(_.extend({}, {_type:currentContext.name},
currentContext), currentContext, this);
// done, break the loop
return false;
}
// same context, break the loop
return false;
}
return true;
}, this);
// return the intention object for chaining
return this;
};
},
_emitter: function(event){
if(typeof event === 'string') {
event={_type:event};
}
if(!event.target){
event.target=this;
}
if(!event._type){
throw new Error(event._type + ' is not a supported event.');
}
if(_.isArray(this._listeners[event._type])){
var listeners = this._listeners[event._type],
i;
for(i=0; i<listeners.length; i++){
listeners[i].apply(this, arguments);
}
}
return this;
},
_fillSpec: function(spec){
var applySpec = function(fn){
_.each(spec, function(axisOptions, axis){
_.each(axisOptions, function(ctxOptions, ctx){
fn(ctxOptions, ctx, axis);
});
});
}, filler={};
applySpec(function(options){
// check to see if the ctx val is an object, could be a string
if(_.isObject(options)){
_.each(options, function(val, func){
filler[func] = '';
});
}
});
applySpec(function(options, ctx, axis){
if(_.isObject(options)){
spec[axis][ctx] = _.extend({}, filler, options);
}
});
return spec;
},
_assocAxis: function(ctx, axes){
var match=false;
_.every(axes.__keys__, function(axis){
if(match === false){
_.every(axes[axis].contexts, function(ctxCandidate){
if(ctxCandidate.name === ctx){
match = axis;
return false;
}
return true;
});
return true;
}else {
return false;
}
});
return match;
},
_makeSpec: function(axis, ctx, sAttr, value, spec){
var axisObj,
ctxObj;
if(spec[axis] !== undefined){
axisObj = spec[axis];
if(axisObj[ctx] === undefined) {
axisObj[ctx] = {};
}
} else {
axisObj = {};
axisObj[ctx] = {};
spec[axis] = axisObj;
}
axisObj[ctx][sAttr] = value;
return spec;
},
_attrsToSpec: function(attrs, axes){
var spec={},
fullPattern = new RegExp(
'^(data-)?(in|intent)-(([a-zA-Z0-9][a-zA-Z0-9]*:)?([a-zA-Z0-9]*))-([A-Za-z:-]+)'),
axisPattern = new RegExp(
'^(data-)?(in|intent)-([a-zA-Z0-9][_a-zA-Z0-9]*):$');
_.each(attrs, function(attr){
var specMatch = attr.name.match(fullPattern),
axisName;
if(specMatch !== null) {
specMatch = specMatch.slice(-3);
axisName = specMatch[0];
if(specMatch[0] === undefined){
// if there is no axis find one:
specMatch[0] = this._assocAxis(specMatch[1], axes);
if(specMatch[0] === false) {
// there is no context, so get outa here
return; // skipt the attr
}
} else {
specMatch[0] = specMatch[0].replace(/:$/, '');}
specMatch.push(attr.value);
specMatch.push(spec);
spec = this._makeSpec.apply(this, specMatch);
} else if(axisPattern.test(attr.name)){
axisName = attr.name.match(axisPattern)[3];
_.each(axes[axisName].contexts,
function(context){
this._makeSpec(axisName, context.name, 'class', context.name +
' ' + attr.value, spec);
},
this);}},
this);
return spec;
},
_contextSpec: function(ctxObj, specs){
if(specs.hasOwnProperty(ctxObj.axis) &&
specs[ctxObj.axis].hasOwnProperty(ctxObj.ctx)){
return specs[ctxObj.axis][ctxObj.ctx];
}
return {};
},
_resolveSpecs: function(currentContexts, specs){
var changes={},
moveFuncs=['append', 'prepend', 'before', 'after'];
_.each(currentContexts, function(ctxObj){
// if the axis or the context to not exist in the specs object
// skip to the next one
_.each(this._contextSpec(ctxObj, specs), function(val, func){
if(func==='class'){
if(!changes[func]){
changes[func] = [];
}
changes[func] = _.union(changes[func], val.split(' '));
} else if(((changes.move === undefined) ||
(changes.move.value === '')) &&
($.inArray(func, moveFuncs) !== -1)){
changes.move = {value:val, placement:func};
} else {
if((changes[func] === undefined) || (changes[func] === '')){
changes[func]=val;
}
}
}, this);
}, this);
return changes;
},
_currentContexts: function(axes) {
var contexts = [];
_.each(axes.__keys__, function(ID){
if(axes[ID].current !== null) {
contexts.push({ctx:axes[ID].current, axis:ID});
return;
}
});
return contexts;
},
_removeClasses: function(specs, axes) {
var toRemove = [];
_.each(axes.__keys__, function(key){
var axis = axes[key];
_.each(axis.contexts, function(ctx){
// ignore the current context, those classes SHOULD be applied
if(ctx.name === axis.current) {
return;
}
var contextSpec = this._contextSpec(
{axis:axis.ID, ctx:ctx.name}, specs),
classes;
if(contextSpec !== undefined) {
if(contextSpec['class'] !== undefined) {
classes = contextSpec['class'].split(' ');
if(classes !== undefined){
toRemove = _.union(toRemove, classes);
}
}
}
}, this);
}, this);
return toRemove;
},
_contextConfig: function(specs, axes){
return this._resolveSpecs(this._currentContexts(axes), specs, axes);
},
_makeChanges: function(elm, specs, axes){
if(_.isEmpty(axes)===false){
var ctxConfig = this._contextConfig(specs, axes);
_.each(ctxConfig, function(change, func){
if(func==='move'){
if( (specs.__placement__ !== change.placement) ||
(specs.__move__ !== change.value)){
$(change.value)[change.placement](elm);
// save the last placement of the element so
// we're not moving it around for no good reason
specs.__placement__ = change.placement;
specs.__move__ = change.value;
}
} else if(func === 'class') {
var classes = elm.attr('class') || '';
// the class add/remove formula
classes = _.union(change,
_.difference(classes.split(' '),
this._removeClasses(specs, axes)));
elm.attr('class', classes.join(' '));
} else {
elm.attr(func, change);
}
}, this);
}
return elm;
},
_respond: function(axes, elms){
// go through all of the responsive elms
elms.each(_.bind(function(i, elm){
var $elm = $(elm.elm);
this._makeChanges($elm, elm.spec, axes);
$elm.trigger('intent', this);
}, this));
},
_contextualize: function(axisID, context, axes){
axes[axisID].current = context;
return axes;
},
// private props
// axis test, does it begin with an underscore? for testing inside
// spec objects
_axis_test_pattern: new RegExp("^_[a-zA-Z0-9]"),
// match a group after the underscore:
_axis_match_pattern: new RegExp("^_([a-zA-Z0-9][_a-zA-Z0-9]*)"),
// simple trim
_trim_pattern:new RegExp( "^\s+|\s+$", "g" )
};
return Intention;
}));

View File

@ -24,7 +24,7 @@
<script src="{{ url_for('static', filename='js/libs/jquery.min.js') }}"></script>
<!-- Include all compiled plugins (below), or include individual files as needed -->
<script src="{{ url_for('static', filename='js/bootstrap.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/underscore.min.js') }}"></script>
<script src="{{ url_for('static', filename='js/underscore-min.js') }}"></script>
<script src="{{ url_for('static', filename='js/intention.js') }}"></script>
<script src="{{ url_for('static', filename='js/context.js') }}"></script>
<script src="{{ url_for('static', filename='js/plugins.js') }}"></script>

View File

@ -23,16 +23,14 @@
<label for="kindle_mail">{{_('Kindle E-Mail')}}</label>
<input type="email" class="form-control" name="kindle_mail" id="kindle_mail" value="{{ content.kindle_mail if content.kindle_mail != None }}">
</div>
{% if not content.role_anonymous() %}
<div class="form-group">
<label for="locale">{{_('Language')}}</label>
<select name="locale" id="locale" class="form-control">
{% for translation in translations %}
<option value="{{translation.language}}" {% if translation.language == content.locale %}selected{% endif %}>{{ translation.display_name }}</option>
<option value="{{translation}}" {% if translation|string == content.locale %}selected{% endif %}>{{ translation.display_name }}</option>
{% endfor %}
</select>
</div>
{% endif %}
<div class="form-group">
<label for="default_language">{{_('Show books with language')}}</label>
<select name="default_language" id="default_language" class="form-control">

Binary file not shown.

View File

@ -0,0 +1,824 @@
# Chinese (Simplified, China) translations for PROJECT.
# Copyright (C) 2017 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-01-03 19:50+0100\n"
"PO-Revision-Date: 2017-01-06 17:00+0800\n"
"Last-Translator: dalin <dalin.lin@gmail.com>\n"
"Language: zh_Hans_CN\n"
"Language-Team: zh_Hans_CN <LL@li.org>\n"
"Plural-Forms: nplurals=1; plural=0\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.3.4\n"
#: cps/book_formats.py:109 cps/book_formats.py:113 cps/web.py:928
msgid "not installed"
msgstr "未安装"
#: cps/helper.py:98
msgid "Calibre-web test email"
msgstr "Calibre-web 测试邮件"
#: cps/helper.py:99 cps/helper.py:152
msgid "This email has been sent via calibre web."
msgstr "此邮件已通过calibre web发送"
#: cps/helper.py:133 cps/helper.py:221
#, python-format
msgid "Failed to send mail: %s"
msgstr "发送邮件失败: %s"
#: cps/helper.py:151 cps/templates/detail.html:124
msgid "Send to Kindle"
msgstr "发送到Kindle"
#: cps/helper.py:174 cps/helper.py:189
msgid "Could not find any formats suitable for sending by email"
msgstr "无法适合邮件发送的格式"
#: cps/helper.py:183
msgid "Could not convert epub to mobi"
msgstr "无法转换epub到mobi"
#: cps/helper.py:241
msgid "The requested file could not be read. Maybe wrong permissions?"
msgstr "无法读取所请求的文件。可能是错误权限不对?"
#: cps/web.py:722
msgid "Latest Books"
msgstr "最新书籍"
#: cps/web.py:747
msgid "Hot Books (most downloaded)"
msgstr "热门书籍(最多下载)"
#: cps/templates/index.xml:29 cps/web.py:755
msgid "Random Books"
msgstr "随机书籍"
#: cps/web.py:768
msgid "Author list"
msgstr "作者列表"
#: cps/web.py:785
#, python-format
msgid "Author: %(nam)s"
msgstr "作者: %(nam)s"
#: cps/templates/index.xml:50 cps/web.py:798
msgid "Series list"
msgstr "丛书列表"
#: cps/web.py:809
#, python-format
msgid "Series: %(serie)s"
msgstr "丛书: %(serie)s"
#: cps/web.py:811 cps/web.py:907 cps/web.py:1102 cps/web.py:1838
msgid "Error opening eBook. File does not exist or file is not accessible:"
msgstr "无法打开电子书。 文件不存在或者文件不可访问:"
#: cps/web.py:842
msgid "Available languages"
msgstr "可用语言"
#: cps/web.py:857
#, python-format
msgid "Language: %(name)s"
msgstr "语言: %(name)s"
#: cps/templates/index.xml:43 cps/web.py:870
msgid "Category list"
msgstr "分类列表"
#: cps/web.py:880
#, python-format
msgid "Category: %(name)s"
msgstr "分类: %(name)s"
#: cps/web.py:936
msgid "Statistics"
msgstr "统计"
#: cps/web.py:944
msgid "Server restarts"
msgstr "重启服务器"
#: cps/web.py:1078 cps/web.py:1085 cps/web.py:1092 cps/web.py:1099
msgid "Read a Book"
msgstr "阅读一本书"
#: cps/web.py:1141 cps/web.py:1474
msgid "Please fill out all fields!"
msgstr "请填写所有字段"
#: cps/web.py:1157
msgid "An unknown error occured. Please try again later."
msgstr "发生一个未知错误。请稍后再试。"
#: cps/web.py:1162
msgid "This username or email address is already in use."
msgstr "此用户名或邮箱已被使用。"
#: cps/web.py:1165
msgid "register"
msgstr "注册"
#: cps/web.py:1181
#, python-format
msgid "you are now logged in as: '%(nickname)s'"
msgstr "您现在已以'%(nickname)s'身份登录"
#: cps/web.py:1185
msgid "Wrong Username or Password"
msgstr "用户名或密码错误"
#: cps/web.py:1187
msgid "login"
msgstr "登录"
#: cps/web.py:1204
msgid "Please configure the SMTP mail settings first..."
msgstr "请先配置SMTP邮箱..."
#: cps/web.py:1208
#, python-format
msgid "Book successfully send to %(kindlemail)s"
msgstr "此书已被成功发给 %(kindlemail)s"
#: cps/web.py:1212
#, python-format
msgid "There was an error sending this book: %(res)s"
msgstr "发送 %(res)s 这本书的时候出现错误"
#: cps/web.py:1214
msgid "Please configure your kindle email address first..."
msgstr "请先配置您的kindle电子邮箱地址..."
#: cps/web.py:1234
#, python-format
msgid "Book has been added to shelf: %(sname)s"
msgstr "此书已被添加到书架: %(sname)s"
#: cps/web.py:1255
#, python-format
msgid "Book has been removed from shelf: %(sname)s"
msgstr "此书已从书架 %(sname)s 中删除"
#: cps/web.py:1273 cps/web.py:1294
#, python-format
msgid "A shelf with the name '%(title)s' already exists."
msgstr "已存在书架 '%(title)s'。"
#: cps/web.py:1278
#, python-format
msgid "Shelf %(title)s created"
msgstr "书架 %(title)s 已被创建"
#: cps/web.py:1280 cps/web.py:1305
msgid "There was an error"
msgstr "发生错误"
#: cps/web.py:1281 cps/web.py:1283
msgid "create a shelf"
msgstr "创建书架"
#: cps/web.py:1303
#, python-format
msgid "Shelf %(title)s changed"
msgstr "书架 %(title)s 已被修改"
#: cps/web.py:1306 cps/web.py:1308
msgid "Edit a shelf"
msgstr "编辑书架"
#: cps/web.py:1329
#, python-format
msgid "successfully deleted shelf %(name)s"
msgstr "成功删除书架 %(name)s"
#: cps/web.py:1350
#, python-format
msgid "Shelf: '%(name)s'"
msgstr "书架: '%(name)s'"
#: cps/web.py:1378
#, python-format
msgid "Change order of Shelf: '%(name)s'"
msgstr "修改书架 '%(name)s' 顺序"
#: cps/web.py:1433
msgid "Found an existing account for this email address."
msgstr "找到已使用此邮箱的账号。"
#: cps/web.py:1435 cps/web.py:1438
#, python-format
msgid "%(name)s's profile"
msgstr "%(name)s 的资料"
#: cps/web.py:1436
msgid "Profile updated"
msgstr "资料已更新"
#: cps/web.py:1447 cps/web.py:1455
msgid "Admin page"
msgstr "管理页"
#: cps/templates/admin.html:32 cps/web.py:1475
msgid "Add new user"
msgstr "添加新用户"
#: cps/web.py:1508
#, python-format
msgid "User '%(user)s' created"
msgstr "用户 '%(user)s' 已被创建"
#: cps/web.py:1512
msgid "Found an existing account for this email address or nickname."
msgstr "已找到使用此邮箱或昵称的账号。"
#: cps/web.py:1535 cps/web.py:1546
msgid "Mail settings updated"
msgstr "邮箱设置已更新"
#: cps/web.py:1541
#, python-format
msgid "Test E-Mail successfully send to %(kindlemail)s"
msgstr "测试邮件已成功发送到 %(kindlemail)s"
#: cps/web.py:1544
#, python-format
msgid "There was an error sending the Test E-Mail: %(res)s"
msgstr "发送测试邮件时发生错误: %(res)s"
#: cps/web.py:1547
msgid "Edit mail settings"
msgstr "编辑邮箱设置"
#: cps/web.py:1570
#, python-format
msgid "User '%(nick)s' deleted"
msgstr "用户 '%(nick)s' 已被删除"
#: cps/web.py:1625
#, python-format
msgid "User '%(nick)s' updated"
msgstr "用户 '%(nick)s' 已被更新"
#: cps/web.py:1628
msgid "An unknown error occured."
msgstr "发生未知错误。"
#: cps/web.py:1630
#, python-format
msgid "Edit User %(nick)s"
msgstr "编辑用户 %(nick)s"
#: cps/web.py:1868
#, python-format
msgid "Failed to create path %s (Permission denied)."
msgstr "创建路径 %s 失败(权限拒绝)。"
#: cps/web.py:1873
#, python-format
msgid "Failed to store file %s (Permission denied)."
msgstr "存储文件 %s 失败(权限拒绝)。"
#: cps/web.py:1878
#, python-format
msgid "Failed to delete file %s (Permission denied)."
msgstr "删除文件 %s 失败(权限拒绝)。"
#: cps/templates/admin.html:4
msgid "User list"
msgstr "用户列表"
#: cps/templates/admin.html:7
msgid "Nickname"
msgstr "昵称"
#: cps/templates/admin.html:8
msgid "Email"
msgstr ""
#: cps/templates/admin.html:9
msgid "Kindle"
msgstr ""
#: cps/templates/admin.html:10
msgid "DLS"
msgstr ""
#: cps/templates/admin.html:11 cps/templates/layout.html:83
msgid "Admin"
msgstr "管理"
#: cps/templates/admin.html:12 cps/templates/detail.html:114
msgid "Download"
msgstr "下载"
#: cps/templates/admin.html:13 cps/templates/layout.html:76
msgid "Upload"
msgstr "上传"
#: cps/templates/admin.html:14
msgid "Edit"
msgstr "编辑"
#: cps/templates/admin.html:15
msgid "Passwd"
msgstr "修改密码"
#: cps/templates/admin.html:33
msgid "SMTP mail settings"
msgstr "SMTP设置"
#: cps/templates/admin.html:36 cps/templates/email_edit.html:7
msgid "SMTP hostname"
msgstr "SMTP地址"
#: cps/templates/admin.html:37
msgid "SMTP port"
msgstr "SMTP端口"
#: cps/templates/admin.html:38
msgid "SSL"
msgstr ""
#: cps/templates/admin.html:39 cps/templates/email_edit.html:19
msgid "SMTP login"
msgstr "SMTP用户名"
#: cps/templates/admin.html:40 cps/templates/email_edit.html:23
msgid "SMTP password"
msgstr "SMTP密码"
#: cps/templates/admin.html:41
msgid "From mail"
msgstr "来自邮箱"
#: cps/templates/admin.html:53
msgid "Change SMTP settings"
msgstr "修改SMTP设置"
#: cps/templates/admin.html:55
msgid "Configuration"
msgstr "配置"
#: cps/templates/admin.html:58
msgid "Log File"
msgstr "日志文件"
#: cps/templates/admin.html:59
msgid "Log Level"
msgstr "日志级别"
#: cps/templates/admin.html:60
msgid "Port"
msgstr "端口"
#: cps/templates/admin.html:61
msgid "Books per page"
msgstr "每页书籍数"
#: cps/templates/admin.html:62
msgid "Uploading"
msgstr "上传"
#: cps/templates/admin.html:63
msgid "Public registration"
msgstr "开放注册"
#: cps/templates/admin.html:64
msgid "Anonymous browsing"
msgstr "匿名浏览"
#: cps/templates/admin.html:75
msgid "Administration"
msgstr "管理"
#: cps/templates/admin.html:77
msgid "Restart Calibre-web"
msgstr "重启 Calibre-web"
#: cps/templates/detail.html:38
msgid "Book"
msgstr ""
#: cps/templates/detail.html:38
msgid "of"
msgstr ""
#: cps/templates/detail.html:44
msgid "language"
msgstr "语言"
#: cps/templates/detail.html:103
msgid "Description:"
msgstr "简介:"
#: cps/templates/detail.html:128
msgid "Read in browser"
msgstr "在浏览器中阅读"
#: cps/templates/detail.html:147
msgid "Add to shelf"
msgstr "添加到书架"
#: cps/templates/detail.html:187
msgid "Edit metadata"
msgstr "编辑元数据"
#: cps/templates/edit_book.html:14 cps/templates/search_form.html:6
msgid "Book Title"
msgstr "书名"
#: cps/templates/edit_book.html:18 cps/templates/search_form.html:10
msgid "Author"
msgstr "作者"
#: cps/templates/edit_book.html:22
msgid "Description"
msgstr "简介"
#: cps/templates/edit_book.html:26 cps/templates/search_form.html:13
msgid "Tags"
msgstr "标签"
#: cps/templates/edit_book.html:31 cps/templates/layout.html:133
#: cps/templates/search_form.html:33
msgid "Series"
msgstr "丛书"
#: cps/templates/edit_book.html:35
msgid "Series id"
msgstr "丛书ID"
#: cps/templates/edit_book.html:39
msgid "Rating"
msgstr "评分"
#: cps/templates/edit_book.html:43
msgid "Cover URL (jpg)"
msgstr "封面URL (jpg)"
#: cps/templates/edit_book.html:48 cps/templates/user_edit.html:27
msgid "Language"
msgstr "语言"
#: cps/templates/edit_book.html:59
msgid "Yes"
msgstr "确认"
#: cps/templates/edit_book.html:60
msgid "No"
msgstr ""
#: cps/templates/edit_book.html:102
msgid "view book after edit"
msgstr "编辑后查看书籍"
#: cps/templates/edit_book.html:105 cps/templates/login.html:19
#: cps/templates/search_form.html:75 cps/templates/shelf_edit.html:15
#: cps/templates/user_edit.html:94
msgid "Submit"
msgstr "提交"
#: cps/templates/edit_book.html:106 cps/templates/email_edit.html:32
#: cps/templates/shelf_edit.html:17 cps/templates/shelf_order.html:12
#: cps/templates/user_edit.html:96
msgid "Back"
msgstr "后退"
#: cps/templates/email_edit.html:11
msgid "SMTP port (usually 25 for plain SMTP and 587 for SSL)"
msgstr "SMTP端口(不加密的SMTP通常是25, SSL加密的是587)"
#: cps/templates/email_edit.html:15
msgid "Server uses SSL (StartTLS)"
msgstr "服务器使用SSL (StartTLS)"
#: cps/templates/email_edit.html:27
msgid "From e-mail"
msgstr "来自邮箱"
#: cps/templates/email_edit.html:30
msgid "Save settings"
msgstr "保存设置"
#: cps/templates/email_edit.html:31
msgid "Save settings and send Test E-Mail"
msgstr "保存设置并发送测试邮件"
#: cps/templates/feed.xml:14
msgid "Next"
msgstr "下一个"
#: cps/templates/index.html:5
msgid "Discover (Random Books)"
msgstr "发现(随机书籍)"
#: cps/templates/index.xml:5
msgid "Start"
msgstr "开始"
#: cps/templates/index.xml:7 cps/templates/layout.html:61
msgid "Search"
msgstr "搜索"
#: cps/templates/index.xml:15 cps/templates/layout.html:124
msgid "Hot Books"
msgstr "热门书籍"
#: cps/templates/index.xml:19
msgid "Popular publications from this catalog based on Rating."
msgstr ""
#: cps/templates/index.xml:22 cps/templates/layout.html:122
msgid "New Books"
msgstr "新书"
#: cps/templates/index.xml:26
msgid "The latest Books"
msgstr "最新书籍"
#: cps/templates/index.xml:33
msgid "Show Random Books"
msgstr "显示随机书籍"
#: cps/templates/index.xml:36 cps/templates/layout.html:135
msgid "Authors"
msgstr "作者"
#: cps/templates/index.xml:40
msgid "Books ordered by Author"
msgstr "书籍按作者排序"
#: cps/templates/index.xml:47
msgid "Books ordered by category"
msgstr "书籍按分类排序"
#: cps/templates/index.xml:54
msgid "Books ordered by series"
msgstr "书籍按丛书排序"
#: cps/templates/layout.html:48
msgid "Toggle navigation"
msgstr ""
#: cps/templates/layout.html:63
msgid "Go!"
msgstr "走起!"
#: cps/templates/layout.html:66
msgid "Advanced Search"
msgstr "高级搜索"
#: cps/templates/layout.html:87
msgid "Logout"
msgstr "注销"
#: cps/templates/layout.html:91 cps/templates/login.html:4
msgid "Login"
msgstr "登录"
#: cps/templates/layout.html:92 cps/templates/register.html:18
msgid "Register"
msgstr "注册"
#: cps/templates/layout.html:121
msgid "Browse"
msgstr "浏览"
#: cps/templates/layout.html:127
msgid "Discover"
msgstr "发现"
#: cps/templates/layout.html:130
msgid "Categories"
msgstr "分类"
#: cps/templates/layout.html:137 cps/templates/search_form.html:54
msgid "Languages"
msgstr "语言"
#: cps/templates/layout.html:140
msgid "Public Shelves"
msgstr "公开书架"
#: cps/templates/layout.html:144
msgid "Your Shelves"
msgstr "您的书架"
#: cps/templates/layout.html:149
msgid "Create a Shelf"
msgstr "创建书架"
#: cps/templates/layout.html:152
msgid "About"
msgstr "关于"
#: cps/templates/login.html:7 cps/templates/login.html:8
#: cps/templates/register.html:7 cps/templates/user_edit.html:8
msgid "Username"
msgstr "用户名"
#: cps/templates/login.html:11 cps/templates/login.html:12
#: cps/templates/register.html:11 cps/templates/user_edit.html:18
msgid "Password"
msgstr "密码"
#: cps/templates/login.html:16
msgid "Remember me"
msgstr "记住我"
#: cps/templates/read.html:136
msgid "Reflow text when sidebars are open."
msgstr ""
#: cps/templates/readpdf.html:29
msgid "PDF.js viewer"
msgstr ""
#: cps/templates/readtxt.html:6
msgid "Basic txt Reader"
msgstr ""
#: cps/templates/register.html:4
msgid "Register a new account"
msgstr "注册新用户"
#: cps/templates/register.html:8
msgid "Choose a username"
msgstr "选择一个用户名"
#: cps/templates/register.html:12
msgid "Choose a password"
msgstr "选择一个密码"
#: cps/templates/register.html:15 cps/templates/user_edit.html:13
msgid "Email address"
msgstr "邮箱地址"
#: cps/templates/register.html:16
msgid "Your email address"
msgstr "您的邮箱地址"
#: cps/templates/search.html:6
msgid "No Results for:"
msgstr "找不到结果:"
#: cps/templates/search.html:7
msgid "Please try a diffrent Search"
msgstr "请尝试别的关键字"
#: cps/templates/search.html:9
msgid "Results for:"
msgstr "结果:"
#: cps/templates/search_form.html:23
msgid "Exclude Tags"
msgstr "排除标签"
#: cps/templates/search_form.html:43
msgid "Exclude Series"
msgstr "排除丛书"
#: cps/templates/search_form.html:64
msgid "Exclude Languages"
msgstr "排除语言"
#: cps/templates/shelf.html:6
msgid "Delete this Shelf"
msgstr "删除此书架"
#: cps/templates/shelf.html:7
msgid "Edit Shelf name"
msgstr "编辑书架名"
#: cps/templates/shelf.html:8
msgid "change order"
msgstr "修改顺序"
#: cps/templates/shelf_edit.html:7
msgid "Title"
msgstr ""
#: cps/templates/shelf_edit.html:12
msgid "should the shelf be public?"
msgstr "要公开此书架吗?"
#: cps/templates/shelf_order.html:5
msgid "Drag 'n drop to rearrange order"
msgstr ""
#: cps/templates/shelf_order.html:11
msgid "Change 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:36
msgid "Calibre library statistics"
msgstr "Calibre书库统计"
#: cps/templates/stats.html:41
msgid "Books in this Library"
msgstr "本书在此书库"
#: cps/templates/stats.html:45
msgid "Authors in this Library"
msgstr "个作者在此书库"
#: cps/templates/stats.html:49
msgid "Series in this Library"
msgstr "个丛书在此书库"
#: cps/templates/stats.html:53
msgid "Tags in this Library"
msgstr "个标签在此书库"
#: cps/templates/stats.html:57
msgid "Usercount for calibre web"
msgstr ""
#: cps/templates/user_edit.html:23
msgid "Kindle E-Mail"
msgstr ""
#: cps/templates/user_edit.html:35
msgid "Show books with language"
msgstr "按语言显示书籍"
#: cps/templates/user_edit.html:37
msgid "Show all"
msgstr "显示全部"
#: cps/templates/user_edit.html:45
msgid "Show random books"
msgstr "显示随机书籍"
#: cps/templates/user_edit.html:49
msgid "Show hot books"
msgstr "显示热门书籍"
#: cps/templates/user_edit.html:53
msgid "Show language selection"
msgstr "显示语言选择"
#: cps/templates/user_edit.html:57
msgid "Show series selection"
msgstr "显示丛书选择"
#: cps/templates/user_edit.html:61
msgid "Show category selection"
msgstr "显示分类选择"
#: cps/templates/user_edit.html:68
msgid "Admin user"
msgstr "管理用户"
#: cps/templates/user_edit.html:72
msgid "Allow Downloads"
msgstr "允许下载"
#: cps/templates/user_edit.html:76
msgid "Allow Uploads"
msgstr "允许上传"
#: cps/templates/user_edit.html:80
msgid "Allow Edit"
msgstr "允许编辑"
#: cps/templates/user_edit.html:84
msgid "Allow Changing Password"
msgstr "允许修改密码"
#: cps/templates/user_edit.html:90
msgid "Delete this user"
msgstr "删除此用户"
#: cps/templates/user_edit.html:101
msgid "Recent Downloads"
msgstr "最近下载"
msgid "Latin"
msgstr ""

View File

@ -132,6 +132,7 @@ class Anonymous(AnonymousUserMixin,UserBase):
self.category_books = data.category_books
self.hot_books = data.hot_books
self.default_language = data.default_language
self.locale = data.locale
def role_admin(self):
return False

View File

@ -122,13 +122,6 @@ lm.anonymous_user = ub.Anonymous
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'
LANGUAGES = {
'en': 'English',
'de': 'Deutsch',
'fr': 'Français',
'es': 'Español'
}
@babel.localeselector
def get_locale():
@ -136,8 +129,9 @@ def get_locale():
user = getattr(g, 'user', None)
if user is not None and hasattr(user, "locale"):
return user.locale
translations=[item.language for item in babel.list_translations()]+ ['en']
preferred = [x.replace('-', '_') for x in request.accept_languages.values()]
return negotiate_locale(preferred, LANGUAGES.keys())
return negotiate_locale(preferred, translations)
@babel.timezoneselector