diff --git a/application/lib/calibre/web/feeds/news.py b/application/lib/calibre/web/feeds/news.py index 4eaf4530..04c6c1cd 100644 --- a/application/lib/calibre/web/feeds/news.py +++ b/application/lib/calibre/web/feeds/news.py @@ -1076,7 +1076,7 @@ def _postprocess_html(self, soup, first_fetch, job_info): #If translation need, translator propery is set by WorkerImpl if (getattr(self, 'translator', None) or {}).get('enable'): - self.translate_html(soup) + self.translate_html(soup, title) if job_info: try: @@ -1098,7 +1098,7 @@ def append_share_links(self, soup, url): shareLinks = self.user.share_links aTags = [] - for type_ in ['Evernote', 'Wiz', 'Pocket', 'Instapaper']: + for type_ in ['Evernote', 'Wiz', 'Pocket', 'Instapaper', 'wallabag']: if shareLinks.get(type_, {}).get('enable'): ashare = soup.new_tag('a', href=self.make_share_link(type_, self.user, url, soup)) ashare.string = _('Save to {}').format(type_) @@ -1125,7 +1125,7 @@ def append_share_links(self, soup, url): #生成保存内容或分享文章链接的KindleEar调用链接 def make_share_link(self, shareType, user, url, soup): - share_key = user.share_links.get('key', '123') + share_key = user.share_links.get('key', '') titleTag = soup.find('title') title = titleTag.string if titleTag else 'Untitled' appDomain = os.getenv('APP_DOMAIN') @@ -1135,6 +1135,8 @@ def make_share_link(self, shareType, user, url, soup): href = f'{appDomain}/share?act=Pocket&u={user.name}&t={title}&k={share_key}&url={quote(url)}' elif shareType == 'Instapaper': href = f'{appDomain}/share?act=Instapaper&u={user.name}&t={title}&k={share_key}&url={quote(url)}' + elif shareType == 'wallabag': + href = f'{appDomain}/share?act=wallabag&u={user.name}&t={title}&k={share_key}&url={quote(url)}' elif shareType == 'Weibo': href = f'https://service.weibo.com/share/share.php?url={quote(url)}' elif shareType == 'Facebook': @@ -1959,9 +1961,10 @@ def internal_postprocess_book(self, oeb, opts, log): seen.add(url) #调用在线翻译服务平台,翻译html - def translate_html(self, soup): + def translate_html(self, soup, title): from ebook_translator import HtmlTranslator translator = HtmlTranslator(self.translator, self.simultaneous_downloads) + self.log.debug(f'Translating [{title}]') translator.translate_soup(soup) #翻译Feed的title,toc时用到 @@ -2074,7 +2077,7 @@ def parse_feeds(self): feeds = [] id_counter = 0 - added = set(); + added = set() for obj in main_urls: main_title, main_url = (self.title, obj) if isinstance(obj, str) else obj feed = Feed() @@ -2114,10 +2117,6 @@ def parse_feeds(self): return feeds - #在一个soup对象中查找所有满足条件的tag - def _soup_find_all(self, tag, rule): - return tag.find_all(**rule) if isinstance(rule, dict) else tag.select(rule) - #从一个网页中根据指定的规则,提取文章链接 def extract_urls(self, main_title, main_url): resp = self.browser.open(main_url, timeout=self.timeout) @@ -2128,14 +2127,9 @@ def extract_urls(self, main_title, main_url): soup = BeautifulSoup(resp.text, 'lxml') articles = [] - for rule in self.url_extract_rules: - resultTags = self._soup_find_all(soup, rule[0]) - for flt in rule[1:]: - resultTags = [self._soup_find_all(tag, flt) for tag in resultTags] - resultTags = [tag for sublist in resultTags for tag in sublist] #二级列表展开为一级列表 - - for item in resultTags: - #如果最终tag不是链接,则在子节点中查找 + for rules in self.url_extract_rules: + for item in self.get_tags_from_rules(soup, rules): + #如果最终tag不是链接,则在子节点中查找,并且添加所有找到的链接 item = item.find_all('a') if item.name.lower() != 'a' else [item] for tag in item: title = ' '.join(tag.stripped_strings) or main_title @@ -2160,17 +2154,23 @@ def preprocess_raw_html(self, raw_html, url): return raw_html newBody = soup.new_tag('body') - for rule in self.content_extract_rules: - resultTags = self._soup_find_all(soup, rule[0]) - for flt in rule[1:]: - resultTags = [self._soup_find_all(tag, flt) for tag in resultTags] - resultTags = [tag for sublist in resultTags for tag in sublist] #二级列表展开为一级列表 - - newBody.extend(resultTags) + for rules in self.content_extract_rules: + newBody.extend(self.get_tags_from_rules(soup, rules)) oldBody.replace_with(newBody) return str(soup) + #根据一个规则列表,从soup中获取符合条件的tag列表 + #rules: 字符串列表或字典列表 + def get_tags_from_rules(self, soup, rules): + if isinstance(rules[0], dict): #使用Tag字典查找 + resultTags = soup.find_all(**rules[0]) + for idx, flt in enumerate(rules[1:]): + resultTags = [tag.find_all(**flt) for tag in resultTags] + resultTags = [tag for sublist in resultTags for tag in sublist] #二级列表展开为一级列表 + else: #使用CSS选择器,每个选择器的总共最长允许字符长度:1366 + resultTags = soup.select(' '.join(rules)) + return resultTags class CalibrePeriodical(BasicNewsRecipe): diff --git a/application/lib/ebook_translator/engines/baidu.py b/application/lib/ebook_translator/engines/baidu.py index d8636d10..95cd0b16 100644 --- a/application/lib/ebook_translator/engines/baidu.py +++ b/application/lib/ebook_translator/engines/baidu.py @@ -2,7 +2,7 @@ import json import random import hashlib - +from urllib.parse import urljoin from .base import Base from .languages import baidu @@ -10,7 +10,8 @@ class BaiduTranslate(Base): name = 'Baidu' alias = 'Baidu' lang_codes = Base.load_lang_codes(baidu) - endpoint = 'https://fanyi-api.baidu.com/api/trans/vip/translate' + default_api_host = 'https://fanyi-api.baidu.com' + endpoint = '/api/trans/vip/translate' api_key_hint = 'appid|appkey' api_key_pattern = r'^[^\s:\|]+?[:\|][^\s:\|]+$' api_key_errors = ['54004'] @@ -34,7 +35,7 @@ def translate(self, text): 'salt': salt, 'sign': sign } - - return self.get_result( - self.endpoint, data, headers, method='POST', + + endpoint = urljoin(self.api_host or self.default_api_host, self.endpoint) + return self.get_result(endpoint, data, headers, method='POST', callback=lambda r: json.loads(r)['trans_result'][0]['dst']) diff --git a/application/lib/ebook_translator/engines/base.py b/application/lib/ebook_translator/engines/base.py index 513686f6..4e47534c 100644 --- a/application/lib/ebook_translator/engines/base.py +++ b/application/lib/ebook_translator/engines/base.py @@ -3,7 +3,7 @@ import ssl import os.path import traceback - +from urllib.parse import urljoin from urlopener import UrlOpener class Base: @@ -12,6 +12,7 @@ class Base: lang_codes = {} endpoint = None need_api_key = True + default_api_host = '' api_key_hint = _('API Keys') api_key_pattern = r'^[^\s]+$' api_key_errors = ['401'] @@ -25,7 +26,7 @@ class Base: request_timeout = 10.0 max_error_count = 10 - def __init__(self): + def __init__(self, config=None): self.source_lang = None #语种显示的名字 self.target_lang = None self.source_code = None #语种代码 @@ -35,7 +36,7 @@ def __init__(self): self.merge_enabled = False - self.set_config() + self.set_config(config) @classmethod def load_lang_codes(cls, codes): @@ -81,6 +82,7 @@ def get_iso639_target_code(cls, lang): def set_config(self, config=None): self.config = config or {} self.api_keys = self.config.get('api_keys', [])[:] + self.api_host = self.config.get('api_host', self.default_api_host) self.bad_api_keys = [] self.api_key = self._get_api_key() @@ -195,8 +197,8 @@ def get_result(self, url, data=None, headers=None, method='GET', if resp.status_code == 200: text = [] if stream: - for line in resp.iter_content(chunk_size=None, decode_unicode=True): - text.append(line) + for line in resp.iter_content(chunk_size=None): + text.append(line if isinstance(line, str) else line.decode('utf-8')) text = ''.join(text) else: text = resp.text diff --git a/application/lib/ebook_translator/engines/chatgpt.py b/application/lib/ebook_translator/engines/chatgpt.py index a45d6af6..ed7e6ae6 100644 --- a/application/lib/ebook_translator/engines/chatgpt.py +++ b/application/lib/ebook_translator/engines/chatgpt.py @@ -1,5 +1,5 @@ import json - +from urllib.parse import urljoin from .base import Base from .languages import google @@ -12,8 +12,9 @@ class ChatgptTranslate(Base): name = 'ChatGPT' alias = 'ChatGPT (OpenAI)' lang_codes = Base.load_lang_codes(google) - endpoint = 'https://api.openai.com/v1/chat/completions' - # api_key_hint = 'sk-xxx...xxx' + default_api_host = 'https://api.openai.com' + endpoint = '/v1/chat/completions' + api_key_hint = 'sk-xxx...xxx' # https://help.openai.com/en/collections/3808446-api-error-codes-explained api_key_errors = ['401', 'unauthorized', 'quota'] @@ -39,8 +40,8 @@ class ChatgptTranslate(Base): top_p = 1 stream = True - def __init__(self): - Base.__init__(self) + def __init__(self, config=None): + Base.__init__(self, config) self.endpoint = self.config.get('endpoint', self.endpoint) self.prompt = self.config.get('prompt', self.prompt) if self.model is None: @@ -91,9 +92,9 @@ def translate(self, text): data = self._get_data(text) sampling_value = getattr(self, self.sampling) data.update({self.sampling: sampling_value}) + endpoint = urljoin(self.api_host or self.default_api_host, self.endpoint) - return self.get_result( - self.endpoint, json.dumps(data), self._get_headers(), + return self.get_result(endpoint, json.dumps(data), self._get_headers(), method='POST', stream=self.stream, callback=self._parse) def _parse(self, data): @@ -102,30 +103,25 @@ def _parse(self, data): return json.loads(data)['choices'][0]['message']['content'] def _parse_stream(self, data): - while True: - try: - line = data.readline().decode('utf-8').strip() - except IncompleteRead: + ret = [] + for line in data.split('\n'): + line = line.strip() + if not line or not line.startswith('data:'): continue - except Exception as e: - raise Exception( - _('Can not parse returned response. Raw data: {}') - .format(str(e))) - if line.startswith('data:'): - chunk = line.split('data: ')[1] - if chunk == '[DONE]': - break - delta = json.loads(chunk)['choices'][0]['delta'] - if 'content' in delta: - yield str(delta['content']) + chunk = line.split('data: ')[1].strip() + if chunk == '[DONE]': + break + delta = json.loads(chunk)['choices'][0]['delta'] + if 'content' in delta: + ret.append(str(delta['content'])) + return ''.join(ret) class AzureChatgptTranslate(ChatgptTranslate): name = 'ChatGPT(Azure)' alias = 'ChatGPT (Azure)' - endpoint = ( - '$AZURE_OPENAI_ENDPOINT/openai/deployments/gpt-35-turbo/chat/' - 'completions?api-version=2023-05-15') + default_api_host = '' + endpoint = '/openai/deployments/gpt-35-turbo/chat/completions?api-version=2023-05-15' model = None def _get_headers(self): diff --git a/application/lib/ebook_translator/engines/deepl.py b/application/lib/ebook_translator/engines/deepl.py index 6013b903..1e149e30 100644 --- a/application/lib/ebook_translator/engines/deepl.py +++ b/application/lib/ebook_translator/engines/deepl.py @@ -1,7 +1,7 @@ import json import time import random - +from urllib.parse import urljoin from .base import Base from .languages import deepl @@ -9,9 +9,10 @@ class DeeplTranslate(Base): name = 'DeepL' alias = 'DeepL' lang_codes = Base.load_lang_codes(deepl) + default_api_host = 'https://api-free.deepl.com' endpoint = { - 'translate': 'https://api-free.deepl.com/v2/translate', - 'usage': 'https://api-free.deepl.com/v2/usage', + 'translate': '/v2/translate', + 'usage': '/v2/usage', } # api_key_hint = 'xxx-xxx-xxx:fx' placeholder = ('', r'') @@ -20,8 +21,8 @@ class DeeplTranslate(Base): def get_usage(self): # See: https://www.deepl.com/docs-api/general/get-usage/ headers = {'Authorization': 'DeepL-Auth-Key %s' % self.api_key} - usage = self.get_result( - self.endpoint.get('usage'), headers=headers, silence=True, + endpoint = urljoin(self.api_host or self.default_api_host, self.endpoint.get('usage')) + usage = self.get_result(endpoint, headers=headers, silence=True, callback=lambda r: json.loads(r)) if usage is None: return None @@ -42,8 +43,8 @@ def translate(self, text): if not self._is_auto_lang(): data.update(source_lang=self._get_source_code()) - return self.get_result( - self.endpoint.get('translate'), data, headers, method='POST', + endpoint = urljoin(self.api_host or self.default_api_host, self.endpoint.get('translate')) + return self.get_result(endpoint, data, headers, method='POST', callback=lambda r: json.loads(r)['translations'][0]['text']) diff --git a/application/lib/ebook_translator/engines/google.py b/application/lib/ebook_translator/engines/google.py index 0487b3bc..f82bc3e5 100644 --- a/application/lib/ebook_translator/engines/google.py +++ b/application/lib/ebook_translator/engines/google.py @@ -5,7 +5,7 @@ import json import os.path import traceback - +from urllib.parse import urljoin from .base import Base from .languages import google, gemini @@ -13,7 +13,8 @@ class GoogleFreeTranslate(Base): name = 'Google(Free)' alias = 'Google (Free)' lang_codes = Base.load_lang_codes(google) - endpoint = 'https://translate.googleapis.com/translate_a/single' + default_api_host = 'https://translate.googleapis.com' + endpoint = '/translate_a/single' need_api_key = False def translate(self, text): @@ -39,8 +40,8 @@ def translate(self, text): # The POST method is unstable, despite its ability to send more text. # However, it can be used occasionally with an unacceptable length. method = 'GET' if len(text) <= 1800 else 'POST' - return self.get_result( - self.endpoint, data, headers, method=method, callback=self._parse) + endpoint = urljoin(self.default_api_host, self.endpoint) + return self.get_result(endpoint, data, headers, method=method, callback=self._parse) def _parse(self, data): # return ''.join(i[0] for i in json.loads(data)[0]) @@ -144,7 +145,8 @@ class GoogleBasicTranslateADC(GoogleTranslate, Base): name = 'Google(Basic)ADC' alias = 'Google (Basic) ADC' lang_codes = Base.load_lang_codes(google) - endpoint = 'https://translation.googleapis.com/language/translate/v2' + default_api_host = 'https://translation.googleapis.com' + endpoint = '/language/translate/v2' api_key_hint = 'API key' need_api_key = False @@ -168,8 +170,8 @@ def translate(self, text): if not self._is_auto_lang(): data.update(source=self._get_source_code()) - return self.get_result( - self.endpoint, self.get_data(data), self.get_headers(), + endpoint = urljoin(self.api_host or self.default_api_host, self.endpoint) + return self.get_result(endpoint, self.get_data(data), self.get_headers(), method='POST', callback=self._parse) def _parse(self, data): @@ -195,13 +197,15 @@ class GoogleAdvancedTranslate(GoogleTranslate, Base): name = 'Google(Advanced)' alias = 'Google (Advanced) ADC' lang_codes = Base.load_lang_codes(google) - endpoint = 'https://translation.googleapis.com/v3/projects/{}' + default_api_host = 'https://translation.googleapis.com' + endpoint = '/v3/projects/{}' api_key_hint = 'PROJECT_ID' need_api_key = False def translate(self, text): project_id = self._get_project_id() endpoint = self.endpoint.format('%s:translateText' % project_id) + endpoint = urljoin(self.api_host or self.default_api_host, endpoint) headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer %s' % self._get_credential(), @@ -230,8 +234,8 @@ class GeminiPro(Base): name = 'GeminiPro' alias = 'Gemini Pro' lang_codes = Base.load_lang_codes(gemini) - endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' \ - 'gemini-pro:{}?key={}' + default_api_host = 'https://generativelanguage.googleapis.com' + endpoint = '/v1beta/models/gemini-pro:{}?key={}' need_api_key = True concurrency_limit = 1 @@ -247,8 +251,8 @@ class GeminiPro(Base): top_p = 1 stream = True - def __init__(self): - Base.__init__(self) + def __init__(self, config=None): + Base.__init__(self, config) self.prompt = self.config.get('prompt', self.prompt) self.temperature = self.config.get('temperature', self.temperature) self.top_k = self.config.get('top_k', self.top_k) @@ -257,7 +261,8 @@ def __init__(self): def _endpoint(self): method = 'streamGenerateContent' if self.stream else 'generateContent' - return self.endpoint.format(method, self.api_key) + endpoint = self.endpoint.format(method, self.api_key) + return urljoin(self.api_host or self.default_api_host, self.endpoint) def _prompt(self, text): prompt = self.prompt.replace('', self.target_lang) @@ -267,8 +272,8 @@ def _prompt(self, text): prompt = prompt.replace('', self.source_lang) # Recommend setting temperature to 0.5 for retaining the placeholder. if self.merge_enabled: - prompt += ' Ensure that placeholders matching the pattern' \ - '{{id_\\d+}} in the content are retained.' + prompt += (' Ensure that placeholders matching the pattern' + '{{id_\\d+}} in the content are retained.') return prompt + ' Start translating: ' + text def _headers(self): diff --git a/application/lib/ebook_translator/engines/microsoft.py b/application/lib/ebook_translator/engines/microsoft.py index 8c7452e7..e49b7622 100644 --- a/application/lib/ebook_translator/engines/microsoft.py +++ b/application/lib/ebook_translator/engines/microsoft.py @@ -1,21 +1,16 @@ import json import base64 from datetime import datetime - +from urllib.parse import urljoin, urlencode from .languages import microsoft from .base import Base - -try: - from urllib.parse import urlencode -except ImportError: - from urllib import urlencode - class MicrosoftEdgeTranslate(Base): name = 'MicrosoftEdge(Free)' alias = 'Microsoft Edge (Free)' lang_codes = Base.load_lang_codes(microsoft) - endpoint = 'https://api-edge.cognitive.microsofttranslator.com/translate' + default_api_host = 'https://api-edge.cognitive.microsofttranslator.com' + endpoint = '/translate' need_api_key = False access_info = None @@ -27,7 +22,8 @@ def _normalized_endpoint(self): } if not self._is_auto_lang(): query['from'] = self._get_source_code() - return '%s?%s' % (self.endpoint, urlencode(query)) + endpoint = urljoin(self.default_api_host, self.endpoint) + return f'{endpoint}?{urlencode(query)}' def _parse_jwt(self, token): parts = token.split(".") diff --git a/application/lib/ebook_translator/engines/youdao.py b/application/lib/ebook_translator/engines/youdao.py index 21363eb7..5edd3b28 100644 --- a/application/lib/ebook_translator/engines/youdao.py +++ b/application/lib/ebook_translator/engines/youdao.py @@ -3,7 +3,7 @@ import time import uuid import hashlib - +from urllib.parse import urljoin from .base import Base from .languages import youdao @@ -11,7 +11,8 @@ class YoudaoTranslate(Base): name = 'Youdao' alias = 'Youdao' lang_codes = Base.load_lang_codes(youdao) - endpoint = 'https://openapi.youdao.com/api' + default_api_host = 'https://openapi.youdao.com' + endpoint = '/api' api_key_hint = 'appid|appsecret' api_key_pattern = r'^[^\s:\|]+?[:\|][^\s:\|]+$' api_key_errors = ['401'] @@ -53,6 +54,6 @@ def translate(self, text): 'vocabId': False, } - return self.get_result( - self.endpoint, data, headers, method='POST', + endpoint = urljoin(self.api_host or self.default_api_host, self.endpoint) + return self.get_result(endpoint, data, headers, method='POST', callback=lambda r: json.loads(r)['translation'][0]) diff --git a/application/lib/ebook_translator/html_translator.py b/application/lib/ebook_translator/html_translator.py index 116d0878..fd90ddc4 100644 --- a/application/lib/ebook_translator/html_translator.py +++ b/application/lib/ebook_translator/html_translator.py @@ -10,6 +10,7 @@ def get_trans_engines(): info = {} for engine in builtin_translate_engines: info[engine.name] = {'alias': engine.alias, 'need_api_key': engine.need_api_key, + 'default_api_host': engine.default_api_host, 'api_key_hint': engine.api_key_hint, 'source': engine.lang_codes.get('source', {}), 'target': engine.lang_codes.get('target', {}),} return info @@ -22,18 +23,17 @@ def __init__(self, params: dict, thread_num: int=1): self.engineName = self.params.get('engine') self.src = self.params.get('src_lang', '') self.dst = self.params.get('dst_lang', 'en') - self.translator = self.create_engine(self.engineName) - self.translator.set_config(params) + self.translator = self.create_engine(self.engineName, params) self.translator.set_source_code(self.src) self.translator.set_target_code(self.dst) - def create_engine(self, name): + def create_engine(self, name, params): engines = {engine.name: engine for engine in builtin_translate_engines} if name in engines: engine_class = engines.get(name) else: engine_class = GoogleFreeTranslate - return engine_class() + return engine_class(params) #翻译文本 #data: 文本/字典/列表 {'text': text, ...}, [{},{}] @@ -55,11 +55,11 @@ def translate_text(self, data): item['error'] = '' item['translated'] = '' if text: - try: + if 1: item['translated'] = self.translator.translate(text) - except Exception as e: - default_log.warning(str(e)) - item['error'] = str(e) + #except Exception as e: + #default_log.warning('translate_text() failed: ' + str(e)) + #item['error'] = str(e) else: item['error'] = _('The input text is empty') ret.append(item) @@ -89,7 +89,7 @@ def translate_soup(self, soup): else: failed += 1 except Exception as e: - default_log.warning(str(e)) + default_log.warning('translate_soup failed: ' + str(e)) failed += 1 if (idx < count - 1) and (self.translator.request_interval > 0.01): time.sleep(self.translator.request_interval) diff --git a/application/lib/pocket.py b/application/lib/pocket.py index c945601b..93596cbb 100644 --- a/application/lib/pocket.py +++ b/application/lib/pocket.py @@ -74,24 +74,24 @@ def __init__(self, consumer_key, redirect_uri=None): self.access_token = None self.opener = UrlOpener(headers=self.POCKET_HEADERS) - def _post(self, method_url, **kw): + def _post(self, method_url, act, **kw): ret = self.opener.open(method_url, data=kw) if ret.status_code > 399: - raise APIError(ret.status_code, ret.headers.get('X-Error-Code',''), ret.headers.get('X-Error',''), 'Get access token') + raise APIError(ret.status_code, ret.headers.get('X-Error-Code',''), ret.headers.get('X-Error',''), act) try: return json.loads(ret.content) except: return json.text - def _authenticated_post(self, method_url, **kw): + def _authenticated_post(self, method_url, act, **kw): kw['consumer_key'] = self.consumer_key kw['access_token'] = self.access_token - return self._post(method_url, **kw) + return self._post(method_url, act, **kw) def get_request_token(self): #此步仅用来直接通过一次http获取一个request_token(code),pocket不会回调redirect_uri - ret = self._post(self.REQUEST_TOKEN_URL, consumer_key=self.consumer_key, redirect_uri=self.redirect_uri) + ret = self._post(self.REQUEST_TOKEN_URL, 'get request token', consumer_key=self.consumer_key, redirect_uri=self.redirect_uri) return ret.get('code', '') if isinstance(ret, dict) else ret.split('=')[-1] def get_authorize_url(self, code): @@ -101,7 +101,7 @@ def get_authorize_url(self, code): def get_access_token(self, code): # access token : {"access_token":"dcba4321-dcba-4321-dcba-4321dc","username":"pocketuser"}. - ret = self._post(self.ACCESS_TOKEN_URL, consumer_key=self.consumer_key, code=code) + ret = self._post(self.ACCESS_TOKEN_URL, 'get access token', consumer_key=self.consumer_key, code=code) self.access_token = ret.get('access_token', '') return self.access_token @@ -115,10 +115,10 @@ def add(self, **kw): #tags : 可选,逗号分隔的字符串列表 #tweet_id : 可选,用于发推的ID #返回一个字典,包含的键可能有:https://getpocket.com/developer/docs/v3/add - return self._authenticated_post('https://getpocket.com/v3/add', **kw) + return self._authenticated_post('https://getpocket.com/v3/add', 'add an entry', **kw) def get(self, **kw): - return self._authenticated_post('https://getpocket.com/v3/get', **kw) + return self._authenticated_post('https://getpocket.com/v3/get', 'get an entry', **kw) def modify(self, **kw): pass diff --git a/application/lib/wallabag.py b/application/lib/wallabag.py new file mode 100644 index 00000000..c03b8b93 --- /dev/null +++ b/application/lib/wallabag.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# -*- coding:utf-8 -*- +#Wallabag api,实现添加一个条目到wallabag +#Wallabag 官方主机:https://app.wallabag.it +from urllib.parse import urljoin +from urlopener import UrlOpener + +class WallaBag: + ALLOW_EXTENSIONS = ('json', 'xml', 'txt', 'csv', 'pdf', 'epub', 'mobi', 'html') + + #config是一个字典,包含键 host, client_id, client_secret, username, password, + #access_token, refresh_token + def __init__(self, config, log, extension='json'): + for item in ['host', 'client_id', 'client_secret', 'username', 'password']: + config.setdefault(item, '') #保证每个元素都存在 + self.config = config + assert(extension in self.ALLOW_EXTENSIONS) + self.extension = extension + self.log = log + self.opener = UrlOpener() + + #添加一个条目到 WallaBag + #url : 要保存的URL,最好先urlencode + #title : 可选,如果保存的是一个图片或PDF,则需要,否则不需要 + #tags : 可选,逗号分隔的字符串列表 + #archive: 0/1 - 条目已存档 + #starred: 0/1 - 条目已加星标 + #content: 如果不需要wallbag联网获取url的内容,可以使用此参数传递,同时title/url也不能为空 + #language: 语言种类 + #published_at: 发布日期,$YYYY-MM-DDTHH:II:SS+TZ 或整数时间戳 + #authors: 逗号分割的作者名字字符串 + #public: 0/1 - 是否创建用于公开分享的链接 + #origin_url: 原始地址,可以和url相同 + #返回字典:{'msg': , 'changed': , 'resp': } + #如果成功,resp保存服务器返回的json,msg为空,changed只是token是否有变化 + #失败时msg保存失败描述 + def add(self, url, **kwargs): + ret = {'msg': '', 'changed': False, 'resp': None} + kwargs['url'] = url + resp, need_get_token = self._add_entry(kwargs) + + if need_get_token: #重新获取access token + self.log.debug('Updating wallabag token') + msg = self.update_token() + if not msg: + self.log.info('Refreshed wallabag token') + ret['changed'] = True + resp, _ = self._add_entry(kwargs) #重新执行 + else: + ret['msg'] = msg + elif not resp: + ret['msg'] = 'No server response or non-JSON format returned.' + + ret['resp'] = resp + return ret + + #更新token,包括access_token/refresh_token + #如果失败,返回错误描述字符串,否则返回空串 + def update_token(self): + c = self.config + url = urljoin(c["host"], '/oauth/v2/token') + data1 = {'username': c['username'], 'password': c['password'], 'client_id': c['client_id'], + 'client_secret': c['client_secret'], 'grant_type': 'password'} + data2 = {'client_id': c['client_id'], 'client_secret': c['client_secret'], + 'refresh_token': c.get('refresh_token', ''), 'grant_type': 'refresh_token'} + + errors = [] + dataList = [data2, data1] if c.get('refresh_token') else [data1] + for data in dataList: + try: + resp = self.opener.open(url, data=data) + except Exception as e: + msg = 'Error while getting token using {}: {}'.format(data['grant_type'], str(e)) + self.log.warning(msg) + errors.append(msg) + continue + + if resp.status_code < 400: + respDict = resp.json() + if isinstance(respDict, dict): + self.config['access_token'] = respDict.get('access_token', '') + self.config['refresh_token'] = respDict.get('refresh_token', '') + return '' #任何一个成功就返回 + else: + msg = 'Error while getting token using {}: {}'.format(data['grant_type'], resp.status_code) + self.log.warning(msg) + errors.append(msg) + + return '\n'.join(errors) + + #将一个url添加到wallabag + #data: 为一个字典 + #返回 resp, need_get_token + def _add_entry(self, data): + access_token = self.config.get('access_token', '') + if not access_token: + return None, True + + headers = {'Authorization': f'Bearer {access_token}'} + + wallaUrl = urljoin(self.config['host'], f'/api/entries.{self.extension}') + try: + resp = self.opener.open(wallaUrl, headers=headers, data=data) + except Exception as e: + self.log.error('Error adding url: {}'.format(str(e))) + return None, False + + self.log.debug('add url to wallabag get Resp: {}, {}'.format(resp.status_code, resp.text)) + if resp.status_code >= 400: + return None, (resp.status_code == 401) + else: + return resp.json(), False + diff --git a/application/static/base.css b/application/static/base.css index b9fcaae3..91d9e41a 100644 --- a/application/static/base.css +++ b/application/static/base.css @@ -916,7 +916,7 @@ div[class="schedule_daytimes"] input { border: 1px solid #333; border-radius: 5px; font-size: 14px; - line-height:20px; + line-height: 18px; top: calc(-100%); /*calc(10% + 5px);*/ left: 50%; transform: translateX(-50%); diff --git a/application/static/base.js b/application/static/base.js index 40c54b60..55b36dbd 100644 --- a/application/static/base.js +++ b/application/static/base.js @@ -733,7 +733,7 @@ function insertBookmarkletGmailThis(subscribeUrl, mailPrefix) { var parent = $('#bookmarklet_content'); var newElement = $('', { class: 'actionButton', - href: "javascript:(function(){popw='';Q='';d=document;w=window;if(d.selection){Q=d.selection.createRange().text;}else if(w.getSelection){Q=w.getSelection();}else if(d.getSelection){Q=d.getSelection();}popw=w.open('http://mail.google.com/mail/s?view=cm&fs=1&tf=1&to=" + addr + + href: "javascript:(function(){popw='';Q='';d=document;w=window;if(d.selection){Q=d.selection.createRange().text;}else if(w.getSelection){Q=w.getSelection();}else if(d.getSelection){Q=d.getSelection();}popw=w.open('https://mail.google.com/mail/s?view=cm&fs=1&tf=1&to=" + addr + "&su='+encodeURIComponent(d.title)+'&body='+encodeURIComponent(Q)+escape('%5Cn%5Cn')+encodeURIComponent(d.location)+'&zx=RANDOMCRAP&shva=1&disablechatbrowsercheck=1&ui=1','gmailForm','scrollbars=yes,width=550,height=400,top=100,left=75,status=no,resizable=yes');if(!d.all)setTimeout(function(){popw.focus();},50);})();", click: function() { return false; @@ -774,15 +774,10 @@ function SelectDeliverNone() { ///[end] adv_delivernow.html ///[start] adv_archive.html -function verifyInstapaper() { - var notifyInstapaperVerify = function () { - $("#averInstapaper").html(i18n.verify); - }; +function VerifyInstapaper() { var instauser = $("#instapaper_username").val(); var instapass = $("#instapaper_password").val(); - notifyInstapaperVerify(); - $.ajax({ url: "/verifyajax/instapaper", type: "POST", @@ -790,18 +785,41 @@ function verifyInstapaper() { success: function (data, textStatus, jqXHR) { if (data.status != "ok") { alert("Error:" + data.status); - notifyInstapaperVerify(); } else if (data.correct == 1) { - alert(i18n.congratulations); - $("#averInstapaper").html(i18n.verified); + ShowSimpleModalDialog('

{0}

{1}

'.format(i18n.congratulations, i18n.configOk)); } else { alert(i18n.passwordWrong); - notifyInstapaperVerify(); } }, error: function (status) { alert(status); - notifyInstapaperVerify(); + } + }); +} + +//测试wallabag的配置信息是否正确 +function VerifyWallaBag() { + let host = $("#wallabag_host").val(); + let name = $("#wallabag_username").val(); + let passwd = $("#wallabag_password").val(); + let id_ = $("#wallabag_client_id").val(); + let secret = $("#wallabag_client_secret").val(); + let data = {'host': host, 'username': name, 'password': passwd, 'client_id': id_, + 'client_secret': secret}; + + $.ajax({ + url: "/verifyajax/wallabag", + type: "POST", + data: data, + success: function (data, textStatus, jqXHR) { + if (data.status == "ok") { + ShowSimpleModalDialog('

{0}

{1}

'.format(i18n.congratulations, i18n.configOk)); + } else { + alert(data.status); + } + }, + error: function (status) { + alert(status.toString()); } }); } @@ -1123,12 +1141,18 @@ function PopulateTranslatorFields(currEngineName) { //选择一个翻译引擎后显示或隐藏ApiKey文本框,语种列表也同步更新 //src_lang/dst_lang: 当前recipe的语种代码 function TranslatorEngineFieldChanged(src_lang, dst_lang) { - //显示或隐藏ApiKey文本框 + //显示或隐藏ApiHost/ApiKey文本框 var engineName = $('#translator_engine').val(); var engine = g_trans_engines[engineName]; if (!engine || engine.need_api_key) { + $('#api_host_input').attr('placeholder', engine.default_api_host); + $('#api_keys_textarea').attr('placeholder', engine.api_key_hint); + $('#api_keys_textarea').prop("required", true); + $('#translator_api_host').show(); $('#translator_api_key').show(); } else { + $('#api_keys_textarea').prop("required", false); + $('#translator_api_host').hide(); $('#translator_api_key').hide(); } @@ -1177,14 +1201,16 @@ function TranslatorEngineFieldChanged(src_lang, dst_lang) { //测试Recipe的翻译器设置是否正确 function TestTranslator(recipeId) { var text = $('#translator_test_src_text').val(); - console.log(text); + //console.log(text); var divDst = $('#translator_test_dst_text'); + divDst.val(i18n.translating); $.post("/translator/test", {recipeId: recipeId, text: text}, function (data) { if (data.status == "ok") { divDst.val(data.text); } else if (data.status == i18n.loginRequired) { window.location.href = '/login'; } else { + divDst.val(''); alert(data.status); } }); diff --git a/application/templates/admin.html b/application/templates/admin.html index 146cf12b..718b4524 100644 --- a/application/templates/admin.html +++ b/application/templates/admin.html @@ -65,7 +65,7 @@ {{_("Yes") if u.enable_send else _("No")}} {{u.email}} {% if u.expiration_days == 0 -%} - {{_("Never expire")}} + {{_("Never")}} {% elif u.expiration_days == 7 -%} {{_("7 Days")}} {% elif u.expiration_days == 30 -%} @@ -82,7 +82,7 @@ {{u.expiration_days}} Days {% endif -%} -
+ {% if u.name == adminName -%} {% else -%} diff --git a/application/templates/adv_archive.html b/application/templates/adv_archive.html index 05994172..460ae07d 100644 --- a/application/templates/adv_archive.html +++ b/application/templates/adv_archive.html @@ -8,6 +8,7 @@ {% set wiz = shareLinks.get('Wiz', {}) %} {% set instapaper = shareLinks.get('Instapaper', {}) %} {% set pocket = shareLinks.get('Pocket', {}) %} +{% set wallabag = shareLinks.get('wallabag', {}) %}

{{_("Archive")}}

@@ -49,13 +50,38 @@ {{appendStrs["Instapaper"]}}
- +
- +
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+
diff --git a/application/templates/base.html b/application/templates/base.html index d42f3785..661ca161 100644 --- a/application/templates/base.html +++ b/application/templates/base.html @@ -92,7 +92,9 @@ abbrSep: '{{_("Sep")|safe}}', abbrLog: '{{_("Log")|safe}}', abbrEmb: '{{_("Emb")|safe}}', - testEmailOk: '{{_("The test email has been successfully sent to the following addresses. Please check your inbox or spam folder to confirm its delivery. Depending on your email server, there may be a slight delay.")|safe}}' + testEmailOk: '{{_("The test email has been successfully sent to the following addresses. Please check your inbox or spam folder to confirm its delivery. Depending on your email server, there may be a slight delay.")|safe}}', + translating: '{{_("Translating...")|safe}}', + configOk: '{{_("The configuration validation is correct.")|safe}}' }; {% block javascript_inhead %} diff --git a/application/templates/book_translator.html b/application/templates/book_translator.html index 5b06f37a..82ea3190 100644 --- a/application/templates/book_translator.html +++ b/application/templates/book_translator.html @@ -4,6 +4,7 @@ {% endblock -%} {% set src_lang = params.get('src_lang', '') %} {% set dst_lang = params.get('dst_lang', '') %} +{% set api_host = params.get('api_host', '') %} {% set api_keys = params.get('api_keys', [])|join('\n') %} {% set position = params.get('position', '') %} {% block content -%} @@ -27,9 +28,13 @@ +
+ + +
- +
diff --git a/application/templates/change_password.html b/application/templates/change_password.html index ba132315..1b4b9826 100644 --- a/application/templates/change_password.html +++ b/application/templates/change_password.html @@ -1,6 +1,6 @@ {% extends "base.html" %} {% block titleTag -%} -{{ _("Change password") }} - KindleEar +{{ _("Edit account") }} - KindleEar {% endblock -%} {% block content -%} @@ -10,7 +10,7 @@ {% endif -%}
-

{{_('Change Password')}}

+

{{_('Edit account')}}

@@ -27,7 +27,12 @@
+
+ + +
+
diff --git a/messages.pot b/application/translations/messages.pot similarity index 82% rename from messages.pot rename to application/translations/messages.pot index ae5363d0..0eb23ae7 100644 --- a/messages.pot +++ b/application/translations/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-04-02 17:17-0300\n" +"POT-Creation-Date: 2024-04-06 20:42-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,7 +17,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.14.0\n" -#: application/templates/admin.html:3 application/templates/base.html:144 +#: application/templates/admin.html:3 application/templates/base.html:146 msgid "Admin" msgstr "" @@ -80,7 +80,8 @@ msgstr "" msgid "Add" msgstr "" -#: application/templates/admin.html:54 application/templates/home.html:15 +#: application/templates/admin.html:54 +#: application/templates/adv_archive.html:69 application/templates/home.html:15 #: application/templates/login.html:21 application/templates/logs.html:65 #: application/templates/reset_password.html:19 #: application/templates/reset_password.html:20 @@ -119,8 +120,7 @@ msgid "No" msgstr "" #: application/templates/admin.html:68 -#: application/templates/user_account.html:42 -msgid "Never expire" +msgid "Never" msgstr "" #: application/templates/admin.html:70 application/templates/setting.html:150 @@ -155,7 +155,9 @@ msgstr "" #: application/templates/admin.html:85 #: application/templates/change_password.html:3 -msgid "Change password" +#: application/templates/change_password.html:13 application/view/admin.py:130 +#: application/view/admin.py:189 +msgid "Edit account" msgstr "" #: application/templates/admin.html:89 @@ -166,41 +168,60 @@ msgid "Delete" msgstr "" #: application/templates/adv_archive.html:3 -#: application/templates/adv_archive.html:13 +#: application/templates/adv_archive.html:14 #: application/templates/adv_base.html:57 #: application/templates/adv_base.html:61 msgid "Archive" msgstr "" -#: application/templates/adv_archive.html:14 +#: application/templates/adv_archive.html:15 msgid "Append hyperlinks for archiving or sharing." msgstr "" -#: application/templates/adv_archive.html:39 +#: application/templates/adv_archive.html:40 msgid "Authorized" msgstr "" -#: application/templates/adv_archive.html:41 +#: application/templates/adv_archive.html:42 msgid "Authorize" msgstr "" -#: application/templates/adv_archive.html:52 +#: application/templates/adv_archive.html:53 msgid "Email or Username" msgstr "" -#: application/templates/adv_archive.html:55 application/templates/base.html:47 +#: application/templates/adv_archive.html:56 +#: application/templates/adv_archive.html:72 application/templates/base.html:47 #: application/templates/home.html:16 application/templates/login.html:25 #: application/templates/setting.html:222 application/templates/signup.html:25 #: application/templates/user_account.html:19 msgid "Password" msgstr "" -#: application/templates/adv_archive.html:58 application/templates/base.html:57 +#: application/templates/adv_archive.html:59 application/templates/base.html:57 msgid "Verify" msgstr "" -#: application/templates/adv_archive.html:89 -#: application/templates/book_translator.html:72 +#: application/templates/adv_archive.html:75 +#: application/templates/book_translator.html:94 +msgid "Test" +msgstr "" + +#: application/templates/adv_archive.html:78 +msgid "client_id" +msgstr "" + +#: application/templates/adv_archive.html:81 +msgid "client_secret" +msgstr "" + +#: application/templates/adv_archive.html:84 +#: application/templates/setting.html:210 +msgid "Host" +msgstr "" + +#: application/templates/adv_archive.html:115 +#: application/templates/book_translator.html:77 #: application/templates/setting.html:244 msgid "Save settings" msgstr "" @@ -496,8 +517,8 @@ msgstr "" msgid "Verified" msgstr "" -#: application/templates/base.html:59 application/view/login.py:81 -#: application/view/share.py:154 +#: application/templates/base.html:59 application/view/login.py:87 +#: application/view/share.py:157 msgid "The username does not exist or password is wrong." msgstr "" @@ -593,14 +614,13 @@ msgstr "" msgid "The account have been deleted." msgstr "" -#: application/templates/base.html:83 application/view/admin.py:201 -#: application/view/share.py:144 +#: application/templates/base.html:83 application/view/share.py:147 msgid "The username or password is empty." msgstr "" #: application/templates/base.html:84 application/view/admin.py:86 -#: application/view/admin.py:165 application/view/admin.py:205 -#: application/view/login.py:222 application/view/login.py:282 +#: application/view/admin.py:166 application/view/admin.py:205 +#: application/view/login.py:229 application/view/login.py:289 msgid "The two new passwords are dismatch." msgstr "" @@ -612,7 +632,7 @@ msgstr "" msgid "Account added successfully." msgstr "" -#: application/templates/base.html:87 application/view/login.py:132 +#: application/templates/base.html:87 application/view/login.py:139 msgid "login required" msgstr "" @@ -628,7 +648,7 @@ msgstr "" #: application/templates/base.html:90 #: application/templates/book_translator.html:3 -#: application/templates/book_translator.html:16 +#: application/templates/book_translator.html:17 msgid "Translator" msgstr "" @@ -655,128 +675,132 @@ msgid "" " on your email server, there may be a slight delay." msgstr "" -#: application/templates/base.html:117 application/templates/home.html:12 +#: application/templates/base.html:96 +msgid "Translating..." +msgstr "" + +#: application/templates/base.html:97 +msgid "The configuration validation is correct." +msgstr "" + +#: application/templates/base.html:119 application/templates/home.html:12 msgid "Logout" msgstr "" -#: application/templates/base.html:119 application/templates/home.html:17 +#: application/templates/base.html:121 application/templates/home.html:17 #: application/templates/login.html:3 application/templates/login.html:19 #: application/templates/login.html:29 msgid "Login" msgstr "" -#: application/templates/base.html:121 application/templates/signup.html:3 +#: application/templates/base.html:123 application/templates/signup.html:3 #: application/templates/signup.html:19 application/templates/signup.html:43 msgid "Signup" msgstr "" -#: application/templates/base.html:141 application/templates/home.html:11 +#: application/templates/base.html:143 application/templates/home.html:11 #: application/templates/my.html:3 msgid "Feeds" msgstr "" -#: application/templates/base.html:142 application/templates/setting.html:3 +#: application/templates/base.html:144 application/templates/setting.html:3 msgid "Settings" msgstr "" -#: application/templates/base.html:143 application/templates/logs.html:3 +#: application/templates/base.html:145 application/templates/logs.html:3 msgid "Logs" msgstr "" -#: application/templates/base.html:145 +#: application/templates/base.html:147 msgid "Advanced" msgstr "" -#: application/templates/base.html:146 application/templates/library.html:3 +#: application/templates/base.html:148 application/templates/library.html:3 msgid "Shared" msgstr "" -#: application/templates/book_translator.html:18 +#: application/templates/book_translator.html:19 msgid "State" msgstr "" -#: application/templates/book_translator.html:20 +#: application/templates/book_translator.html:21 msgid "Enable" msgstr "" -#: application/templates/book_translator.html:21 +#: application/templates/book_translator.html:22 msgid "Disable" msgstr "" -#: application/templates/book_translator.html:25 +#: application/templates/book_translator.html:26 msgid "Engine" msgstr "" -#: application/templates/book_translator.html:31 +#: application/templates/book_translator.html:32 +msgid "Api Host" +msgstr "" + +#: application/templates/book_translator.html:36 msgid "Api Key" msgstr "" -#: application/templates/book_translator.html:32 +#: application/templates/book_translator.html:37 msgid "One key per line" msgstr "" -#: application/templates/book_translator.html:35 +#: application/templates/book_translator.html:40 msgid "Source language" msgstr "" -#: application/templates/book_translator.html:41 +#: application/templates/book_translator.html:46 msgid "Target language" msgstr "" -#: application/templates/book_translator.html:47 +#: application/templates/book_translator.html:52 msgid "Translation Position" msgstr "" -#: application/templates/book_translator.html:49 +#: application/templates/book_translator.html:54 msgid "Below original" msgstr "" -#: application/templates/book_translator.html:50 +#: application/templates/book_translator.html:55 msgid "Above original" msgstr "" -#: application/templates/book_translator.html:51 +#: application/templates/book_translator.html:56 msgid "Left to original" msgstr "" -#: application/templates/book_translator.html:52 +#: application/templates/book_translator.html:57 msgid "Right to original" msgstr "" -#: application/templates/book_translator.html:53 +#: application/templates/book_translator.html:58 msgid "Translated text only" msgstr "" -#: application/templates/book_translator.html:57 +#: application/templates/book_translator.html:62 msgid "Original text style" msgstr "" -#: application/templates/book_translator.html:61 +#: application/templates/book_translator.html:66 msgid "Translated text style" msgstr "" -#: application/templates/book_translator.html:67 +#: application/templates/book_translator.html:72 msgid "Apply to all subscribed recipes" msgstr "" -#: application/templates/book_translator.html:78 +#: application/templates/book_translator.html:83 msgid "Test (Please save settings firstly)" msgstr "" -#: application/templates/book_translator.html:80 +#: application/templates/book_translator.html:85 msgid "Text" msgstr "" -#: application/templates/book_translator.html:84 -msgid "Translation" -msgstr "" - #: application/templates/book_translator.html:89 -msgid "Test" -msgstr "" - -#: application/templates/change_password.html:13 -msgid "Change Password" +msgid "Translation" msgstr "" #: application/templates/change_password.html:15 @@ -797,7 +821,11 @@ msgstr "" msgid "Confirm password" msgstr "" -#: application/templates/change_password.html:32 +#: application/templates/change_password.html:31 +msgid "Share key" +msgstr "" + +#: application/templates/change_password.html:37 msgid "Confirm Change" msgstr "" @@ -837,8 +865,8 @@ msgstr "" msgid "Search" msgstr "" -#: application/templates/login.html:34 application/view/login.py:199 -#: application/view/login.py:206 +#: application/templates/login.html:34 application/view/login.py:206 +#: application/view/login.py:213 msgid "" "The website does not allow registration. You can ask the owner for an " "account." @@ -1061,10 +1089,6 @@ msgstr "" msgid "SecretKey" msgstr "" -#: application/templates/setting.html:210 -msgid "Host" -msgstr "" - #: application/templates/setting.html:214 msgid "Port" msgstr "" @@ -1102,8 +1126,12 @@ msgstr "" msgid "User account" msgstr "" -#: application/view/admin.py:51 application/view/adv.py:350 -#: application/view/setting.py:96 application/view/subscribe.py:241 +#: application/templates/user_account.html:42 +msgid "Never expire" +msgstr "" + +#: application/view/admin.py:51 application/view/adv.py:376 +#: application/view/setting.py:96 application/view/subscribe.py:245 msgid "Settings Saved!" msgstr "" @@ -1113,138 +1141,140 @@ msgid "Add account" msgstr "" #: application/view/admin.py:68 application/view/admin.py:109 -#: application/view/admin.py:137 +#: application/view/admin.py:136 msgid "You do not have sufficient privileges." msgstr "" -#: application/view/admin.py:82 application/view/admin.py:152 -#: application/view/login.py:218 application/view/login.py:247 -#: application/view/setting.py:61 application/view/setting.py:63 -#: application/view/setting.py:65 application/view/share.py:36 +#: application/view/admin.py:82 application/view/admin.py:153 +#: application/view/admin.py:203 application/view/login.py:225 +#: application/view/login.py:254 application/view/setting.py:61 +#: application/view/setting.py:63 application/view/setting.py:65 +#: application/view/share.py:37 msgid "Some parameters are missing or wrong." msgstr "" #: application/view/admin.py:84 application/view/login.py:40 -#: application/view/login.py:224 +#: application/view/login.py:231 msgid "The username includes unsafe chars." msgstr "" -#: application/view/admin.py:88 application/view/login.py:226 +#: application/view/admin.py:88 application/view/login.py:233 msgid "Already exist the username." msgstr "" -#: application/view/admin.py:93 application/view/admin.py:171 -#: application/view/admin.py:198 application/view/login.py:273 +#: application/view/admin.py:93 application/view/admin.py:172 +#: application/view/admin.py:199 application/view/login.py:280 msgid "The password includes non-ascii chars." msgstr "" -#: application/view/admin.py:113 application/view/admin.py:134 -#: application/view/admin.py:163 +#: application/view/admin.py:113 application/view/admin.py:133 +#: application/view/admin.py:164 msgid "The username '{}' does not exist." msgstr "" -#: application/view/admin.py:129 +#: application/view/admin.py:124 msgid "The password will not be changed if the fields are empties." msgstr "" -#: application/view/admin.py:130 application/view/admin.py:188 -msgid "Change account" -msgstr "" - -#: application/view/admin.py:131 application/view/admin.py:189 +#: application/view/admin.py:131 application/view/admin.py:190 msgid "Change" msgstr "" -#: application/view/admin.py:186 +#: application/view/admin.py:187 msgid "Change success." msgstr "" -#: application/view/admin.py:203 -msgid "The old password is wrong." +#: application/view/admin.py:201 +msgid "Changes saved successfully." msgstr "" -#: application/view/admin.py:218 -msgid "Changes saved successfully." +#: application/view/admin.py:212 +msgid "The old password is wrong." msgstr "" -#: application/view/adv.py:79 application/view/adv.py:80 -#: application/view/adv.py:81 application/view/adv.py:82 -#: application/view/adv.py:83 application/view/adv.py:84 -#: application/view/adv.py:85 application/view/adv.py:86 -#: application/view/adv.py:87 +#: application/view/adv.py:80 application/view/adv.py:81 +#: application/view/adv.py:82 application/view/adv.py:83 +#: application/view/adv.py:84 application/view/adv.py:85 +#: application/view/adv.py:86 application/view/adv.py:87 +#: application/view/adv.py:88 application/view/adv.py:89 msgid "Append hyperlink '{}' to article" msgstr "" -#: application/view/adv.py:79 application/view/adv.py:80 -#: application/view/adv.py:81 application/view/adv.py:82 +#: application/view/adv.py:80 application/view/adv.py:81 +#: application/view/adv.py:82 application/view/adv.py:83 +#: application/view/adv.py:84 msgid "Save to {}" msgstr "" -#: application/view/adv.py:79 +#: application/view/adv.py:80 msgid "evernote" msgstr "" -#: application/view/adv.py:80 +#: application/view/adv.py:81 msgid "wiz" msgstr "" -#: application/view/adv.py:81 +#: application/view/adv.py:82 msgid "pocket" msgstr "" -#: application/view/adv.py:82 +#: application/view/adv.py:83 msgid "instapaper" msgstr "" -#: application/view/adv.py:83 application/view/adv.py:84 +#: application/view/adv.py:84 +msgid "wallabag" +msgstr "" + #: application/view/adv.py:85 application/view/adv.py:86 +#: application/view/adv.py:87 application/view/adv.py:88 msgid "Share on {}" msgstr "" -#: application/view/adv.py:83 +#: application/view/adv.py:85 msgid "weibo" msgstr "" -#: application/view/adv.py:84 +#: application/view/adv.py:86 msgid "facebook" msgstr "" -#: application/view/adv.py:86 +#: application/view/adv.py:88 msgid "tumblr" msgstr "" -#: application/view/adv.py:87 +#: application/view/adv.py:89 msgid "Open in browser" msgstr "" -#: application/view/adv.py:352 +#: application/view/adv.py:378 msgid "The format is invalid." msgstr "" -#: application/view/adv.py:385 +#: application/view/adv.py:411 msgid "Authorization Error!
{}" msgstr "" -#: application/view/adv.py:408 +#: application/view/adv.py:434 msgid "Success authorized by Pocket!" msgstr "" -#: application/view/adv.py:414 +#: application/view/adv.py:440 msgid "" "Failed to request authorization of Pocket!
See details " "below:

{}" msgstr "" -#: application/view/adv.py:424 -msgid "Request type [{}] unsupported" +#: application/view/adv.py:462 +msgid "The Instapaper service encountered an error. Please try again later." msgstr "" -#: application/view/adv.py:439 -msgid "The Instapaper service encountered an error. Please try again later." +#: application/view/adv.py:475 +msgid "Request type [{}] unsupported" msgstr "" -#: application/view/deliver.py:67 application/view/login.py:169 -#: application/view/share.py:40 +#: application/view/deliver.py:67 application/view/login.py:176 +#: application/view/share.py:41 msgid "The username does not exist or the email is empty." msgstr "" @@ -1261,13 +1291,13 @@ msgid "Cannot fetch data from {}, status: {}" msgstr "" #: application/view/library.py:51 application/view/subscribe.py:195 -#: application/view/subscribe.py:221 application/view/subscribe.py:272 -#: application/view/subscribe.py:301 application/view/subscribe.py:421 -#: application/view/subscribe.py:449 application/view/subscribe.py:456 +#: application/view/subscribe.py:221 application/view/subscribe.py:276 +#: application/view/subscribe.py:305 application/view/subscribe.py:425 +#: application/view/subscribe.py:453 application/view/subscribe.py:460 msgid "The recipe does not exist." msgstr "" -#: application/view/login.py:26 application/view/login.py:78 +#: application/view/login.py:26 application/view/login.py:84 msgid "Please use {}/{} to login at first time." msgstr "" @@ -1279,61 +1309,61 @@ msgstr "" msgid "The len of username reached the limit of 25 chars." msgstr "" -#: application/view/login.py:82 +#: application/view/login.py:88 msgid "Forgot password?" msgstr "" -#: application/view/login.py:148 application/view/login.py:284 +#: application/view/login.py:155 application/view/login.py:291 msgid "The token is wrong or expired." msgstr "" -#: application/view/login.py:151 +#: application/view/login.py:158 msgid "Please input the correct username and email to reset password." msgstr "" -#: application/view/login.py:153 +#: application/view/login.py:160 msgid "The email of account '{name}' is {email}." msgstr "" -#: application/view/login.py:174 +#: application/view/login.py:181 msgid "Reset password success, Please close this page and login again." msgstr "" -#: application/view/login.py:177 +#: application/view/login.py:184 msgid "The email you input is not associated with this account." msgstr "" -#: application/view/login.py:188 +#: application/view/login.py:195 msgid "The link to reset your password has been sent to your email." msgstr "" -#: application/view/login.py:189 +#: application/view/login.py:196 msgid "Please check your email inbox within 24 hours." msgstr "" -#: application/view/login.py:220 +#: application/view/login.py:227 msgid "The invitation code is invalid." msgstr "" -#: application/view/login.py:228 +#: application/view/login.py:235 msgid "" "Failed to create an account. Please contact the administrator for " "assistance." msgstr "" -#: application/view/login.py:238 +#: application/view/login.py:245 msgid "Successfully created account." msgstr "" -#: application/view/login.py:249 +#: application/view/login.py:256 msgid "Reset KindleEar password" msgstr "" -#: application/view/login.py:250 +#: application/view/login.py:257 msgid "This is an automated email. Please do not reply to it." msgstr "" -#: application/view/login.py:251 +#: application/view/login.py:258 msgid "You can click the following link to reset your KindleEar password." msgstr "" @@ -1449,38 +1479,38 @@ msgstr "" msgid "Hausa" msgstr "" -#: application/view/share.py:51 application/view/subscribe.py:313 +#: application/view/share.py:54 application/view/subscribe.py:317 msgid "Unknown command: {}" msgstr "" -#: application/view/share.py:57 +#: application/view/share.py:60 msgid "There is no {} email yet." msgstr "" -#: application/view/share.py:105 application/view/share.py:130 -#: application/view/share.py:152 +#: application/view/share.py:108 application/view/share.py:133 +#: application/view/share.py:155 application/view/share.py:177 msgid "Saved to your {} account." msgstr "" -#: application/view/share.py:108 application/view/share.py:126 -#: application/view/share.py:155 +#: application/view/share.py:111 application/view/share.py:129 +#: application/view/share.py:158 application/view/share.py:179 msgid "Failed save to {}." msgstr "" -#: application/view/share.py:109 application/view/share.py:127 -#: application/view/share.py:156 +#: application/view/share.py:112 application/view/share.py:130 +#: application/view/share.py:159 application/view/share.py:180 msgid "Reason :" msgstr "" -#: application/view/share.py:118 +#: application/view/share.py:121 msgid "Unauthorized {} account!" msgstr "" -#: application/view/share.py:131 +#: application/view/share.py:134 msgid "See details below:" msgstr "" -#: application/view/share.py:154 +#: application/view/share.py:157 msgid "Unknown: {}" msgstr "" @@ -1500,7 +1530,7 @@ msgstr "" msgid "Failed to fetch the recipe." msgstr "" -#: application/view/subscribe.py:125 application/view/subscribe.py:383 +#: application/view/subscribe.py:125 application/view/subscribe.py:387 msgid "Failed to save the recipe. Error:" msgstr "" @@ -1508,46 +1538,46 @@ msgstr "" msgid "The Rss does not exist." msgstr "" -#: application/view/subscribe.py:204 application/view/subscribe.py:257 -#: application/view/subscribe.py:276 application/view/subscribe.py:358 +#: application/view/subscribe.py:204 application/view/subscribe.py:261 +#: application/view/subscribe.py:280 application/view/subscribe.py:362 msgid "This recipe has not been subscribed to yet." msgstr "" -#: application/view/subscribe.py:237 +#: application/view/subscribe.py:239 msgid "The api key is required." msgstr "" -#: application/view/subscribe.py:280 +#: application/view/subscribe.py:284 msgid "The text is empty." msgstr "" -#: application/view/subscribe.py:339 +#: application/view/subscribe.py:343 msgid "You can only delete the uploaded recipe." msgstr "" -#: application/view/subscribe.py:343 +#: application/view/subscribe.py:347 msgid "The recipe have been subscribed, please unsubscribe it before delete." msgstr "" -#: application/view/subscribe.py:370 +#: application/view/subscribe.py:374 msgid "Can not read uploaded file, Error:" msgstr "" -#: application/view/subscribe.py:378 +#: application/view/subscribe.py:382 msgid "" "Failed to decode the recipe. Please ensure that your recipe is saved in " "utf-8 encoding." msgstr "" -#: application/view/subscribe.py:398 +#: application/view/subscribe.py:402 msgid "The recipe is already in the library." msgstr "" -#: application/view/subscribe.py:428 +#: application/view/subscribe.py:432 msgid "The login information for this recipe has been cleared." msgstr "" -#: application/view/subscribe.py:432 +#: application/view/subscribe.py:436 msgid "The login information for this recipe has been saved." msgstr "" diff --git a/application/translations/tr_TR/LC_MESSAGES/messages.mo b/application/translations/tr_TR/LC_MESSAGES/messages.mo index bff340fd..84824bad 100644 Binary files a/application/translations/tr_TR/LC_MESSAGES/messages.mo and b/application/translations/tr_TR/LC_MESSAGES/messages.mo differ diff --git a/application/translations/tr_TR/LC_MESSAGES/messages.po b/application/translations/tr_TR/LC_MESSAGES/messages.po index 6dda8260..2efde65e 100644 --- a/application/translations/tr_TR/LC_MESSAGES/messages.po +++ b/application/translations/tr_TR/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-04-02 17:17-0300\n" +"POT-Creation-Date: 2024-04-06 20:42-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: tr_TR\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.14.0\n" -#: application/templates/admin.html:3 application/templates/base.html:144 +#: application/templates/admin.html:3 application/templates/base.html:146 msgid "Admin" msgstr "Yönetim" @@ -81,7 +81,8 @@ msgstr "Hesaplar" msgid "Add" msgstr "Ekle" -#: application/templates/admin.html:54 application/templates/home.html:15 +#: application/templates/admin.html:54 +#: application/templates/adv_archive.html:69 application/templates/home.html:15 #: application/templates/login.html:21 application/templates/logs.html:65 #: application/templates/reset_password.html:19 #: application/templates/reset_password.html:20 @@ -120,9 +121,8 @@ msgid "No" msgstr "Hayir" #: application/templates/admin.html:68 -#: application/templates/user_account.html:42 -msgid "Never expire" -msgstr "hiç sona ermeyen" +msgid "Never" +msgstr "Asla" #: application/templates/admin.html:70 application/templates/setting.html:150 #: application/templates/user_account.html:43 @@ -156,8 +156,10 @@ msgstr "2 yıl" #: application/templates/admin.html:85 #: application/templates/change_password.html:3 -msgid "Change password" -msgstr "Şifre Değiştirme" +#: application/templates/change_password.html:13 application/view/admin.py:130 +#: application/view/admin.py:189 +msgid "Edit account" +msgstr "Hesabı Düzenle" #: application/templates/admin.html:89 #: application/templates/adv_uploadcss.html:31 @@ -167,41 +169,60 @@ msgid "Delete" msgstr "Sil" #: application/templates/adv_archive.html:3 -#: application/templates/adv_archive.html:13 +#: application/templates/adv_archive.html:14 #: application/templates/adv_base.html:57 #: application/templates/adv_base.html:61 msgid "Archive" msgstr "Arşiv" -#: application/templates/adv_archive.html:14 +#: application/templates/adv_archive.html:15 msgid "Append hyperlinks for archiving or sharing." msgstr "Paylaşım linklerini makaleye ekle." -#: application/templates/adv_archive.html:39 +#: application/templates/adv_archive.html:40 msgid "Authorized" msgstr "Yetkili" -#: application/templates/adv_archive.html:41 +#: application/templates/adv_archive.html:42 msgid "Authorize" msgstr "Yetki vermek" -#: application/templates/adv_archive.html:52 +#: application/templates/adv_archive.html:53 msgid "Email or Username" msgstr "Kullanıcı adı" -#: application/templates/adv_archive.html:55 application/templates/base.html:47 +#: application/templates/adv_archive.html:56 +#: application/templates/adv_archive.html:72 application/templates/base.html:47 #: application/templates/home.html:16 application/templates/login.html:25 #: application/templates/setting.html:222 application/templates/signup.html:25 #: application/templates/user_account.html:19 msgid "Password" msgstr "Şifre" -#: application/templates/adv_archive.html:58 application/templates/base.html:57 +#: application/templates/adv_archive.html:59 application/templates/base.html:57 msgid "Verify" msgstr "Doğrulamak" -#: application/templates/adv_archive.html:89 -#: application/templates/book_translator.html:72 +#: application/templates/adv_archive.html:75 +#: application/templates/book_translator.html:94 +msgid "Test" +msgstr "Test" + +#: application/templates/adv_archive.html:78 +msgid "client_id" +msgstr "client_id" + +#: application/templates/adv_archive.html:81 +msgid "client_secret" +msgstr "client_secret" + +#: application/templates/adv_archive.html:84 +#: application/templates/setting.html:210 +msgid "Host" +msgstr "Ana bilgisayar" + +#: application/templates/adv_archive.html:115 +#: application/templates/book_translator.html:77 #: application/templates/setting.html:244 msgid "Save settings" msgstr "Ayarları kaydet" @@ -505,8 +526,8 @@ msgstr "Kindle'laştırma Seçimi" msgid "Verified" msgstr "Doğrulanmış" -#: application/templates/base.html:59 application/view/login.py:81 -#: application/view/share.py:154 +#: application/templates/base.html:59 application/view/login.py:87 +#: application/view/share.py:157 msgid "The username does not exist or password is wrong." msgstr "Kullanıcı adı mevcut değil veya şifre yanlış." @@ -602,14 +623,13 @@ msgstr "Tarif için özel teslimat zamanı başarıyla kaydedildi." msgid "The account have been deleted." msgstr "Hesap silindi." -#: application/templates/base.html:83 application/view/admin.py:201 -#: application/view/share.py:144 +#: application/templates/base.html:83 application/view/share.py:147 msgid "The username or password is empty." msgstr "Kullanıcı adı veya şifre boş." #: application/templates/base.html:84 application/view/admin.py:86 -#: application/view/admin.py:165 application/view/admin.py:205 -#: application/view/login.py:222 application/view/login.py:282 +#: application/view/admin.py:166 application/view/admin.py:205 +#: application/view/login.py:229 application/view/login.py:289 msgid "The two new passwords are dismatch." msgstr "İki yeni şifre uyuşmuyor." @@ -621,7 +641,7 @@ msgstr "Şifre başarıyla değiştirildi." msgid "Account added successfully." msgstr "Hesap başarıyla eklendi." -#: application/templates/base.html:87 application/view/login.py:132 +#: application/templates/base.html:87 application/view/login.py:139 msgid "login required" msgstr "Giriş yapılması gerekiyor" @@ -639,7 +659,7 @@ msgstr "" #: application/templates/base.html:90 #: application/templates/book_translator.html:3 -#: application/templates/book_translator.html:16 +#: application/templates/book_translator.html:17 msgid "Translator" msgstr "Çevirmen" @@ -665,134 +685,138 @@ msgid "" "Please check your inbox or spam folder to confirm its delivery. Depending" " on your email server, there may be a slight delay." msgstr "" -"Test e-postası başarıyla adresine gönderildi. Teslimatını onaylamak" -" için gelen kutunuzu veya spam klasörünüzü kontrol edin. E-posta " -"sunucunuza bağlı olarak hafif bir gecikme olabilir." +"Test e-postası başarıyla adresine gönderildi. Teslimatını onaylamak için " +"gelen kutunuzu veya spam klasörünüzü kontrol edin. E-posta sunucunuza " +"bağlı olarak hafif bir gecikme olabilir." -#: application/templates/base.html:117 application/templates/home.html:12 +#: application/templates/base.html:96 +msgid "Translating..." +msgstr "Translating..." + +#: application/templates/base.html:97 +msgid "The configuration validation is correct." +msgstr "Konfigürasyon doğrulaması doğru." + +#: application/templates/base.html:119 application/templates/home.html:12 msgid "Logout" msgstr "Çıkış" -#: application/templates/base.html:119 application/templates/home.html:17 +#: application/templates/base.html:121 application/templates/home.html:17 #: application/templates/login.html:3 application/templates/login.html:19 #: application/templates/login.html:29 msgid "Login" msgstr "Giriş" -#: application/templates/base.html:121 application/templates/signup.html:3 +#: application/templates/base.html:123 application/templates/signup.html:3 #: application/templates/signup.html:19 application/templates/signup.html:43 msgid "Signup" msgstr "Kaydol" -#: application/templates/base.html:141 application/templates/home.html:11 +#: application/templates/base.html:143 application/templates/home.html:11 #: application/templates/my.html:3 msgid "Feeds" msgstr "RSSler" -#: application/templates/base.html:142 application/templates/setting.html:3 +#: application/templates/base.html:144 application/templates/setting.html:3 msgid "Settings" msgstr "Ayarlar" -#: application/templates/base.html:143 application/templates/logs.html:3 +#: application/templates/base.html:145 application/templates/logs.html:3 msgid "Logs" msgstr "Kayıtlar" -#: application/templates/base.html:145 +#: application/templates/base.html:147 msgid "Advanced" msgstr "Gelişmiş" -#: application/templates/base.html:146 application/templates/library.html:3 +#: application/templates/base.html:148 application/templates/library.html:3 msgid "Shared" msgstr "Paylaşılan" -#: application/templates/book_translator.html:18 +#: application/templates/book_translator.html:19 msgid "State" msgstr "Durum" -#: application/templates/book_translator.html:20 +#: application/templates/book_translator.html:21 msgid "Enable" msgstr "Etkinleştir" -#: application/templates/book_translator.html:21 +#: application/templates/book_translator.html:22 msgid "Disable" msgstr "Devre dışı bırak" -#: application/templates/book_translator.html:25 +#: application/templates/book_translator.html:26 msgid "Engine" msgstr "Motor" -#: application/templates/book_translator.html:31 +#: application/templates/book_translator.html:32 +msgid "Api Host" +msgstr "Api Host" + +#: application/templates/book_translator.html:36 msgid "Api Key" msgstr "Api Key" -#: application/templates/book_translator.html:32 +#: application/templates/book_translator.html:37 msgid "One key per line" msgstr "Satır başına bir tuş" -#: application/templates/book_translator.html:35 +#: application/templates/book_translator.html:40 msgid "Source language" msgstr "Kaynak dil" -#: application/templates/book_translator.html:41 +#: application/templates/book_translator.html:46 msgid "Target language" msgstr "Hedef dil" -#: application/templates/book_translator.html:47 +#: application/templates/book_translator.html:52 msgid "Translation Position" msgstr "Çeviri Konumu" -#: application/templates/book_translator.html:49 +#: application/templates/book_translator.html:54 msgid "Below original" msgstr "Orjinalin altında" -#: application/templates/book_translator.html:50 +#: application/templates/book_translator.html:55 msgid "Above original" msgstr "Orjinalin üstünde" -#: application/templates/book_translator.html:51 +#: application/templates/book_translator.html:56 msgid "Left to original" msgstr "Orjinalin solunda" -#: application/templates/book_translator.html:52 +#: application/templates/book_translator.html:57 msgid "Right to original" msgstr "Orjinalin sağından" -#: application/templates/book_translator.html:53 +#: application/templates/book_translator.html:58 msgid "Translated text only" msgstr "Sadece çevrilmiş metin" -#: application/templates/book_translator.html:57 +#: application/templates/book_translator.html:62 msgid "Original text style" msgstr "Orijinal metin stili" -#: application/templates/book_translator.html:61 +#: application/templates/book_translator.html:66 msgid "Translated text style" msgstr "Çevrilmiş metin stili" -#: application/templates/book_translator.html:67 +#: application/templates/book_translator.html:72 msgid "Apply to all subscribed recipes" msgstr "Tüm abone olunan tarifelere uygula" -#: application/templates/book_translator.html:78 +#: application/templates/book_translator.html:83 msgid "Test (Please save settings firstly)" msgstr "Test (Lütfen önce ayarları kaydedin)" -#: application/templates/book_translator.html:80 +#: application/templates/book_translator.html:85 msgid "Text" msgstr "Metin" -#: application/templates/book_translator.html:84 +#: application/templates/book_translator.html:89 msgid "Translation" msgstr "Çeviri" -#: application/templates/book_translator.html:89 -msgid "Test" -msgstr "Test" - -#: application/templates/change_password.html:13 -msgid "Change Password" -msgstr "Şifre Değiştirme" - #: application/templates/change_password.html:15 msgid "Old password" msgstr "Eski şifre" @@ -811,7 +835,11 @@ msgstr "Yeni şifre" msgid "Confirm password" msgstr "Şifreyi onayla" -#: application/templates/change_password.html:32 +#: application/templates/change_password.html:31 +msgid "Share key" +msgstr "Paylaşma anahtarı" + +#: application/templates/change_password.html:37 msgid "Confirm Change" msgstr "Değişikliği Onayla" @@ -858,8 +886,8 @@ msgstr "" msgid "Search" msgstr "Ara" -#: application/templates/login.html:34 application/view/login.py:199 -#: application/view/login.py:206 +#: application/templates/login.html:34 application/view/login.py:206 +#: application/view/login.py:213 msgid "" "The website does not allow registration. You can ask the owner for an " "account." @@ -1086,10 +1114,6 @@ msgstr "ApiKey" msgid "SecretKey" msgstr "SecretKey" -#: application/templates/setting.html:210 -msgid "Host" -msgstr "Ana bilgisayar" - #: application/templates/setting.html:214 msgid "Port" msgstr "Port" @@ -1132,8 +1156,12 @@ msgstr "Davetiye kodu" msgid "User account" msgstr "Kullanıcı hesabı" -#: application/view/admin.py:51 application/view/adv.py:350 -#: application/view/setting.py:96 application/view/subscribe.py:241 +#: application/templates/user_account.html:42 +msgid "Never expire" +msgstr "hiç sona ermeyen" + +#: application/view/admin.py:51 application/view/adv.py:376 +#: application/view/setting.py:96 application/view/subscribe.py:245 msgid "Settings Saved!" msgstr "Ayarlar Kaydedildi!" @@ -1143,123 +1171,125 @@ msgid "Add account" msgstr "Hesap ekle" #: application/view/admin.py:68 application/view/admin.py:109 -#: application/view/admin.py:137 +#: application/view/admin.py:136 msgid "You do not have sufficient privileges." msgstr "Yeterli yetkiniz yok." -#: application/view/admin.py:82 application/view/admin.py:152 -#: application/view/login.py:218 application/view/login.py:247 -#: application/view/setting.py:61 application/view/setting.py:63 -#: application/view/setting.py:65 application/view/share.py:36 +#: application/view/admin.py:82 application/view/admin.py:153 +#: application/view/admin.py:203 application/view/login.py:225 +#: application/view/login.py:254 application/view/setting.py:61 +#: application/view/setting.py:63 application/view/setting.py:65 +#: application/view/share.py:37 msgid "Some parameters are missing or wrong." msgstr "Bazı parametreler eksik veya yanlış." #: application/view/admin.py:84 application/view/login.py:40 -#: application/view/login.py:224 +#: application/view/login.py:231 msgid "The username includes unsafe chars." msgstr "Kullanıcı adı güvensiz karakterler içeriyor." -#: application/view/admin.py:88 application/view/login.py:226 +#: application/view/admin.py:88 application/view/login.py:233 msgid "Already exist the username." msgstr "Kullanıcı adı zaten var." -#: application/view/admin.py:93 application/view/admin.py:171 -#: application/view/admin.py:198 application/view/login.py:273 +#: application/view/admin.py:93 application/view/admin.py:172 +#: application/view/admin.py:199 application/view/login.py:280 msgid "The password includes non-ascii chars." msgstr "Şifre ascii olmayan karakterler içeriyor." -#: application/view/admin.py:113 application/view/admin.py:134 -#: application/view/admin.py:163 +#: application/view/admin.py:113 application/view/admin.py:133 +#: application/view/admin.py:164 msgid "The username '{}' does not exist." msgstr "'{}' kullanıcı adı mevcut değil." -#: application/view/admin.py:129 +#: application/view/admin.py:124 msgid "The password will not be changed if the fields are empties." msgstr "Alanlar boş bırakılırsa şifre değiştirilmeyecek." -#: application/view/admin.py:130 application/view/admin.py:188 -msgid "Change account" -msgstr "Hesabı değiştir" - -#: application/view/admin.py:131 application/view/admin.py:189 +#: application/view/admin.py:131 application/view/admin.py:190 msgid "Change" msgstr "Değiştir" -#: application/view/admin.py:186 +#: application/view/admin.py:187 msgid "Change success." msgstr "Değişim başarılı." -#: application/view/admin.py:203 -msgid "The old password is wrong." -msgstr "Eski şifre yanlış." - -#: application/view/admin.py:218 +#: application/view/admin.py:201 msgid "Changes saved successfully." msgstr "Değişiklikler başarıyla kaydedildi." -#: application/view/adv.py:79 application/view/adv.py:80 -#: application/view/adv.py:81 application/view/adv.py:82 -#: application/view/adv.py:83 application/view/adv.py:84 -#: application/view/adv.py:85 application/view/adv.py:86 -#: application/view/adv.py:87 +#: application/view/admin.py:212 +msgid "The old password is wrong." +msgstr "Eski şifre yanlış." + +#: application/view/adv.py:80 application/view/adv.py:81 +#: application/view/adv.py:82 application/view/adv.py:83 +#: application/view/adv.py:84 application/view/adv.py:85 +#: application/view/adv.py:86 application/view/adv.py:87 +#: application/view/adv.py:88 application/view/adv.py:89 msgid "Append hyperlink '{}' to article" msgstr "'{}' linkini makaleye ekle" -#: application/view/adv.py:79 application/view/adv.py:80 -#: application/view/adv.py:81 application/view/adv.py:82 +#: application/view/adv.py:80 application/view/adv.py:81 +#: application/view/adv.py:82 application/view/adv.py:83 +#: application/view/adv.py:84 msgid "Save to {}" msgstr "{} kaydedildi" -#: application/view/adv.py:79 +#: application/view/adv.py:80 msgid "evernote" msgstr "evernote" -#: application/view/adv.py:80 +#: application/view/adv.py:81 msgid "wiz" msgstr "wiz" -#: application/view/adv.py:81 +#: application/view/adv.py:82 msgid "pocket" msgstr "pocket" -#: application/view/adv.py:82 +#: application/view/adv.py:83 msgid "instapaper" msgstr "instapaper" -#: application/view/adv.py:83 application/view/adv.py:84 +#: application/view/adv.py:84 +msgid "wallabag" +msgstr "wallabag" + #: application/view/adv.py:85 application/view/adv.py:86 +#: application/view/adv.py:87 application/view/adv.py:88 msgid "Share on {}" msgstr "{} üzerinde paylaş" -#: application/view/adv.py:83 +#: application/view/adv.py:85 msgid "weibo" msgstr "weibo" -#: application/view/adv.py:84 +#: application/view/adv.py:86 msgid "facebook" msgstr "facebook" -#: application/view/adv.py:86 +#: application/view/adv.py:88 msgid "tumblr" msgstr "tumblr" -#: application/view/adv.py:87 +#: application/view/adv.py:89 msgid "Open in browser" msgstr "Tarayıcıda aç" -#: application/view/adv.py:352 +#: application/view/adv.py:378 msgid "The format is invalid." msgstr "Format geçersiz." -#: application/view/adv.py:385 +#: application/view/adv.py:411 msgid "Authorization Error!
{}" msgstr "Yetkilendirme Hatası!
{}" -#: application/view/adv.py:408 +#: application/view/adv.py:434 msgid "Success authorized by Pocket!" msgstr "Pocket tarafından yetkilendirilen başarı!" -#: application/view/adv.py:414 +#: application/view/adv.py:440 msgid "" "Failed to request authorization of Pocket!
See details " "below:

{}" @@ -1267,18 +1297,18 @@ msgstr "" "Pocket yetkilendirme isteği başarısız oldu!
Aşağıdaki ayrıntılara " "bakın:

{}" -#: application/view/adv.py:424 -msgid "Request type [{}] unsupported" -msgstr "İstek türü [{}] desteklenmiyor" - -#: application/view/adv.py:439 +#: application/view/adv.py:462 msgid "The Instapaper service encountered an error. Please try again later." msgstr "" "Instapaper servisi bir hata ile karşılaştı. Lütfen daha sonra tekrar " "deneyin." -#: application/view/deliver.py:67 application/view/login.py:169 -#: application/view/share.py:40 +#: application/view/adv.py:475 +msgid "Request type [{}] unsupported" +msgstr "İstek türü [{}] desteklenmiyor" + +#: application/view/deliver.py:67 application/view/login.py:176 +#: application/view/share.py:41 msgid "The username does not exist or the email is empty." msgstr "Kullanıcı adı mevcut değil veya e-posta boş." @@ -1295,13 +1325,13 @@ msgid "Cannot fetch data from {}, status: {}" msgstr "{}, durumundan veri alınamıyor: {}" #: application/view/library.py:51 application/view/subscribe.py:195 -#: application/view/subscribe.py:221 application/view/subscribe.py:272 -#: application/view/subscribe.py:301 application/view/subscribe.py:421 -#: application/view/subscribe.py:449 application/view/subscribe.py:456 +#: application/view/subscribe.py:221 application/view/subscribe.py:276 +#: application/view/subscribe.py:305 application/view/subscribe.py:425 +#: application/view/subscribe.py:453 application/view/subscribe.py:460 msgid "The recipe does not exist." msgstr "Tarif mevcut değil." -#: application/view/login.py:26 application/view/login.py:78 +#: application/view/login.py:26 application/view/login.py:84 msgid "Please use {}/{} to login at first time." msgstr "İlk giriş için kullanıcı adı:'{}' ve şifre: '{}'" @@ -1313,65 +1343,65 @@ msgstr "Kullanıcı adı boş." msgid "The len of username reached the limit of 25 chars." msgstr "Kullanıcı adının uzunluğu 25 karakter sınırına ulaştı." -#: application/view/login.py:82 +#: application/view/login.py:88 msgid "Forgot password?" msgstr "Forgot password?" -#: application/view/login.py:148 application/view/login.py:284 +#: application/view/login.py:155 application/view/login.py:291 msgid "The token is wrong or expired." msgstr "Belirteç yanlış veya süresi dolmuş." -#: application/view/login.py:151 +#: application/view/login.py:158 msgid "Please input the correct username and email to reset password." msgstr "" "Şifreyi sıfırlamak için lütfen doğru kullanıcı adı ve e-posta adresini " "girin." -#: application/view/login.py:153 +#: application/view/login.py:160 msgid "The email of account '{name}' is {email}." msgstr "'{name}' hesabının e-postası {email}." -#: application/view/login.py:174 +#: application/view/login.py:181 msgid "Reset password success, Please close this page and login again." msgstr "" "Şifre sıfırlama başarılı, Lütfen bu sayfayı kapatın ve yeniden giriş " "yapın." -#: application/view/login.py:177 +#: application/view/login.py:184 msgid "The email you input is not associated with this account." msgstr "Girdiğiniz e-posta bu hesapla ilişkilendirilmemiştir." -#: application/view/login.py:188 +#: application/view/login.py:195 msgid "The link to reset your password has been sent to your email." msgstr "Şifrenizi sıfırlamak için gerekli bağlantı e-postanıza gönderilmiştir." -#: application/view/login.py:189 +#: application/view/login.py:196 msgid "Please check your email inbox within 24 hours." msgstr "Lütfen e-posta gelen kutunuzu 24 saat içinde kontrol edin." -#: application/view/login.py:220 +#: application/view/login.py:227 msgid "The invitation code is invalid." msgstr "Davetiye kodu geçersiz." -#: application/view/login.py:228 +#: application/view/login.py:235 msgid "" "Failed to create an account. Please contact the administrator for " "assistance." msgstr "Bir hesap oluşturulamadı. Yardım için lütfen yöneticiyle iletişime geçin." -#: application/view/login.py:238 +#: application/view/login.py:245 msgid "Successfully created account." msgstr "Hesap başarıyla oluşturuldu." -#: application/view/login.py:249 +#: application/view/login.py:256 msgid "Reset KindleEar password" msgstr "KindleEar şifrenizi sıfırlama" -#: application/view/login.py:250 +#: application/view/login.py:257 msgid "This is an automated email. Please do not reply to it." msgstr "Bu otomatik bir e-postadır. Lütfen yanıt vermeyin." -#: application/view/login.py:251 +#: application/view/login.py:258 msgid "You can click the following link to reset your KindleEar password." msgstr "" "KindleEar şifrenizi sıfırlamak için aşağıdaki bağlantıya " @@ -1489,38 +1519,38 @@ msgstr "Tagalog" msgid "Hausa" msgstr "Hausa" -#: application/view/share.py:51 application/view/subscribe.py:313 +#: application/view/share.py:54 application/view/subscribe.py:317 msgid "Unknown command: {}" msgstr "Bilinmeyen komut: {}" -#: application/view/share.py:57 +#: application/view/share.py:60 msgid "There is no {} email yet." msgstr "Henüz {} e-postası bulunmamaktadır." -#: application/view/share.py:105 application/view/share.py:130 -#: application/view/share.py:152 +#: application/view/share.py:108 application/view/share.py:133 +#: application/view/share.py:155 application/view/share.py:177 msgid "Saved to your {} account." msgstr "{} hesabınıza kaydedildi." -#: application/view/share.py:108 application/view/share.py:126 -#: application/view/share.py:155 +#: application/view/share.py:111 application/view/share.py:129 +#: application/view/share.py:158 application/view/share.py:179 msgid "Failed save to {}." msgstr "{}'e kaydetme işlemi başarısız oldu." -#: application/view/share.py:109 application/view/share.py:127 -#: application/view/share.py:156 +#: application/view/share.py:112 application/view/share.py:130 +#: application/view/share.py:159 application/view/share.py:180 msgid "Reason :" msgstr "Neden :" -#: application/view/share.py:118 +#: application/view/share.py:121 msgid "Unauthorized {} account!" msgstr "{} tarafından yetkilendirilen başarı!" -#: application/view/share.py:131 +#: application/view/share.py:134 msgid "See details below:" msgstr "Aşağıdaki ayrıntılara bakın:" -#: application/view/share.py:154 +#: application/view/share.py:157 msgid "Unknown: {}" msgstr "Bilinmeyen: {}" @@ -1540,7 +1570,7 @@ msgstr "Başlık veya URL boş." msgid "Failed to fetch the recipe." msgstr "Tarif alınamadı." -#: application/view/subscribe.py:125 application/view/subscribe.py:383 +#: application/view/subscribe.py:125 application/view/subscribe.py:387 msgid "Failed to save the recipe. Error:" msgstr "Tarif kaydedilemedi. Hata:" @@ -1548,32 +1578,32 @@ msgstr "Tarif kaydedilemedi. Hata:" msgid "The Rss does not exist." msgstr "Rss mevcut değil." -#: application/view/subscribe.py:204 application/view/subscribe.py:257 -#: application/view/subscribe.py:276 application/view/subscribe.py:358 +#: application/view/subscribe.py:204 application/view/subscribe.py:261 +#: application/view/subscribe.py:280 application/view/subscribe.py:362 msgid "This recipe has not been subscribed to yet." msgstr "Bu tarife henüz abone olunmadı." -#: application/view/subscribe.py:237 +#: application/view/subscribe.py:239 msgid "The api key is required." msgstr "API anahtarı gereklidir." -#: application/view/subscribe.py:280 +#: application/view/subscribe.py:284 msgid "The text is empty." msgstr "Metin boş." -#: application/view/subscribe.py:339 +#: application/view/subscribe.py:343 msgid "You can only delete the uploaded recipe." msgstr "Yalnızca yüklenen tarifi silebilirsiniz." -#: application/view/subscribe.py:343 +#: application/view/subscribe.py:347 msgid "The recipe have been subscribed, please unsubscribe it before delete." msgstr "Tarif abone olunmuş, silmeden önce aboneliği iptal edin." -#: application/view/subscribe.py:370 +#: application/view/subscribe.py:374 msgid "Can not read uploaded file, Error:" msgstr "Yüklenen dosya okunamıyor, Hata:" -#: application/view/subscribe.py:378 +#: application/view/subscribe.py:382 msgid "" "Failed to decode the recipe. Please ensure that your recipe is saved in " "utf-8 encoding." @@ -1581,15 +1611,14 @@ msgstr "" "Tarif çözümlenemedi. Lütfen tarifinizin utf-8 kodlamasında " "kaydedildiğinden emin olun." -#: application/view/subscribe.py:398 +#: application/view/subscribe.py:402 msgid "The recipe is already in the library." msgstr "Tarif zaten kütüphanede." -#: application/view/subscribe.py:428 +#: application/view/subscribe.py:432 msgid "The login information for this recipe has been cleared." msgstr "Bu tarifin giriş bilgileri temizlendi." -#: application/view/subscribe.py:432 +#: application/view/subscribe.py:436 msgid "The login information for this recipe has been saved." msgstr "Bu tarifin giriş bilgileri kaydedildi." - diff --git a/application/translations/zh/LC_MESSAGES/messages.mo b/application/translations/zh/LC_MESSAGES/messages.mo index c50eebe2..bd5fb17a 100644 Binary files a/application/translations/zh/LC_MESSAGES/messages.mo and b/application/translations/zh/LC_MESSAGES/messages.mo differ diff --git a/application/translations/zh/LC_MESSAGES/messages.po b/application/translations/zh/LC_MESSAGES/messages.po index 4ceb78d9..eafc01c5 100644 --- a/application/translations/zh/LC_MESSAGES/messages.po +++ b/application/translations/zh/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: KindleEar v3.0.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-04-02 17:17-0300\n" +"POT-Creation-Date: 2024-04-06 20:42-0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh\n" @@ -18,7 +18,7 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.14.0\n" -#: application/templates/admin.html:3 application/templates/base.html:144 +#: application/templates/admin.html:3 application/templates/base.html:146 msgid "Admin" msgstr "账号管理" @@ -81,7 +81,8 @@ msgstr "用户账号列表" msgid "Add" msgstr "添加" -#: application/templates/admin.html:54 application/templates/home.html:15 +#: application/templates/admin.html:54 +#: application/templates/adv_archive.html:69 application/templates/home.html:15 #: application/templates/login.html:21 application/templates/logs.html:65 #: application/templates/reset_password.html:19 #: application/templates/reset_password.html:20 @@ -120,9 +121,8 @@ msgid "No" msgstr "否" #: application/templates/admin.html:68 -#: application/templates/user_account.html:42 -msgid "Never expire" -msgstr "永久有效" +msgid "Never" +msgstr "永不过期" #: application/templates/admin.html:70 application/templates/setting.html:150 #: application/templates/user_account.html:43 @@ -156,8 +156,10 @@ msgstr "2年" #: application/templates/admin.html:85 #: application/templates/change_password.html:3 -msgid "Change password" -msgstr "修改密码" +#: application/templates/change_password.html:13 application/view/admin.py:130 +#: application/view/admin.py:189 +msgid "Edit account" +msgstr "编辑账号" #: application/templates/admin.html:89 #: application/templates/adv_uploadcss.html:31 @@ -167,41 +169,60 @@ msgid "Delete" msgstr "删除" #: application/templates/adv_archive.html:3 -#: application/templates/adv_archive.html:13 +#: application/templates/adv_archive.html:14 #: application/templates/adv_base.html:57 #: application/templates/adv_base.html:61 msgid "Archive" msgstr "归档" -#: application/templates/adv_archive.html:14 +#: application/templates/adv_archive.html:15 msgid "Append hyperlinks for archiving or sharing." msgstr "在文章最后附加归档或分享链接。" -#: application/templates/adv_archive.html:39 +#: application/templates/adv_archive.html:40 msgid "Authorized" msgstr "已授权" -#: application/templates/adv_archive.html:41 +#: application/templates/adv_archive.html:42 msgid "Authorize" msgstr "申请授权" -#: application/templates/adv_archive.html:52 +#: application/templates/adv_archive.html:53 msgid "Email or Username" msgstr "邮箱或用户名" -#: application/templates/adv_archive.html:55 application/templates/base.html:47 +#: application/templates/adv_archive.html:56 +#: application/templates/adv_archive.html:72 application/templates/base.html:47 #: application/templates/home.html:16 application/templates/login.html:25 #: application/templates/setting.html:222 application/templates/signup.html:25 #: application/templates/user_account.html:19 msgid "Password" msgstr "密码" -#: application/templates/adv_archive.html:58 application/templates/base.html:57 +#: application/templates/adv_archive.html:59 application/templates/base.html:57 msgid "Verify" msgstr "校验" -#: application/templates/adv_archive.html:89 -#: application/templates/book_translator.html:72 +#: application/templates/adv_archive.html:75 +#: application/templates/book_translator.html:94 +msgid "Test" +msgstr "测试" + +#: application/templates/adv_archive.html:78 +msgid "client_id" +msgstr "client_id" + +#: application/templates/adv_archive.html:81 +msgid "client_secret" +msgstr "client_secret" + +#: application/templates/adv_archive.html:84 +#: application/templates/setting.html:210 +msgid "Host" +msgstr "主机" + +#: application/templates/adv_archive.html:115 +#: application/templates/book_translator.html:77 #: application/templates/setting.html:244 msgid "Save settings" msgstr "保存设置" @@ -497,8 +518,8 @@ msgstr "选择内容发送到Kindle" msgid "Verified" msgstr "已校验" -#: application/templates/base.html:59 application/view/login.py:81 -#: application/view/share.py:154 +#: application/templates/base.html:59 application/view/login.py:87 +#: application/view/share.py:157 msgid "The username does not exist or password is wrong." msgstr "用户名不存在或密码错误。" @@ -594,14 +615,13 @@ msgstr "这个Recipe的自定义推送时间已经设定成功。" msgid "The account have been deleted." msgstr "这个账号已经被删除。" -#: application/templates/base.html:83 application/view/admin.py:201 -#: application/view/share.py:144 +#: application/templates/base.html:83 application/view/share.py:147 msgid "The username or password is empty." msgstr "用户名或密码为空。" #: application/templates/base.html:84 application/view/admin.py:86 -#: application/view/admin.py:165 application/view/admin.py:205 -#: application/view/login.py:222 application/view/login.py:282 +#: application/view/admin.py:166 application/view/admin.py:205 +#: application/view/login.py:229 application/view/login.py:289 msgid "The two new passwords are dismatch." msgstr "两个密码不匹配。" @@ -613,7 +633,7 @@ msgstr "修改密码成功。" msgid "Account added successfully." msgstr "添加账号成功。" -#: application/templates/base.html:87 application/view/login.py:132 +#: application/templates/base.html:87 application/view/login.py:139 msgid "login required" msgstr "需要登录" @@ -629,7 +649,7 @@ msgstr "您选择的文件总和超过16MB,请减小图像分辨率或分批 #: application/templates/base.html:90 #: application/templates/book_translator.html:3 -#: application/templates/book_translator.html:16 +#: application/templates/book_translator.html:17 msgid "Translator" msgstr "书籍翻译器" @@ -656,130 +676,134 @@ msgid "" " on your email server, there may be a slight delay." msgstr "测试邮件已经成功发送到以下地址, 请打开收件箱或垃圾箱确认是否到达,根据服务器不同,可能稍有延迟。" -#: application/templates/base.html:117 application/templates/home.html:12 +#: application/templates/base.html:96 +msgid "Translating..." +msgstr "正在翻译..." + +#: application/templates/base.html:97 +msgid "The configuration validation is correct." +msgstr "配置校验正确。" + +#: application/templates/base.html:119 application/templates/home.html:12 msgid "Logout" msgstr "退出" -#: application/templates/base.html:119 application/templates/home.html:17 +#: application/templates/base.html:121 application/templates/home.html:17 #: application/templates/login.html:3 application/templates/login.html:19 #: application/templates/login.html:29 msgid "Login" msgstr "登录" -#: application/templates/base.html:121 application/templates/signup.html:3 +#: application/templates/base.html:123 application/templates/signup.html:3 #: application/templates/signup.html:19 application/templates/signup.html:43 msgid "Signup" msgstr "注册" -#: application/templates/base.html:141 application/templates/home.html:11 +#: application/templates/base.html:143 application/templates/home.html:11 #: application/templates/my.html:3 msgid "Feeds" msgstr "我的订阅" -#: application/templates/base.html:142 application/templates/setting.html:3 +#: application/templates/base.html:144 application/templates/setting.html:3 msgid "Settings" msgstr "设置" -#: application/templates/base.html:143 application/templates/logs.html:3 +#: application/templates/base.html:145 application/templates/logs.html:3 msgid "Logs" msgstr "投递日志" -#: application/templates/base.html:145 +#: application/templates/base.html:147 msgid "Advanced" msgstr "高级设置" -#: application/templates/base.html:146 application/templates/library.html:3 +#: application/templates/base.html:148 application/templates/library.html:3 msgid "Shared" msgstr "网友分享" -#: application/templates/book_translator.html:18 +#: application/templates/book_translator.html:19 msgid "State" msgstr "状态" -#: application/templates/book_translator.html:20 +#: application/templates/book_translator.html:21 msgid "Enable" msgstr "启用" -#: application/templates/book_translator.html:21 +#: application/templates/book_translator.html:22 msgid "Disable" msgstr "禁止" -#: application/templates/book_translator.html:25 +#: application/templates/book_translator.html:26 msgid "Engine" msgstr "翻译引擎" -#: application/templates/book_translator.html:31 +#: application/templates/book_translator.html:32 +msgid "Api Host" +msgstr "主机" + +#: application/templates/book_translator.html:36 msgid "Api Key" msgstr "Api Key" -#: application/templates/book_translator.html:32 +#: application/templates/book_translator.html:37 msgid "One key per line" msgstr "一行一码" -#: application/templates/book_translator.html:35 +#: application/templates/book_translator.html:40 msgid "Source language" msgstr "源语言" -#: application/templates/book_translator.html:41 +#: application/templates/book_translator.html:46 msgid "Target language" msgstr "目标语言" -#: application/templates/book_translator.html:47 +#: application/templates/book_translator.html:52 msgid "Translation Position" msgstr "译文位置" -#: application/templates/book_translator.html:49 +#: application/templates/book_translator.html:54 msgid "Below original" msgstr "原文下方" -#: application/templates/book_translator.html:50 +#: application/templates/book_translator.html:55 msgid "Above original" msgstr "原文上方" -#: application/templates/book_translator.html:51 +#: application/templates/book_translator.html:56 msgid "Left to original" msgstr "原文左边" -#: application/templates/book_translator.html:52 +#: application/templates/book_translator.html:57 msgid "Right to original" msgstr "原文右边" -#: application/templates/book_translator.html:53 +#: application/templates/book_translator.html:58 msgid "Translated text only" msgstr "仅保留译文" -#: application/templates/book_translator.html:57 +#: application/templates/book_translator.html:62 msgid "Original text style" msgstr "原文样式" -#: application/templates/book_translator.html:61 +#: application/templates/book_translator.html:66 msgid "Translated text style" msgstr "译文样式" -#: application/templates/book_translator.html:67 +#: application/templates/book_translator.html:72 msgid "Apply to all subscribed recipes" msgstr "应用到当前所有已订阅Recipe" -#: application/templates/book_translator.html:78 +#: application/templates/book_translator.html:83 msgid "Test (Please save settings firstly)" msgstr "测试 (请先保存设置)" -#: application/templates/book_translator.html:80 +#: application/templates/book_translator.html:85 msgid "Text" msgstr "原文" -#: application/templates/book_translator.html:84 +#: application/templates/book_translator.html:89 msgid "Translation" msgstr "译文" -#: application/templates/book_translator.html:89 -msgid "Test" -msgstr "测试" - -#: application/templates/change_password.html:13 -msgid "Change Password" -msgstr "修改密码" - #: application/templates/change_password.html:15 msgid "Old password" msgstr "原密码" @@ -798,7 +822,11 @@ msgstr "新密码" msgid "Confirm password" msgstr "确认密码" -#: application/templates/change_password.html:32 +#: application/templates/change_password.html:31 +msgid "Share key" +msgstr "分享密钥" + +#: application/templates/change_password.html:37 msgid "Confirm Change" msgstr "确认修改" @@ -838,8 +866,8 @@ msgstr "使用开源 %(kindleear)s 应用,您可以部署您自己的网站, msgid "Search" msgstr "搜索" -#: application/templates/login.html:34 application/view/login.py:199 -#: application/view/login.py:206 +#: application/templates/login.html:34 application/view/login.py:206 +#: application/view/login.py:213 msgid "" "The website does not allow registration. You can ask the owner for an " "account." @@ -1062,10 +1090,6 @@ msgstr "ApiKey" msgid "SecretKey" msgstr "SecretKey" -#: application/templates/setting.html:210 -msgid "Host" -msgstr "主机" - #: application/templates/setting.html:214 msgid "Port" msgstr "端口" @@ -1105,8 +1129,12 @@ msgstr "邀请码" msgid "User account" msgstr "账号" -#: application/view/admin.py:51 application/view/adv.py:350 -#: application/view/setting.py:96 application/view/subscribe.py:241 +#: application/templates/user_account.html:42 +msgid "Never expire" +msgstr "永久有效" + +#: application/view/admin.py:51 application/view/adv.py:376 +#: application/view/setting.py:96 application/view/subscribe.py:245 msgid "Settings Saved!" msgstr "恭喜,保存成功!" @@ -1116,138 +1144,140 @@ msgid "Add account" msgstr "添加账号" #: application/view/admin.py:68 application/view/admin.py:109 -#: application/view/admin.py:137 +#: application/view/admin.py:136 msgid "You do not have sufficient privileges." msgstr "您没有足够的权限。" -#: application/view/admin.py:82 application/view/admin.py:152 -#: application/view/login.py:218 application/view/login.py:247 -#: application/view/setting.py:61 application/view/setting.py:63 -#: application/view/setting.py:65 application/view/share.py:36 +#: application/view/admin.py:82 application/view/admin.py:153 +#: application/view/admin.py:203 application/view/login.py:225 +#: application/view/login.py:254 application/view/setting.py:61 +#: application/view/setting.py:63 application/view/setting.py:65 +#: application/view/share.py:37 msgid "Some parameters are missing or wrong." msgstr "一些参数为空或错误。" #: application/view/admin.py:84 application/view/login.py:40 -#: application/view/login.py:224 +#: application/view/login.py:231 msgid "The username includes unsafe chars." msgstr "用户名包含不安全字符。" -#: application/view/admin.py:88 application/view/login.py:226 +#: application/view/admin.py:88 application/view/login.py:233 msgid "Already exist the username." msgstr "此账号已经存在。" -#: application/view/admin.py:93 application/view/admin.py:171 -#: application/view/admin.py:198 application/view/login.py:273 +#: application/view/admin.py:93 application/view/admin.py:172 +#: application/view/admin.py:199 application/view/login.py:280 msgid "The password includes non-ascii chars." msgstr "密码包含非ASCII字符。" -#: application/view/admin.py:113 application/view/admin.py:134 -#: application/view/admin.py:163 +#: application/view/admin.py:113 application/view/admin.py:133 +#: application/view/admin.py:164 msgid "The username '{}' does not exist." msgstr "账号名 '{}' 不存在。" -#: application/view/admin.py:129 +#: application/view/admin.py:124 msgid "The password will not be changed if the fields are empties." msgstr "如果不填写密码,则密码不会被修改。" -#: application/view/admin.py:130 application/view/admin.py:188 -msgid "Change account" -msgstr "修改用户账号" - -#: application/view/admin.py:131 application/view/admin.py:189 +#: application/view/admin.py:131 application/view/admin.py:190 msgid "Change" msgstr "修改" -#: application/view/admin.py:186 +#: application/view/admin.py:187 msgid "Change success." msgstr "修改成功。" -#: application/view/admin.py:203 -msgid "The old password is wrong." -msgstr "原密码错误。" - -#: application/view/admin.py:218 +#: application/view/admin.py:201 msgid "Changes saved successfully." msgstr "账号设置已更新。" -#: application/view/adv.py:79 application/view/adv.py:80 -#: application/view/adv.py:81 application/view/adv.py:82 -#: application/view/adv.py:83 application/view/adv.py:84 -#: application/view/adv.py:85 application/view/adv.py:86 -#: application/view/adv.py:87 +#: application/view/admin.py:212 +msgid "The old password is wrong." +msgstr "原密码错误。" + +#: application/view/adv.py:80 application/view/adv.py:81 +#: application/view/adv.py:82 application/view/adv.py:83 +#: application/view/adv.py:84 application/view/adv.py:85 +#: application/view/adv.py:86 application/view/adv.py:87 +#: application/view/adv.py:88 application/view/adv.py:89 msgid "Append hyperlink '{}' to article" msgstr "在每篇文章后附加 '{}' 超链接" -#: application/view/adv.py:79 application/view/adv.py:80 -#: application/view/adv.py:81 application/view/adv.py:82 +#: application/view/adv.py:80 application/view/adv.py:81 +#: application/view/adv.py:82 application/view/adv.py:83 +#: application/view/adv.py:84 msgid "Save to {}" msgstr "保存到 {}" -#: application/view/adv.py:79 +#: application/view/adv.py:80 msgid "evernote" msgstr "evernote" -#: application/view/adv.py:80 +#: application/view/adv.py:81 msgid "wiz" msgstr "为知笔记" -#: application/view/adv.py:81 +#: application/view/adv.py:82 msgid "pocket" msgstr "pocket" -#: application/view/adv.py:82 +#: application/view/adv.py:83 msgid "instapaper" msgstr "instapaper" -#: application/view/adv.py:83 application/view/adv.py:84 +#: application/view/adv.py:84 +msgid "wallabag" +msgstr "wallabag" + #: application/view/adv.py:85 application/view/adv.py:86 +#: application/view/adv.py:87 application/view/adv.py:88 msgid "Share on {}" msgstr "分享到 {}" -#: application/view/adv.py:83 +#: application/view/adv.py:85 msgid "weibo" msgstr "微博" -#: application/view/adv.py:84 +#: application/view/adv.py:86 msgid "facebook" msgstr "facebook" -#: application/view/adv.py:86 +#: application/view/adv.py:88 msgid "tumblr" msgstr "tumblr" -#: application/view/adv.py:87 +#: application/view/adv.py:89 msgid "Open in browser" msgstr "在浏览器打开" -#: application/view/adv.py:352 +#: application/view/adv.py:378 msgid "The format is invalid." msgstr "格式非法。" -#: application/view/adv.py:385 +#: application/view/adv.py:411 msgid "Authorization Error!
{}" msgstr "申请授权过程失败!
{}" -#: application/view/adv.py:408 +#: application/view/adv.py:434 msgid "Success authorized by Pocket!" msgstr "已经成功获得Pocket的授权!" -#: application/view/adv.py:414 +#: application/view/adv.py:440 msgid "" "Failed to request authorization of Pocket!
See details " "below:

{}" msgstr "申请Pocket授权失败!
错误信息参考如下:

{}" -#: application/view/adv.py:424 -msgid "Request type [{}] unsupported" -msgstr "不支持你请求的命令类型 [{}]" - -#: application/view/adv.py:439 +#: application/view/adv.py:462 msgid "The Instapaper service encountered an error. Please try again later." msgstr "Instapaper服务器异常,请稍候再试。" -#: application/view/deliver.py:67 application/view/login.py:169 -#: application/view/share.py:40 +#: application/view/adv.py:475 +msgid "Request type [{}] unsupported" +msgstr "不支持你请求的命令类型 [{}]" + +#: application/view/deliver.py:67 application/view/login.py:176 +#: application/view/share.py:41 msgid "The username does not exist or the email is empty." msgstr "用户名不存在或email为空。" @@ -1264,13 +1294,13 @@ msgid "Cannot fetch data from {}, status: {}" msgstr "无法从 {} 获取数据,状态: {}" #: application/view/library.py:51 application/view/subscribe.py:195 -#: application/view/subscribe.py:221 application/view/subscribe.py:272 -#: application/view/subscribe.py:301 application/view/subscribe.py:421 -#: application/view/subscribe.py:449 application/view/subscribe.py:456 +#: application/view/subscribe.py:221 application/view/subscribe.py:276 +#: application/view/subscribe.py:305 application/view/subscribe.py:425 +#: application/view/subscribe.py:453 application/view/subscribe.py:460 msgid "The recipe does not exist." msgstr "此Recipe不存在。" -#: application/view/login.py:26 application/view/login.py:78 +#: application/view/login.py:26 application/view/login.py:84 msgid "Please use {}/{} to login at first time." msgstr "初次登录请使用用户名'{}'/密码'{}'。" @@ -1282,61 +1312,61 @@ msgstr "账号名为空。" msgid "The len of username reached the limit of 25 chars." msgstr "用户名超过25字符。" -#: application/view/login.py:82 +#: application/view/login.py:88 msgid "Forgot password?" msgstr "忘记密码?" -#: application/view/login.py:148 application/view/login.py:284 +#: application/view/login.py:155 application/view/login.py:291 msgid "The token is wrong or expired." msgstr "Token码错误或已经逾期。" -#: application/view/login.py:151 +#: application/view/login.py:158 msgid "Please input the correct username and email to reset password." msgstr "请输入正确的用户名和Email来重置密码。" -#: application/view/login.py:153 +#: application/view/login.py:160 msgid "The email of account '{name}' is {email}." msgstr "账号 '{name}' 的Email为 {email}" -#: application/view/login.py:174 +#: application/view/login.py:181 msgid "Reset password success, Please close this page and login again." msgstr "修改密码成功,请关闭此页面重新登录。" -#: application/view/login.py:177 +#: application/view/login.py:184 msgid "The email you input is not associated with this account." msgstr "您输入的email不正确。" -#: application/view/login.py:188 +#: application/view/login.py:195 msgid "The link to reset your password has been sent to your email." msgstr "重置密码的邮件已经被发送至你的email。" -#: application/view/login.py:189 +#: application/view/login.py:196 msgid "Please check your email inbox within 24 hours." msgstr "请在24小时内检查你的email收件箱。" -#: application/view/login.py:220 +#: application/view/login.py:227 msgid "The invitation code is invalid." msgstr "邀请码无效。" -#: application/view/login.py:228 +#: application/view/login.py:235 msgid "" "Failed to create an account. Please contact the administrator for " "assistance." msgstr "创建账号失败,请联系管理员请求协助。" -#: application/view/login.py:238 +#: application/view/login.py:245 msgid "Successfully created account." msgstr "成功创建账号。" -#: application/view/login.py:249 +#: application/view/login.py:256 msgid "Reset KindleEar password" msgstr "重置KindleEar密码" -#: application/view/login.py:250 +#: application/view/login.py:257 msgid "This is an automated email. Please do not reply to it." msgstr "这个是自动发送的邮件,请勿直接回复。" -#: application/view/login.py:251 +#: application/view/login.py:258 msgid "You can click the following link to reset your KindleEar password." msgstr "你可以点击下面的链接来重置你的KindleEar密码。" @@ -1452,38 +1482,38 @@ msgstr "他加禄语" msgid "Hausa" msgstr "豪萨语" -#: application/view/share.py:51 application/view/subscribe.py:313 +#: application/view/share.py:54 application/view/subscribe.py:317 msgid "Unknown command: {}" msgstr "未知命令:{}" -#: application/view/share.py:57 +#: application/view/share.py:60 msgid "There is no {} email yet." msgstr "还没有设置 {} email." -#: application/view/share.py:105 application/view/share.py:130 -#: application/view/share.py:152 +#: application/view/share.py:108 application/view/share.py:133 +#: application/view/share.py:155 application/view/share.py:177 msgid "Saved to your {} account." msgstr "已经成功被保存到你的 {} 账户。" -#: application/view/share.py:108 application/view/share.py:126 -#: application/view/share.py:155 +#: application/view/share.py:111 application/view/share.py:129 +#: application/view/share.py:158 application/view/share.py:179 msgid "Failed save to {}." msgstr "无法保存到 {}." -#: application/view/share.py:109 application/view/share.py:127 -#: application/view/share.py:156 +#: application/view/share.py:112 application/view/share.py:130 +#: application/view/share.py:159 application/view/share.py:180 msgid "Reason :" msgstr "原因:" -#: application/view/share.py:118 +#: application/view/share.py:121 msgid "Unauthorized {} account!" msgstr "尚未获得Pocket的授权!" -#: application/view/share.py:131 +#: application/view/share.py:134 msgid "See details below:" msgstr "下面是一些技术细节:" -#: application/view/share.py:154 +#: application/view/share.py:157 msgid "Unknown: {}" msgstr "未知: {}" @@ -1503,7 +1533,7 @@ msgstr "标题或URL为空。" msgid "Failed to fetch the recipe." msgstr "抓取Recipe失败。" -#: application/view/subscribe.py:125 application/view/subscribe.py:383 +#: application/view/subscribe.py:125 application/view/subscribe.py:387 msgid "Failed to save the recipe. Error:" msgstr "保存Recipe失败。错误:" @@ -1511,46 +1541,45 @@ msgstr "保存Recipe失败。错误:" msgid "The Rss does not exist." msgstr "此RSS不存在。" -#: application/view/subscribe.py:204 application/view/subscribe.py:257 -#: application/view/subscribe.py:276 application/view/subscribe.py:358 +#: application/view/subscribe.py:204 application/view/subscribe.py:261 +#: application/view/subscribe.py:280 application/view/subscribe.py:362 msgid "This recipe has not been subscribed to yet." msgstr "此Recipe尚未被订阅。" -#: application/view/subscribe.py:237 +#: application/view/subscribe.py:239 msgid "The api key is required." msgstr "需要填写api key." -#: application/view/subscribe.py:280 +#: application/view/subscribe.py:284 msgid "The text is empty." msgstr "文本为空。" -#: application/view/subscribe.py:339 +#: application/view/subscribe.py:343 msgid "You can only delete the uploaded recipe." msgstr "您只能删除你自己上传的Recipe。" -#: application/view/subscribe.py:343 +#: application/view/subscribe.py:347 msgid "The recipe have been subscribed, please unsubscribe it before delete." msgstr "此Recipe已经被订阅,请先取消订阅然后再删除。" -#: application/view/subscribe.py:370 +#: application/view/subscribe.py:374 msgid "Can not read uploaded file, Error:" msgstr "无法读取上传的文件,错误:" -#: application/view/subscribe.py:378 +#: application/view/subscribe.py:382 msgid "" "Failed to decode the recipe. Please ensure that your recipe is saved in " "utf-8 encoding." msgstr "解码Recipe失败,请确保您的Recipe为utf-8编码。" -#: application/view/subscribe.py:398 +#: application/view/subscribe.py:402 msgid "The recipe is already in the library." msgstr "此Recipe已经在新闻源中。" -#: application/view/subscribe.py:428 +#: application/view/subscribe.py:432 msgid "The login information for this recipe has been cleared." msgstr "此Recipe的网站登录信息已经被删除。" -#: application/view/subscribe.py:432 +#: application/view/subscribe.py:436 msgid "The login information for this recipe has been saved." msgstr "此Recipe的网站登录信息已经保存。" - diff --git a/application/view/admin.py b/application/view/admin.py index 452473c6..0e1eb0fe 100644 --- a/application/view/admin.py +++ b/application/view/admin.py @@ -29,7 +29,7 @@ def Admin(): return render_template('admin.html', title='Admin', tab='admin', users=users, adminName=adminName, mailSrv=mailSrv, signupType=signupType, inviteCodes=inviteCodes, tips='') else: - return render_template('change_password.html', tips='', tab='admin', user=user) + return render_template('change_password.html', tips='', tab='admin', user=user, shareKey=user.share_links.get('key')) @bpAdmin.post("/admin", endpoint='AdminPost') @login_required() @@ -108,12 +108,12 @@ def AdminDeleteAccountAjax(): if user.name != adminName or not name or name == adminName: return {'status': _("You do not have sufficient privileges.")} - u = KeUser.get_or_none(KeUser.name == name) - if not u: + dbItem = KeUser.get_or_none(KeUser.name == name) + if not dbItem: return {'status': _("The username '{}' does not exist.").format(name)} else: - u.erase_traces() #删除账号订阅的书,白名单,过滤器等,完全的清理其痕迹 - u.delete_instance() + dbItem.erase_traces() #删除账号订阅的书,白名单,过滤器等,完全的清理其痕迹 + dbItem.delete_instance() return {'status': 'ok'} #修改密码,可能是修改自己的密码或管理员修改其他用户的密码 @@ -121,20 +121,20 @@ def AdminDeleteAccountAjax(): @login_required() def AdminAccountChange(name): user = get_login_user() - if user.name == name: - return render_template('change_password.html', tips='', tab='admin', user=user) - elif user.name == app.config['ADMIN_NAME']: - u = KeUser.get_or_none(KeUser.name == name) - if u: - tips = _('The password will not be changed if the fields are empties.') - return render_template('user_account.html', tips=tips, formTitle=_('Change account'), - submitTitle=_('Change'), user=u, tab='admin') + tips = _('The password will not be changed if the fields are empties.') + if user.name == name: #修改自己的密码和一些设置 + return render_template('change_password.html', tips=tips, tab='admin', user=user, shareKey=user.share_links.get('key')) + elif user.name == app.config['ADMIN_NAME']: #管理员修改其他人的密码和其他设置 + dbItem = KeUser.get_or_none(KeUser.name == name) + if dbItem: + return render_template('user_account.html', tips=tips, formTitle=_('Edit account'), + submitTitle=_('Change'), user=dbItem, tab='admin') else: - return render_template('tipsback.html', title='error', urltoback=url_for('bpAdmin.Admin'), - tips=_("The username '{}' does not exist.").format(name)) + tips=_("The username '{}' does not exist.").format(name) + return render_template('tipsback.html', title='error', urltoback=url_for('bpAdmin.Admin'), tips=tips) else: - return render_template('tipsback.html', title='privileges', urltoback=url_for('bpAdmin.Admin'), - tips=_('You do not have sufficient privileges.')) + tips=_('You do not have sufficient privileges.') + return render_template('tipsback.html', title='privileges', urltoback=url_for('bpAdmin.Admin'), tips=tips) @bpAdmin.post("/account/change/", endpoint='AdminAccountChangePost') @login_required() @@ -142,70 +142,80 @@ def AdminAccountChangePost(name): user = get_login_user() form = request.form username = form.get('username') - orgpwd = form.get('orgpwd') - p1 = form.get('password1') - p2 = form.get('password2') + orgPwd = form.get('orgpwd', '') + p1 = form.get('password1', '') + p2 = form.get('password2', '') email = form.get('email') - u = None + shareKey = form.get('shareKey') + dbItem = None if name != username: return render_template('tipsback.html', title='error', urltoback=url_for('bpAdmin.Admin'), tips=_('Some parameters are missing or wrong.')) elif user.name == name: #修改自己的密码 - tips = ChangePassword(user, orgpwd, p1, p2, email) - return render_template('change_password.html', tips=tips, tab='admin', user=user) + tips = ChangePassword(user, orgPwd, p1, p2, email, shareKey) + return render_template('change_password.html', tips=tips, tab='admin', user=user, shareKey=user.share_links.get('key')) elif user.name == app.config['ADMIN_NAME']: #管理员修改其他账号 email = form.get('email', '') sm_service = form.get('sm_service') expiration = str_to_int(form.get('expiration', '0')) - u = KeUser.get_or_none(KeUser.name == username) - if not u: + dbItem = KeUser.get_or_none(KeUser.name == username) + if not dbItem: tips = _("The username '{}' does not exist.").format(username) elif (p1 or p2) and (p1 != p2): tips = _("The two new passwords are dismatch.") else: try: if p1 or p2: - u.passwd = hashlib.md5((p1 + u.secret_key).encode()).hexdigest() + dbItem.passwd = hashlib.md5((p1 + dbItem.secret_key).encode('utf-8')).hexdigest() except: tips = _("The password includes non-ascii chars.") else: - u.expiration_days = expiration + dbItem.expiration_days = expiration if expiration: - u.expires = datetime.datetime.utcnow() + datetime.timedelta(days=expiration) + dbItem.expires = datetime.datetime.utcnow() + datetime.timedelta(days=expiration) else: - u.expires = None + dbItem.expires = None if sm_service == 'admin': - u.send_mail_service = {'service': 'admin'} - elif u.send_mail_service.get('service', 'admin') == 'admin': + dbItem.send_mail_service = {'service': 'admin'} + elif dbItem.send_mail_service.get('service', 'admin') == 'admin': send_mail_service = user.send_mail_service send_mail_service['service'] = '' - u.send_mail_service = send_mail_service - u.email = email - u.save() + dbItem.send_mail_service = send_mail_service + dbItem.email = email + dbItem.save() tips = _("Change success.") - return render_template('user_account.html', tips=tips, formTitle=_('Change account'), - submitTitle=_('Change'), user=u, tab='admin') + return render_template('user_account.html', tips=tips, formTitle=_('Edit account'), + submitTitle=_('Change'), user=dbItem, tab='admin') #修改一个账号的密码,返回执行结果字符串 -def ChangePassword(user, orgPwd, p1, p2, email): +def ChangePassword(user, orgPwd, p1, p2, email, shareKey): secret_key = user.secret_key try: - oldPwd = hashlib.md5((orgPwd + secret_key).encode()).hexdigest() - newPwd = hashlib.md5((p1 + secret_key).encode()).hexdigest() + oldPwd = hashlib.md5((orgPwd + secret_key).encode('utf-8')).hexdigest() + newPwd = hashlib.md5((p1 + secret_key).encode('utf-8')).hexdigest() except: - tips = _("The password includes non-ascii chars.") + return _("The password includes non-ascii chars.") + + tips = _("Changes saved successfully.") + if not email or not shareKey: + tips = _("Some parameters are missing or wrong.") + elif p1 != p2: + tips = _("The two new passwords are dismatch.") else: - if not all((orgPwd, p1, p2, email)): - tips = _("The username or password is empty.") - elif user.passwd != oldPwd: + if not (orgPwd or p1 or p2): # 如果不修改密码,则三个密码都必须为空 + newPwd = user.passwd + oldPwd = user.passwd + + if user.passwd != oldPwd: tips = _("The old password is wrong.") - elif p1 != p2: - tips = _("The two new passwords are dismatch.") else: user.passwd = newPwd user.email = email + shareLinks = user.share_links + shareLinks['key'] = shareKey + user.share_links = shareLinks if user.name == app.config['ADMIN_NAME']: #如果管理员修改email,也同步更新其他用户的发件地址 user.sender = email SyncSenderAddress(user) @@ -215,7 +225,6 @@ def ChangePassword(user, orgPwd, p1, p2, email): user.sender = email user.save() - tips = _("Changes saved successfully.") return tips #将管理员的email同步到所有用户 diff --git a/application/view/adv.py b/application/view/adv.py index 374d908c..a762638b 100644 --- a/application/view/adv.py +++ b/application/view/adv.py @@ -11,6 +11,7 @@ from ..back_end.db_models import * from ..utils import local_time, ke_encrypt, ke_decrypt, str_to_bool, safe_eval, xml_escape, xml_unescape from ..lib.pocket import Pocket +from ..lib.wallabag import WallaBag from ..lib.urlopener import UrlOpener bpAdv = Blueprint('bpAdv', __name__) @@ -80,6 +81,7 @@ def AdvArchive(): appendStrs["Wiz"] = _("Append hyperlink '{}' to article").format(_('Save to {}').format(_('wiz'))) appendStrs["Pocket"] = _("Append hyperlink '{}' to article").format(_('Save to {}').format(_('pocket'))) appendStrs["Instapaper"] = _("Append hyperlink '{}' to article").format(_('Save to {}').format(_('instapaper'))) + appendStrs["wallabag"] = _("Append hyperlink '{}' to article").format(_('Save to {}').format(_('wallabag'))) appendStrs["Weibo"] = _("Append hyperlink '{}' to article").format(_('Share on {}').format(_('weibo'))) appendStrs["Facebook"] = _("Append hyperlink '{}' to article").format(_('Share on {}').format(_('facebook'))) appendStrs["X"] = _("Append hyperlink '{}' to article").format(_('Share on {}').format('X')) @@ -104,20 +106,43 @@ def AdvArchivePost(): instapaper = str_to_bool(form.get('instapaper')) instaName = form.get('instapaper_username', '').strip() instaPwd = form.get('instapaper_password', '') - #将instapaper的密码加密 + + wallabag = str_to_bool(form.get('wallabag')) + wallaHost = form.get('wallabag_host', '') + wallaUsername = form.get('wallabag_username', '') + wallaPassword = form.get('wallabag_password', '') + wallaId = form.get('wallabag_client_id', '') + wallaSecret = form.get('wallabag_client_secret', '') + if not all((wallaHost, wallaUsername, wallaPassword, wallaId, wallaSecret)): + wallabag = False + + #将instapaper/wallabag的密码加密 if instaName and instaPwd: instaPwd = ke_encrypt(instaPwd, user.secret_key) else: instaName = '' instaPwd = '' + if wallaUsername and wallaPassword: + wallaPassword = ke_encrypt(wallaPassword, user.secret_key) + else: + wallaUsername = '' + wallaPassword = '' shareLinks = user.share_links - oldPocket = shareLinks.get('pocket', {}) - accessToken = oldPocket.get('access_token', '') if oldPocket else '' + pocketToken = shareLinks.get('pocket', {}).get('access_token', '') + oldWalla = shareLinks.get('wallabag', {}) + newWalla = oldWalla.copy() + newWalla.update({'enable': '1' if wallabag else '', 'host': wallaHost, 'username': wallaUsername, + 'password': wallaPassword, 'client_id': wallaId, 'client_secret': wallaSecret}) + if newWalla != oldWalla: #如果任何数据有变化,清除之前的token + newWalla['access_token'] = '' + newWalla['refresh_token'] = '' + shareLinks['Evernote'] = {'enable': '1' if evernote else '', 'email': evernoteMail} shareLinks['Wiz'] = {'enable': '1' if wiz else '', 'email': wizMail} - shareLinks['Pocket'] = {'enable': '1' if pocket else '', 'access_token': accessToken} + shareLinks['Pocket'] = {'enable': '1' if pocket else '', 'access_token': pocketToken} shareLinks['Instapaper'] = {'enable': '1' if instapaper else '', 'username': instaName, 'password': instaPwd} + shareLinks['wallabag'] = newWalla shareLinks['Weibo'] = str_to_bool(form.get('weibo')) shareLinks['Facebook'] = str_to_bool(form.get('facebook')) shareLinks['X'] = str_to_bool(form.get('x')) @@ -414,29 +439,38 @@ def AdvOAuth2Callback(authType): tips=_('Failed to request authorization of Pocket!
See details below:

{}').format(e)) #通过AJAX验证密码等信息的函数 -@bpAdv.post("/verifyajax/verifType", endpoint='VerifyAjaxPost') +@bpAdv.post("/verifyajax/", endpoint='VerifyAjaxPost') @login_required() def VerifyAjaxPost(verifType): - INSTAPAPER_API_AUTH_URL = "https://www.instapaper.com/api/authenticate" - - respDict = {'status': 'ok', 'correct': 0} - if verifType.lower() != 'instapaper': - respDict['status'] = _('Request type [{}] unsupported').format(verifType) - return respDict - user = get_login_user() - - userName = request.form.get('username', '') - password = request.form.get('password', '') - opener = UrlOpener() - apiParameters = {'username': userName, 'password':password} - ret = opener.open(INSTAPAPER_API_AUTH_URL, data=apiParameters) - if ret.status_code in (200, 201): - respDict['correct'] = 1 - elif ret.status_code == 403: - respDict['correct'] = 0 + form = request.form + respDict = {'status': 'ok'} + if verifType == 'instapaper': + INSTAPAPER_API_AUTH_URL = "https://www.instapaper.com/api/authenticate" + userName = form.get('username', '') + password = form.get('password', '') + opener = UrlOpener() + apiParameters = {'username': userName, 'password':password} + ret = opener.open(INSTAPAPER_API_AUTH_URL, data=apiParameters) + if ret.status_code in (200, 201): + respDict['correct'] = 1 + elif ret.status_code == 403: + respDict['correct'] = 0 + else: + respDict['correct'] = 0 + respDict['status'] = _("The Instapaper service encountered an error. Please try again later.") + elif verifType == 'wallabag': + host = form.get('host', '') + name = form.get('username', '') + passwd = form.get('password', '') + id_ = form.get('client_id', '') + secret = form.get('client_secret', '') + config = {'host': host, 'username': name, 'password': passwd, 'client_id': id_, + 'client_secret': secret} + wallabag = WallaBag(config, default_log) + msg = wallabag.update_token() + return {'status': msg or 'ok'} else: - respDict['status'] = _("The Instapaper service encountered an error. Please try again later.") - + respDict['status'] = _('Request type [{}] unsupported').format(verifType) return respDict \ No newline at end of file diff --git a/application/view/login.py b/application/view/login.py index cfa00b39..9bf24591 100644 --- a/application/view/login.py +++ b/application/view/login.py @@ -45,29 +45,35 @@ def LoginPost(): adminName = app.config['ADMIN_NAME'] isFirstTime = CreateAccountIfNotExist(adminName) #确认管理员账号是否存在 - u = KeUser.get_or_none(KeUser.name == name) - if u: - secret_key = u.secret_key or '' - pwdhash = hashlib.md5((passwd + secret_key).encode()).hexdigest() - if u.passwd != pwdhash: - u = None + user = KeUser.get_or_none(KeUser.name == name) + if user: + secret_key = user.secret_key + try: + pwdHash = hashlib.md5((passwd + secret_key).encode('utf-8')).hexdigest() + nameHash = hashlib.md5((name + secret_key).encode('utf-8')).hexdigest() + except Exception as e: + default_log.warning(f"Failed to hash password and username: {str(e)}") + user = None + else: + if user.passwd != pwdHash: + user = None - if u: + if user: session['login'] = 1 session['userName'] = name session['role'] = 'admin' if name == adminName else 'user' - if u.expires and u.expiration_days > 0: #用户登陆后自动续期 - u.expires = datetime.datetime.utcnow() + datetime.timedelta(days=u.expiration_days) - u.save() - if 'resetpwd' in u.custom: - custom = u.custom + if user.expires and user.expiration_days > 0: #用户登陆后自动续期 + user.expires = datetime.datetime.utcnow() + datetime.timedelta(days=user.expiration_days) + user.save() + if 'resetpwd' in user.custom: #成功登录后清除复位密码的设置 + custom = user.custom custom.pop('resetpwd', None) - u.custom = custom - u.save() + user.custom = custom + user.save() - if not u.sender: + if not user.sender or (nameHash == pwdHash): url = url_for('bpAdmin.AdminAccountChange', name=name) - elif not u.kindle_email: + elif not user.kindle_email: url = url_for("bpSetting.Setting") else: url = url_for("bpSubscribe.MySubscription") @@ -96,9 +102,10 @@ def CreateAccountIfNotExist(name, password=None, email='', sender=None, sm_servi secretKey = new_secret_key() shareKey = new_secret_key(length=4) try: - password = hashlib.md5((password + secretKey).encode()).hexdigest() + pwdHash = hashlib.md5((password + secretKey).encode('utf-8')).hexdigest() + nameHash = hashlib.md5((name + secretKey).encode('utf-8')).hexdigest() #避免名字非法 except Exception as e: - default_log.warning('CreateAccountIfNotExist failed to hash password: {}'.format(str(e))) + default_log.warning(f'CreateAccountIfNotExist failed to hash password and name: {str(e)}') return False adminName = app.config['ADMIN_NAME'] @@ -112,7 +119,7 @@ def CreateAccountIfNotExist(name, password=None, email='', sender=None, sm_servi sender = sender or email - user = KeUser(name=name, passwd=password, expires=None, secret_key=secretKey, expiration_days=expiration, + user = KeUser(name=name, passwd=pwdHash, expires=None, secret_key=secretKey, expiration_days=expiration, share_links={'key': shareKey}, email=email, sender=sender, send_mail_service=sm_service) if expiration: user.expires = datetime.datetime.utcnow() + datetime.timedelta(days=expiration) @@ -268,7 +275,7 @@ def reset_pwd_final_step(user, token, new_p1, new_p2): if (token == pre_set.get('token')) and (now < pre_time): if new_p1 == new_p2: try: - pwd = hashlib.md5((new_p1 + user.secret_key).encode()).hexdigest() + pwd = hashlib.md5((new_p1 + user.secret_key).encode('utf-8')).hexdigest() except: tips = _("The password includes non-ascii chars.") else: diff --git a/application/view/share.py b/application/view/share.py index 0911a2e9..df5a5954 100644 --- a/application/view/share.py +++ b/application/view/share.py @@ -13,6 +13,7 @@ from ..back_end.send_mail_adpt import send_html_mail from ..utils import hide_email, ke_decrypt from ..lib.pocket import Pocket +from ..lib.wallabag import WallaBag from ..lib.urlopener import UrlOpener from filesystem_dict import FsDictStub @@ -47,6 +48,8 @@ def Share(): return SaveToPocket(user, action, url, title) elif action == 'Instapaper': return SaveToInstapaper(user, action, url, title) + elif action == 'wallabag': + return SaveToWallabag(user, action, url, title) else: return _('Unknown command: {}').format(action) @@ -157,4 +160,24 @@ def SaveToInstapaper(user, action, url, title): info.append(reason) return SHARE_INFO_TPL.format(title=html_title, info='
'.join(info)) - \ No newline at end of file + +def SaveToWallabag(user, action, url, title): + config = user.share_links.get('wallabag', {}) + config['password'] = ke_decrypt(config.get('password', ''), user.secret_key) + wallabag = WallaBag(config, default_log) + ret = wallabag.add(url, title=title) + if ret['changed']: #保存新的token + shareLinks = user.share_links + shareLinks['wallabag'] = wallabag.config + user.share_links = shareLinks + user.save() + + info = [title, '
'] + if ret['resp']: + info.append(_("Saved to your {} account.").format('wallabag')) + else: + info.append(_("Failed save to {}.").format('wallabag')) + info.append(_('Reason :')) + info.append(ret['msg']) + + return SHARE_INFO_TPL.format(title='Share to wallabag', info='
'.join(info)) \ No newline at end of file diff --git a/application/view/subscribe.py b/application/view/subscribe.py index 6ee74fa1..bc27bc6c 100644 --- a/application/view/subscribe.py +++ b/application/view/subscribe.py @@ -224,19 +224,23 @@ def BookTranslatorPost(recipeId): #构建配置参数 form = request.form engineName = form.get('engine', '') + apiHost = form.get('api_host', '') apiKeys = form.get('api_keys', '') apiKeys = apiKeys.split('\n') if apiKeys else [] params = {'enable': str_to_bool(form.get('enable', '')), 'engine': engineName, - 'api_keys': apiKeys, 'src_lang': form.get('src_lang', ''), + 'api_host': apiHost, 'api_keys': apiKeys, 'src_lang': form.get('src_lang', ''), 'dst_lang': form.get('dst_lang', 'en'), 'position': form.get('position', 'below'), 'orig_style': form.get('orig_style', ''), 'trans_style': form.get('trans_style', '')} engines = get_trans_engines() engine = engines.get(engineName, None) - if engine and engine.get('need_api_key') and not apiKeys: - tips = _('The api key is required.') - return render_template('book_translator.html', tab="my", tips=tips, params=params, title=recipe.title, - recipeId=recipeId, engines=json.dumps(engines, separators=(',', ':'))) + if engine and engine.get('need_api_key'): + if not apiKeys: + tips = _('The api key is required.') + return render_template('book_translator.html', tab="my", tips=tips, params=params, title=recipe.title, + recipeId=recipeId, engines=json.dumps(engines, separators=(',', ':'))) + else: + params['api_host'] = '' tips = _("Settings Saved!") apply_all = str_to_bool(form.get('apply_all', '')) diff --git a/tests/readme.developer.md b/tests/readme.developer.md index be09dbfd..d4f69ce0 100644 --- a/tests/readme.developer.md +++ b/tests/readme.developer.md @@ -67,9 +67,8 @@ gcloud app deploy queue.yaml # Docker ## 构建镜像 -1. 将docker/Dockerfile文件拷贝到根目录 -2. 执行 ```bash +cp ./docker/Dockerfile . sudo docker build -t kindleear/kindleear . #or sudo docker build --no-cache -t kindleear/kindleear . @@ -84,6 +83,8 @@ sudo docker rm name sudo docker ps -a sudo docker compose up -d sudo docker run -d +sudo docker run -it id /bin/bash + ``` # Python托管平台的一些了解 diff --git a/tools/bookmarklet_src/send_to_kindle.js b/tools/bookmarklet_src/send_to_kindle.js index 3c32f2f4..60cbfabe 100644 --- a/tools/bookmarklet_src/send_to_kindle.js +++ b/tools/bookmarklet_src/send_to_kindle.js @@ -9,8 +9,8 @@ if(s){ (function(url, formData){ var h = (tag, props)=>Object.assign(document.createElement(tag), props); var form = h("form", {action:url, method:"post", hidden:true, target:"_blank"}); - for (var [name,value] of Object.entries(formData)){ - form.appendChild(h("input", {name, value})); + for (var [name, value] of Object.entries(formData)){ + form.appendChild(h("input", {name: value})); } document.body.appendChild(form); form.submit(); diff --git a/tools/pybabel_extract.bat b/tools/pybabel_extract.bat index eac7a574..2ae7ed66 100644 --- a/tools/pybabel_extract.bat +++ b/tools/pybabel_extract.bat @@ -1,4 +1,4 @@ D: cd D:\Programer\Project\KindleEar -pybabel extract -F tools\babel.cfg --ignore-dirs lib --ignore-dirs tests -o messages.pot . +pybabel extract -F tools\babel.cfg --ignore-dirs lib --ignore-dirs tests -o application\translations\messages.pot . pause diff --git a/tools/pybabel_update.bat b/tools/pybabel_update.bat index 30fccaec..bb429e51 100644 --- a/tools/pybabel_update.bat +++ b/tools/pybabel_update.bat @@ -1,4 +1,4 @@ D: cd D:\Programer\Project\KindleEar -pybabel update -i messages.pot -d application/translations +pybabel update -i application\translations\messages.pot -d application/translations pause \ No newline at end of file diff --git a/tools/run_flask.bat b/tools/run_flask.bat index d163e65b..236e19db 100644 --- a/tools/run_flask.bat +++ b/tools/run_flask.bat @@ -5,6 +5,7 @@ set APP_DOMAIN=http://localhost:5000/ set DATABASE_URL=sqlite:///database.db set TASK_QUEUE_SERVICE=apscheduler set TASK_QUEUE_BROKER_URL=redis://127.0.0.1:6379/ -set TEMP_DIR=d:/temp +set KE_TEMP_DIR=d:/temp +set LOG_LEVEL=debug python -m flask run --host=0.0.0.0 --debug pause