Compare commits
18 Commits
fix/issue1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
488dc9a3f3 | ||
|
|
1a65ba6150 | ||
|
|
9474238dee | ||
|
|
60e663316b | ||
|
|
770d05c72c | ||
|
|
ea75d8e832 | ||
|
|
145dda8c19 | ||
|
|
149a79cb2c | ||
|
|
69f77020b8 | ||
|
|
5ac481a4b4 | ||
|
|
56470eec01 | ||
|
|
9b4903b230 | ||
|
|
74c3946609 | ||
|
|
feabed6565 | ||
|
|
af13a3cc47 | ||
|
|
466e3e79b7 | ||
|
|
3f1ce7565b | ||
|
|
265ca4db8f |
2
.github/workflows/main.yaml
vendored
@@ -48,7 +48,7 @@ jobs:
|
|||||||
pip install flake8 flake8-print
|
pip install flake8 flake8-print
|
||||||
- name: Linting
|
- name: Linting
|
||||||
run: |
|
run: |
|
||||||
python -m flake8 snikket_web
|
make flake8
|
||||||
|
|
||||||
translation-check:
|
translation-check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
18
Makefile
@@ -5,6 +5,8 @@ generated_css_files = $(patsubst snikket_web/scss/%.scss,snikket_web/static/css/
|
|||||||
translation_basepath = snikket_web/translations
|
translation_basepath = snikket_web/translations
|
||||||
pot_file = $(translation_basepath)/messages.pot
|
pot_file = $(translation_basepath)/messages.pot
|
||||||
|
|
||||||
|
black_formatted_py = snikket_web/prosodyclient.py
|
||||||
|
|
||||||
PYTHON3 ?= python3
|
PYTHON3 ?= python3
|
||||||
SCSSC ?= sassc --load-path snikket_web/scss/
|
SCSSC ?= sassc --load-path snikket_web/scss/
|
||||||
|
|
||||||
@@ -34,4 +36,20 @@ compile_translations:
|
|||||||
-pybabel compile -d $(translation_basepath)
|
-pybabel compile -d $(translation_basepath)
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint: format flake8
|
||||||
|
|
||||||
|
.PHONY: format
|
||||||
|
format:
|
||||||
|
$(PYTHON3) -m black $(black_formatted_py)
|
||||||
|
|
||||||
|
.PHONY: flake8
|
||||||
|
flake8:
|
||||||
|
$(PYTHON3) -m flake8 --exclude=$(subst $(space),$(comma),$(strip $(black_formatted_py))) snikket_web
|
||||||
|
$(PYTHON3) -m flake8 --ignore=E501,W503 $(black_formatted_py)
|
||||||
|
|
||||||
|
.PHONY: mypy
|
||||||
|
mypy:
|
||||||
|
$(PYTHON3) -m mypy --python-version 3.11 snikket_web
|
||||||
|
|
||||||
.PHONY: build_css clean update_translations compile_translations extract_translations force_update_translations
|
.PHONY: build_css clean update_translations compile_translations extract_translations force_update_translations
|
||||||
|
|||||||
@@ -47,10 +47,20 @@ def apple_store_badge() -> str:
|
|||||||
return url_for("static", filename="img/apple/en.svg")
|
return url_for("static", filename="img/apple/en.svg")
|
||||||
|
|
||||||
|
|
||||||
|
def play_store_badge() -> str:
|
||||||
|
locale = selected_locale()
|
||||||
|
filename = "{}_badge_web_generic.png".format(locale)
|
||||||
|
static_path = pathlib.Path(__file__).parent / "static" / "img" / "google"
|
||||||
|
if (static_path / filename).exists():
|
||||||
|
return url_for("static", filename="img/google/{}".format(filename))
|
||||||
|
return url_for("static", filename="img/google/en_badge_web_generic.png")
|
||||||
|
|
||||||
|
|
||||||
@bp.context_processor
|
@bp.context_processor
|
||||||
def context() -> typing.Dict[str, typing.Any]:
|
def context() -> typing.Dict[str, typing.Any]:
|
||||||
return {
|
return {
|
||||||
"apple_store_badge": apple_store_badge,
|
"apple_store_badge": apple_store_badge,
|
||||||
|
"play_store_badge": play_store_badge,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,11 +12,15 @@ import typing_extensions
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
from aiohttp import BasicAuth
|
||||||
|
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from quart import (
|
from quart import (
|
||||||
current_app, session as http_session, abort, redirect,
|
current_app,
|
||||||
|
session as http_session,
|
||||||
|
abort,
|
||||||
|
redirect,
|
||||||
url_for,
|
url_for,
|
||||||
)
|
)
|
||||||
import quart
|
import quart
|
||||||
@@ -25,7 +29,7 @@ from flask import g as _app_ctx_stack
|
|||||||
|
|
||||||
import werkzeug.exceptions
|
import werkzeug.exceptions
|
||||||
|
|
||||||
from . import xmpputil
|
from . import xmpputil, _version
|
||||||
from .xmpputil import split_jid
|
from .xmpputil import split_jid
|
||||||
|
|
||||||
|
|
||||||
@@ -56,14 +60,10 @@ class UserDeletionRequestInfo:
|
|||||||
if data is None:
|
if data is None:
|
||||||
return None
|
return None
|
||||||
return cls(
|
return cls(
|
||||||
deleted_at=datetime.fromtimestamp(
|
deleted_at=datetime.fromtimestamp(data["deleted_at"], tz=timezone.utc),
|
||||||
data["deleted_at"],
|
|
||||||
tz=timezone.utc
|
|
||||||
),
|
|
||||||
pending_until=datetime.fromtimestamp(
|
pending_until=datetime.fromtimestamp(
|
||||||
data["pending_until"],
|
data["pending_until"], tz=timezone.utc
|
||||||
tz=timezone.utc
|
),
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -221,9 +221,8 @@ class AdminGroupInfo:
|
|||||||
name=data["name"],
|
name=data["name"],
|
||||||
members=data.get("members", []),
|
members=data.get("members", []),
|
||||||
chats=[
|
chats=[
|
||||||
AdminGroupChatInfo.from_api_response(x)
|
AdminGroupChatInfo.from_api_response(x) for x in data.get("chats", [])
|
||||||
for x in data.get("chats", [])
|
],
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -252,10 +251,12 @@ class HTTPSessionManager:
|
|||||||
self._app_context_attribute = app_context_attribute
|
self._app_context_attribute = app_context_attribute
|
||||||
|
|
||||||
async def _create(self) -> aiohttp.ClientSession:
|
async def _create(self) -> aiohttp.ClientSession:
|
||||||
return aiohttp.ClientSession(headers={
|
return aiohttp.ClientSession(
|
||||||
|
headers={
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
"Host": current_app.config["SNIKKET_DOMAIN"],
|
"Host": current_app.config["SNIKKET_DOMAIN"],
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
async def teardown(self, exc: typing.Optional[BaseException]) -> None:
|
async def teardown(self, exc: typing.Optional[BaseException]) -> None:
|
||||||
app_ctx = _app_ctx_stack
|
app_ctx = _app_ctx_stack
|
||||||
@@ -298,10 +299,7 @@ class HTTPSessionManager:
|
|||||||
|
|
||||||
|
|
||||||
class HTTPAuthSessionManager(HTTPSessionManager):
|
class HTTPAuthSessionManager(HTTPSessionManager):
|
||||||
def __init__(
|
def __init__(self, app_context_attribute: str, session_token_key: str):
|
||||||
self,
|
|
||||||
app_context_attribute: str,
|
|
||||||
session_token_key: str):
|
|
||||||
super().__init__(app_context_attribute)
|
super().__init__(app_context_attribute)
|
||||||
self._session_token_key = session_token_key
|
self._session_token_key = session_token_key
|
||||||
|
|
||||||
@@ -325,19 +323,20 @@ class AuthSessionProvider(typing_extensions.Protocol):
|
|||||||
|
|
||||||
|
|
||||||
def autosession(
|
def autosession(
|
||||||
f: typing.Callable[..., typing.Coroutine[typing.Any, typing.Any, T]]
|
f: typing.Callable[..., typing.Coroutine[typing.Any, typing.Any, T]],
|
||||||
) -> typing.Callable[...,
|
) -> typing.Callable[..., typing.Coroutine[typing.Any, typing.Any, T]]:
|
||||||
typing.Coroutine[typing.Any, typing.Any, T]]:
|
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
async def wrapper(
|
async def wrapper(
|
||||||
self: AuthSessionProvider,
|
self: AuthSessionProvider,
|
||||||
*args: typing.Any,
|
*args: typing.Any,
|
||||||
session: typing.Optional[aiohttp.ClientSession] = None,
|
session: typing.Optional[aiohttp.ClientSession] = None,
|
||||||
**kwargs: typing.Any) -> T:
|
**kwargs: typing.Any,
|
||||||
|
) -> T:
|
||||||
if session is None:
|
if session is None:
|
||||||
async with self._auth_session as session:
|
async with self._auth_session as session:
|
||||||
return (await f(self, *args, session=session, **kwargs))
|
return await f(self, *args, session=session, **kwargs)
|
||||||
return (await f(self, *args, session=session, **kwargs))
|
return await f(self, *args, session=session, **kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
@@ -352,15 +351,16 @@ class ProsodyClient:
|
|||||||
def __init__(self, app: typing.Optional[quart.Quart] = None):
|
def __init__(self, app: typing.Optional[quart.Quart] = None):
|
||||||
self._default_login_redirect: typing.Optional[str] = None
|
self._default_login_redirect: typing.Optional[str] = None
|
||||||
self._plain_session = HTTPSessionManager(self.CTX_PLAIN_SESSION)
|
self._plain_session = HTTPSessionManager(self.CTX_PLAIN_SESSION)
|
||||||
self._auth_session = HTTPAuthSessionManager(self.CTX_AUTH_SESSION,
|
self._auth_session = HTTPAuthSessionManager(
|
||||||
self.SESSION_TOKEN)
|
self.CTX_AUTH_SESSION, self.SESSION_TOKEN
|
||||||
self.logger = logging.getLogger(
|
|
||||||
".".join([__name__, type(self).__qualname__])
|
|
||||||
)
|
)
|
||||||
|
self.logger = logging.getLogger(".".join([__name__, type(self).__qualname__]))
|
||||||
self.app = app
|
self.app = app
|
||||||
if app is not None:
|
if app is not None:
|
||||||
self.init_app(app)
|
self.init_app(app)
|
||||||
|
|
||||||
|
self._client_info: typing.Optional[typing.Mapping[str, str]] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_login_redirect(self) -> typing.Optional[str]:
|
def default_login_redirect(self) -> typing.Optional[str]:
|
||||||
return self._default_login_redirect
|
return self._default_login_redirect
|
||||||
@@ -390,6 +390,10 @@ class ProsodyClient:
|
|||||||
def _rest_endpoint(self) -> str:
|
def _rest_endpoint(self) -> str:
|
||||||
return "{}/rest".format(self._endpoint_base)
|
return "{}/rest".format(self._endpoint_base)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _register_client_endpoint(self) -> str:
|
||||||
|
return "{}/oauth2/register".format(self._endpoint_base)
|
||||||
|
|
||||||
def _admin_v1_endpoint(self, subpath: str) -> str:
|
def _admin_v1_endpoint(self, subpath: str) -> str:
|
||||||
return "{}/admin_api{}".format(self._endpoint_base, subpath)
|
return "{}/admin_api{}".format(self._endpoint_base, subpath)
|
||||||
|
|
||||||
@@ -399,26 +403,30 @@ class ProsodyClient:
|
|||||||
def _xep227_endpoint(self, subpath: str) -> str:
|
def _xep227_endpoint(self, subpath: str) -> str:
|
||||||
return "{}/xep227{}".format(self._endpoint_base, subpath)
|
return "{}/xep227{}".format(self._endpoint_base, subpath)
|
||||||
|
|
||||||
async def _oauth2_bearer_token(self,
|
async def _oauth2_bearer_token(
|
||||||
session: aiohttp.ClientSession,
|
self, session: aiohttp.ClientSession, jid: str, password: str
|
||||||
jid: str,
|
) -> TokenInfo:
|
||||||
password: str) -> TokenInfo:
|
if not self.is_client_registered():
|
||||||
|
self.logger.debug("registering oauth client...")
|
||||||
|
await self.register_client()
|
||||||
|
self.logger.debug("registered client!")
|
||||||
request = aiohttp.FormData()
|
request = aiohttp.FormData()
|
||||||
request.add_field("grant_type", "password")
|
request.add_field("grant_type", "password")
|
||||||
request.add_field("username", jid)
|
request.add_field("username", jid.split("@")[0])
|
||||||
request.add_field("password", password)
|
request.add_field("password", password)
|
||||||
request.add_field(
|
request.add_field(
|
||||||
"scope",
|
"scope", " ".join([SCOPE_RESTRICTED, SCOPE_DEFAULT, SCOPE_ADMIN])
|
||||||
" ".join([SCOPE_RESTRICTED, SCOPE_DEFAULT, SCOPE_ADMIN])
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.logger.debug("sending OAuth2 request (payload omitted)")
|
self.logger.debug("sending OAuth2 request (payload omitted)")
|
||||||
async with session.post(self._login_endpoint, data=request) as resp:
|
async with session.post(
|
||||||
|
self._login_endpoint, auth=self.get_client_auth(), data=request
|
||||||
|
) as resp:
|
||||||
auth_status = resp.status
|
auth_status = resp.status
|
||||||
auth_info: typing.Mapping[str, str] = (await resp.json())
|
auth_info: typing.Mapping[str, str] = await resp.json()
|
||||||
|
|
||||||
if auth_status in [400, 401]:
|
if auth_status in [400, 401]:
|
||||||
self.logger.debug("oauth2 error: %r", auth_info)
|
self.logger.warning("oauth2 error: %r", auth_info)
|
||||||
# OAuth2 spec says that’s what can happen when some stuff is
|
# OAuth2 spec says that’s what can happen when some stuff is
|
||||||
# wrong.
|
# wrong.
|
||||||
# we have to interpret the JSON further
|
# we have to interpret the JSON further
|
||||||
@@ -430,9 +438,7 @@ class ProsodyClient:
|
|||||||
self.logger.debug("oauth2 success: token_type=%r", token_type)
|
self.logger.debug("oauth2 success: token_type=%r", token_type)
|
||||||
if token_type != "bearer":
|
if token_type != "bearer":
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
"unsupported token type: {!r}".format(
|
"unsupported token type: {!r}".format(auth_info["token_type"])
|
||||||
auth_info["token_type"]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return TokenInfo(
|
return TokenInfo(
|
||||||
token=auth_info["access_token"],
|
token=auth_info["access_token"],
|
||||||
@@ -449,10 +455,61 @@ class ProsodyClient:
|
|||||||
http_session[self.SESSION_TOKEN] = token_info.token
|
http_session[self.SESSION_TOKEN] = token_info.token
|
||||||
http_session[self.SESSION_CACHED_SCOPE] = " ".join(token_info.scopes)
|
http_session[self.SESSION_CACHED_SCOPE] = " ".join(token_info.scopes)
|
||||||
|
|
||||||
|
def is_client_registered(self) -> bool:
|
||||||
|
return self._client_info is not None
|
||||||
|
|
||||||
|
async def register_client(self) -> None:
|
||||||
|
self.logger.debug(
|
||||||
|
"sending OAuth2 client registration request (payload omitted)"
|
||||||
|
)
|
||||||
|
registration_data = {
|
||||||
|
"client_name": "Snikket web portal",
|
||||||
|
"client_uri": "https://{}".format(current_app.config["SNIKKET_DOMAIN"]),
|
||||||
|
# This redirect URI is not used, because we use the password grant type.
|
||||||
|
# However, we're registering it with a sensible value because 1) Prosody
|
||||||
|
# requires us to provide at least one redirect_uri, and 2) if we ever
|
||||||
|
# need it in the future, we won't have to re-register.
|
||||||
|
"redirect_uris": [
|
||||||
|
"https://{}/login_result".format(current_app.config["SNIKKET_DOMAIN"])
|
||||||
|
],
|
||||||
|
"application_type": "web",
|
||||||
|
"grant_types": ["password"],
|
||||||
|
"response_types": [],
|
||||||
|
"token_endpoint_auth_method": "client_secret_post",
|
||||||
|
"scope": " ".join([SCOPE_RESTRICTED, SCOPE_DEFAULT, SCOPE_ADMIN]),
|
||||||
|
"software_id": "22aa246e-4373-51cb-bcaa-9f73bb235b84", # web-portal.snikket.org
|
||||||
|
"software_version": _version.version,
|
||||||
|
}
|
||||||
|
async with self._plain_session as session:
|
||||||
|
async with session.post(
|
||||||
|
self._register_client_endpoint, json=registration_data
|
||||||
|
) as resp:
|
||||||
|
reg_status = resp.status
|
||||||
|
|
||||||
|
if reg_status != 201:
|
||||||
|
raise RuntimeError(
|
||||||
|
"Failed to register with backend server: ({}): {}",
|
||||||
|
reg_status,
|
||||||
|
await resp.text(),
|
||||||
|
)
|
||||||
|
|
||||||
|
self._client_info = await resp.json()
|
||||||
|
|
||||||
|
def get_client_auth(self) -> BasicAuth:
|
||||||
|
if self._client_info is None:
|
||||||
|
raise RuntimeError("Client is not registered")
|
||||||
|
|
||||||
|
return BasicAuth(
|
||||||
|
login=self._client_info["client_id"],
|
||||||
|
password=self._client_info["client_secret"],
|
||||||
|
)
|
||||||
|
|
||||||
async def login(self, jid: str, password: str) -> bool:
|
async def login(self, jid: str, password: str) -> bool:
|
||||||
async with self._plain_session as session:
|
async with self._plain_session as session:
|
||||||
token_info = await self._oauth2_bearer_token(
|
token_info = await self._oauth2_bearer_token(
|
||||||
session, jid, password,
|
session,
|
||||||
|
jid,
|
||||||
|
password,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._store_token_in_session(token_info)
|
self._store_token_in_session(token_info)
|
||||||
@@ -485,12 +542,15 @@ class ProsodyClient:
|
|||||||
redirect_to: typing.Optional[str] = None,
|
redirect_to: typing.Optional[str] = None,
|
||||||
) -> typing.Callable[
|
) -> typing.Callable[
|
||||||
[typing.Callable[..., typing.Awaitable[T]]],
|
[typing.Callable[..., typing.Awaitable[T]]],
|
||||||
typing.Callable[..., typing.Awaitable[
|
typing.Callable[
|
||||||
typing.Union[T, quart.Response, werkzeug.Response]]]]:
|
..., typing.Awaitable[typing.Union[T, quart.Response, werkzeug.Response]]
|
||||||
|
],
|
||||||
|
]:
|
||||||
def decorator(
|
def decorator(
|
||||||
f: typing.Callable[..., typing.Awaitable[T]],
|
f: typing.Callable[..., typing.Awaitable[T]],
|
||||||
) -> typing.Callable[..., typing.Awaitable[
|
) -> typing.Callable[
|
||||||
typing.Union[T, quart.Response, werkzeug.Response]]]:
|
..., typing.Awaitable[typing.Union[T, quart.Response, werkzeug.Response]]
|
||||||
|
]:
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
async def wrapped(
|
async def wrapped(
|
||||||
*args: typing.Any,
|
*args: typing.Any,
|
||||||
@@ -499,14 +559,17 @@ class ProsodyClient:
|
|||||||
if not self.has_session or not (await self.test_session()):
|
if not self.has_session or not (await self.test_session()):
|
||||||
redirect_to_value = redirect_to
|
redirect_to_value = redirect_to
|
||||||
if redirect_to_value is not False:
|
if redirect_to_value is not False:
|
||||||
redirect_to_value = \
|
redirect_to_value = (
|
||||||
redirect_to_value or self._default_login_redirect
|
redirect_to_value or self._default_login_redirect
|
||||||
|
)
|
||||||
if not redirect_to_value:
|
if not redirect_to_value:
|
||||||
raise abort(401, "Not Authorized")
|
raise abort(401, "Not Authorized")
|
||||||
return redirect(url_for(redirect_to_value))
|
return redirect(url_for(redirect_to_value))
|
||||||
|
|
||||||
return await f(*args, **kwargs)
|
return await f(*args, **kwargs)
|
||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
def require_admin_session(
|
def require_admin_session(
|
||||||
@@ -514,12 +577,15 @@ class ProsodyClient:
|
|||||||
redirect_to: typing.Optional[str] = None,
|
redirect_to: typing.Optional[str] = None,
|
||||||
) -> typing.Callable[
|
) -> typing.Callable[
|
||||||
[typing.Callable[..., typing.Awaitable[T]]],
|
[typing.Callable[..., typing.Awaitable[T]]],
|
||||||
typing.Callable[..., typing.Awaitable[
|
typing.Callable[
|
||||||
typing.Union[T, quart.Response, werkzeug.Response]]]]:
|
..., typing.Awaitable[typing.Union[T, quart.Response, werkzeug.Response]]
|
||||||
|
],
|
||||||
|
]:
|
||||||
def decorator(
|
def decorator(
|
||||||
f: typing.Callable[..., typing.Awaitable[T]],
|
f: typing.Callable[..., typing.Awaitable[T]],
|
||||||
) -> typing.Callable[..., typing.Awaitable[
|
) -> typing.Callable[
|
||||||
typing.Union[T, quart.Response, werkzeug.Response]]]:
|
..., typing.Awaitable[typing.Union[T, quart.Response, werkzeug.Response]]
|
||||||
|
]:
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
@self.require_session(redirect_to=redirect_to)
|
@self.require_session(redirect_to=redirect_to)
|
||||||
async def wrapped(
|
async def wrapped(
|
||||||
@@ -530,7 +596,9 @@ class ProsodyClient:
|
|||||||
raise abort(403, "This is not for you.")
|
raise abort(403, "This is not for you.")
|
||||||
|
|
||||||
return await f(*args, **kwargs)
|
return await f(*args, **kwargs)
|
||||||
|
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
async def _xml_iq_call(
|
async def _xml_iq_call(
|
||||||
@@ -544,10 +612,12 @@ class ProsodyClient:
|
|||||||
final_headers: typing.MutableMapping[str, str] = {}
|
final_headers: typing.MutableMapping[str, str] = {}
|
||||||
if headers is not None:
|
if headers is not None:
|
||||||
final_headers.update(headers)
|
final_headers.update(headers)
|
||||||
final_headers.update({
|
final_headers.update(
|
||||||
|
{
|
||||||
"Content-Type": "application/xmpp+xml",
|
"Content-Type": "application/xmpp+xml",
|
||||||
"Accept": "application/xmpp+xml",
|
"Accept": "application/xmpp+xml",
|
||||||
})
|
}
|
||||||
|
)
|
||||||
if not payload.get("id"):
|
if not payload.get("id"):
|
||||||
payload.set("id", secrets.token_hex(8))
|
payload.set("id", secrets.token_hex(8))
|
||||||
|
|
||||||
@@ -555,15 +625,15 @@ class ProsodyClient:
|
|||||||
id_ = payload.get("id")
|
id_ = payload.get("id")
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
"sending IQ (id=%s): %r",
|
"sending IQ (id=%s): %r",
|
||||||
id_, "(sensitive)" if sensitive else serialised,
|
id_,
|
||||||
|
"(sensitive)" if sensitive else serialised,
|
||||||
)
|
)
|
||||||
async with session.post(self._rest_endpoint,
|
async with session.post(
|
||||||
headers=final_headers,
|
self._rest_endpoint, headers=final_headers, data=serialised
|
||||||
data=serialised) as resp:
|
) as resp:
|
||||||
if resp.status != 200:
|
if resp.status != 200:
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
"IQ HTTP response (in-reply-to id=%s) with non-OK status "
|
"IQ HTTP response (in-reply-to id=%s) with non-OK status " "%s: %s",
|
||||||
"%s: %s",
|
|
||||||
id_,
|
id_,
|
||||||
resp.status,
|
resp.status,
|
||||||
resp.reason,
|
resp.reason,
|
||||||
@@ -572,7 +642,8 @@ class ProsodyClient:
|
|||||||
reply_payload = await resp.read()
|
reply_payload = await resp.read()
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
"received IQ (in-reply-to id=%s): %r",
|
"received IQ (in-reply-to id=%s): %r",
|
||||||
id_, "(sensitive)" if sensitive else reply_payload,
|
id_,
|
||||||
|
"(sensitive)" if sensitive else reply_payload,
|
||||||
)
|
)
|
||||||
return ET.fromstring(reply_payload)
|
return ET.fromstring(reply_payload)
|
||||||
|
|
||||||
@@ -633,9 +704,9 @@ class ProsodyClient:
|
|||||||
return (await resp.json())["version"]["version"]
|
return (await resp.json())["version"]["version"]
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
"failed to parse prosody version from response"
|
"failed to parse prosody version from response" " (%s: %s)",
|
||||||
" (%s: %s)",
|
type(exc),
|
||||||
type(exc), exc,
|
exc,
|
||||||
)
|
)
|
||||||
return "unknown"
|
return "unknown"
|
||||||
|
|
||||||
@@ -646,8 +717,7 @@ class ProsodyClient:
|
|||||||
session: aiohttp.ClientSession,
|
session: aiohttp.ClientSession,
|
||||||
) -> typing.Optional[str]:
|
) -> typing.Optional[str]:
|
||||||
iq_resp = await self._xml_iq_call(
|
iq_resp = await self._xml_iq_call(
|
||||||
session,
|
session, xmpputil.make_nickname_get_request(self.session_address)
|
||||||
xmpputil.make_nickname_get_request(self.session_address)
|
|
||||||
)
|
)
|
||||||
return xmpputil.extract_nickname_get_reply(iq_resp)
|
return xmpputil.extract_nickname_get_reply(iq_resp)
|
||||||
|
|
||||||
@@ -660,8 +730,7 @@ class ProsodyClient:
|
|||||||
) -> None:
|
) -> None:
|
||||||
iq_resp = await self._xml_iq_call(
|
iq_resp = await self._xml_iq_call(
|
||||||
session,
|
session,
|
||||||
xmpputil.make_nickname_set_request(self.session_address,
|
xmpputil.make_nickname_set_request(self.session_address, new_nickname),
|
||||||
new_nickname)
|
|
||||||
)
|
)
|
||||||
# just to throw errors
|
# just to throw errors
|
||||||
xmpputil.extract_iq_reply(iq_resp)
|
xmpputil.extract_iq_reply(iq_resp)
|
||||||
@@ -675,8 +744,7 @@ class ProsodyClient:
|
|||||||
session: aiohttp.ClientSession,
|
session: aiohttp.ClientSession,
|
||||||
) -> typing.Mapping:
|
) -> typing.Mapping:
|
||||||
metadata_resp = await self._xml_iq_call(
|
metadata_resp = await self._xml_iq_call(
|
||||||
session,
|
session, xmpputil.make_avatar_metadata_request(from_)
|
||||||
xmpputil.make_avatar_metadata_request(from_)
|
|
||||||
)
|
)
|
||||||
info = xmpputil.extract_avatar_metadata_get_reply(metadata_resp)
|
info = xmpputil.extract_avatar_metadata_get_reply(metadata_resp)
|
||||||
if info is None:
|
if info is None:
|
||||||
@@ -684,7 +752,8 @@ class ProsodyClient:
|
|||||||
|
|
||||||
if not metadata_only:
|
if not metadata_only:
|
||||||
info["data"] = await self.get_avatar_data(
|
info["data"] = await self.get_avatar_data(
|
||||||
from_, info["sha1"],
|
from_,
|
||||||
|
info["sha1"],
|
||||||
session=session,
|
session=session,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -699,19 +768,14 @@ class ProsodyClient:
|
|||||||
session: aiohttp.ClientSession,
|
session: aiohttp.ClientSession,
|
||||||
) -> typing.Optional[bytes]:
|
) -> typing.Optional[bytes]:
|
||||||
data_resp = await self._xml_iq_call(
|
data_resp = await self._xml_iq_call(
|
||||||
session,
|
session, xmpputil.make_avatar_data_request(from_, id_)
|
||||||
xmpputil.make_avatar_data_request(from_, id_)
|
|
||||||
)
|
)
|
||||||
return xmpputil.extract_avatar_data_get_reply(data_resp)
|
return xmpputil.extract_avatar_data_get_reply(data_resp)
|
||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def get_pubsub_node_access_model(
|
async def get_pubsub_node_access_model(
|
||||||
self,
|
self, to: str, node: str, default: str, *, session: aiohttp.ClientSession
|
||||||
to: str,
|
) -> str:
|
||||||
node: str,
|
|
||||||
default: str,
|
|
||||||
*,
|
|
||||||
session: aiohttp.ClientSession) -> str:
|
|
||||||
config = xmpputil.extract_pubsub_node_config_get_reply(
|
config = xmpputil.extract_pubsub_node_config_get_reply(
|
||||||
await self._xml_iq_call(
|
await self._xml_iq_call(
|
||||||
session,
|
session,
|
||||||
@@ -734,26 +798,26 @@ class ProsodyClient:
|
|||||||
new_access_model: str,
|
new_access_model: str,
|
||||||
*,
|
*,
|
||||||
ignore_not_found: bool = False,
|
ignore_not_found: bool = False,
|
||||||
session: aiohttp.ClientSession) -> None:
|
session: aiohttp.ClientSession,
|
||||||
|
) -> None:
|
||||||
try:
|
try:
|
||||||
xmpputil.extract_iq_reply(await self._xml_iq_call(
|
xmpputil.extract_iq_reply(
|
||||||
|
await self._xml_iq_call(
|
||||||
session,
|
session,
|
||||||
xmpputil.make_pubsub_access_model_put_request(
|
xmpputil.make_pubsub_access_model_put_request(
|
||||||
to,
|
to,
|
||||||
node,
|
node,
|
||||||
new_access_model,
|
new_access_model,
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
))
|
|
||||||
except werkzeug.exceptions.NotFound:
|
except werkzeug.exceptions.NotFound:
|
||||||
if ignore_not_found:
|
if ignore_not_found:
|
||||||
return
|
return
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def get_nickname_access_model(
|
async def get_nickname_access_model(self, *, session: aiohttp.ClientSession) -> str:
|
||||||
self,
|
|
||||||
*,
|
|
||||||
session: aiohttp.ClientSession) -> str:
|
|
||||||
return await self.get_pubsub_node_access_model(
|
return await self.get_pubsub_node_access_model(
|
||||||
self.session_address,
|
self.session_address,
|
||||||
xmpputil.NODE_USER_NICKNAME,
|
xmpputil.NODE_USER_NICKNAME,
|
||||||
@@ -763,10 +827,8 @@ class ProsodyClient:
|
|||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def set_nickname_access_model(
|
async def set_nickname_access_model(
|
||||||
self,
|
self, new_access_model: str, *, session: aiohttp.ClientSession
|
||||||
new_access_model: str,
|
) -> None:
|
||||||
*,
|
|
||||||
session: aiohttp.ClientSession) -> None:
|
|
||||||
await self.set_pubsub_node_access_model(
|
await self.set_pubsub_node_access_model(
|
||||||
self.session_address,
|
self.session_address,
|
||||||
xmpputil.NODE_USER_NICKNAME,
|
xmpputil.NODE_USER_NICKNAME,
|
||||||
@@ -776,10 +838,7 @@ class ProsodyClient:
|
|||||||
)
|
)
|
||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def get_avatar_access_model(
|
async def get_avatar_access_model(self, *, session: aiohttp.ClientSession) -> str:
|
||||||
self,
|
|
||||||
*,
|
|
||||||
session: aiohttp.ClientSession) -> str:
|
|
||||||
return await self.get_pubsub_node_access_model(
|
return await self.get_pubsub_node_access_model(
|
||||||
self.session_address,
|
self.session_address,
|
||||||
xmpputil.NODE_USER_AVATAR_METADATA,
|
xmpputil.NODE_USER_AVATAR_METADATA,
|
||||||
@@ -789,10 +848,8 @@ class ProsodyClient:
|
|||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def set_avatar_access_model(
|
async def set_avatar_access_model(
|
||||||
self,
|
self, new_access_model: str, *, session: aiohttp.ClientSession
|
||||||
new_access_model: str,
|
) -> None:
|
||||||
*,
|
|
||||||
session: aiohttp.ClientSession) -> None:
|
|
||||||
await asyncio.gather(
|
await asyncio.gather(
|
||||||
self.set_pubsub_node_access_model(
|
self.set_pubsub_node_access_model(
|
||||||
self.session_address,
|
self.session_address,
|
||||||
@@ -807,14 +864,11 @@ class ProsodyClient:
|
|||||||
new_access_model,
|
new_access_model,
|
||||||
ignore_not_found=True,
|
ignore_not_found=True,
|
||||||
session=session,
|
session=session,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def get_vcard_access_model(
|
async def get_vcard_access_model(self, *, session: aiohttp.ClientSession) -> str:
|
||||||
self,
|
|
||||||
*,
|
|
||||||
session: aiohttp.ClientSession) -> str:
|
|
||||||
return await self.get_pubsub_node_access_model(
|
return await self.get_pubsub_node_access_model(
|
||||||
self.session_address,
|
self.session_address,
|
||||||
xmpputil.NODE_VCARD,
|
xmpputil.NODE_VCARD,
|
||||||
@@ -824,10 +878,8 @@ class ProsodyClient:
|
|||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def set_vcard_access_model(
|
async def set_vcard_access_model(
|
||||||
self,
|
self, new_access_model: str, *, session: aiohttp.ClientSession
|
||||||
new_access_model: str,
|
) -> None:
|
||||||
*,
|
|
||||||
session: aiohttp.ClientSession) -> None:
|
|
||||||
await self.set_pubsub_node_access_model(
|
await self.set_pubsub_node_access_model(
|
||||||
self.session_address,
|
self.session_address,
|
||||||
xmpputil.NODE_VCARD,
|
xmpputil.NODE_VCARD,
|
||||||
@@ -848,9 +900,7 @@ class ProsodyClient:
|
|||||||
|
|
||||||
data_resp = await self._xml_iq_call(
|
data_resp = await self._xml_iq_call(
|
||||||
session,
|
session,
|
||||||
xmpputil.make_avatar_data_set_request(self.session_address,
|
xmpputil.make_avatar_data_set_request(self.session_address, data, id_),
|
||||||
data,
|
|
||||||
id_)
|
|
||||||
)
|
)
|
||||||
xmpputil.extract_iq_reply(data_resp)
|
xmpputil.extract_iq_reply(data_resp)
|
||||||
|
|
||||||
@@ -863,7 +913,7 @@ class ProsodyClient:
|
|||||||
size=len(data),
|
size=len(data),
|
||||||
width=None,
|
width=None,
|
||||||
height=None,
|
height=None,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
xmpputil.extract_iq_reply(metadata_resp)
|
xmpputil.extract_iq_reply(metadata_resp)
|
||||||
|
|
||||||
@@ -880,7 +930,7 @@ class ProsodyClient:
|
|||||||
self.get_nickname_access_model(session=session),
|
self.get_nickname_access_model(session=session),
|
||||||
self.get_vcard_access_model(session=session),
|
self.get_vcard_access_model(session=session),
|
||||||
return_exceptions=True,
|
return_exceptions=True,
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
order = [
|
order = [
|
||||||
@@ -921,8 +971,7 @@ class ProsodyClient:
|
|||||||
password_changed = await self._xml_iq_call(
|
password_changed = await self._xml_iq_call(
|
||||||
session,
|
session,
|
||||||
xmpputil.make_password_change_request(
|
xmpputil.make_password_change_request(
|
||||||
self.session_address,
|
self.session_address, new_password
|
||||||
new_password
|
|
||||||
),
|
),
|
||||||
headers={
|
headers={
|
||||||
"Authorization": "Bearer {}".format(token_info.token),
|
"Authorization": "Bearer {}".format(token_info.token),
|
||||||
@@ -1057,8 +1106,7 @@ class ProsodyClient:
|
|||||||
) -> typing.Collection[AdminInviteInfo]:
|
) -> typing.Collection[AdminInviteInfo]:
|
||||||
async with session.get(self._admin_v1_endpoint("/invites")) as resp:
|
async with session.get(self._admin_v1_endpoint("/invites")) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
return list(map(AdminInviteInfo.from_api_response,
|
return list(map(AdminInviteInfo.from_api_response, await resp.json()))
|
||||||
await resp.json()))
|
|
||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def get_invite_by_id(
|
async def get_invite_by_id(
|
||||||
@@ -1107,8 +1155,8 @@ class ProsodyClient:
|
|||||||
payload["note"] = note
|
payload["note"] = note
|
||||||
|
|
||||||
async with session.post(
|
async with session.post(
|
||||||
self._admin_v1_endpoint("/invites/account"),
|
self._admin_v1_endpoint("/invites/account"), json=payload
|
||||||
json=payload) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
return AdminInviteInfo.from_api_response(await resp.json())
|
return AdminInviteInfo.from_api_response(await resp.json())
|
||||||
|
|
||||||
@@ -1132,8 +1180,8 @@ class ProsodyClient:
|
|||||||
payload["note"] = note
|
payload["note"] = note
|
||||||
|
|
||||||
async with session.post(
|
async with session.post(
|
||||||
self._admin_v1_endpoint("/invites/group"),
|
self._admin_v1_endpoint("/invites/group"), json=payload
|
||||||
json=payload) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
return AdminInviteInfo.from_api_response(await resp.json())
|
return AdminInviteInfo.from_api_response(await resp.json())
|
||||||
|
|
||||||
@@ -1152,8 +1200,8 @@ class ProsodyClient:
|
|||||||
payload["ttl"] = ttl
|
payload["ttl"] = ttl
|
||||||
|
|
||||||
async with session.post(
|
async with session.post(
|
||||||
self._admin_v1_endpoint("/invites/reset"),
|
self._admin_v1_endpoint("/invites/reset"), json=payload
|
||||||
json=payload) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
return AdminInviteInfo.from_api_response(await resp.json())
|
return AdminInviteInfo.from_api_response(await resp.json())
|
||||||
|
|
||||||
@@ -1171,8 +1219,8 @@ class ProsodyClient:
|
|||||||
}
|
}
|
||||||
|
|
||||||
async with session.post(
|
async with session.post(
|
||||||
self._admin_v1_endpoint("/groups"),
|
self._admin_v1_endpoint("/groups"), json=payload
|
||||||
json=payload) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
return AdminGroupInfo.from_api_response(await resp.json())
|
return AdminGroupInfo.from_api_response(await resp.json())
|
||||||
|
|
||||||
@@ -1184,10 +1232,12 @@ class ProsodyClient:
|
|||||||
) -> typing.Collection[AdminGroupInfo]:
|
) -> typing.Collection[AdminGroupInfo]:
|
||||||
async with session.get(self._admin_v1_endpoint("/groups")) as resp:
|
async with session.get(self._admin_v1_endpoint("/groups")) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
return list(map(
|
return list(
|
||||||
|
map(
|
||||||
AdminGroupInfo.from_api_response,
|
AdminGroupInfo.from_api_response,
|
||||||
await resp.json(),
|
await resp.json(),
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def get_group_by_id(
|
async def get_group_by_id(
|
||||||
@@ -1215,9 +1265,7 @@ class ProsodyClient:
|
|||||||
payload["name"] = new_name
|
payload["name"] = new_name
|
||||||
|
|
||||||
async with session.put(
|
async with session.put(
|
||||||
self._admin_v1_endpoint(
|
self._admin_v1_endpoint("/groups/{}".format(id_)),
|
||||||
"/groups/{}".format(id_)
|
|
||||||
),
|
|
||||||
json=payload,
|
json=payload,
|
||||||
) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
@@ -1231,9 +1279,7 @@ class ProsodyClient:
|
|||||||
session: aiohttp.ClientSession,
|
session: aiohttp.ClientSession,
|
||||||
) -> None:
|
) -> None:
|
||||||
async with session.put(
|
async with session.put(
|
||||||
self._admin_v1_endpoint(
|
self._admin_v1_endpoint("/groups/{}/members/{}".format(id_, localpart)),
|
||||||
"/groups/{}/members/{}".format(id_, localpart)
|
|
||||||
),
|
|
||||||
) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
|
|
||||||
@@ -1251,9 +1297,7 @@ class ProsodyClient:
|
|||||||
}
|
}
|
||||||
|
|
||||||
async with session.post(
|
async with session.post(
|
||||||
self._admin_v1_endpoint(
|
self._admin_v1_endpoint("/groups/{}/chats".format(id_)),
|
||||||
"/groups/{}/chats".format(id_)
|
|
||||||
),
|
|
||||||
json=payload,
|
json=payload,
|
||||||
) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
@@ -1267,9 +1311,7 @@ class ProsodyClient:
|
|||||||
session: aiohttp.ClientSession,
|
session: aiohttp.ClientSession,
|
||||||
) -> None:
|
) -> None:
|
||||||
async with session.delete(
|
async with session.delete(
|
||||||
self._admin_v1_endpoint(
|
self._admin_v1_endpoint("/groups/{}/members/{}".format(id_, localpart)),
|
||||||
"/groups/{}/members/{}".format(id_, localpart)
|
|
||||||
),
|
|
||||||
) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
|
|
||||||
@@ -1282,9 +1324,7 @@ class ProsodyClient:
|
|||||||
session: aiohttp.ClientSession,
|
session: aiohttp.ClientSession,
|
||||||
) -> None:
|
) -> None:
|
||||||
async with session.delete(
|
async with session.delete(
|
||||||
self._admin_v1_endpoint(
|
self._admin_v1_endpoint("/groups/{}/chats/{}".format(group_id, chat_id)),
|
||||||
"/groups/{}/chats/{}".format(group_id, chat_id)
|
|
||||||
),
|
|
||||||
) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
|
|
||||||
@@ -1307,7 +1347,9 @@ class ProsodyClient:
|
|||||||
session: aiohttp.ClientSession,
|
session: aiohttp.ClientSession,
|
||||||
) -> typing.Optional[str]:
|
) -> typing.Optional[str]:
|
||||||
async with session.get(
|
async with session.get(
|
||||||
self._xep227_endpoint("/export?stores=roster,vcard,pep,pep_data"), # noqa:E501
|
self._xep227_endpoint(
|
||||||
|
"/export?stores=roster,vcard,pep,pep_data"
|
||||||
|
), # noqa:E501
|
||||||
) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
if resp.status == 204:
|
if resp.status == 204:
|
||||||
@@ -1322,16 +1364,15 @@ class ProsodyClient:
|
|||||||
session: aiohttp.ClientSession,
|
session: aiohttp.ClientSession,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
async with session.put(
|
async with session.put(
|
||||||
self._xep227_endpoint("/import?stores=roster,vcard,pep,pep_data"), # noqa:E501
|
self._xep227_endpoint(
|
||||||
|
"/import?stores=roster,vcard,pep,pep_data"
|
||||||
|
), # noqa:E501
|
||||||
data=user_xml,
|
data=user_xml,
|
||||||
) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def revoke_token(
|
async def revoke_token(self, *, session: aiohttp.ClientSession) -> None:
|
||||||
self,
|
|
||||||
*,
|
|
||||||
session: aiohttp.ClientSession) -> None:
|
|
||||||
request = aiohttp.FormData()
|
request = aiohttp.FormData()
|
||||||
request.add_field("token", self.session_token)
|
request.add_field("token", self.session_token)
|
||||||
request.add_field("token_type_hint", "access_token")
|
request.add_field("token_type_hint", "access_token")
|
||||||
@@ -1344,8 +1385,7 @@ class ProsodyClient:
|
|||||||
async with self._plain_session as session:
|
async with self._plain_session as session:
|
||||||
await self.revoke_token(session=session)
|
await self.revoke_token(session=session)
|
||||||
except aiohttp.ClientError:
|
except aiohttp.ClientError:
|
||||||
self.logger.warn("failed to revoke token!",
|
self.logger.warn("failed to revoke token!", exc_info=True)
|
||||||
exc_info=True)
|
|
||||||
http_session.pop(self.SESSION_TOKEN, None)
|
http_session.pop(self.SESSION_TOKEN, None)
|
||||||
http_session.pop(self.SESSION_ADDRESS, None)
|
http_session.pop(self.SESSION_ADDRESS, None)
|
||||||
http_session.pop(self.SESSION_CACHED_SCOPE, None)
|
http_session.pop(self.SESSION_CACHED_SCOPE, None)
|
||||||
@@ -1359,9 +1399,9 @@ class ProsodyClient:
|
|||||||
|
|
||||||
async def get_public_invite_by_id(self, id_: str) -> PublicInviteInfo:
|
async def get_public_invite_by_id(self, id_: str) -> PublicInviteInfo:
|
||||||
async with self._plain_session as session:
|
async with self._plain_session as session:
|
||||||
async with session.get(self._public_v1_endpoint(
|
async with session.get(
|
||||||
"/invite/{}".format(id_)
|
self._public_v1_endpoint("/invite/{}".format(id_))
|
||||||
)) as resp:
|
) as resp:
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
return PublicInviteInfo.from_api_response(await resp.json())
|
return PublicInviteInfo.from_api_response(await resp.json())
|
||||||
|
|
||||||
@@ -1378,16 +1418,15 @@ class ProsodyClient:
|
|||||||
}
|
}
|
||||||
async with self._plain_session as session:
|
async with self._plain_session as session:
|
||||||
async with session.post(
|
async with session.post(
|
||||||
self._public_v1_endpoint("/register"),
|
self._public_v1_endpoint("/register"), json=payload
|
||||||
json=payload) as resp:
|
) as resp:
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
return (await resp.json())["jid"]
|
return (await resp.json())["jid"]
|
||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def get_system_metrics(
|
async def get_system_metrics(
|
||||||
self,
|
self, *, session: aiohttp.ClientSession
|
||||||
*,
|
) -> typing.Mapping:
|
||||||
session: aiohttp.ClientSession) -> typing.Mapping:
|
|
||||||
async with session.get(
|
async with session.get(
|
||||||
self._admin_v1_endpoint("/server/metrics"),
|
self._admin_v1_endpoint("/server/metrics"),
|
||||||
) as resp:
|
) as resp:
|
||||||
@@ -1399,11 +1438,8 @@ class ProsodyClient:
|
|||||||
|
|
||||||
@autosession
|
@autosession
|
||||||
async def post_announcement(
|
async def post_announcement(
|
||||||
self,
|
self, body: str, recipients: str, *, session: aiohttp.ClientSession
|
||||||
body: str,
|
) -> None:
|
||||||
recipients: str,
|
|
||||||
*,
|
|
||||||
session: aiohttp.ClientSession) -> None:
|
|
||||||
recipients_payload: typing.Union[str, typing.Sequence[str]]
|
recipients_payload: typing.Union[str, typing.Sequence[str]]
|
||||||
if recipients == "self":
|
if recipients == "self":
|
||||||
recipients_payload = [self.session_address]
|
recipients_payload = [self.session_address]
|
||||||
@@ -1416,7 +1452,7 @@ class ProsodyClient:
|
|||||||
}
|
}
|
||||||
|
|
||||||
async with session.post(
|
async with session.post(
|
||||||
self._admin_v1_endpoint("/server/announcement"),
|
self._admin_v1_endpoint("/server/announcement"), json=payload
|
||||||
json=payload) as resp:
|
) as resp:
|
||||||
self._raise_error_from_response(resp)
|
self._raise_error_from_response(resp)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
|
|||||||
BIN
snikket_web/static/img/google/da_badge_web_generic.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
snikket_web/static/img/google/de_badge_web_generic.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
snikket_web/static/img/google/en_badge_web_generic.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
snikket_web/static/img/google/es_badge_web_generic.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
snikket_web/static/img/google/fr_badge_web_generic.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
snikket_web/static/img/google/id_badge_web_generic.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
snikket_web/static/img/google/it_badge_web_generic.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
snikket_web/static/img/google/ja_badge_web_generic.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
snikket_web/static/img/google/pl_badge_web_generic.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
snikket_web/static/img/google/ru_badge_web_generic.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
snikket_web/static/img/google/sv_badge_web_generic.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
@@ -32,7 +32,7 @@
|
|||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
<div class="install-buttons">
|
<div class="install-buttons">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{{ play_store_url }}"><img alt='{% trans %}Get it on Google Play{% endtrans %}' src='https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png' class="play"/></a></li>
|
<li><a href="{{ play_store_url }}"><img alt='{% trans %}Get it on Google Play{% endtrans %}' src='{{ play_store_badge() }}' class="play"/></a></li>
|
||||||
{%- if apple_store_url -%}
|
{%- if apple_store_url -%}
|
||||||
<li><a href="{{ apple_store_url }}" class="popover" data-popover-id="apple-popover"><img alt='{% trans %}Download on the App Store{% endtrans %}' src="{{ apple_store_badge() }}" class="apple"></a></li>
|
<li><a href="{{ apple_store_url }}" class="popover" data-popover-id="apple-popover"><img alt='{% trans %}Download on the App Store{% endtrans %}' src="{{ apple_store_badge() }}" class="apple"></a></li>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2022-05-30 14:01+0000\n"
|
"PO-Revision-Date: 2022-05-30 14:01+0000\n"
|
||||||
"Last-Translator: Daniel Holmgaard <fovatis@tutanota.com>\n"
|
"Last-Translator: Daniel Holmgaard <fovatis@tutanota.com>\n"
|
||||||
"Language-Team: Danish <http://i18n.sotecware.net/projects/snikket/web-portal/"
|
"Language-Team: Danish <http://i18n.sotecware.net/projects/snikket/web-portal/"
|
||||||
@@ -353,7 +353,7 @@ msgstr ""
|
|||||||
"fil i XEP-0227-format (forudsat format: %(mimetype)s)."
|
"fil i XEP-0227-format (forudsat format: %(mimetype)s)."
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Fejl"
|
msgstr "Fejl"
|
||||||
|
|
||||||
@@ -425,15 +425,15 @@ msgstr "Kontodata"
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "Upload"
|
msgstr "Upload"
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "Forkert adgangskode."
|
msgstr "Forkert adgangskode."
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "Adgangskode ændret"
|
msgstr "Adgangskode ændret"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
@@ -441,15 +441,15 @@ msgstr ""
|
|||||||
"Den valgte avatar er for stor. For at kunne uploade større avatarer skal du "
|
"Den valgte avatar er for stor. For at kunne uploade større avatarer skal du "
|
||||||
"bruge appen."
|
"bruge appen."
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "Profil opdateret"
|
msgstr "Profil opdateret"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "Exporter"
|
msgstr "Exporter"
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr "Du har i øjeblikket ingen kontodata at eksportere."
|
msgstr "Du har i øjeblikket ingen kontodata at eksportere."
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: SnikketWeb 0.1.0\n"
|
"Project-Id-Version: SnikketWeb 0.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2024-01-03 16:08+0000\n"
|
"PO-Revision-Date: 2024-01-03 16:08+0000\n"
|
||||||
"Last-Translator: Jonas Schäfer <jonas@zombofant.net>\n"
|
"Last-Translator: Jonas Schäfer <jonas@zombofant.net>\n"
|
||||||
"Language-Team: German <http://i18n.sotecware.net/projects/snikket/web-portal/"
|
"Language-Team: German <http://i18n.sotecware.net/projects/snikket/web-portal/"
|
||||||
@@ -340,7 +340,7 @@ msgstr ""
|
|||||||
"im XEP-0227-Format verarbeitet werden (erhaltenes Format: %(mimetype)s)."
|
"im XEP-0227-Format verarbeitet werden (erhaltenes Format: %(mimetype)s)."
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Fehler"
|
msgstr "Fehler"
|
||||||
|
|
||||||
@@ -412,15 +412,15 @@ msgstr "Kontodaten"
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "Hochladen"
|
msgstr "Hochladen"
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "Ungültiges Passwort."
|
msgstr "Ungültiges Passwort."
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "Passwort geändert"
|
msgstr "Passwort geändert"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
@@ -428,15 +428,15 @@ msgstr ""
|
|||||||
"Das gewählte Profilbild ist zu groß. Benutze die App um größere Bilder "
|
"Das gewählte Profilbild ist zu groß. Benutze die App um größere Bilder "
|
||||||
"hochladen zu können."
|
"hochladen zu können."
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "Profil gespeichert"
|
msgstr "Profil gespeichert"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "Exportieren"
|
msgstr "Exportieren"
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr "Du hast derzeit keine Kontodaten, die exportiert werden können."
|
msgstr "Du hast derzeit keine Kontodaten, die exportiert werden können."
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2021-06-19 15:01+0000\n"
|
"PO-Revision-Date: 2021-06-19 15:01+0000\n"
|
||||||
"Last-Translator: Jonas Schäfer <jonas@zombofant.net>\n"
|
"Last-Translator: Jonas Schäfer <jonas@zombofant.net>\n"
|
||||||
"Language-Team: English <https://i18n.sotecware.net/projects/snikket/web-"
|
"Language-Team: English <https://i18n.sotecware.net/projects/snikket/web-"
|
||||||
@@ -391,7 +391,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -465,35 +465,35 @@ msgstr ""
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Incorrect password"
|
#| msgid "Incorrect password"
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "Incorrect password"
|
msgstr "Incorrect password"
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Password change failed"
|
#| msgid "Password change failed"
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "Password change failed"
|
msgstr "Password change failed"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Profile"
|
#| msgid "Profile"
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "Profile"
|
msgstr "Profile"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2021-06-19 15:01+0000\n"
|
"PO-Revision-Date: 2021-06-19 15:01+0000\n"
|
||||||
"Last-Translator: Jonas Schäfer <jonas@zombofant.net>\n"
|
"Last-Translator: Jonas Schäfer <jonas@zombofant.net>\n"
|
||||||
"Language-Team: English (United Kingdom) <https://i18n.sotecware.net/projects/"
|
"Language-Team: English (United Kingdom) <https://i18n.sotecware.net/projects/"
|
||||||
@@ -391,7 +391,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -467,35 +467,35 @@ msgstr ""
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Incorrect password"
|
#| msgid "Incorrect password"
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "Incorrect password"
|
msgstr "Incorrect password"
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Password change failed"
|
#| msgid "Password change failed"
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "Password change failed"
|
msgstr "Password change failed"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Profile"
|
#| msgid "Profile"
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "Profile"
|
msgstr "Profile"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: translations@snikket.org\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2024-05-06 05:04+0000\n"
|
"PO-Revision-Date: 2024-05-06 05:04+0000\n"
|
||||||
"Last-Translator: J👀 <j@upsub.me>\n"
|
"Last-Translator: J👀 <j@upsub.me>\n"
|
||||||
"Language-Team: Spanish <http://i18n.sotecware.net/projects/snikket/"
|
"Language-Team: Spanish <http://i18n.sotecware.net/projects/snikket/web-"
|
||||||
"web-portal/es/>\n"
|
"portal/es/>\n"
|
||||||
"Language: es\n"
|
"Language: es\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
@@ -343,7 +343,7 @@ msgstr ""
|
|||||||
"facilitado: %(mimetype)s)."
|
"facilitado: %(mimetype)s)."
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Error"
|
msgstr "Error"
|
||||||
|
|
||||||
@@ -415,15 +415,15 @@ msgstr "Datos de la cuenta"
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "Subida"
|
msgstr "Subida"
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "Contraseña incorrecta."
|
msgstr "Contraseña incorrecta."
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "Contraseña cambiada"
|
msgstr "Contraseña cambiada"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
@@ -431,15 +431,15 @@ msgstr ""
|
|||||||
"El avatar elegido es demasiado grande. Para poder subir avatares más "
|
"El avatar elegido es demasiado grande. Para poder subir avatares más "
|
||||||
"grandes, utilice la aplicación, por favor."
|
"grandes, utilice la aplicación, por favor."
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "Perfil actualizado"
|
msgstr "Perfil actualizado"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "Exportar"
|
msgstr "Exportar"
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr "No dispone actualmente de datos de cuenta para exportar."
|
msgstr "No dispone actualmente de datos de cuenta para exportar."
|
||||||
|
|
||||||
@@ -1428,8 +1428,8 @@ msgid ""
|
|||||||
"This page allows you to reset the password of your account, <strong>"
|
"This page allows you to reset the password of your account, <strong>"
|
||||||
"%(account_jid)s</strong>, once."
|
"%(account_jid)s</strong>, once."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"En esta página puedes restablecer la contraseña de tu cuenta, "
|
"En esta página puedes restablecer la contraseña de tu cuenta, <strong>"
|
||||||
"<strong>%(account_jid)s</strong>, una vez."
|
"%(account_jid)s</strong>, una vez."
|
||||||
|
|
||||||
#: snikket_web/templates/invite_reset_view.html:17
|
#: snikket_web/templates/invite_reset_view.html:17
|
||||||
msgid "Using the app"
|
msgid "Using the app"
|
||||||
@@ -1518,8 +1518,8 @@ msgid ""
|
|||||||
"You can now safely close this page, or log in to the web portal to <a href="
|
"You can now safely close this page, or log in to the web portal to <a href="
|
||||||
"\"%(login_url)s\">manage your account</a>."
|
"\"%(login_url)s\">manage your account</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Puedes cerrar esta página o entrar en sesión al portal web para <a href=\""
|
"Puedes cerrar esta página o entrar en sesión al portal web para <a href="
|
||||||
"%(login_url)s\">gestionar tu cuenta</a>."
|
"\"%(login_url)s\">gestionar tu cuenta</a>."
|
||||||
|
|
||||||
#: snikket_web/templates/invite_success.html:21
|
#: snikket_web/templates/invite_success.html:21
|
||||||
msgid "Import successful"
|
msgid "Import successful"
|
||||||
@@ -1595,9 +1595,9 @@ msgid ""
|
|||||||
"Install the Snikket App on your Android device (<a href=\"%(ios_info_url)s\" "
|
"Install the Snikket App on your Android device (<a href=\"%(ios_info_url)s\" "
|
||||||
"rel=\"noopener noreferrer\" target=\"_blank\">iOS coming soon!</a>)."
|
"rel=\"noopener noreferrer\" target=\"_blank\">iOS coming soon!</a>)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Instala la App Snikket en tu dispositivo Android (<a href=\"%(ios_info_url)"
|
"Instala la App Snikket en tu dispositivo Android (<a href=\"%(ios_info_url)s"
|
||||||
"s\" rel=\"noopener noreferrer\" target=\"_blank\">¡iOS disponible en "
|
"\" rel=\"noopener noreferrer\" target=\"_blank\">¡iOS disponible en breve!</"
|
||||||
"breve!</a>)."
|
"a>)."
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:35
|
#: snikket_web/templates/invite_view.html:35
|
||||||
msgid "Get it on Google Play"
|
msgid "Get it on Google Play"
|
||||||
@@ -1632,8 +1632,8 @@ msgid ""
|
|||||||
"\">register an account manually</a>."
|
"\">register an account manually</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Puedes conectarte a Snikket usando software compatible con XMPP. Si el botón "
|
"Puedes conectarte a Snikket usando software compatible con XMPP. Si el botón "
|
||||||
"de arriba no funciona con tu app, puede que necesites <a href=\""
|
"de arriba no funciona con tu app, puede que necesites <a href="
|
||||||
"%(register_url)s\">registrar una cuenta manualmente</a>."
|
"\"%(register_url)s\">registrar una cuenta manualmente</a>."
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:59
|
#: snikket_web/templates/invite_view.html:59
|
||||||
msgid "Scan invite code"
|
msgid "Scan invite code"
|
||||||
@@ -1780,8 +1780,8 @@ msgid ""
|
|||||||
"This Snikket service only hosts addresses ending in <em>@%(snikket_domain)s</"
|
"This Snikket service only hosts addresses ending in <em>@%(snikket_domain)s</"
|
||||||
"em>. Your password was not sent."
|
"em>. Your password was not sent."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"El servicio Snikket sólo aloja direcciones que terminan en "
|
"El servicio Snikket sólo aloja direcciones que terminan en <em>@"
|
||||||
"<em>@%(snikket_domain)s</em>. Tu contraseña no se envió."
|
"%(snikket_domain)s</em>. Tu contraseña no se envió."
|
||||||
|
|
||||||
#: snikket_web/templates/policies.html:4 snikket_web/templates/policies.html:10
|
#: snikket_web/templates/policies.html:4 snikket_web/templates/policies.html:10
|
||||||
msgid "Policies"
|
msgid "Policies"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2021-03-31 13:00+0000\n"
|
"PO-Revision-Date: 2021-03-31 13:00+0000\n"
|
||||||
"Last-Translator: Tilman Jiménez <tilman.jimenez@tu-dortmund.de>\n"
|
"Last-Translator: Tilman Jiménez <tilman.jimenez@tu-dortmund.de>\n"
|
||||||
"Language-Team: Spanish (Mexico) <https://i18n.sotecware.net/projects/snikket/"
|
"Language-Team: Spanish (Mexico) <https://i18n.sotecware.net/projects/snikket/"
|
||||||
@@ -377,7 +377,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -451,33 +451,33 @@ msgstr ""
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Incorrect password"
|
#| msgid "Incorrect password"
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "Contraseña incorrecta"
|
msgstr "Contraseña incorrecta"
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Password"
|
#| msgid "Password"
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "Contraseña"
|
msgstr "Contraseña"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: translations@snikket.org\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2024-05-02 21:15+0000\n"
|
"PO-Revision-Date: 2024-05-02 21:15+0000\n"
|
||||||
"Last-Translator: BetaRays <BetaRays@proton.me>\n"
|
"Last-Translator: BetaRays <BetaRays@proton.me>\n"
|
||||||
"Language-Team: French <http://i18n.sotecware.net/projects/snikket/web-portal/"
|
"Language-Team: French <http://i18n.sotecware.net/projects/snikket/web-portal/"
|
||||||
@@ -342,7 +342,7 @@ msgstr ""
|
|||||||
"fourni : %(mimetype)s)."
|
"fourni : %(mimetype)s)."
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Erreur"
|
msgstr "Erreur"
|
||||||
|
|
||||||
@@ -414,15 +414,15 @@ msgstr "Données du compte"
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "Télécharger"
|
msgstr "Télécharger"
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "Mot de passe incorrect."
|
msgstr "Mot de passe incorrect."
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "Mot de passe changé"
|
msgstr "Mot de passe changé"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
@@ -430,15 +430,15 @@ msgstr ""
|
|||||||
"L’avatar choisi est trop gros. Pour utiliser un avatar aussi large, veuillez "
|
"L’avatar choisi est trop gros. Pour utiliser un avatar aussi large, veuillez "
|
||||||
"utiliser l’application."
|
"utiliser l’application."
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "Profil mis à jour"
|
msgstr "Profil mis à jour"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "Exporter"
|
msgstr "Exporter"
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr "Vous n’avez actuellement aucune donnée de compte à exporter."
|
msgstr "Vous n’avez actuellement aucune donnée de compte à exporter."
|
||||||
|
|
||||||
@@ -661,7 +661,8 @@ msgstr ""
|
|||||||
#: snikket_web/templates/admin_create_invite_form.html:16
|
#: snikket_web/templates/admin_create_invite_form.html:16
|
||||||
msgid ""
|
msgid ""
|
||||||
"Choose whether this invitation link will allow more than one person to join."
|
"Choose whether this invitation link will allow more than one person to join."
|
||||||
msgstr "Choisissez si ce lien d’invitation pourra être utilisé plus d’une fois."
|
msgstr ""
|
||||||
|
"Choisissez si ce lien d’invitation pourra être utilisé plus d’une fois."
|
||||||
|
|
||||||
#: snikket_web/templates/admin_create_invite_form.html:21
|
#: snikket_web/templates/admin_create_invite_form.html:21
|
||||||
#, python-format
|
#, python-format
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: translations@snikket.org\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2024-07-18 17:07+0000\n"
|
"PO-Revision-Date: 2024-07-18 17:07+0000\n"
|
||||||
"Last-Translator: uira <inboxriau@illiyy.in>\n"
|
"Last-Translator: uira <inboxriau@illiyy.in>\n"
|
||||||
"Language-Team: Indonesian <http://i18n.sotecware.net/projects/snikket/"
|
"Language-Team: Indonesian <http://i18n.sotecware.net/projects/snikket/web-"
|
||||||
"web-portal/id/>\n"
|
"portal/id/>\n"
|
||||||
"Language: id\n"
|
"Language: id\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
@@ -341,7 +341,7 @@ msgstr ""
|
|||||||
"file XML dalam format XEP-0227 (format yang disediakan: %(mimetype)s)."
|
"file XML dalam format XEP-0227 (format yang disediakan: %(mimetype)s)."
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Kesalahan"
|
msgstr "Kesalahan"
|
||||||
|
|
||||||
@@ -413,15 +413,15 @@ msgstr "Data akun"
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "Unggah"
|
msgstr "Unggah"
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "Kata sandi salah."
|
msgstr "Kata sandi salah."
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "Kata sandi diganti"
|
msgstr "Kata sandi diganti"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
@@ -429,15 +429,15 @@ msgstr ""
|
|||||||
"Avatar yang dipilih terlalu besar. Untuk dapat mengunggah avatar yang lebih "
|
"Avatar yang dipilih terlalu besar. Untuk dapat mengunggah avatar yang lebih "
|
||||||
"besar, sila gunakan aplikasi."
|
"besar, sila gunakan aplikasi."
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "Profil diperbarui"
|
msgstr "Profil diperbarui"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "Ekspor"
|
msgstr "Ekspor"
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr "Anda tidak memiliki data akun untuk diekspor."
|
msgstr "Anda tidak memiliki data akun untuk diekspor."
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: translations@snikket.org\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2024-05-02 21:15+0000\n"
|
"PO-Revision-Date: 2024-05-02 21:15+0000\n"
|
||||||
"Last-Translator: Federico <federico@tebaldi.eu>\n"
|
"Last-Translator: Federico <federico@tebaldi.eu>\n"
|
||||||
"Language-Team: Italian <http://i18n.sotecware.net/projects/snikket/"
|
"Language-Team: Italian <http://i18n.sotecware.net/projects/snikket/web-"
|
||||||
"web-portal/it/>\n"
|
"portal/it/>\n"
|
||||||
"Language: it\n"
|
"Language: it\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
@@ -342,7 +342,7 @@ msgstr ""
|
|||||||
"fornito: %(mimetype)s)."
|
"fornito: %(mimetype)s)."
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Errore"
|
msgstr "Errore"
|
||||||
|
|
||||||
@@ -414,15 +414,15 @@ msgstr "Dati dell'utenza"
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "Carica"
|
msgstr "Carica"
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "Password errata."
|
msgstr "Password errata."
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "Password cambiata"
|
msgstr "Password cambiata"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
@@ -430,15 +430,15 @@ msgstr ""
|
|||||||
"L'avatar scelto è troppo grande. Utilizza direttamente l'applicazione "
|
"L'avatar scelto è troppo grande. Utilizza direttamente l'applicazione "
|
||||||
"Snikket per caricare un avatar così grande."
|
"Snikket per caricare un avatar così grande."
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "Profilo aggiornato"
|
msgstr "Profilo aggiornato"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "Esportare"
|
msgstr "Esportare"
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr "Al momento non hai dati utente da esportare."
|
msgstr "Al momento non hai dati utente da esportare."
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2021-01-28 17:55+0000\n"
|
"PO-Revision-Date: 2021-01-28 17:55+0000\n"
|
||||||
"Last-Translator: pep <pep@bouah.net>\n"
|
"Last-Translator: pep <pep@bouah.net>\n"
|
||||||
"Language-Team: Japanese <https://i18n.sotecware.net/projects/snikket/web-"
|
"Language-Team: Japanese <https://i18n.sotecware.net/projects/snikket/web-"
|
||||||
@@ -389,7 +389,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@@ -463,35 +463,35 @@ msgstr ""
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Incorrect password"
|
#| msgid "Incorrect password"
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "パスワード不正"
|
msgstr "パスワード不正"
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Password reset"
|
#| msgid "Password reset"
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "パスワード再設定"
|
msgstr "パスワード再設定"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Profile"
|
#| msgid "Profile"
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "プロファイル"
|
msgstr "プロファイル"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
# Translations template for PROJECT.
|
# Translations template for PROJECT.
|
||||||
# Copyright (C) 2024 ORGANIZATION
|
# Copyright (C) 2025 ORGANIZATION
|
||||||
# This file is distributed under the same license as the PROJECT project.
|
# This file is distributed under the same license as the PROJECT project.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2024.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, 2025.
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
"POT-Creation-Date: 2025-04-12 20:21+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: Babel 2.16.0\n"
|
"Generated-By: Babel 2.17.0\n"
|
||||||
|
|
||||||
#: snikket_web/admin.py:69 snikket_web/templates/admin_delete_user.html:10
|
#: snikket_web/admin.py:69 snikket_web/templates/admin_delete_user.html:10
|
||||||
#: snikket_web/templates/admin_edit_circle.html:73
|
#: snikket_web/templates/admin_edit_circle.html:73
|
||||||
@@ -270,7 +270,7 @@ msgstr ""
|
|||||||
msgid "Yesterday"
|
msgid "Yesterday"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/infra.py:105
|
#: snikket_web/infra.py:104
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(time)s ago"
|
msgid "%(time)s ago"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -281,59 +281,59 @@ msgid ""
|
|||||||
"contact your Snikket operator."
|
"contact your Snikket operator."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:114
|
#: snikket_web/invite.py:124
|
||||||
msgid "Username"
|
msgid "Username"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:118 snikket_web/invite.py:190 snikket_web/main.py:43
|
#: snikket_web/invite.py:128 snikket_web/invite.py:200 snikket_web/main.py:43
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:126 snikket_web/invite.py:198
|
#: snikket_web/invite.py:136 snikket_web/invite.py:208
|
||||||
msgid "Confirm password"
|
msgid "Confirm password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:130 snikket_web/invite.py:202
|
#: snikket_web/invite.py:140 snikket_web/invite.py:212
|
||||||
msgid "The passwords must match."
|
msgid "The passwords must match."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:135
|
#: snikket_web/invite.py:145
|
||||||
msgid "Create account"
|
msgid "Create account"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:162
|
#: snikket_web/invite.py:172
|
||||||
msgid "That username is already taken."
|
msgid "That username is already taken."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:166 snikket_web/invite.py:235
|
#: snikket_web/invite.py:176 snikket_web/invite.py:245
|
||||||
msgid "Registration was declined for unknown reasons."
|
msgid "Registration was declined for unknown reasons."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:170
|
#: snikket_web/invite.py:180
|
||||||
msgid "The username is not valid."
|
msgid "The username is not valid."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:207 snikket_web/templates/user_home.html:37
|
#: snikket_web/invite.py:217 snikket_web/templates/user_home.html:37
|
||||||
#: snikket_web/templates/user_passwd.html:29
|
#: snikket_web/templates/user_passwd.html:29
|
||||||
msgid "Change password"
|
msgid "Change password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:254
|
#: snikket_web/invite.py:264
|
||||||
msgid "Account data file"
|
msgid "Account data file"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:258
|
#: snikket_web/invite.py:268
|
||||||
msgid "Import data"
|
msgid "Import data"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:279
|
#: snikket_web/invite.py:289
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The account data you tried to import is in an unknown format. Please "
|
"The account data you tried to import is in an unknown format. Please "
|
||||||
"upload an XML file in XEP-0227 format (provided format: %(mimetype)s)."
|
"upload an XML file in XEP-0227 format (provided format: %(mimetype)s)."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:309 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:192
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -414,7 +414,7 @@ msgstr ""
|
|||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: snikket_web/user.py:137
|
#: snikket_web/user.py:138
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please"
|
"The chosen avatar is too big. To be able to upload larger avatars, please"
|
||||||
" use the app."
|
" use the app."
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: translations@snikket.org\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2024-05-07 17:04+0000\n"
|
"PO-Revision-Date: 2024-05-07 17:04+0000\n"
|
||||||
"Last-Translator: misiek <migelazur@mailbox.org>\n"
|
"Last-Translator: misiek <migelazur@mailbox.org>\n"
|
||||||
"Language-Team: Polish <http://i18n.sotecware.net/projects/snikket/web-portal/"
|
"Language-Team: Polish <http://i18n.sotecware.net/projects/snikket/web-portal/"
|
||||||
@@ -342,7 +342,7 @@ msgstr ""
|
|||||||
"wybrać plik w formacie XML zgodnym z XEP-0227 (podany format: %(mimetype)s)."
|
"wybrać plik w formacie XML zgodnym z XEP-0227 (podany format: %(mimetype)s)."
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Błąd"
|
msgstr "Błąd"
|
||||||
|
|
||||||
@@ -414,15 +414,15 @@ msgstr "Dane konta"
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "Prześlij"
|
msgstr "Prześlij"
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "Nieprawidłowe hasło."
|
msgstr "Nieprawidłowe hasło."
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "Hasło zostało zmienione"
|
msgstr "Hasło zostało zmienione"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
@@ -430,15 +430,15 @@ msgstr ""
|
|||||||
"Wybrany awatar jest zbyt duży. Awatary o większych rozmiarach możesz ustawić "
|
"Wybrany awatar jest zbyt duży. Awatary o większych rozmiarach możesz ustawić "
|
||||||
"korzystając z aplikacji."
|
"korzystając z aplikacji."
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "Zaktualizowano profil"
|
msgstr "Zaktualizowano profil"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "Eksportuj"
|
msgstr "Eksportuj"
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr "Obecnie nie masz danych konta, które można wyeksportować."
|
msgstr "Obecnie nie masz danych konta, które można wyeksportować."
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: translations@snikket.org\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2024-07-18 17:07+0000\n"
|
"PO-Revision-Date: 2024-07-18 17:07+0000\n"
|
||||||
"Last-Translator: Andrey <Elisoandr@gmail.com>\n"
|
"Last-Translator: Andrey <Elisoandr@gmail.com>\n"
|
||||||
"Language-Team: Russian <http://i18n.sotecware.net/projects/snikket/"
|
"Language-Team: Russian <http://i18n.sotecware.net/projects/snikket/web-"
|
||||||
"web-portal/ru/>\n"
|
"portal/ru/>\n"
|
||||||
"Language: ru\n"
|
"Language: ru\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
@@ -343,7 +343,7 @@ msgstr ""
|
|||||||
"%(mimetype)s)."
|
"%(mimetype)s)."
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Ошибка"
|
msgstr "Ошибка"
|
||||||
|
|
||||||
@@ -415,15 +415,15 @@ msgstr "Данные аккаунта"
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "Загрузить"
|
msgstr "Загрузить"
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "Неверный пароль."
|
msgstr "Неверный пароль."
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "Пароль изменен"
|
msgstr "Пароль изменен"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
@@ -431,15 +431,15 @@ msgstr ""
|
|||||||
"Выбранный аватар слишком велик. Чтобы иметь возможность загружать аватары "
|
"Выбранный аватар слишком велик. Чтобы иметь возможность загружать аватары "
|
||||||
"большего размера, используйте приложение."
|
"большего размера, используйте приложение."
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "Профиль обновлен"
|
msgstr "Профиль обновлен"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "Экспорт"
|
msgstr "Экспорт"
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr "В настоящее время у вас нет данных аккаунта для экспорта."
|
msgstr "В настоящее время у вас нет данных аккаунта для экспорта."
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: translations@snikket.org\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2024-05-02 21:15+0000\n"
|
"PO-Revision-Date: 2024-05-02 21:15+0000\n"
|
||||||
"Last-Translator: Kim Alvefur <zash@zash.se>\n"
|
"Last-Translator: Kim Alvefur <zash@zash.se>\n"
|
||||||
"Language-Team: Swedish <http://i18n.sotecware.net/projects/snikket/"
|
"Language-Team: Swedish <http://i18n.sotecware.net/projects/snikket/web-"
|
||||||
"web-portal/sv/>\n"
|
"portal/sv/>\n"
|
||||||
"Language: sv\n"
|
"Language: sv\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
@@ -341,7 +341,7 @@ msgstr ""
|
|||||||
"XML-fil i XEP-0227-format (angivet format: %(mimetype)s)."
|
"XML-fil i XEP-0227-format (angivet format: %(mimetype)s)."
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Fel"
|
msgstr "Fel"
|
||||||
|
|
||||||
@@ -413,15 +413,15 @@ msgstr "Kontodata"
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "Ladda upp"
|
msgstr "Ladda upp"
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "Fel lösenord."
|
msgstr "Fel lösenord."
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "Lösenord ändrat"
|
msgstr "Lösenord ändrat"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
@@ -429,15 +429,15 @@ msgstr ""
|
|||||||
"Den valda profilbilden är för stor. Vänligen använd appen för att kunna "
|
"Den valda profilbilden är för stor. Vänligen använd appen för att kunna "
|
||||||
"välja större bilder."
|
"välja större bilder."
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "Profilen uppdaterad"
|
msgstr "Profilen uppdaterad"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "Exportera"
|
msgstr "Exportera"
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr "Du har för närvarande inget data att exportera."
|
msgstr "Du har för närvarande inget data att exportera."
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ msgid ""
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2023-12-25 16:00+0000\n"
|
"PO-Revision-Date: 2023-12-25 16:00+0000\n"
|
||||||
"Last-Translator: Dmytro Vozniuk <plibnik@gmail.com>\n"
|
"Last-Translator: Dmytro Vozniuk <plibnik@gmail.com>\n"
|
||||||
"Language-Team: Ukrainian <http://i18n.sotecware.net/projects/snikket/web-"
|
"Language-Team: Ukrainian <http://i18n.sotecware.net/projects/snikket/web-"
|
||||||
@@ -343,7 +343,7 @@ msgstr ""
|
|||||||
"%(mimetype)s)."
|
"%(mimetype)s)."
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Помилка"
|
msgstr "Помилка"
|
||||||
|
|
||||||
@@ -415,15 +415,15 @@ msgstr "Дані облікового запису"
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "Завантажити"
|
msgstr "Завантажити"
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "Хибний пароль."
|
msgstr "Хибний пароль."
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "Пароль змінено"
|
msgstr "Пароль змінено"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
@@ -431,15 +431,15 @@ msgstr ""
|
|||||||
"Вибраний аватар надто великий. Щоб завантажити аватари великого розміру, "
|
"Вибраний аватар надто великий. Щоб завантажити аватари великого розміру, "
|
||||||
"скористуйтесь застосунком."
|
"скористуйтесь застосунком."
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "Профіль оновлено"
|
msgstr "Профіль оновлено"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "Експортувати"
|
msgstr "Експортувати"
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr "Наразі у вас немає даних облікового запису, щоб експортувати їх."
|
msgstr "Наразі у вас немає даних облікового запису, щоб експортувати їх."
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PROJECT VERSION\n"
|
"Project-Id-Version: PROJECT VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: translations@snikket.org\n"
|
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
|
||||||
"POT-Creation-Date: 2024-04-30 10:52+0100\n"
|
"POT-Creation-Date: 2024-08-11 16:36+0200\n"
|
||||||
"PO-Revision-Date: 2024-05-02 21:15+0000\n"
|
"PO-Revision-Date: 2024-05-02 21:15+0000\n"
|
||||||
"Last-Translator: Rosebud <postage_quizzical060@simplelogin.com>\n"
|
"Last-Translator: Rosebud <postage_quizzical060@simplelogin.com>\n"
|
||||||
"Language-Team: Chinese (Simplified) <http://i18n.sotecware.net/projects/"
|
"Language-Team: Chinese (Simplified) <http://i18n.sotecware.net/projects/"
|
||||||
@@ -334,11 +334,12 @@ msgstr "导入数据"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"The account data you tried to import is in an unknown format. Please upload "
|
"The account data you tried to import is in an unknown format. Please upload "
|
||||||
"an XML file in XEP-0227 format (provided format: %(mimetype)s)."
|
"an XML file in XEP-0227 format (provided format: %(mimetype)s)."
|
||||||
msgstr "您尝试导入的账号数据的格式未知。请上传 XEP-0227 格式的 XML "
|
msgstr ""
|
||||||
"文件(提供的格式:%(mimetype)s)。"
|
"您尝试导入的账号数据的格式未知。请上传 XEP-0227 格式的 XML 文件(提供的格式:"
|
||||||
|
"%(mimetype)s)。"
|
||||||
|
|
||||||
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
#: snikket_web/invite.py:299 snikket_web/templates/unauth.html:18
|
||||||
#: snikket_web/user.py:189
|
#: snikket_web/user.py:192
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "错误"
|
msgstr "错误"
|
||||||
|
|
||||||
@@ -410,29 +411,29 @@ msgstr "账号数据"
|
|||||||
msgid "Upload"
|
msgid "Upload"
|
||||||
msgstr "上传"
|
msgstr "上传"
|
||||||
|
|
||||||
#: snikket_web/user.py:122
|
#: snikket_web/user.py:125
|
||||||
msgid "Incorrect password."
|
msgid "Incorrect password."
|
||||||
msgstr "密码不正确。"
|
msgstr "密码不正确。"
|
||||||
|
|
||||||
#: snikket_web/user.py:126
|
#: snikket_web/user.py:129
|
||||||
msgid "Password changed"
|
msgid "Password changed"
|
||||||
msgstr "密码已更改"
|
msgstr "密码已更改"
|
||||||
|
|
||||||
#: snikket_web/user.py:134
|
#: snikket_web/user.py:137
|
||||||
msgid ""
|
msgid ""
|
||||||
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
"The chosen avatar is too big. To be able to upload larger avatars, please "
|
||||||
"use the app."
|
"use the app."
|
||||||
msgstr "所选头像太大。要上传更大的头像,请使用应用。"
|
msgstr "所选头像太大。要上传更大的头像,请使用应用。"
|
||||||
|
|
||||||
#: snikket_web/user.py:181
|
#: snikket_web/user.py:184
|
||||||
msgid "Profile updated"
|
msgid "Profile updated"
|
||||||
msgstr "个人资料已更新"
|
msgstr "个人资料已更新"
|
||||||
|
|
||||||
#: snikket_web/user.py:195
|
#: snikket_web/user.py:198
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "导出"
|
msgstr "导出"
|
||||||
|
|
||||||
#: snikket_web/user.py:213
|
#: snikket_web/user.py:216
|
||||||
msgid "You currently have no account data to export."
|
msgid "You currently have no account data to export."
|
||||||
msgstr "您目前没有账号数据要导出。"
|
msgstr "您目前没有账号数据要导出。"
|
||||||
|
|
||||||
@@ -460,15 +461,17 @@ msgstr "关于此服务"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"This is the Snikket service <em>%(site_name)s</em>, running open-source "
|
"This is the Snikket service <em>%(site_name)s</em>, running open-source "
|
||||||
"software from the Snikket project."
|
"software from the Snikket project."
|
||||||
msgstr "这是 Snikket 服务 <em>%(site_name)s</em>,运行来自 Snikket 项目的开源软件。"
|
msgstr ""
|
||||||
|
"这是 Snikket 服务 <em>%(site_name)s</em>,运行来自 Snikket 项目的开源软件。"
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:11
|
#: snikket_web/templates/about.html:11
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"To learn more about Snikket, visit the <a href=\"%(snikket_url)s\">Snikket "
|
"To learn more about Snikket, visit the <a href=\"%(snikket_url)s\">Snikket "
|
||||||
"website</a>."
|
"website</a>."
|
||||||
msgstr "要了解有关 Snikket 的更多信息,请访问 <a href=\"%(snikket_url)s\">Snikket "
|
msgstr ""
|
||||||
"网站</a>。"
|
"要了解有关 Snikket 的更多信息,请访问 <a href=\"%(snikket_url)s\">Snikket 网"
|
||||||
|
"站</a>。"
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:13
|
#: snikket_web/templates/about.html:13
|
||||||
msgid "View service policies"
|
msgid "View service policies"
|
||||||
@@ -486,16 +489,17 @@ msgid ""
|
|||||||
"a>. The full terms of the license can be reviewed using the aforementioned "
|
"a>. The full terms of the license can be reviewed using the aforementioned "
|
||||||
"link."
|
"link."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"门户网站软件根据 <a href=\"%(agpl_url)s\">GNU Affero 通用公共许可证 3.0 "
|
"门户网站软件根据 <a href=\"%(agpl_url)s\">GNU Affero 通用公共许可证 3.0 版及"
|
||||||
"版及之后版本</a> 的条款获得许可。可通过上述链接查看许可证的完整条款。"
|
"之后版本</a> 的条款获得许可。可通过上述链接查看许可证的完整条款。"
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:17
|
#: snikket_web/templates/about.html:17
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"The source code of the web portal can be downloaded and viewed in <a href="
|
"The source code of the web portal can be downloaded and viewed in <a href="
|
||||||
"\"%(source_url)s\">its GitHub repository</a>."
|
"\"%(source_url)s\">its GitHub repository</a>."
|
||||||
msgstr "门户网站的源代码可在 <a href=\"%(source_url)s\">其 GitHub repository</a> "
|
msgstr ""
|
||||||
"中下载和查看。"
|
"门户网站的源代码可在 <a href=\"%(source_url)s\">其 GitHub repository</a> 中下"
|
||||||
|
"载和查看。"
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:18
|
#: snikket_web/templates/about.html:18
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -504,9 +508,9 @@ msgid ""
|
|||||||
"Material Icons</a>, made available by Google under the terms of the <a href="
|
"Material Icons</a>, made available by Google under the terms of the <a href="
|
||||||
"\"%(apache20_url)s\">Apache 2.0 License</a>."
|
"\"%(apache20_url)s\">Apache 2.0 License</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"门户网站中使用的图标是 <a href=\"%(source_url)s\">Google 的 Material "
|
"门户网站中使用的图标是 <a href=\"%(source_url)s\">Google 的 Material 图标</"
|
||||||
"图标</a>,由 Google 根据 <a href=\"%(apache20_url)s\">Apache 2.0 许可证</a> "
|
"a>,由 Google 根据 <a href=\"%(apache20_url)s\">Apache 2.0 许可证</a> 的条款"
|
||||||
"的条款提供。"
|
"提供。"
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:20
|
#: snikket_web/templates/about.html:20
|
||||||
msgid "Trademarks"
|
msgid "Trademarks"
|
||||||
@@ -519,9 +523,8 @@ msgid ""
|
|||||||
"Company. For more information about the trademarks, visit the <a href="
|
"Company. For more information about the trademarks, visit the <a href="
|
||||||
"\"%(trademarks_url)s\">Snikket Trademarks information page</a>."
|
"\"%(trademarks_url)s\">Snikket Trademarks information page</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"“Snikket” 和鹦鹉标志是 Snikket Community Interest Company "
|
"“Snikket” 和鹦鹉标志是 Snikket Community Interest Company 的商标。有关商标的"
|
||||||
"的商标。有关商标的更多信息,请访问 <a href=\"%(trademarks_url)s\">Snikket "
|
"更多信息,请访问 <a href=\"%(trademarks_url)s\">Snikket 商标信息页</a>。"
|
||||||
"商标信息页</a>。"
|
|
||||||
|
|
||||||
#: snikket_web/templates/about.html:23
|
#: snikket_web/templates/about.html:23
|
||||||
msgid "Software Versions"
|
msgid "Software Versions"
|
||||||
@@ -551,7 +554,8 @@ msgid ""
|
|||||||
"Users who are in the same circle will see each other in their contact list. "
|
"Users who are in the same circle will see each other in their contact list. "
|
||||||
"In addition, each circle may have group chats where the circle members are "
|
"In addition, each circle may have group chats where the circle members are "
|
||||||
"included."
|
"included."
|
||||||
msgstr "在同一个圈子里的用户将在他们的联系人列表中看到对方。此外,每个圈子还有群聊,"
|
msgstr ""
|
||||||
|
"在同一个圈子里的用户将在他们的联系人列表中看到对方。此外,每个圈子还有群聊,"
|
||||||
"圈子成员也包括在内。"
|
"圈子成员也包括在内。"
|
||||||
|
|
||||||
#: snikket_web/templates/admin_circles.html:13
|
#: snikket_web/templates/admin_circles.html:13
|
||||||
@@ -617,8 +621,9 @@ msgid ""
|
|||||||
"This is only for creating group chats that automatically include <em>all</"
|
"This is only for creating group chats that automatically include <em>all</"
|
||||||
"em> members of the circle. If you want a normal group chat, create it in the "
|
"em> members of the circle. If you want a normal group chat, create it in the "
|
||||||
"Snikket app instead."
|
"Snikket app instead."
|
||||||
msgstr "这仅适用于创建自动包含<em>全部</em>圈子成员的群聊。如果您想要普通的群聊,"
|
msgstr ""
|
||||||
"请在 Snikket 应用中创建。"
|
"这仅适用于创建自动包含<em>全部</em>圈子成员的群聊。如果您想要普通的群聊,请"
|
||||||
|
"在 Snikket 应用中创建。"
|
||||||
|
|
||||||
#: snikket_web/templates/admin_create_invite.html:3
|
#: snikket_web/templates/admin_create_invite.html:3
|
||||||
msgid "Create invitation"
|
msgid "Create invitation"
|
||||||
@@ -708,8 +713,9 @@ msgid ""
|
|||||||
"The circle and the corresponding chat will be deleted, permanently and "
|
"The circle and the corresponding chat will be deleted, permanently and "
|
||||||
"immediately upon pushing the below button. <strong>There is no way back!</"
|
"immediately upon pushing the below button. <strong>There is no way back!</"
|
||||||
"strong>"
|
"strong>"
|
||||||
msgstr "按下下面的按钮后,圈子和相应的聊天将立即永久删除。<strong>此操作无法撤销!</s"
|
msgstr ""
|
||||||
"trong>"
|
"按下下面的按钮后,圈子和相应的聊天将立即永久删除。<strong>此操作无法撤销!</"
|
||||||
|
"strong>"
|
||||||
|
|
||||||
#: snikket_web/templates/admin_delete_circle.html:17
|
#: snikket_web/templates/admin_delete_circle.html:17
|
||||||
#: snikket_web/templates/admin_delete_user.html:19
|
#: snikket_web/templates/admin_delete_user.html:19
|
||||||
@@ -740,7 +746,8 @@ msgid ""
|
|||||||
"The user and their data will be deleted irrevocably, permanently and "
|
"The user and their data will be deleted irrevocably, permanently and "
|
||||||
"immediately upon pushing the below button. <strong>There is no way back!</"
|
"immediately upon pushing the below button. <strong>There is no way back!</"
|
||||||
"strong>"
|
"strong>"
|
||||||
msgstr "按下下面的按钮后,用户及其数据将不可撤销、永久立即删除。<strong>此操作无法撤"
|
msgstr ""
|
||||||
|
"按下下面的按钮后,用户及其数据将不可撤销、永久立即删除。<strong>此操作无法撤"
|
||||||
"销!</strong>"
|
"销!</strong>"
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_circle.html:14
|
#: snikket_web/templates/admin_edit_circle.html:14
|
||||||
@@ -947,8 +954,9 @@ msgstr "重置密码"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"If the user has lost their password, you can use the button below to create "
|
"If the user has lost their password, you can use the button below to create "
|
||||||
"a special link which allows to change the password of the account, once."
|
"a special link which allows to change the password of the account, once."
|
||||||
msgstr "如果用户丢失了密码,您可以使用下方按钮创建一个特殊链接,允许更改账号密码一次"
|
msgstr ""
|
||||||
"。"
|
"如果用户丢失了密码,您可以使用下方按钮创建一个特殊链接,允许更改账号密码一"
|
||||||
|
"次。"
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_user.html:73
|
#: snikket_web/templates/admin_edit_user.html:73
|
||||||
msgid "Debug information"
|
msgid "Debug information"
|
||||||
@@ -959,7 +967,8 @@ msgid ""
|
|||||||
"In some cases, extended information about the user account and the connected "
|
"In some cases, extended information about the user account and the connected "
|
||||||
"devices is necessary to troubleshoot issues. The button below reveals this "
|
"devices is necessary to troubleshoot issues. The button below reveals this "
|
||||||
"(sensitive) information."
|
"(sensitive) information."
|
||||||
msgstr "在某些情况下,需要有关用户账号和连接设备的扩展信息来排查问题。下方按钮显示此"
|
msgstr ""
|
||||||
|
"在某些情况下,需要有关用户账号和连接设备的扩展信息来排查问题。下方按钮显示此"
|
||||||
"(敏感)信息。"
|
"(敏感)信息。"
|
||||||
|
|
||||||
#: snikket_web/templates/admin_edit_user.html:79
|
#: snikket_web/templates/admin_edit_user.html:79
|
||||||
@@ -1273,7 +1282,8 @@ msgid ""
|
|||||||
"Snikket app or compatible software. If you already have the app installed, "
|
"Snikket app or compatible software. If you already have the app installed, "
|
||||||
"we recommend that you continue the account creation process inside the app "
|
"we recommend that you continue the account creation process inside the app "
|
||||||
"by clicking on the button below:"
|
"by clicking on the button below:"
|
||||||
msgstr "创建账号将允许使用 Snikket 应用或兼容软件与其他人进行交流。如果您已经安装了应"
|
msgstr ""
|
||||||
|
"创建账号将允许使用 Snikket 应用或兼容软件与其他人进行交流。如果您已经安装了应"
|
||||||
"用,我们建议您单击下方按钮后在应用内完成账号注册流程:"
|
"用,我们建议您单击下方按钮后在应用内完成账号注册流程:"
|
||||||
|
|
||||||
#: snikket_web/templates/invite_register.html:14
|
#: snikket_web/templates/invite_register.html:14
|
||||||
@@ -1302,8 +1312,9 @@ msgstr "在线创建账号"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"If you plan to use a legacy XMPP client, you can register an account online "
|
"If you plan to use a legacy XMPP client, you can register an account online "
|
||||||
"and enter your credentials into any XMPP-compatible software."
|
"and enter your credentials into any XMPP-compatible software."
|
||||||
msgstr "如果您打算使用传统的 XMPP 客户端,可以在线注册账号,在任何兼容 XMPP "
|
msgstr ""
|
||||||
"的软件中输入您的凭据。"
|
"如果您打算使用传统的 XMPP 客户端,可以在线注册账号,在任何兼容 XMPP 的软件中"
|
||||||
|
"输入您的凭据。"
|
||||||
|
|
||||||
#: snikket_web/templates/invite_register.html:27
|
#: snikket_web/templates/invite_register.html:27
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -1379,7 +1390,8 @@ msgid ""
|
|||||||
"Your camera will turn on. Point it at the square code below until it is "
|
"Your camera will turn on. Point it at the square code below until it is "
|
||||||
"within the highlighted square on your screen, and wait until the app "
|
"within the highlighted square on your screen, and wait until the app "
|
||||||
"recognises it."
|
"recognises it."
|
||||||
msgstr "您的相机将打开。将摄像头对准下方二维码,直到它位于屏幕上突出显示的正方形内,"
|
msgstr ""
|
||||||
|
"您的相机将打开。将摄像头对准下方二维码,直到它位于屏幕上突出显示的正方形内,"
|
||||||
"然后等待应用识别。"
|
"然后等待应用识别。"
|
||||||
|
|
||||||
#: snikket_web/templates/invite_reset_view.html:27
|
#: snikket_web/templates/invite_reset_view.html:27
|
||||||
@@ -1396,8 +1408,9 @@ msgstr "其他方式"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"You can also <a href=\"%(reset_url)s\">reset your password online</a> if the "
|
"You can also <a href=\"%(reset_url)s\">reset your password online</a> if the "
|
||||||
"above button or scanning the QR code does not work for you."
|
"above button or scanning the QR code does not work for you."
|
||||||
msgstr "如果上方按钮或扫描二维码对您不起作用,您也可以 <a href=\"%(reset_url)s\""
|
msgstr ""
|
||||||
">在线重置密码</a>。"
|
"如果上方按钮或扫描二维码对您不起作用,您也可以 <a href=\"%(reset_url)s\">在线"
|
||||||
|
"重置密码</a>。"
|
||||||
|
|
||||||
#: snikket_web/templates/invite_success.html:5
|
#: snikket_web/templates/invite_success.html:5
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -1435,8 +1448,9 @@ msgstr "您现在可以使用上方地址和注册时选择的密码设置传统
|
|||||||
msgid ""
|
msgid ""
|
||||||
"You can now safely close this page, or log in to the web portal to <a href="
|
"You can now safely close this page, or log in to the web portal to <a href="
|
||||||
"\"%(login_url)s\">manage your account</a>."
|
"\"%(login_url)s\">manage your account</a>."
|
||||||
msgstr "您现在可以安全地关闭此页面,或登录到门户网站 <a href=\"%(login_url)s\""
|
msgstr ""
|
||||||
">管理您的账号</a>。"
|
"您现在可以安全地关闭此页面,或登录到门户网站 <a href=\"%(login_url)s\">管理您"
|
||||||
|
"的账号</a>。"
|
||||||
|
|
||||||
#: snikket_web/templates/invite_success.html:21
|
#: snikket_web/templates/invite_success.html:21
|
||||||
msgid "Import successful"
|
msgid "Import successful"
|
||||||
@@ -1476,16 +1490,17 @@ msgid ""
|
|||||||
"You have been invited to chat with %(inviter_name)s using Snikket, a secure, "
|
"You have been invited to chat with %(inviter_name)s using Snikket, a secure, "
|
||||||
"privacy-friendly chat app on %(site_name)s."
|
"privacy-friendly chat app on %(site_name)s."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"您已受邀使用 Snikket 与 %(inviter_name)s 在 %(site_name)s 上聊天,Snikket "
|
"您已受邀使用 Snikket 与 %(inviter_name)s 在 %(site_name)s 上聊天,Snikket 是"
|
||||||
"是安全、隐私友好的聊天应用。"
|
"安全、隐私友好的聊天应用。"
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:18
|
#: snikket_web/templates/invite_view.html:18
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"You have been invited to chat on %(site_name)s using Snikket, a secure, "
|
"You have been invited to chat on %(site_name)s using Snikket, a secure, "
|
||||||
"privacy-friendly chat app."
|
"privacy-friendly chat app."
|
||||||
msgstr "您已受邀使用 Snikket 在 %(site_name)s 上聊天,Snikket "
|
msgstr ""
|
||||||
"是安全、隐私友好的聊天应用。"
|
"您已受邀使用 Snikket 在 %(site_name)s 上聊天,Snikket 是安全、隐私友好的聊天"
|
||||||
|
"应用。"
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:23
|
#: snikket_web/templates/invite_view.html:23
|
||||||
#, python-format
|
#, python-format
|
||||||
@@ -1493,8 +1508,8 @@ msgid ""
|
|||||||
"By continuing, you agree to the <a href=\"%(tos_uri)s\">Terms of Service</a> "
|
"By continuing, you agree to the <a href=\"%(tos_uri)s\">Terms of Service</a> "
|
||||||
"and <a href=\"%(privacy_uri)s\">Privacy Policy</a>."
|
"and <a href=\"%(privacy_uri)s\">Privacy Policy</a>."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"继续,即表示您同意 <a href=\"%(tos_uri)s\">服务条款</a> 和 <a href=\""
|
"继续,即表示您同意 <a href=\"%(tos_uri)s\">服务条款</a> 和 <a href="
|
||||||
"%(privacy_uri)s\">隐私政策</a>。"
|
"\"%(privacy_uri)s\">隐私政策</a>。"
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:27
|
#: snikket_web/templates/invite_view.html:27
|
||||||
msgid "Get started"
|
msgid "Get started"
|
||||||
@@ -1563,8 +1578,9 @@ msgstr "关闭"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"You can transfer this invite to your mobile device by scanning a code with "
|
"You can transfer this invite to your mobile device by scanning a code with "
|
||||||
"your camera. You can use either a QR scanner app or the Snikket app itself."
|
"your camera. You can use either a QR scanner app or the Snikket app itself."
|
||||||
msgstr "您可以使用相机扫描二维码将此邀请传送到您的移动设备。"
|
msgstr ""
|
||||||
"您可以使用二维码扫描仪应用或 Snikket 应用本身。"
|
"您可以使用相机扫描二维码将此邀请传送到您的移动设备。您可以使用二维码扫描仪应"
|
||||||
|
"用或 Snikket 应用本身。"
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:78
|
#: snikket_web/templates/invite_view.html:78
|
||||||
msgid "Install on iOS"
|
msgid "Install on iOS"
|
||||||
@@ -1574,8 +1590,9 @@ msgstr "在 iOS 安装"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"After downloading Snikket from the App Store, you have to return to this "
|
"After downloading Snikket from the App Store, you have to return to this "
|
||||||
"invite link and tap on \"Open the app\" to proceed."
|
"invite link and tap on \"Open the app\" to proceed."
|
||||||
msgstr "从 App Store 下载 Snikket "
|
msgstr ""
|
||||||
"后,您必须返回到此邀请链接,然后轻击“打开应用”继续。"
|
"从 App Store 下载 Snikket 后,您必须返回到此邀请链接,然后轻击“打开应用”继"
|
||||||
|
"续。"
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:86
|
#: snikket_web/templates/invite_view.html:86
|
||||||
msgid "First download Snikket from the App Store using the button below:"
|
msgid "First download Snikket from the App Store using the button below:"
|
||||||
@@ -1597,8 +1614,9 @@ msgstr "通过 F-Droid 安装"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"After installing Snikket via F-Droid, you have to return to this invite link "
|
"After installing Snikket via F-Droid, you have to return to this invite link "
|
||||||
"and tap on \"Open the app\" to proceed."
|
"and tap on \"Open the app\" to proceed."
|
||||||
msgstr "通过 F-Droid 安装 Snikket "
|
msgstr ""
|
||||||
"后,您必须返回到此邀请链接,然后轻击“打开应用”继续。"
|
"通过 F-Droid 安装 Snikket 后,您必须返回到此邀请链接,然后轻击“打开应用”继"
|
||||||
|
"续。"
|
||||||
|
|
||||||
#: snikket_web/templates/invite_view.html:114
|
#: snikket_web/templates/invite_view.html:114
|
||||||
msgid "First install Snikket from F-Droid using the button below:"
|
msgid "First install Snikket from F-Droid using the button below:"
|
||||||
@@ -1673,8 +1691,9 @@ msgstr "地址不正确"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"This Snikket service only hosts addresses ending in <em>@%(snikket_domain)s</"
|
"This Snikket service only hosts addresses ending in <em>@%(snikket_domain)s</"
|
||||||
"em>. Your password was not sent."
|
"em>. Your password was not sent."
|
||||||
msgstr "此 Snikket 服务仅托管以 <em>@%(snikket_domain)s</em> "
|
msgstr ""
|
||||||
"结尾的地址。未发送您的密码。"
|
"此 Snikket 服务仅托管以 <em>@%(snikket_domain)s</em> 结尾的地址。未发送您的密"
|
||||||
|
"码。"
|
||||||
|
|
||||||
#: snikket_web/templates/policies.html:4 snikket_web/templates/policies.html:10
|
#: snikket_web/templates/policies.html:4 snikket_web/templates/policies.html:10
|
||||||
msgid "Policies"
|
msgid "Policies"
|
||||||
@@ -1711,8 +1730,9 @@ msgid ""
|
|||||||
"To report policy violations or other abuse from this service, please send an "
|
"To report policy violations or other abuse from this service, please send an "
|
||||||
"email to %(email)s. Specify the domain name of this instance (%(domain)s) "
|
"email to %(email)s. Specify the domain name of this instance (%(domain)s) "
|
||||||
"and include details of the incident(s)."
|
"and include details of the incident(s)."
|
||||||
msgstr "要报告此服务违反策略或其他滥用行为,请发送电子邮件至 "
|
msgstr ""
|
||||||
"%(email)s。指定此实例的域名(%(domain)s)并包括事件的详情。"
|
"要报告此服务违反策略或其他滥用行为,请发送电子邮件至 %(email)s。指定此实例的"
|
||||||
|
"域名(%(domain)s)并包括事件的详情。"
|
||||||
|
|
||||||
#: snikket_web/templates/unauth.html:16
|
#: snikket_web/templates/unauth.html:16
|
||||||
msgid "Operation successful"
|
msgid "Operation successful"
|
||||||
@@ -1726,8 +1746,9 @@ msgstr "欢迎使用 Snikket!"
|
|||||||
msgid ""
|
msgid ""
|
||||||
"Now your Snikket instance is up and running, the next step is to invite "
|
"Now your Snikket instance is up and running, the next step is to invite "
|
||||||
"people to join it. Family, friends, colleagues... you choose!"
|
"people to join it. Family, friends, colleagues... you choose!"
|
||||||
msgstr "现在,您的 Snikket "
|
msgstr ""
|
||||||
"实例已经启动并运行,下一步就是邀请他人加入。家人、朋友、同事…由您选择!"
|
"现在,您的 Snikket 实例已经启动并运行,下一步就是邀请他人加入。家人、朋友、同"
|
||||||
|
"事…由您选择!"
|
||||||
|
|
||||||
#: snikket_web/templates/user_home.html:19
|
#: snikket_web/templates/user_home.html:19
|
||||||
msgid "Your account"
|
msgid "Your account"
|
||||||
@@ -1787,7 +1808,8 @@ msgid ""
|
|||||||
"To change your password, you need to provide the current password as well as "
|
"To change your password, you need to provide the current password as well as "
|
||||||
"the new one. To reduce the chance of typos, we ask for your new password "
|
"the new one. To reduce the chance of typos, we ask for your new password "
|
||||||
"twice."
|
"twice."
|
||||||
msgstr "要更改您的密码,您需要提供当前密码和新密码。为了减少输入错误的机会,我们会要"
|
msgstr ""
|
||||||
|
"要更改您的密码,您需要提供当前密码和新密码。为了减少输入错误的机会,我们会要"
|
||||||
"求您输入两次新密码。"
|
"求您输入两次新密码。"
|
||||||
|
|
||||||
#: snikket_web/templates/user_passwd.html:24
|
#: snikket_web/templates/user_passwd.html:24
|
||||||
|
|||||||