Add translation capability

This commit is contained in:
sqozz 2017-12-29 08:03:17 +01:00
parent 2c220afced
commit 9c053b5021
15 changed files with 342 additions and 132 deletions

2
.gitignore vendored
View File

@ -1,5 +1,7 @@
*.swp *.swp
*.swo *.swo
*.mo
*.pot
backup backup
uploadTest uploadTest
torrentFiles/* torrentFiles/*

10
HACKING.md Normal file
View File

@ -0,0 +1,10 @@
Localization
=====
1) Mark all translatable strings by wrapping them in gettext() (or \_())
2) Extract all those strings:
pybabel extract -F babel.cfg -o messages.pot .
3) Update the language-specific translation files:
pybabel update -i messages.pot -d translations/ -l [LANGCODE]
4) Translate the strings in the generated .po files using your favourite editor
5) Compile the .po files to .mo files:
pybabel compile -d translations

6
babel.cfg Normal file
View File

@ -0,0 +1,6 @@
[ignore: venv/**]
[python: **.py]
[jinja2: **/templates/**.html]
[json: **.json]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

View File

@ -5,35 +5,43 @@ from hashlib import sha1
import bencoder import bencoder
import requests import requests
from flask import Flask, render_template, url_for, request, send_file, redirect from flask import Flask, render_template, url_for, request, send_file, redirect
from flask_babel import Babel, gettext as _, lazy_gettext
from werkzeug import secure_filename from werkzeug import secure_filename
from hurry.filesize import size from hurry.filesize import size
app = Flask(__name__) app = Flask(__name__)
strings = None babel = Babel(app)
LANGUAGES = ['en', 'de']
settings = None settings = None
def get_categories(): def get_categories():
return settings["categories"] cats = settings["categories"]
for c in cats:
c["label"] = str(lazy_gettext(c["label"]))
for s in c["subcategories"]:
s["label"] = str(lazy_gettext(s["label"]))
return cats
@app.route("/") @app.route("/")
def index(): def index():
return render_template("search.html", language="english", categories=get_categories(), strings=strings) return render_template("search.html", categories=get_categories())
@app.route("/categories") @app.route("/categories")
def categorys(): def categorys():
global strings return render_template("categories.html", categories=get_categories())
return render_template("categories.html", categories=get_categories(), strings=strings)
@app.route("/create", methods=['GET','POST']) @app.route("/create", methods=['GET','POST'])
def create(): def create():
if request.method == "GET": if request.method == "GET":
return render_template("create.html", language="english", categories=get_categories(), strings=strings, errors=None) return render_template("create.html", categories=get_categories(), errors=None)
elif request.method == "POST": elif request.method == "POST":
newTorrent = createNewTorrent(request) newTorrent = createNewTorrent(request)
if len(newTorrent.errors) == 0: if len(newTorrent.errors) == 0:
message = "Successfully created torrent <a href=\"/search?h={}\">{}</a>".format(newTorrent.fileid, newTorrent.fileid[:-20]) message = _("Successfully created torrent <a href=\"/search?h={}\">{}</a>").format(newTorrent.fileid, newTorrent.fileid[:-20])
return render_template("create.html", language="english", categories=get_categories(), strings=strings, messages=[message]) return render_template("create.html", categories=get_categories(), messages=[message])
else: else:
return render_template("create.html", language="english", categories=get_categories(), strings=strings, errors=newTorrent.errors) return render_template("create.html", categories=get_categories(), errors=newTorrent.errors)
@app.route("/download/<filename>") @app.route("/download/<filename>")
def download(filename): def download(filename):
@ -45,8 +53,6 @@ def download(filename):
@app.route("/search", methods=['GET']) @app.route("/search", methods=['GET'])
def search(): def search():
global strings
print(strings)
connection = sqlite3.connect("torrentdb.sqlite") connection = sqlite3.connect("torrentdb.sqlite")
c = connection.cursor() c = connection.cursor()
@ -80,13 +86,13 @@ def search():
r = row[0:2] + (size(float(row[2])) , ) + row[3:] r = row[0:2] + (size(float(row[2])) , ) + row[3:]
results.append(r) results.append(r)
return render_template("result.html", results=results, strings=strings, language="english", categories=get_categories()) return render_template("result.html", results=results, categories=get_categories())
@app.route("/details", methods=['GET']) @app.route("/details", methods=['GET'])
def details(): def details():
info_hash = request.args["h"] info_hash = request.args["h"]
print(info_hash) print(info_hash)
return render_template("details.html", strings=strings, language="english", categories=get_categories()) return render_template("details.html", categories=get_categories())
def scrapeAll(): def scrapeAll():
TRACKER_URL = "http://tracker.lootbox.cf:6969/" TRACKER_URL = "http://tracker.lootbox.cf:6969/"
@ -95,11 +101,7 @@ def scrapeAll():
return return
def init(): def init():
global strings
global settings global settings
with open("strings.json") as stringsJson:
strings = json.load(stringsJson)
with open("settings.json") as settingsJson: with open("settings.json") as settingsJson:
settings = json.load(settingsJson) settings = json.load(settingsJson)
initDb() initDb()
@ -113,16 +115,6 @@ def initDb():
connection.commit() connection.commit()
connection.close() connection.close()
def getLocalString(language, descriptor):
global strings
if language in strings.keys():
if descriptor in strings[language].keys():
return strings[language][descriptor]
else:
return descriptor
else:
return descriptor
def createNewTorrent(reuqest): def createNewTorrent(reuqest):
uploadfile = request.files["torrentFile"] uploadfile = request.files["torrentFile"]
filename = secure_filename(uploadfile.filename) filename = secure_filename(uploadfile.filename)
@ -160,9 +152,9 @@ def createNewTorrent(reuqest):
connection.close() connection.close()
except sqlite3.IntegrityError as e: except sqlite3.IntegrityError as e:
print(e) print(e)
newTFile.errors = ["Torrent <a href=\"/search?h={}\">{}</a> already exists".format(info_hash, info_hash[:-20])] newTFile.errors = [_("Torrent <a href=\"/search?h={}\">{}</a> already exists").format(info_hash, info_hash[:-20])]
except Exception as e: except Exception as e:
newTFile.errors = ["Unknown error in creation"] newTFile.errors = [_("Unknown error in creation")]
return newTFile return newTFile
@ -214,9 +206,12 @@ class TorrentFile():
b64videoquality_description = base64.b64encode(self.videoquality_description.encode()) b64videoquality_description = base64.b64encode(self.videoquality_description.encode())
c.execute("INSERT INTO torrents(fileid, name, category, subcategory, description, audioquality_description, videoquality_description) VALUES(:fileid, :name, :category, :subcategory, :description, :audioquality_description, :videoquality_description)", { 'fileid' : self.fileid, 'name' : self.name, 'category' : self.category, 'subcategory' : self.subcategory, 'description' : b64description , 'audioquality_description' : b64audioquality_description, 'videoquality_description' : b64videoquality_description}) c.execute("INSERT INTO torrents(fileid, name, category, subcategory, description, audioquality_description, videoquality_description) VALUES(:fileid, :name, :category, :subcategory, :description, :audioquality_description, :videoquality_description)", { 'fileid' : self.fileid, 'name' : self.name, 'category' : self.category, 'subcategory' : self.subcategory, 'description' : b64description , 'audioquality_description' : b64audioquality_description, 'videoquality_description' : b64videoquality_description})
@babel.localeselector
def get_locale():
return request.accept_languages.best_match(LANGUAGES)
if __name__ == "__main__": if __name__ == "__main__":
init() init()
app.jinja_env.globals.update(getLocalString=getLocalString)
app.jinja_env.globals.update(json=json) app.jinja_env.globals.update(json=json)
app.jinja_env.globals.update(sorted=sorted) app.jinja_env.globals.update(sorted=sorted)
app.run(debug=True, host="127.0.0.1") app.run(debug=True, host="127.0.0.1")

View File

@ -1,4 +1,6 @@
bencoder==0.2.0 bencoder==0.2.0
requests==2.18.2 requests==2.18.2
hurry.filesize==0.9 hurry.filesize==0.9
Flask-Babel==0.11.2
PyBabel-json==0.2.0

View File

@ -75,8 +75,9 @@ function fillDetectedFilelist(file) {
function fillSubcategory(value) { function fillSubcategory(value) {
var subSelect = $('#subcategory') var subSelect = $('#subcategory')
var selText = $(':first-child', subSelect).text()
subSelect.empty(); subSelect.empty();
subSelect.append($('<option value="-1" selected>--- Select Subcategory ---</selected>')) subSelect.append($('<option value="-1" selected>'+selText+'</selected>'))
if (value >= 0) { if (value >= 0) {
var subcategories = null var subcategories = null
for(var i = 0; i < global_categories.length; i++) { for(var i = 0; i < global_categories.length; i++) {

View File

@ -1,20 +1,3 @@
function getLocalString(language, descriptor){
if(global_strings[language]){
return global_strings[language][descriptor] || global_strings["english"][descriptor] || descriptor
} else {
return descriptor
}
}
function getDescriptorByLocalString(language, localString){
for(string in global_strings[language]){
if(global_strings[language][string] == localString){
return string
}
}
return localString
}
function getNextUnit(bSize) { function getNextUnit(bSize) {
units = ["B", "kB", "MB", "GB", "TB", "PB", "To damn high"] units = ["B", "kB", "MB", "GB", "TB", "PB", "To damn high"]
iters = 0 iters = 0

View File

@ -1,46 +0,0 @@
{
"english" : {
"audio" : "Audio",
"lossless" : "Lossless",
"lossy" : "Lossy",
"audiobooks" : "Audiobooks",
"other" : "Other",
"video" : "Video",
"movies_3d" : "Movies 3D",
"movies_4k" : "Movies 4K",
"movies_hd" : "Movies HD",
"movies_sd" : "Movies SD",
"series_hd" : "Series HD",
"series_sd" : "Series SD",
"clips" : "Clips",
"other" : "Other",
"porn" : "Porn",
"pictures" : "Pictures",
"games" : "Games",
"pc" : "PC",
"mac" : "Mac",
"ios" : "iOS",
"android" : "Android",
"consoles" : "Consoles",
"applications" : "Applications",
"windows" : "Windows",
"unix" : "*nix",
"ebooks" : "E-Books",
"comics" : "Comics",
"please_choose" : "Please choose...",
"search" : "Search",
"categories" : "Categories",
"category" : "Category",
"subcategory" : "Subcategory",
"create" : "Create",
"torrent_file" : "Torrent file",
"name" : "Name",
"create_new_torrent" : "Create a new torrent",
"description" : "Description",
"audio_quality" : "Audio quality",
"video_quality" : "Video quality",
"size" : "Size",
"tracker" : "Tracker",
"detected_files" : "Detected files"
}
}

View File

@ -6,7 +6,7 @@ vim: ts=2 noexpandtab
{% set active_page = "categories" %} {% set active_page = "categories" %}
{% block content %} {% block content %}
<link href="{{ url_for("static", filename="css/categories.css") }}" rel="stylesheet"> <link href="{{ url_for("static", filename="css/categories.css") }}" rel="stylesheet">
<h2 class="headline">Categories</h2> <h2 class="headline">{{ _("Categories") }}</h2>
<div id="categories"> <div id="categories">
{% for category in categories %} {% for category in categories %}
<div> <div>

View File

@ -2,13 +2,13 @@
vim: ts=2 noexpandtab vim: ts=2 noexpandtab
--> -->
{% extends "index.html" %} {% extends "index.html" %}
{% block title %}{{ super() }} - {{ getLocalString(language, "create") }}{% endblock%} {% block title %}{{ super() }} - {{ _("Create") }}{% endblock%}
{% set active_page = "create" %} {% set active_page = "create" %}
{% block content %} {% block content %}
<link href="{{ url_for("static", filename="css/create.css") }}" rel="stylesheet"> <link href="{{ url_for("static", filename="css/create.css") }}" rel="stylesheet">
<script src="{{ url_for("static", filename="js/create.js") }}"></script> <script src="{{ url_for("static", filename="js/create.js") }}"></script>
<div> <div>
<h2 class="headline">{{ getLocalString(language, "create_new_torrent") }}</h2> <h2 class="headline">{{ _("Create new torrent") }}</h2>
{% if errors %} {% if errors %}
{% for error in errors %} {% for error in errors %}
<div class="alert alert-danger alert-dismissible" role="alert"> <div class="alert alert-danger alert-dismissible" role="alert">
@ -27,24 +27,24 @@ vim: ts=2 noexpandtab
{% endif %} {% endif %}
<form class="form-horizontal" action="/create" method="post" enctype="multipart/form-data" onsubmit="return validateForm()"> <form class="form-horizontal" action="/create" method="post" enctype="multipart/form-data" onsubmit="return validateForm()">
<div class="form-group"> <div class="form-group">
<label for="inputTorrentFile" class="col-sm-3 control-label">{{ getLocalString(language, "torrent_file") }}</label> <label for="inputTorrentFile" class="col-sm-3 control-label">{{ _("Torrent file") }}</label>
<div class="col-sm-9"> <div class="col-sm-9">
<input name="torrentFile" class="file" type="file" size="50" maxlength="100000" accept="application/x-bittorrent" onchange="setButtonToFilename(event)"> <input name="torrentFile" class="file" type="file" size="50" maxlength="100000" accept="application/x-bittorrent" onchange="setButtonToFilename(event)">
</div> </div>
</div> </div>
<div class="form-group detectedInfosGroup"> <div class="form-group detectedInfosGroup">
<label for="inputTorrentFile" class="col-sm-3 control-label">File info</label> <label for="inputTorrentFile" class="col-sm-3 control-label">{{ _("File info") }}</label>
<div class="col-sm-9 detectedInfos"> <div class="col-sm-9 detectedInfos">
<div class="detectedGroup sizeGroup"> <div class="detectedGroup sizeGroup">
<h5>{{ getLocalString(language, "size") }}:</h5> <h5>{{ _("Size") }}:</h5>
<p class="detectedSize"></p> <p class="detectedSize"></p>
</div> </div>
<div class="detectedGroup trackerGroup"> <div class="detectedGroup trackerGroup">
<h5>{{ getLocalString(language, "tracker") }}:</h5> <h5>{{ _("Tracker") }}:</h5>
<p class="detectedTracker"></p> <p class="detectedTracker"></p>
</div> </div>
<div class="detectedGroup filesGroup"> <div class="detectedGroup filesGroup">
<h5>{{ getLocalString(language, "detected_files") }}:</h5> <h5>{{ _("Detected files") }}:</h5>
<div class="detectedFiles"> <div class="detectedFiles">
<ul> <ul>
</ul> </ul>
@ -53,12 +53,12 @@ vim: ts=2 noexpandtab
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="inputCategory" class="col-sm-3 control-label">{{ getLocalString(language, "category") }}</label> <label for="category" class="col-sm-3 control-label">{{ _("Category") }}</label>
<div class="col-sm-9"> <div class="col-sm-9">
<div class="row row-container"> <div class="row row-container">
<div class="col-md-6 category-column"> <div class="col-md-6 category-column">
<select class="form-control dropdown" id="category" name="category"> <select class="form-control dropdown" id="category" name="category">
<option value="-1" selected>--- Select Category ---</option> <option value="-1" selected>--- {{ _("Select Category") }} ---</option>
{% for category in categories %} {% for category in categories %}
<option value="{{ category.id }}">{{ category.label }}</option> <option value="{{ category.id }}">{{ category.label }}</option>
{% endfor %} {% endfor %}
@ -66,28 +66,28 @@ vim: ts=2 noexpandtab
</div> </div>
<div class="col-md-6 subcategory-column"> <div class="col-md-6 subcategory-column">
<select class="form-control dropdown" id="subcategory" name="subcategory"> <select class="form-control dropdown" id="subcategory" name="subcategory">
<option value="-1" selected>--- Select Subcategory ---</option> <option value="-1" selected>--- {{ _("Select Subcategory") }} ---</option>
</select> </select>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="inputName" class="col-sm-3 control-label">{{ getLocalString(language, "name") }}</label> <label for="inputName" class="col-sm-3 control-label">{{ _("Name") }}</label>
<div class="col-sm-9"> <div class="col-sm-9">
<input type="text" name="name" class="form-control name" placeholder="e.g. Attack of the Killer Tomatoes" aria-describedby="basic-addon1"> <input type="text" name="name" class="form-control name" placeholder="{{ _("e.g. Attack of the Killer Tomatoes") }}" aria-describedby="basic-addon1">
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="inputName" class="col-sm-3 control-label">{{ getLocalString(language, "audio_quality") }}</label> <label for="inputName" class="col-sm-3 control-label">{{ _("Audio quality") }}</label>
<div class="col-sm-9"> <div class="col-sm-9">
<input type="text" name="audioquality_description" class="form-control name" placeholder="e.g. English, AC-3 at 384kbps" aria-describedby="basic-addon1"> <input type="text" name="audioquality_description" class="form-control name" placeholder="{{ _("e.g. English, AC-3 @ 384kbps") }}" aria-describedby="basic-addon1">
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="inputName" class="col-sm-3 control-label">{{ getLocalString(language, "video_quality") }}</label> <label for="inputName" class="col-sm-3 control-label">{{ _("Video quality") }}</label>
<div class="col-sm-9"> <div class="col-sm-9">
<input type="text" name="videoquality_description" class="form-control name" placeholder="e.g. XviD, 720×400 at 1809 kbps" aria-describedby="basic-addon1"> <input type="text" name="videoquality_description" class="form-control name" placeholder="{{ _("e.g. XviD, 720×400 @ 1809 kbps") }}" aria-describedby="basic-addon1">
</div> </div>
</div> </div>
<!-- <!--
@ -115,14 +115,14 @@ vim: ts=2 noexpandtab
</div> </div>
--> -->
<div class="form-group"> <div class="form-group">
<label for="inputName" class="col-sm-3 control-label">{{ getLocalString(language, "description") }}</label> <label for="inputName" class="col-sm-3 control-label">{{ _("Description") }}</label>
<div class="col-sm-9"> <div class="col-sm-9">
<textarea name="description" class="form-control description" rows="10" placeholder="Hint: Markdown is supported in this field"></textarea> <textarea name="description" class="form-control description" rows="10" placeholder="{{ _("Hint: Markdown is supported in this field") }}"></textarea>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-offset-3 col-sm-9"> <div class="col-sm-offset-3 col-sm-9">
<button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> {{ getLocalString(language, "create") }}!</button> <button type="submit" class="btn btn-default"><span class="glyphicon glyphicon-plus" aria-hidden="true"></span> {{ _("Create") }}!</button>
</div> </div>
</div> </div>
</form> </form>

View File

@ -2,7 +2,7 @@
vim: ts=2 noexpandtab vim: ts=2 noexpandtab
--> -->
{% extends "index.html" %} {% extends "index.html" %}
{% block title %}{{ super() }} - Results{% endblock%} {% block title %}{{ super() }} - {{ _("Results") }}{% endblock%}
{% set active_page = "search" %} {% set active_page = "search" %}
{% block content %} {% block content %}
<link href="{{ url_for("static", filename="css/details.css") }}" rel="stylesheet"> <link href="{{ url_for("static", filename="css/details.css") }}" rel="stylesheet">

View File

@ -2,9 +2,9 @@
vim: ts=2 noexpandtab vim: ts=2 noexpandtab
--> -->
{% set navigation_bar = [ {% set navigation_bar = [
("/", "search", "Search", "glyphicon-search"), ("/", "search", _("Search"), "glyphicon-search"),
("/categories", "categories", "Categories", "glyphicon-th"), ("/categories", "categories", _("Categories"), "glyphicon-th"),
("/create", "create", "Create", "glyphicon-plus") ("/create", "create", _("Create"), "glyphicon-plus")
] -%} ] -%}
<!DOCTYPE html> <!DOCTYPE html>
@ -23,7 +23,6 @@ vim: ts=2 noexpandtab
<body> <body>
<script type="text/javascript"> <script type="text/javascript">
var global_strings = {{json.dumps(strings)|safe}};
var global_categories = {{json.dumps(categories)|safe}}; var global_categories = {{json.dumps(categories)|safe}};
</script> </script>
<div class="site-wrapper"> <div class="site-wrapper">

View File

@ -2,26 +2,26 @@
vim: ts=2 noexpandtab vim: ts=2 noexpandtab
--> -->
{% extends "index.html" %} {% extends "index.html" %}
{% block title %}{{ super() }} - Results{% endblock%} {% block title %}{{ super() }} - {{ _("Results") }}{% endblock%}
{% set active_page = "search" %} {% set active_page = "search" %}
{% block content %} {% block content %}
<link href="{{ url_for("static", filename="css/result.css") }}" rel="stylesheet"> <link href="{{ url_for("static", filename="css/result.css") }}" rel="stylesheet">
<script src="{{ url_for("static", filename="js/main.js") }}"></script> <script src="{{ url_for("static", filename="js/main.js") }}"></script>
<table> <table>
<tr> <tr>
<th>Name</th> <th>{{ _("Name") }}</th>
<th>Size</th> <th>{{ _("Size") }}</th>
<th>Snatches</th> <th>{{ _("Snatches") }}</th>
<th>UL</th> <th>{{ _("UL") }}</th>
<th>DL</th> <th>{{ _("DL") }}</th>
</tr> </tr>
{% for result in results %} {% for result in results %}
<tr> <tr>
<td><a href="/download/{{ result[0] }}">{{ result[1] }}</a></td> <td><a href="/download/{{ result[0] }}">{{ result[1] }}</a></td>
<td>{{ result[2] }}</td> <td>{{ result[2] }}</td>
<td>N/A</td> <td>{{ _("N/A") }}</td>
<td>N/A</td> <td>{{ _("N/A") }}</td>
<td>N/A</td> <td>{{ _("N/A") }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

View File

@ -2,7 +2,7 @@
vim: ts=2 noexpandtab vim: ts=2 noexpandtab
--> -->
{% extends "index.html" %} {% extends "index.html" %}
{% block title %}{{ super() }} - Search{% endblock%} {% block title %}{{ super() }} - {{ _("Search") }}{% endblock%}
{% set active_page = "search" %} {% set active_page = "search" %}
{% block content %} {% block content %}
<link href="{{ url_for("static", filename="css/search.css") }}" rel="stylesheet"> <link href="{{ url_for("static", filename="css/search.css") }}" rel="stylesheet">
@ -12,11 +12,11 @@ vim: ts=2 noexpandtab
<form action="search" > <form action="search" >
<div class="form-group"> <div class="form-group">
<div class="input-group search-box"> <div class="input-group search-box">
<input type="text" name="q" class="form-control" placeholder="Seach for…" aria-describedby="basic-addon2"> <input type="text" name="q" class="form-control" placeholder="{{ _("Seach for") }}" aria-describedby="basic-addon2">
<span class="input-group-btn"> <span class="input-group-btn">
<button class="btn btn-default" type="submit"> <button class="btn btn-default" type="submit">
<span class="glyphicon glyphicon-search" aria-hidden="true"></span> <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
{{ getLocalString(language, "search") }}! {{ _("Search!") }}
</button> </button>
</span> </span>
</div> </div>

View File

@ -0,0 +1,258 @@
# German translations for PROJECT.
# Copyright (C) 2017 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2017.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2017-12-29 06:12+0100\n"
"PO-Revision-Date: 2017-12-28 04:10+0100\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n"
"Language-Team: de <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.5.1\n"
#: indexer.py:41
msgid "Successfully created torrent <a href=\"/search?h={}\">{}</a>"
msgstr "Torrent <a href=\"/search?h={}\">{}</a> erfolgreich erstellt"
#: indexer.py:155
msgid "Torrent <a href=\"/search?h={}\">{}</a> already exists"
msgstr "Torrent <a href=\"/search?h={}\">{}</a> ist bereits vorhanden"
#: indexer.py:157
msgid "Unknown error in creation"
msgstr "Unbekannter Fehler bei der Erstellung"
#: settings.json:4 settings.json:8 settings.json:12 settings.json:16
#: settings.json:20 settings.json:26 settings.json:30 settings.json:34
#: settings.json:38 settings.json:42 settings.json:46 settings.json:50
#: settings.json:54 settings.json:58 settings.json:64 settings.json:68
#: settings.json:72 settings.json:76 settings.json:80 settings.json:84
#: settings.json:88 settings.json:92 settings.json:96 settings.json:100
#: settings.json:106 settings.json:110 settings.json:114 settings.json:118
#: settings.json:122 settings.json:126 settings.json:130 settings.json:136
#: settings.json:140 settings.json:144 settings.json:148 settings.json:152
#: settings.json:156 settings.json:160 settings.json:166 settings.json:170
#: settings.json:174 settings.json:178 settings.json:182
msgid "id"
msgstr "id"
#: settings.json:5
msgid "Audio"
msgstr "Audio"
#: settings.json:9
msgid "Lossless"
msgstr "Verlustfrei"
#: settings.json:13
msgid "Lossy"
msgstr "Verlustbehaftet"
#: settings.json:17
msgid "Audio book"
msgstr "Hörbuch"
#: settings.json:21 settings.json:59 settings.json:101 settings.json:131
#: settings.json:161 settings.json:167 settings.json:183
msgid "Other"
msgstr "Sonstige"
#: settings.json:27
msgid "Video"
msgstr "Video"
#: settings.json:31 settings.json:69
msgid "3D Movie"
msgstr "3D-Film"
#: settings.json:35 settings.json:73
msgid "4k Movie"
msgstr "4k-Film"
#: settings.json:39 settings.json:77
msgid "HD Movie"
msgstr "HD-Film"
#: settings.json:43 settings.json:81
msgid "SD Movie"
msgstr "SD-Film"
#: settings.json:47 settings.json:85
msgid "HD Series"
msgstr "HD-Serie"
#: settings.json:51 settings.json:89
msgid "SD Series"
msgstr "SD-Serie"
#: settings.json:55 settings.json:93
msgid "Clip"
msgstr "Clip"
#: settings.json:65
msgid "Porn"
msgstr "Pornographie"
#: settings.json:97
msgid "Pictures"
msgstr "Bilder"
#: settings.json:107
msgid "Games"
msgstr "Spiele"
#: settings.json:111 settings.json:141
msgid "Windows"
msgstr "Windows"
#: settings.json:115 settings.json:145
msgid "Mac"
msgstr "Mac"
#: settings.json:119 settings.json:153
msgid "iOS"
msgstr "iOS"
#: settings.json:123 settings.json:157
msgid "Android"
msgstr "Android"
#: settings.json:127
msgid "Consoles"
msgstr "Konsolen"
#: settings.json:137
msgid "Application"
msgstr "Anwendungen"
#: settings.json:149
msgid "*nix"
msgstr "*nix"
#: settings.json:171
msgid "eBook"
msgstr "eBook"
#: settings.json:175
msgid "Comic"
msgstr "Comic"
#: settings.json:179
msgid "Picture"
msgstr "Bild"
#: templates/categories.html:9 templates/index.html:6
msgid "Categories"
msgstr "Kategorien"
#: templates/create.html:5 templates/create.html:125 templates/index.html:7
msgid "Create"
msgstr "Erstellen"
#: templates/create.html:11
msgid "Create new torrent"
msgstr "Neuen Torrent erstellen"
#: templates/create.html:30
msgid "Torrent file"
msgstr "Torrentdatei"
#: templates/create.html:36
msgid "File info"
msgstr ""
#: templates/create.html:39 templates/result.html:13
msgid "Size"
msgstr "Größe"
#: templates/create.html:43
msgid "Tracker"
msgstr "Tracker"
#: templates/create.html:47
msgid "Detected files"
msgstr "Erkannte Dateien"
#: templates/create.html:56
msgid "Category"
msgstr "Kategorie"
#: templates/create.html:61
msgid "Select Category"
msgstr "Kategorie wählen"
#: templates/create.html:69
msgid "Select Subcategory"
msgstr "Unterkategorie wählen"
#: templates/create.html:76 templates/result.html:12
msgid "Name"
msgstr "Name"
#: templates/create.html:78
msgid "e.g. Attack of the Killer Tomatoes"
msgstr "z.B. Angriff der Killertomaten"
#: templates/create.html:82
msgid "Audio quality"
msgstr "Audioqualität"
#: templates/create.html:84
msgid "e.g. English, AC-3 @ 384kbps"
msgstr "z.B. Deutsch, AC-3 @ 384kbps"
#: templates/create.html:88
msgid "Video quality"
msgstr "Videoqualität"
#: templates/create.html:90
msgid "e.g. XviD, 720×400 @ 1809 kbps"
msgstr "z.B. XviD, 720x400 @ 1809 kbps"
#: templates/create.html:118
msgid "Description"
msgstr "Beschreibung"
#: templates/create.html:120
msgid "Hint: Markdown is supported in this field"
msgstr "Hinweis: In diesem Feld kann Markdown verwendet werden"
#: templates/details.html:5 templates/result.html:5
msgid "Results"
msgstr "Ergebnisse"
#: templates/index.html:5 templates/search.html:5
msgid "Search"
msgstr "Suche"
#: templates/result.html:14
msgid "Snatches"
msgstr "Schnapper"
#: templates/result.html:15
msgid "UL"
msgstr "UL"
#: templates/result.html:16
msgid "DL"
msgstr "DL"
#: templates/result.html:22 templates/result.html:23 templates/result.html:24
msgid "N/A"
msgstr "N/V"
#: templates/search.html:15
msgid "Seach for…"
msgstr "Suche nach…"
#: templates/search.html:19
msgid "Search!"
msgstr "Suchen!"