unwieldy commit 2
このコミットが含まれているのは:
コミット
0c6792894a
|
@ -2,17 +2,21 @@ from quart import current_app
|
|||
|
||||
LOCALES = current_app.locales
|
||||
|
||||
def get_lang_and_locale_from(context):
|
||||
def get_lang_and_locale_from(context, burrow=(), validate=True):
|
||||
lang = context.args.get('lang')
|
||||
locale = LOCALES.get(lang)
|
||||
if locale is None:
|
||||
lang, locale = None, LOCALES[None]
|
||||
if validate:
|
||||
lang = None
|
||||
locale = LOCALES[None]
|
||||
for key in burrow:
|
||||
locale = locale[key]
|
||||
return lang, locale
|
||||
|
||||
def get_lang_from(context):
|
||||
lang, locale = get_lang_and_locale_from(context)
|
||||
def get_lang_from(context, validate=True):
|
||||
lang, locale = get_lang_and_locale_from(context, validate=validate)
|
||||
return lang
|
||||
|
||||
def get_locale_from(context):
|
||||
def get_locale_from(context, burrow=()):
|
||||
lang, locale = get_lang_and_locale_from(context)
|
||||
return locale
|
||||
|
|
|
@ -26,7 +26,7 @@ LANG = current_app.lang
|
|||
@current_app.route('/')
|
||||
@with_user_from(request, fallback_to_token=True, ignore_allowedness=True)
|
||||
async def home(timestamp, user_or_token):
|
||||
lang, locale = get_lang_and_locale_from(request)
|
||||
lang, locale = get_lang_and_locale_from(request, burrow=('anonstream',))
|
||||
match user_or_token:
|
||||
case str() | None as token:
|
||||
failure_id = request.args.get('failure', type=int)
|
||||
|
@ -35,25 +35,27 @@ async def home(timestamp, user_or_token):
|
|||
'captcha.html',
|
||||
csp=generate_csp(),
|
||||
token=token,
|
||||
locale=locale['anonstream']['captcha'],
|
||||
request_lang=get_lang_from(request, validate=False),
|
||||
locale=locale['captcha'],
|
||||
digest=get_random_captcha_digest(),
|
||||
failure=locale['anonstream']['internal'].get(failure),
|
||||
failure=locale['internal'].get(failure),
|
||||
)
|
||||
case dict() as user:
|
||||
try:
|
||||
ensure_allowedness(user, timestamp=timestamp)
|
||||
except Blacklisted:
|
||||
raise Forbidden(locale['anonstream']['error']['blacklisted'])
|
||||
raise Forbidden(locale['error']['blacklisted'])
|
||||
except SecretClub:
|
||||
# TODO allow changing tripcode
|
||||
raise Forbidden(locale['anonstream']['error']['not_whitelisted'])
|
||||
raise Forbidden(locale['error']['not_whitelisted'])
|
||||
else:
|
||||
response = await render_template(
|
||||
'home.html',
|
||||
csp=generate_csp(),
|
||||
user=user,
|
||||
lang=lang or LANG,
|
||||
locale=locale['anonstream']['home'],
|
||||
lang=lang,
|
||||
default_lang=LANG,
|
||||
locale=locale['home'],
|
||||
version=current_app.version,
|
||||
)
|
||||
return response
|
||||
|
@ -99,7 +101,7 @@ async def stream(timestamp, user):
|
|||
@current_app.route('/login')
|
||||
@auth_required
|
||||
async def login():
|
||||
return redirect(url_for('home'), 303)
|
||||
return redirect(url_for('home', lang=get_lang_from(request)), 303)
|
||||
|
||||
@current_app.route('/captcha.jpg')
|
||||
@with_user_from(request, fallback_to_token=True)
|
||||
|
@ -114,7 +116,6 @@ async def captcha(timestamp, user_or_token):
|
|||
@current_app.post('/access')
|
||||
@with_user_from(request, fallback_to_token=True, ignore_allowedness=True)
|
||||
async def access(timestamp, user_or_token):
|
||||
lang = get_lang_from(request)
|
||||
match user_or_token:
|
||||
case str() | None as token:
|
||||
form = await request.form
|
||||
|
@ -130,12 +131,11 @@ async def access(timestamp, user_or_token):
|
|||
case Answer.OK:
|
||||
failure_id = None
|
||||
user = generate_and_add_user(timestamp, token, verified=True)
|
||||
if failure_id is not None:
|
||||
url = url_for('home', token=token, lang=lang, failure=failure_id)
|
||||
raise abort(redirect(url, 303))
|
||||
case dict() as user:
|
||||
pass
|
||||
url = url_for('home', token=user['token'])
|
||||
token = user['token']
|
||||
failure_id = None
|
||||
lang = get_lang_from(request, validate=failure_id is None)
|
||||
url = url_for('home', token=token, lang=lang, failure=failure_id)
|
||||
return redirect(url, 303)
|
||||
|
||||
@current_app.route('/static/<path:filename>')
|
||||
|
|
|
@ -5,13 +5,12 @@ from anonstream.locale import get_locale_from
|
|||
|
||||
for error in default_exceptions:
|
||||
async def handle(error):
|
||||
locale = get_locale_from(request)['http']
|
||||
error.description = locale.get(error.description)
|
||||
if error.description == error.__class__.description:
|
||||
error.description = None
|
||||
return (
|
||||
await render_template(
|
||||
'error.html',
|
||||
error=error,
|
||||
locale=locale,
|
||||
'error.html', error=error,
|
||||
locale=get_locale_from(request)['http'],
|
||||
), error.code
|
||||
)
|
||||
current_app.register_error_handler(error, handle)
|
||||
|
|
|
@ -5,7 +5,7 @@ from quart import current_app, request, render_template, redirect, url_for, esca
|
|||
|
||||
from anonstream.captcha import get_random_captcha_digest_for
|
||||
from anonstream.chat import add_chat_message, Rejected
|
||||
from anonstream.locale import get_locale_from
|
||||
from anonstream.locale import get_lang_and_locale_from, get_lang_from, get_locale_from
|
||||
from anonstream.stream import is_online, get_stream_title, get_stream_uptime_and_viewership
|
||||
from anonstream.user import add_state, pop_state, try_change_appearance, update_presence, get_users_by_presence, Presence, verify, deverify, BadCaptcha, reading
|
||||
from anonstream.routes.wrappers import with_user_from, render_template_with_etag
|
||||
|
@ -64,7 +64,12 @@ async def nojs_chat_messages(timestamp, user):
|
|||
@current_app.route('/chat/messages')
|
||||
@with_user_from(request)
|
||||
async def nojs_chat_messages_redirect(timestamp, user):
|
||||
url = url_for('nojs_chat_messages', token=user['token'], _anchor='end')
|
||||
url = url_for(
|
||||
'nojs_chat_messages',
|
||||
token=user['token'],
|
||||
lang=get_lang_from(request),
|
||||
_anchor='end',
|
||||
)
|
||||
return redirect(url, 303)
|
||||
|
||||
@current_app.route('/chat/users.html')
|
||||
|
@ -86,17 +91,18 @@ async def nojs_chat_users(timestamp, user):
|
|||
@current_app.route('/chat/form.html')
|
||||
@with_user_from(request)
|
||||
async def nojs_chat_form(timestamp, user):
|
||||
lang, locale = get_lang_and_locale_from(request)
|
||||
state_id = request.args.get('state', type=int)
|
||||
state = pop_state(user, state_id)
|
||||
prefer_chat_form = request.args.get('landing') != 'appearance'
|
||||
print(state)
|
||||
return await render_template(
|
||||
'nojs_chat_form.html',
|
||||
csp=generate_csp(),
|
||||
user=user,
|
||||
prefer_chat_form=prefer_chat_form,
|
||||
state=state,
|
||||
locale=get_locale_from(request)['anonstream'],
|
||||
lang=lang,
|
||||
locale=locale['anonstream'],
|
||||
nonce=generate_nonce(),
|
||||
digest=get_random_captcha_digest_for(user),
|
||||
default_name=get_default_name(user),
|
||||
|
@ -116,7 +122,12 @@ async def nojs_chat_form_redirect(timestamp, user):
|
|||
)
|
||||
else:
|
||||
state_id = None
|
||||
url = url_for('nojs_chat_form', token=user['token'], state=state_id)
|
||||
url = url_for(
|
||||
'nojs_chat_form',
|
||||
token=user['token'],
|
||||
lang=get_lang_from(request),
|
||||
state=state_id,
|
||||
)
|
||||
return redirect(url, 303)
|
||||
|
||||
@current_app.post('/chat/message')
|
||||
|
@ -163,6 +174,7 @@ async def nojs_submit_message(timestamp, user):
|
|||
url = url_for(
|
||||
'nojs_chat_form',
|
||||
token=user['token'],
|
||||
lang=get_lang_from(request),
|
||||
landing='chat',
|
||||
state=state_id,
|
||||
)
|
||||
|
@ -201,6 +213,7 @@ async def nojs_submit_appearance(timestamp, user):
|
|||
url = url_for(
|
||||
'nojs_chat_form',
|
||||
token=user['token'],
|
||||
lang=get_lang_from(request),
|
||||
landing='appearance' if errors else 'chat',
|
||||
state=state_id,
|
||||
)
|
||||
|
|
|
@ -8,11 +8,12 @@ import string
|
|||
from functools import wraps
|
||||
from urllib.parse import quote, unquote
|
||||
|
||||
from quart import current_app, request, make_response, render_template, request, url_for, Markup
|
||||
from quart import current_app, request, make_response, render_template, request, url_for, escape, Markup
|
||||
from werkzeug.exceptions import BadRequest, Unauthorized, Forbidden
|
||||
from werkzeug.security import check_password_hash
|
||||
|
||||
from anonstream.broadcast import broadcast
|
||||
from anonstream.locale import get_lang_and_locale_from, get_locale_from
|
||||
from anonstream.user import ensure_allowedness, Blacklisted, SecretClub
|
||||
from anonstream.helpers.user import generate_user
|
||||
from anonstream.utils.user import generate_token, Presence
|
||||
|
@ -53,18 +54,11 @@ def auth_required(f):
|
|||
async def wrapper(*args, **kwargs):
|
||||
if check_auth(request):
|
||||
return await f(*args, **kwargs)
|
||||
hint = (
|
||||
'The broadcaster should log in with the credentials printed in '
|
||||
'their terminal.'
|
||||
)
|
||||
locale = get_locale_from(request)['anonstream']['error']
|
||||
if request.authorization is None:
|
||||
description = hint
|
||||
description = locale['broadcaster_should_log_in']
|
||||
else:
|
||||
description = Markup(
|
||||
f'Wrong username or password. Refresh the page to try again. '
|
||||
f'<br>'
|
||||
f'{hint}'
|
||||
)
|
||||
description = locale['wrong_username_or_password']
|
||||
error = Unauthorized(description)
|
||||
response = await current_app.handle_http_exception(error)
|
||||
response = await make_response(response)
|
||||
|
@ -107,11 +101,11 @@ def with_user_from(context, fallback_to_token=False, ignore_allowedness=False):
|
|||
|
||||
# Reject invalid tokens
|
||||
if isinstance(token, str) and not RE_TOKEN.fullmatch(token):
|
||||
raise BadRequest(Markup(
|
||||
f'Your token contains disallowed characters or is too '
|
||||
f'long. Tokens must match this regular expression: <br>'
|
||||
f'<code>{RE_TOKEN.pattern}</code>'
|
||||
))
|
||||
locale = get_locale_from(context)
|
||||
args = (
|
||||
Markup(f'<br><code>{RE_TOKEN.pattern}</code>'),
|
||||
)
|
||||
raise BadRequest(escape(locale['invalid_token']) % args)
|
||||
|
||||
# Only logged in broadcaster may have the broadcaster's token
|
||||
if (
|
||||
|
@ -119,15 +113,16 @@ def with_user_from(context, fallback_to_token=False, ignore_allowedness=False):
|
|||
and isinstance(token, str)
|
||||
and hmac.compare_digest(token, CONFIG['AUTH_TOKEN'])
|
||||
):
|
||||
raise Unauthorized(Markup(
|
||||
f"You are using the broadcaster's token but you are "
|
||||
f"not logged in. The broadcaster should "
|
||||
f"<a href=\"{url_for('login')}\" target=\"_top\">"
|
||||
f"click here"
|
||||
f"</a> "
|
||||
f"and log in with the credentials printed in their "
|
||||
f"terminal when they started anonstream."
|
||||
))
|
||||
lang, locale = get_lang_and_locale_from(
|
||||
context, burrow=('anonstream', 'error'),
|
||||
)
|
||||
args = (
|
||||
Markup(f'''<a href="{url_for('login', lang=lang)}" target="_top">'''),
|
||||
Markup(f'''</a>'''),
|
||||
)
|
||||
raise Unauthorized(
|
||||
escape(locale['impostor']) % args
|
||||
)
|
||||
|
||||
# Create response
|
||||
user = USERS_BY_TOKEN.get(token)
|
||||
|
@ -136,19 +131,25 @@ def with_user_from(context, fallback_to_token=False, ignore_allowedness=False):
|
|||
user['last']['seen'] = timestamp
|
||||
user['headers'] = tuple(context.headers)
|
||||
if not ignore_allowedness:
|
||||
assert_allowedness(timestamp, user)
|
||||
assert_allowedness(context, timestamp, user)
|
||||
if user is not None and user['verified'] is not None:
|
||||
response = await f(timestamp, user, *args, **kwargs)
|
||||
elif fallback_to_token:
|
||||
#assert not broadcaster
|
||||
response = await f(timestamp, token, *args, **kwargs)
|
||||
else:
|
||||
raise Forbidden(Markup(
|
||||
f"You have not solved the access captcha. "
|
||||
f"<a href=\"{url_for('home', token=token)}\" target=\"_top\">"
|
||||
f"Click here."
|
||||
f"</a>"
|
||||
))
|
||||
lang, locale = get_lang_and_locale_from(
|
||||
context, burrow=('anonstream', 'error'),
|
||||
)
|
||||
args = (
|
||||
Markup(f'''<a href="{url_for('home', token=token, lang=lang)}" target="_top">'''),
|
||||
Markup(f'''</a>'''),
|
||||
)
|
||||
if user is None:
|
||||
string = locale['captcha']
|
||||
else:
|
||||
string = locale['captcha_again']
|
||||
raise Forbidden(escape(string) % args)
|
||||
else:
|
||||
if user is not None:
|
||||
user['last']['seen'] = timestamp
|
||||
|
@ -161,7 +162,7 @@ def with_user_from(context, fallback_to_token=False, ignore_allowedness=False):
|
|||
headers=tuple(context.headers),
|
||||
)
|
||||
if not ignore_allowedness:
|
||||
assert_allowedness(timestamp, user)
|
||||
assert_allowedness(context, timestamp, user)
|
||||
response = await f(timestamp, user, *args, **kwargs)
|
||||
|
||||
# Set cookie
|
||||
|
@ -229,10 +230,12 @@ def etag_conditional(f):
|
|||
|
||||
return wrapper
|
||||
|
||||
def assert_allowedness(timestamp, user):
|
||||
def assert_allowedness(context, timestamp, user):
|
||||
try:
|
||||
ensure_allowedness(user, timestamp=timestamp)
|
||||
except Blacklisted as e:
|
||||
raise Forbidden('You have been blacklisted.')
|
||||
locale = get_locale_from(context)['anonstream']['error']
|
||||
raise Forbidden(locale['blacklisted'])
|
||||
except SecretClub as e:
|
||||
raise Forbidden('You have not been whitelisted.')
|
||||
locale = get_locale_from(context)['anonstream']['error']
|
||||
raise Forbidden(locale['whitelisted'])
|
||||
|
|
|
@ -7,6 +7,9 @@
|
|||
const TOKEN = document.body.dataset.token;
|
||||
const TOKEN_HASH = document.body.dataset.tokenHash;
|
||||
|
||||
/* language */
|
||||
const LANG = document.firstElementChild.lang;
|
||||
|
||||
/* Content Security Policy nonce */
|
||||
const CSP = document.body.dataset.csp;
|
||||
|
||||
|
@ -868,7 +871,7 @@ const connect_websocket = () => {
|
|||
chat_live_ball.style.borderColor = "gold";
|
||||
chat_live_status.innerHTML = `<span data-verbose='true'>${locale.connecting_to_chat || "Connecting to chat..."}</span><span data-verbose='false'>···</span>`;
|
||||
ws = null;
|
||||
ws = new WebSocket(`ws://${document.domain}:${location.port}/live?token=${encodeURIComponent(TOKEN)}`);
|
||||
ws = new WebSocket(`ws://${document.domain}:${location.port}/live?token=${encodeURIComponent(TOKEN)}&lang=${encodeURIComponent(LANG)}`);
|
||||
ws.addEventListener("open", (event) => {
|
||||
console.log("websocket open", event);
|
||||
chat_form_submit.disabled = false;
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<img src="{{ url_for('captcha', digest=digest) }}" width="72" height="30">
|
||||
<form action="{{ url_for('access', token=token) }}" method="post">
|
||||
<form action="{{ url_for('access', token=token, lang=request_lang) }}" method="post">
|
||||
<input type="hidden" name="digest" value="{{ digest }}">
|
||||
<input name="answer" placeholder="{{ locale.captcha }}" required autofocus>
|
||||
<input type="submit" value="Submit">
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
##}
|
||||
<!doctype html>
|
||||
<html id="nochat" lang="{{ lang }}">
|
||||
<html id="nochat" lang="{{ lang or default_lang }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
@ -15,7 +15,7 @@
|
|||
<noscript><iframe id="stream_nojs" name="stream_nojs" src="{{ url_for('nojs_stream', token=user.token) }}"></iframe></noscript>
|
||||
</article>
|
||||
<article id="info">
|
||||
<noscript><iframe id="info_nojs" src="{{ url_for('nojs_info', token=user.token) }}" data-js="false"></iframe></noscript>
|
||||
<noscript><iframe id="info_nojs" src="{{ url_for('nojs_info', token=user.token, lang=lang) }}" data-js="false"></iframe></noscript>
|
||||
</article>
|
||||
<aside id="chat">
|
||||
<input id="chat__toggle" type="checkbox">
|
||||
|
@ -25,21 +25,21 @@
|
|||
</header>
|
||||
<article id="chat__body">
|
||||
<section id="chat__body__messages">
|
||||
<noscript><iframe id="chat-messages_nojs" src="{{ url_for('nojs_chat_messages', token=user.token, _anchor='end') }}" data-js="false"></iframe></noscript>
|
||||
<noscript><iframe id="chat-messages_nojs" src="{{ url_for('nojs_chat_messages', token=user.token, lang=lang, _anchor='end') }}" data-js="false"></iframe></noscript>
|
||||
</section>
|
||||
<section id="chat__body__users">
|
||||
<header id="chat-users-header"><h4>{{ locale.users_in_chat }}</h4></header>
|
||||
<noscript><iframe id="chat-users_nojs" src="{{ url_for('nojs_chat_users', token=user.token) }}" data-js="false"></iframe></noscript>
|
||||
<noscript><iframe id="chat-users_nojs" src="{{ url_for('nojs_chat_users', token=user.token, lang=lang) }}" data-js="false"></iframe></noscript>
|
||||
</section>
|
||||
</article>
|
||||
<section id="chat__form">
|
||||
<noscript><iframe id="chat-form_nojs" src="{{ url_for('nojs_chat_form', token=user.token) }}" data-js="false"></iframe></noscript>
|
||||
<noscript><iframe id="chat-form_nojs" src="{{ url_for('nojs_chat_form', token=user.token, lang=lang) }}" data-js="false"></iframe></noscript>
|
||||
</section>
|
||||
</aside>
|
||||
<nav id="nav">
|
||||
<a href="#info">info</a>
|
||||
<a href="#chat">chat</a>
|
||||
<a href="#both">both</a>
|
||||
<a href="#info">{{ locale.info }}</a>
|
||||
<a href="#chat">{{ locale.chat }}</a>
|
||||
<a href="#both">{{ locale.both }}</a>
|
||||
</nav>
|
||||
<footer>anonstream {{ version }} — <a href="https://git.076.ne.jp/ninya9k/anonstream" target="_blank">{{ locale.source }}</a></footer>
|
||||
<script src="{{ url_for('static', filename='anonstream.js', token=user.token) }}" type="text/javascript"></script>
|
||||
|
|
|
@ -136,7 +136,7 @@
|
|||
#appearance-form__buttons {
|
||||
grid-column: 1 / span 3;
|
||||
display: grid;
|
||||
grid-template-columns: auto 5rem;
|
||||
grid-template-columns: auto 6rem;
|
||||
}
|
||||
#password-column {
|
||||
display: grid;
|
||||
|
@ -224,7 +224,7 @@
|
|||
<small>{{ locale.form.click_to_dismiss }}</small>
|
||||
</label>
|
||||
{% endif %}
|
||||
<form id="chat-form" action="{{ url_for('nojs_submit_message', token=user.token) }}" method="post">
|
||||
<form id="chat-form" action="{{ url_for('nojs_submit_message', token=user.token, lang=lang) }}" method="post">
|
||||
<input type="hidden" name="nonce" value="{{ nonce }}">
|
||||
<textarea id="chat-form__comment" name="comment" maxlength="{{ max_comment_length }}" {% if digest is none %}required {% endif %} placeholder="{{ locale.form.send_a_message }}" rows="1" tabindex="1" autofocus accesskey="m">{{ state.comment }}</textarea>
|
||||
<input id="chat-form__submit" type="submit" value="{{ locale.form.chat }}" tabindex="4" accesskey="p">
|
||||
|
@ -235,7 +235,7 @@
|
|||
<input id="chat-form__captcha-answer" name="captcha-answer" required placeholder="Captcha" tabindex="3">
|
||||
{% endif %}
|
||||
</form>
|
||||
<form id="appearance-form" action="{{ url_for('nojs_submit_appearance', token=user.token) }}" method="post">
|
||||
<form id="appearance-form" action="{{ url_for('nojs_submit_appearance', token=user.token, lang=lang) }}" method="post">
|
||||
<label id="appearance-form__label-name" for="appearance-form__name">{{ locale.form.name }}</label>
|
||||
<input id="appearance-form__name" name="name" value="{{ user.name or '' }}" placeholder="{{ default_name }}" maxlength="{{ max_name_length }}">
|
||||
<input type="color" name="color" value="{{ user.color }}">
|
||||
|
|
|
@ -3,6 +3,12 @@ import types
|
|||
SPEC = {
|
||||
'anonstream': {
|
||||
'error': {
|
||||
'invalid_token': str,
|
||||
'captcha': str,
|
||||
'captcha_again': str,
|
||||
'impostor': str,
|
||||
'broadcaster_should_log_in': str,
|
||||
'wrong_username_or_password': str,
|
||||
'blacklisted': str,
|
||||
'not_whitelisted': str,
|
||||
'offline': str,
|
||||
|
@ -32,6 +38,9 @@ SPEC = {
|
|||
'click_for_a_new_captcha': str,
|
||||
},
|
||||
'home': {
|
||||
'info': str,
|
||||
'chat': str,
|
||||
'both': str,
|
||||
'source': str,
|
||||
'users': str,
|
||||
'users_in_chat': str,
|
||||
|
|
|
@ -2,7 +2,7 @@ secret_key = "place secret key here"
|
|||
|
||||
[locale]
|
||||
default = "en"
|
||||
offered = ["en"]
|
||||
offered = ["en", "de"]
|
||||
directory = "l10n/"
|
||||
|
||||
[socket.control]
|
||||
|
@ -31,6 +31,7 @@ file_cache_lifetime = 0.5
|
|||
|
||||
[access]
|
||||
captcha = true
|
||||
hide_offered_locales = 0 #"don't" "from-new" "from-everyone"
|
||||
|
||||
[captcha]
|
||||
lifetime = 1800
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"anonstream": {
|
||||
"error": {
|
||||
"invalid_token": "invalid_token%s",
|
||||
"captcha": "captcha%s%s",
|
||||
"captcha_again": "captcha_again%s%s",
|
||||
"impostor": "impostor%s%s",
|
||||
"broadcaster_should_log_in": "broadcaster_should_log_in",
|
||||
"wrong_username_or_password": "wrong_username_or_password%s",
|
||||
"blacklisted": "Du wurdest ge-blacklist-et.",
|
||||
"not_whitelisted": "Du wurdest nicht ge-whitelist-et.",
|
||||
"offline": "Der Stream ist offline.",
|
||||
"ratelimit": "Du hast den Stream bereits vor kurzem angefragt. Versuche es erneut in %.1f Sekunden.",
|
||||
"limit": "Du hast eine Stream-Verbindung %d mal gleichzeitig angefragt. Beende eine Verbindung bevor du eine neue Anfrage versuchst."
|
||||
},
|
||||
"internal": {
|
||||
"captcha_required": "Captcha benötigt",
|
||||
"captcha_incorrect": "Falsches Captcha",
|
||||
"captcha_expired": "Captcha abgelaufen",
|
||||
"message_ratelimited": "Chat-Überlastung in den letzten %.0f Sekunden",
|
||||
"message_suspected_duplicate": "Verworfen, Duplikat vermutet",
|
||||
"message_empty": "Die Nachricht war leer",
|
||||
"message_practically_empty": "Die Nachricht war praktisch leer",
|
||||
"message_too_long": "Nachricht hat %d Zeichen überschritten",
|
||||
"message_too_many_lines": "Nachricht hat %d Zeilen überschritten",
|
||||
"message_too_many_apparent_lines": "Nachricht würde %d oder mehr Zeilen umfassen",
|
||||
"appearance_changed": "Aussehen geändert",
|
||||
"name_empty": "Namensfeld war leer",
|
||||
"name_too_long": "Name überschreitet %d Zeichen",
|
||||
"colour_invalid_css": "Ungültige CSS-Farbe",
|
||||
"colour_insufficient_contrast": "Farbe hat nicht ausreichend Kontrast: %s",
|
||||
"password_too_long": "Passwort überschreitet %d Zeichen"
|
||||
},
|
||||
"captcha": {
|
||||
"captcha_failed_to_load": "Captcha konnte nicht geladen werden",
|
||||
"click_for_a_new_captcha": "Klick für ein neues Captcha"
|
||||
},
|
||||
"home": {
|
||||
"info": "Info",
|
||||
"chat": "Chat",
|
||||
"both": "Beide",
|
||||
"users": "Benutzer",
|
||||
"stream_chat": "Stream-Chat",
|
||||
"users_in_chat": "Benutzer im Chat",
|
||||
"source": "Quelltext"
|
||||
},
|
||||
"stream": {
|
||||
"offline": "[offline]"
|
||||
},
|
||||
"info": {
|
||||
"viewers": "%d Zuschauer",
|
||||
"uptime": "Zeit:",
|
||||
"reload_stream": "Stream neu laden"
|
||||
},
|
||||
"chat": {
|
||||
"users": "Benutzer",
|
||||
"click_to_refresh": "Klicken zum aktualisieren",
|
||||
"hide_timeout_notice": "Auszeitnachricht ausblenden",
|
||||
"watching": "Zuschauer (%d)",
|
||||
"not_watching": "Inaktive Zuschauer (%d)",
|
||||
"you": " (Du)",
|
||||
"timed_out": "Zeitüberschreitung"
|
||||
},
|
||||
"form": {
|
||||
"click_to_dismiss": "Klicken zum Ausblenden",
|
||||
"send_a_message": "Schreib eine Nachricht...",
|
||||
"captcha": "Captcha",
|
||||
"settings": "Einstellungen",
|
||||
"captcha_failed_to_load": "Captcha konnte nicht geladen werden",
|
||||
"click_for_a_new_captcha": "Klick für ein neues Captcha",
|
||||
"chat": "Chat",
|
||||
"name": "Name:",
|
||||
"tripcode": "Tripcode:",
|
||||
"no_tripcode": "(kein Tripcode)",
|
||||
"set": "setzen",
|
||||
"cleared": "(geleert)",
|
||||
"undo": "zurück",
|
||||
"tripcode_password": "(Tripcode-Passwort)",
|
||||
"return_to_chat": "Zurück zum Chat",
|
||||
"update": "Aktualisieren"
|
||||
},
|
||||
"js": {
|
||||
"offline": "[offline]",
|
||||
"reload_stream": "Stream neuladen",
|
||||
"chat_scroll_paused": "Chat-Rollen pausiert. Klick zum Wiederaufnehmen.",
|
||||
"not_connected": "Nicht mit dem Chat verbunden",
|
||||
"broadcaster": "Sender",
|
||||
"loading": "Läd...",
|
||||
"click_for_a_new_captcha": "Klick für ein neues Captcha",
|
||||
"viewers": "{0} Zuschauer",
|
||||
"you": " (Du)",
|
||||
"watching": "Zuschauer ({0})",
|
||||
"not_watching": "Inaktive Zuschauer ({0})",
|
||||
"errors": "Fehler:",
|
||||
"connecting_to_chat": "Baue Verbindung zum Chat auf...",
|
||||
"connected_to_chat": "Verbunden mit dem Chat",
|
||||
"disconnected_from_chat": "Verbindung zum Chat getrennt",
|
||||
"error_connecting_to_chat": "Fehler bei der Verbindung zum Chat",
|
||||
"error_connecting_to_chat_terse": "Fehler"
|
||||
}
|
||||
},
|
||||
"http": {
|
||||
"400": null,
|
||||
"401": null,
|
||||
"403": null,
|
||||
"404": null,
|
||||
"405": null,
|
||||
"410": null,
|
||||
"500": null
|
||||
}
|
||||
}
|
27
l10n/en.json
27
l10n/en.json
|
@ -1,11 +1,17 @@
|
|||
{
|
||||
"anonstream": {
|
||||
"error": {
|
||||
"invalid_token": "Your token contains disallowed characters or is too long. Tokens must match this regular expression: %s",
|
||||
"captcha": "You have not solved the access captcha. %sClick here.%s",
|
||||
"captcha_again": "You must solve the access captcha again because you have been away. %sClick here.%s",
|
||||
"impostor": "You are using the broadcaster's token but you are not logged in. The broadcaster should %sclick here%s and log in with the credentials printed in their terminal when they started anonstream.",
|
||||
"broadcaster_should_log_in": "The broadcaster should log in with the credentials printed in their terminal.",
|
||||
"wrong_username_or_password": "Wrong username or password. Refresh the page to try again. %sThe broadcaster should log in with the credentials printed in their terminal.",
|
||||
"blacklisted": "You have been blacklisted.",
|
||||
"not_whitelisted": "You have not been whitelisted.",
|
||||
"offline": "The stream is offline.",
|
||||
"ratelimit": "You have requested the stream recently. Try again in %.1f seconds.",
|
||||
"limit": "You have made %d concurrent requests or the stream. End one of those before making a new request."
|
||||
"ratelimit": "You have requested the stream recently. Try again in %.1f seconds.",
|
||||
"limit": "You have made %d concurrent requests for the stream. End one of those before making a new request."
|
||||
},
|
||||
"internal": {
|
||||
"captcha_required": "Captcha required",
|
||||
|
@ -30,6 +36,9 @@
|
|||
"click_for_a_new_captcha": "Click for a new captcha"
|
||||
},
|
||||
"home": {
|
||||
"info": "info",
|
||||
"chat": "chat",
|
||||
"both": "both",
|
||||
"users": "Users",
|
||||
"stream_chat": "Stream chat",
|
||||
"users_in_chat": "Users in chat",
|
||||
|
@ -91,12 +100,12 @@
|
|||
}
|
||||
},
|
||||
"http": {
|
||||
"400": null,
|
||||
"401": null,
|
||||
"403": null,
|
||||
"404": null,
|
||||
"405": null,
|
||||
"410": null,
|
||||
"500": null
|
||||
"400": "Bad Request",
|
||||
"401": "Unauthorized",
|
||||
"403": "Forbidden",
|
||||
"404": "Not Found",
|
||||
"405": "Method Not Allowed",
|
||||
"410": "Gone",
|
||||
"500": "Internal Server Error"
|
||||
}
|
||||
}
|
||||
|
|
読み込み中…
新しいイシューから参照