Compare commits

..

1 Commits

Author SHA1 Message Date
74530347e2 WIP: Groups support 2018-07-26 21:58:36 +02:00
10 changed files with 411 additions and 929 deletions

View File

@ -29,6 +29,7 @@ from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
from calsocial.account import AccountBlueprint from calsocial.account import AccountBlueprint
from calsocial.cache import CachedSessionInterface, cache from calsocial.cache import CachedSessionInterface, cache
from calsocial.group import GroupBlueprint
from calsocial.utils import RoutedMixin from calsocial.utils import RoutedMixin
@ -109,6 +110,7 @@ class CalendarSocialApp(Flask, RoutedMixin):
RoutedMixin.register_routes(self) RoutedMixin.register_routes(self)
AccountBlueprint().init_app(self, '/accounts/') AccountBlueprint().init_app(self, '/accounts/')
GroupBlueprint().init_app(self, '/groups/')
self.before_request(self.goto_first_steps) self.before_request(self.goto_first_steps)

View File

@ -28,7 +28,8 @@ from wtforms.ext.dateutil.fields import DateTimeField
from wtforms.validators import DataRequired, Email, StopValidation, ValidationError from wtforms.validators import DataRequired, Email, StopValidation, ValidationError
from wtforms.widgets import TextArea from wtforms.widgets import TextArea
from calsocial.models import EventVisibility, EVENT_VISIBILITY_TRANSLATIONS from calsocial.models import EventVisibility, EVENT_VISIBILITY_TRANSLATIONS, GroupVisibility, \
GROUP_VISIBILITY_TRANSLATIONS
class UsernameAvailable: # pylint: disable=too-few-public-methods class UsernameAvailable: # pylint: disable=too-few-public-methods
@ -411,3 +412,43 @@ class ProfileForm(FlaskForm):
self.builtin_avatar.choices = [(name, name) self.builtin_avatar.choices = [(name, name)
for name in current_app.config['BUILTIN_AVATARS']] for name in current_app.config['BUILTIN_AVATARS']]
self.profile = profile self.profile = profile
class HandleAvailable: # pylint: disable=too-few-public-methods
"""Checks if a group handle is available
"""
def __init__(self, message=None):
self.message = message
def __call__(self, form, field):
from sqlalchemy.orm.exc import NoResultFound
from calsocial.models import Group
# If there is no data, we dont raise an error; its not the task of this validator to
# check the validity of the username
if not field.data:
return
try:
Group.query.filter(Group.handle == field.data).one()
except NoResultFound:
return
if self.message is None:
message = field.gettext('This group handle is not available')
else:
message = self.message
field.errors[:] = []
raise StopValidation(message)
class GroupForm(FlaskForm):
"""Form for editing a group
"""
handle = StringField(label=_('Handle'), validators=[DataRequired(), HandleAvailable()])
display_name = StringField(label=_('Display name'))
visibility = EnumField(GroupVisibility, GROUP_VISIBILITY_TRANSLATIONS, label=_('Visibility'))

100
calsocial/group.py Normal file
View File

@ -0,0 +1,100 @@
# Calendar.social
# Copyright (C) 2018 Gergely Polonkai
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Group related endpoints for Calendar.social
"""
from flask import Blueprint, abort, redirect, render_template, url_for
from flask_security import current_user, login_required
from calsocial.utils import RoutedMixin
class GroupBlueprint(Blueprint, RoutedMixin):
def __init__(self):
Blueprint.__init__(self, 'group', __name__)
self.app = None
RoutedMixin.register_routes(self)
def init_app(self, app, url_prefix=None):
"""Initialise the blueprint, registering it with ``app``.
"""
self.app = app
app.register_blueprint(self, url_prefix=url_prefix)
@staticmethod
@RoutedMixin.route('/')
def list_groups():
"""View to list all public groups known by this instance
"""
from calsocial.models import db, Group, GroupMembership, GroupVisibility
groups = Group.query
if current_user.is_authenticated:
groups = groups.outerjoin(GroupMembership) \
.filter(db.or_(Group.visibility == GroupVisibility.public,
GroupMembership.profile == current_user.profile))
else:
groups = groups.filter(Group.visibility == GroupVisibility.public)
return render_template('group/list.html', groups=groups)
@staticmethod
@login_required
@RoutedMixin.route('/create', methods=['GET', 'POST'])
def create():
from datetime import datetime
from .forms import GroupForm
from .models import db, Group, GroupMemberLevel, GroupMembership
form = GroupForm()
if form.validate_on_submit():
group = Group(created_by=current_user.profile)
form.populate_obj(group)
db.session.add(group)
member = GroupMembership(group=group,
profile=current_user.profile,
level=GroupMemberLevel.admin,
accepted_at=datetime.utcnow(),
accepted_by=current_user.profile)
db.session.add(member)
db.session.commit()
return redirect(url_for('group.list'))
return render_template('group/create.html', form=form)
@staticmethod
@RoutedMixin.route('/<fqn>')
def display(fqn):
from .models import Group
group = Group.get_by_fqn(fqn)
if not group.visible_to(current_user.profile):
abort(404)
return render_template('group/display.html', group=group)

View File

@ -123,6 +123,64 @@ EVENT_VISIBILITY_TRANSLATIONS = {
} }
class GroupVisibility(Enum):
"""Enumeration for group visibility
"""
#: The group is secret, ie. completely unlisted
secret = 0
#: The group is closed, ie. it can be joined only with an invitation, but otherwise public
closed = 1
#: The group is public
public = 2
def __hash__(self):
return Enum.__hash__(self)
def __eq__(self, other):
if isinstance(other, str):
return self.name.lower() == other.lower() # pylint: disable=no-member
if isinstance(other, (int, float)):
return self.value == other # pylint: disable=comparison-with-callable
return Enum.__eq__(self, other)
GROUP_VISIBILITY_TRANSLATIONS = {
GroupVisibility.secret: _('Secret'),
GroupVisibility.closed: _('Closed'),
GroupVisibility.public: _('Public'),
}
class GroupMemberLevel(Enum):
"""Enumeration for group membership level
"""
#: The member is a spectator only (ie. read only)
spectator = 0
#: The member is a user with all privileges
user = 1
#: The member is a moderator
moderator = 2
#: The member is an administrator
admin = 3
GROUP_MEMBER_LEVEL_TRANSLATIONS = {
GroupMemberLevel.spectator: _('Spectator'),
GroupMemberLevel.user: _('User'),
GroupMemberLevel.moderator: _('Moderator'),
GroupMemberLevel.admin: _('Administrator'),
}
class SettingsProxy: class SettingsProxy:
"""Proxy object to get settings for a user """Proxy object to get settings for a user
""" """
@ -851,3 +909,150 @@ class AppState(app_state_base(db.Model)): # pylint: disable=too-few-public-meth
def __repr__(self): def __repr__(self):
return f'<AppState {self.env}:{self.key}="{self.value}"' return f'<AppState {self.env}:{self.key}="{self.value}"'
class Group(db.Model):
"""Database model for groups
"""
__tablename__ = 'groups'
__table_args__ = (
db.UniqueConstraint('handle', 'domain'),
)
id = db.Column(db.Integer(), primary_key=True)
handle = db.Column(db.String(length=50), nullable=False)
domain = db.Column(db.String(length=100), nullable=True)
display_name = db.Column(db.Unicode(length=100), nullable=True)
created_at = db.Column(db.DateTime(), default=datetime.utcnow)
created_by_id = db.Column(db.Integer(), db.ForeignKey('profiles.id'), nullable=False)
created_by = db.relationship('Profile')
default_level = db.Column(db.Enum(GroupMemberLevel))
visibility = db.Column(db.Enum(GroupVisibility), nullable=False)
@property
def members(self):
return Profile.query.join(GroupMembership, GroupMembership.profile_id == Profile.id).filter(GroupMembership.group == self)
@property
def fqn(self):
"""The fully qualified name of the group
For local profiles, this is in the form ``!username``; for remote users, its in the form
``!handle@domain``.
"""
if not self.domain:
return f'!{self.handle}'
return f'!{self.handle}@{self.domain}'
@property
def url(self):
"""Get the URL for this group
"""
from flask import url_for
return url_for('group.display', fqn=self.fqn)
def __repr__(self):
return f'<Group {self.id}: !{self.handle}@{self.domain}>'
def __str__(self):
return self.fqn
@classmethod
def get_by_fqn(cls, fqn):
import re
match = re.match(r'^!([a-z0-9_]+)(@.*)?', fqn)
if not match:
raise ValueError(f'Invalid Group FQN {fqn}')
handle, domain = match.groups()
return Group.query.filter(Group.handle == handle).filter(Group.domain == domain).one()
def visible_to(self, profile):
"""Checks whether this group is visible to ``profile``
It is so if the group is public or closed or, given it is secret, ``profile`` is a member
of the group.
:param profile: a :class:`Profile` object, or ``None`` to check for anonymous access
:type profile: Profile
:returns: ``True`` if the group is visible, ``False`` otherwise
:rtype: bool
"""
if self.visibility == GroupVisibility.secret:
try:
GroupMembership.query \
.filter(GroupMembership.group == self) \
.filter(GroupMembership.profile == profile) \
.one()
except NoResultFound:
return False
return True
def details_visible_to(self, profile):
"""Checks whether the details of this group is visible to ``profile``
Details include member list and events shared with this group.
It is so if the group is public or ``profile`` is a member of the group.
:param profile: a :class:`Profile` object, or ``None`` to check for anonymous access
:type profile: Profile
:returns: ``True`` if the group is visible, ``False`` otherwise
:rtype: bool
"""
if self.visibility != GroupVisibility.public:
try:
GroupMembership.query \
.filter(GroupMembership.group == self) \
.filter(GroupMembership.profile == profile) \
.one()
except NoResultFound:
return False
return True
class GroupMembership(db.Model):
"""Database model for group membership
"""
__tablename__ = 'group_members'
group_id = db.Column(db.Integer(), db.ForeignKey('groups.id'), primary_key=True)
group = db.relationship('Group')
profile_id = db.Column(db.Integer(), db.ForeignKey('profiles.id'), primary_key=True)
profile = db.relationship('Profile', foreign_keys=[profile_id])
level = db.Column(db.Enum(GroupMemberLevel), nullable=False)
requested_at = db.Column(db.DateTime(), default=datetime.utcnow, nullable=False)
joined_at = db.Column(db.DateTime(), nullable=True)
accepted_at = db.Column(db.DateTime(), nullable=True)
accepted_by_id = db.Column(db.Integer(), db.ForeignKey('profiles.id'), nullable=True)
accepted_by = db.relationship('Profile', foreign_keys=[accepted_by_id])

View File

@ -34,6 +34,7 @@
{%- endtrans %} {%- endtrans %}
</div> </div>
<a class="item" href="{{ url_for('hello') }}">{% trans %}Calendar view{% endtrans %}</a> <a class="item" href="{{ url_for('hello') }}">{% trans %}Calendar view{% endtrans %}</a>
<a class="item" href="{{ url_for('group.list_groups') }}">{% trans %}Groups{% endtrans %}</a>
<a class="item" href="{{ url_for('account.notifications') }}">{% trans %}Notifications{% endtrans %}</a> <a class="item" href="{{ url_for('account.notifications') }}">{% trans %}Notifications{% endtrans %}</a>
<a class="item" href="{{ url_for('account.settings') }}">{% trans %}Settings{% endtrans %}</a> <a class="item" href="{{ url_for('account.settings') }}">{% trans %}Settings{% endtrans %}</a>
<a class="item" href="{{ url_for('security.logout') }}">{% trans %}Logout{% endtrans %}</a> <a class="item" href="{{ url_for('security.logout') }}">{% trans %}Logout{% endtrans %}</a>

View File

@ -0,0 +1,17 @@
{% extends 'base.html' %}
{% from '_macros.html' import field %}
{% block content %}
<h2>{% trans %}Create a group{% endtrans %}</h2>
<form method="post" class="ui form">
{{ form.hidden_tag() }}
{{ field(form.handle) }}
{{ field(form.display_name) }}
{{ field(form.visibility) }}
<button class="ui primary button">{% trans %}Create{% endtrans %}</button>
<a href="{{ url_for('group.list_groups') }}" class="ui button">{% trans %}Cancel{% endtrans %}</a>
</form>
{% endblock content %}

View File

@ -0,0 +1,33 @@
{% extends 'base.html' %}
{% from '_macros.html' import profile_link %}
{% block content %}
<h2 class="ui header">
{% if group.visibility == 'secret' %}
[secret]
{% elif group.visibility == 'closed' %}
[closed]
{% elif group.visibility == 'public' %}
[public]
{% endif %}
{{group.display_name }}
<small>{{ group.fqn }}</small>
</h2>
{% if not current_user.profile or not current_user.profile.is_member_of(group) %}
{% if group.visibility == 'public' %}
Join
{% else %}
Request membership
{% endif %}
{% else %}
Invitation form
{% endif %}<br>
{% if group.details_visible_to(current_user.profile) %}
<h2>{% trans %}Members{% endtrans %}</h2>
{% for member in group.members %}
{{ profile_link(member) }}
{% endfor %}
{% else %}
The details of this grop are not visible to you.
{% endif %}
{% endblock content %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% block content %}
<h2>{% trans %}Groups{% endtrans %}</h2>
{% for group in groups %}
<a href="{{ group.url }}">{{ group }}</a>
{% else %}
{% trans %}No groups.{% endtrans %}
<a href="{{ url_for('group.create') }}">{% trans %}Do you want to create one?{% endtrans %}
{% endfor %}
{% endblock content %}

View File

@ -1,462 +0,0 @@
# Translations template for Calendar.social.
# Copyright (C) 2018 Sylke Vicious
# This file is distributed under the same license as the Calendar.social project.
# Sylke Vicious <silkevicious@layer8.space>, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: 0.1\n"
"Report-Msgid-Bugs-To: gergely@polonkai.eu\n"
"POT-Creation-Date: 2018-07-31 09:55+0200\n"
"PO-Revision-Date: 2018-07-31 09:56+0200\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
"X-Generator: Poedit 2.0.9\n"
"Last-Translator: Sylke Vicious <silkevicious@layer8.space>\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2);\n"
"Language: it\n"
#: calsocial/account.py:227
msgid "Cant invalidate your current session"
msgstr "Non puoi revocare la tua sessione corrente"
#: calsocial/forms.py:57
msgid "This username is not available"
msgstr "Questo nome utente non è disponibile"
#: calsocial/forms.py:88
msgid "This email address can not be used"
msgstr "Questo indirizzo email non può essere usato"
#: calsocial/forms.py:100
msgid "Username"
msgstr "Nome utente"
#: calsocial/forms.py:101
msgid "Email address"
msgstr "Indirizzo email"
#: calsocial/forms.py:102
msgid "Password"
msgstr "Password"
#: calsocial/forms.py:103
msgid "Password, once more"
msgstr "Ripeti la password"
#: calsocial/forms.py:112
msgid "The two passwords must match!"
msgstr "Le due password devono corrispondere!"
#: calsocial/forms.py:219
msgid "Title"
msgstr "Titolo"
#: calsocial/forms.py:220 calsocial/forms.py:260
msgid "Time zone"
msgstr "Fuso orario"
#: calsocial/forms.py:221
msgid "Start time"
msgstr "Ora d'inizio"
#: calsocial/forms.py:222
msgid "End time"
msgstr "Ora di fine"
#: calsocial/forms.py:223
msgid "All day"
msgstr "Tutto il giorno"
#: calsocial/forms.py:224
msgid "Description"
msgstr "Descrizione"
#: calsocial/forms.py:225
msgid "Visibility"
msgstr "Visibilità"
#: calsocial/forms.py:253
msgid "End time must be later than start time!"
msgstr ""
#: calsocial/forms.py:284
msgid "Username or email"
msgstr "Nome utente o email"
#: calsocial/forms.py:372
msgid "User is already invited"
msgstr "L'utente è già invitato"
#: calsocial/forms.py:382 calsocial/forms.py:396
msgid "Display name"
msgstr "Mostra nome"
#: calsocial/forms.py:385
msgid ""
"This will be shown to other users as your name. You can use your real "
"name, or any nickname you like."
msgstr "Questo sarà mostrato agli altri utenti come tuo nome. Puoi usare il tuo vero "
"nome, o qualsiasi altro soprannome che preferisci."
#: calsocial/forms.py:387
msgid "Your time zone"
msgstr "Il tuo fuso orario"
#: calsocial/forms.py:389
msgid "The start and end times of events will be displayed in this time zone."
msgstr "L'ora di inizio e di fine degli eventi sarà mostrata con questo fuso orario"
#: calsocial/forms.py:397
msgid "Use a built-in avatar"
msgstr "Usa un avatar predefinito"
#: calsocial/forms.py:398
msgid "Lock profile"
msgstr "Blocca profilo"
#: calsocial/models.py:75
#, python-format
msgid "%(actor)s followed you"
msgstr "%(actor)s ti segue"
#: calsocial/models.py:75
#, python-format
msgid "%(actor)s followed %(item)s"
msgstr "%(actor)s segue %(item)s"
#: calsocial/models.py:76
#, python-format
msgid "%(actor)s invited you to %(item)s"
msgstr "%(actor)s ti ha invitato a %(item)s"
#: calsocial/models.py:121
msgid "Visible only to attendees"
msgstr "Visibile solo ai partecipanti"
#: calsocial/models.py:122
msgid "Visible to everyone"
msgstr "Visibile a tutti"
#: calsocial/models.py:542
#, python-format
msgid "%(user)s logged in"
msgstr "%(user)s ha effettuato l'accesso"
#: calsocial/models.py:543
#, python-format
msgid "%(user)s failed to log in"
msgstr "%(user)s non è riuscito ad accedere"
#: calsocial/models.py:544
#, python-format
msgid "%(user)s logged out"
msgstr "%(user)s si è disconnesso"
#: calsocial/models.py:561
#, python-format
msgid "UNKNOWN RECORD \"%(log_type)s\" for %(user)s"
msgstr "RECORD SCONOSCIUTO \"%(log_type)s\" per %(user)s"
#: calsocial/calendar_system/gregorian.py:31
msgid "Gregorian"
msgstr "Gregoriano"
#: calsocial/calendar_system/gregorian.py:39
msgid "January"
msgstr "Gennaio"
#: calsocial/calendar_system/gregorian.py:40
msgid "February"
msgstr "Febbraio"
#: calsocial/calendar_system/gregorian.py:41
msgid "March"
msgstr "Marzo"
#: calsocial/calendar_system/gregorian.py:42
msgid "April"
msgstr "Aprile"
#: calsocial/calendar_system/gregorian.py:43
msgid "May"
msgstr "Maggio"
#: calsocial/calendar_system/gregorian.py:44
msgid "June"
msgstr "Giugno"
#: calsocial/calendar_system/gregorian.py:45
msgid "July"
msgstr "Luglio"
#: calsocial/calendar_system/gregorian.py:46
msgid "August"
msgstr "Agosto"
#: calsocial/calendar_system/gregorian.py:47
msgid "September"
msgstr "Settembre"
#: calsocial/calendar_system/gregorian.py:48
msgid "October"
msgstr "Ottobre"
#: calsocial/calendar_system/gregorian.py:49
msgid "November"
msgstr "Novembre"
#: calsocial/calendar_system/gregorian.py:50
msgid "December"
msgstr "Dicembre"
#: calsocial/calendar_system/gregorian.py:54
msgid "Monday"
msgstr "Lunedì"
#: calsocial/calendar_system/gregorian.py:55
msgid "Tuesday"
msgstr "Martedì"
#: calsocial/calendar_system/gregorian.py:56
msgid "Wednesday"
msgstr "Mercoledì"
#: calsocial/calendar_system/gregorian.py:57
msgid "Thursday"
msgstr "Giovedì"
#: calsocial/calendar_system/gregorian.py:58
msgid "Friday"
msgstr "Venerdì"
#: calsocial/calendar_system/gregorian.py:59
msgid "Saturday"
msgstr "Sabato"
#: calsocial/calendar_system/gregorian.py:60
msgid "Sunday"
msgstr "Domenica"
#: calsocial/templates/base.html:29 calsocial/templates/login.html:9
#: calsocial/templates/login.html:24 calsocial/templates/welcome.html:9
#: calsocial/templates/welcome.html:16
msgid "Login"
msgstr "Accedi"
#: calsocial/templates/base.html:32
#, python-format
msgid "Logged in as %(username)s"
msgstr "Effettuato l'accesso come %(username)s"
#: calsocial/templates/base.html:36
msgid "Calendar view"
msgstr "Vista calendario"
#: calsocial/templates/base.html:37
msgid "Notifications"
msgstr "Notifiche"
#: calsocial/templates/account/settings-base.html:8
#: calsocial/templates/account/user-settings.html:5
#: calsocial/templates/base.html:38
msgid "Settings"
msgstr "Impostazioni"
#: calsocial/templates/base.html:39
msgid "Logout"
msgstr "Disconnettiti"
#: calsocial/templates/base.html:48
msgid "About this instance"
msgstr "A proposito di questa istanza"
#: calsocial/templates/event-details.html:5
#, python-format
msgid ""
"This event is organised in the %(timezone)s time zone, in which it "
"happens between %(start_time)s and %(end_time)s"
msgstr ""
"Questo evento è organizzato nel fuso orario %(timezone)s, nel quale "
"avverrà tra le %(start_time)s e le %(end_time)s"
#: calsocial/templates/event-details.html:29
msgid "Invited users"
msgstr "Utenti invitati"
#: calsocial/templates/event-details.html:41
#: calsocial/templates/event-details.html:47
msgid "Invite"
msgstr "Invita"
#: calsocial/templates/account/first-steps.html:25
#: calsocial/templates/account/profile-edit.html:20
#: calsocial/templates/account/user-settings.html:18
#: calsocial/templates/event-edit.html:24
msgid "Save"
msgstr "Salva"
#: calsocial/templates/index.html:4
#, python-format
msgid "Welcome to Calendar.social, %(username)s!"
msgstr "Benvenuto su Calendar.social, %(username)s!"
#: calsocial/templates/index.html:10
msgid "Add event"
msgstr "Crea evento"
#: calsocial/templates/profile-details.html:10
#: calsocial/templates/profile-details.html:11
msgid "locked profile"
msgstr "profilo bloccato"
#: calsocial/templates/profile-details.html:17
msgid "Follow"
msgstr "Segui"
#: calsocial/templates/profile-details.html:21
msgid "Follows"
msgstr "Seguendo"
#: calsocial/templates/profile-details.html:29
msgid "Followers"
msgstr "Seguito da"
#: calsocial/templates/welcome.html:20
msgid "Or"
msgstr "O"
#: calsocial/templates/welcome.html:22
msgid "Register an account"
msgstr "Registra un account"
#: calsocial/templates/welcome.html:26
msgid "What is Calendar.social?"
msgstr "Cos'è Calendar.social"
#: calsocial/templates/welcome.html:28
msgid ""
"Calendar.social is a calendar app based on open protocols and free, open "
"source software."
msgstr ""
"Calendar.social è un'app calendario basata su protocolli aperti e software "
"open source."
#: calsocial/templates/welcome.html:29
msgid "It is decentralised like one of its counterparts, email."
msgstr "È decentralizzato come una delle sue controparti, l'email"
#: calsocial/templates/welcome.html:33
msgid "Go to your calendar"
msgstr "Vai al tuo calendario"
#: calsocial/templates/welcome.html:37
msgid "Peek inside"
msgstr "Uno sguardo all'interno"
#: calsocial/templates/welcome.html:43
msgid "Built with users in mind"
msgstr "Creato pensando agli utenti"
#: calsocial/templates/welcome.html:45
msgid ""
"From planning your appointments to organising large scale events "
"Calendar.social can help with all your scheduling needs."
msgstr ""
"Dalla pianificazione dei tuoi appuntamenti all'organizzazione di eventi su larga scala "
"Calendar.social ti può aiutare con tutte le tue esigenze di organizzazione."
#: calsocial/templates/welcome.html:54
msgid "user"
msgid_plural "users"
msgstr[0] "utente"
msgstr[1] "utenti"
#: calsocial/templates/welcome.html:60
msgid "event"
msgid_plural "events"
msgstr[0] "evento"
msgstr[1] "eventi"
#: calsocial/templates/welcome.html:67
msgid "Built for people"
msgstr "Creato per le persone"
#: calsocial/templates/welcome.html:69
msgid "Calendar.social is not a commercial network."
msgstr "Calendar.social non è una rete commerciale."
#: calsocial/templates/welcome.html:70
msgid "No advertising, no data mining, no walled gardens."
msgstr "No pubblicità, no analisi dei dati, no restrizioni proprietarie"
#: calsocial/templates/welcome.html:71
msgid "There is no central authority."
msgstr "Non esiste un'amministrazione centrale"
#: calsocial/templates/welcome.html:77
msgid "Administered by"
msgstr "Amministrato da"
#: calsocial/templates/account/active-sessions.html:4
#: calsocial/templates/account/settings-base.html:9
msgid "Active sessions"
msgstr "Sessioni attive"
#: calsocial/templates/account/active-sessions.html:10
msgid "Invalidate"
msgstr "Revoca"
#: calsocial/templates/account/first-steps.html:5
msgid "First steps"
msgstr "Primi passi"
#: calsocial/templates/account/first-steps.html:7
msgid "Welcome to Calendar.social!"
msgstr "Benvenuto su Calendar.social!"
#: calsocial/templates/account/first-steps.html:10
msgid ""
"These are the first steps you should make before you can start using the "
"site."
msgstr ""
"Questi sono i primi passi che dovresti fare prima di iniziare ad usare "
"il sito."
#: calsocial/templates/account/follow-requests.html:4
msgid "Follow requests"
msgstr "Richieste di seguirti"
#: calsocial/templates/account/follow-requests.html:10
msgid "Accept"
msgstr "Accetta"
#: calsocial/templates/account/follow-requests.html:15
msgid "No requests to display."
msgstr "Nessuna richiesta da mostrare."
#: calsocial/templates/account/follow-requests.html:19
msgid "Your profile is not locked."
msgstr "Il tuo profilo non è bloccato."
#: calsocial/templates/account/follow-requests.html:20
msgid "Anyone can follow you without your consent."
msgstr "Chiunque può seguirti senza chiederti il permesso."
#: calsocial/templates/account/notifications.html:7
msgid "Nothing to show."
msgstr "Niente da mostrare."
#: calsocial/templates/account/profile-edit.html:5
#: calsocial/templates/account/settings-base.html:7
msgid "Edit profile"
msgstr "Modifica profilo"
#: calsocial/templates/account/registration.html:17
msgid "Register"
msgstr "Registrati"

View File

@ -1,466 +0,0 @@
# Polish template for Calendar.social.
# Copyright (C) 2018 Marcin Mikołajczak
# This file is distributed under the same license as the Calendar.social project.
# Marcin Mikołajczak <me@m4sk.in>, 2018.
#
msgid ""
msgstr ""
"Project-Id-Version: 0.1\n"
"Report-Msgid-Bugs-To: gergely@polonkai.eu\n"
"POT-Creation-Date: 2018-07-30 22:05+0200\n"
"PO-Revision-Date: 2018-07-30 22:23+0200\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.6.0\n"
"X-Generator: Poedit 2.0.9\n"
"Last-Translator: Marcin Mikołajczak <me@m4sk.in>\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
"|| n%100>=20) ? 1 : 2);\n"
"Language: pl\n"
#: calsocial/account.py:227
msgid "Cant invalidate your current session"
msgstr "Nie udało się unieważnić obecnej sesji"
#: calsocial/forms.py:57
msgid "This username is not available"
msgstr "Ta nazwa użytkownika nie jest dostepna"
#: calsocial/forms.py:88
msgid "This email address can not be used"
msgstr "Nie można użyć tego adresu e-mail"
#: calsocial/forms.py:100
msgid "Username"
msgstr "Nazwa użytkownika"
#: calsocial/forms.py:101
msgid "Email address"
msgstr "Adres e-mail"
#: calsocial/forms.py:102
msgid "Password"
msgstr "Hasło"
#: calsocial/forms.py:103
msgid "Password, once more"
msgstr "Potwórz hasło"
#: calsocial/forms.py:112
msgid "The two passwords must match!"
msgstr "Hasła muszą do siebie pasować!"
#: calsocial/forms.py:219
msgid "Title"
msgstr "Tytuł"
#: calsocial/forms.py:220 calsocial/forms.py:260
msgid "Time zone"
msgstr "Strefa czasowa"
#: calsocial/forms.py:221
msgid "Start time"
msgstr "Czas rozpoczęcia"
#: calsocial/forms.py:222
msgid "End time"
msgstr "Czas zakończenia"
#: calsocial/forms.py:223
msgid "All day"
msgstr "Cały dzień"
#: calsocial/forms.py:224
msgid "Description"
msgstr "Opis"
#: calsocial/forms.py:225
msgid "Visibility"
msgstr "Widoczność"
#: calsocial/forms.py:253
msgid "End time must be later than start time!"
msgstr "Czas zakończenia musi nastąpić po czasie rozpoczęcia!"
#: calsocial/forms.py:284
msgid "Username or email"
msgstr "Nazwa użytkownika lub adres e-mail"
#: calsocial/forms.py:372
msgid "User is already invited"
msgstr "Użytkownik został już zaproszony"
#: calsocial/forms.py:382 calsocial/forms.py:396
msgid "Display name"
msgstr "Nazwa wyświetlana"
#: calsocial/forms.py:385
msgid ""
"This will be shown to other users as your name. You can use your real name, "
"or any nickname you like."
msgstr ""
"Będzie widoczna dla innych użytkowników jako Twoja nazwa. Może to być Twoje "
"imię i nazwisko lub dowolny pseudonim."
#: calsocial/forms.py:387
msgid "Your time zone"
msgstr "Twoja strefa czasowa"
#: calsocial/forms.py:389
msgid "The start and end times of events will be displayed in this time zone."
msgstr ""
"Czas rozpoczęcia i zakończenia wydarzeń będą widoczne w tej strefie czasowej."
#: calsocial/forms.py:397
msgid "Use a built-in avatar"
msgstr "Użyj wbudowanego awatara"
#: calsocial/forms.py:398
msgid "Lock profile"
msgstr "Zablokuj konto"
#: calsocial/models.py:75
#, python-format
msgid "%(actor)s followed you"
msgstr "%(actor)s zaczął Cię śledzić"
#: calsocial/models.py:75
#, python-format
msgid "%(actor)s followed %(item)s"
msgstr "%(actor)s zaczął śledzić %(item)s"
#: calsocial/models.py:76
#, python-format
msgid "%(actor)s invited you to %(item)s"
msgstr "%(actor)s zaprosił Cię na %(item)s"
#: calsocial/models.py:121
msgid "Visible only to attendees"
msgstr "Widoczne tylko dla uczestników"
#: calsocial/models.py:122
msgid "Visible to everyone"
msgstr "Widoczne dla wszystkich"
#: calsocial/models.py:542
#, python-format
msgid "%(user)s logged in"
msgstr "%(user)s zalogował się"
#: calsocial/models.py:543
#, python-format
msgid "%(user)s failed to log in"
msgstr "nie udało się zalogować %(user)s"
#: calsocial/models.py:544
#, python-format
msgid "%(user)s logged out"
msgstr "%(user)s wylogował się"
#: calsocial/models.py:561
#, python-format
msgid "UNKNOWN RECORD \"%(log_type)s\" for %(user)s"
msgstr "NIEZNANY WPIS \"%(log_type)s\" dla %(user)s"
#: calsocial/calendar_system/gregorian.py:31
msgid "Gregorian"
msgstr "Gregoriański"
#: calsocial/calendar_system/gregorian.py:39
msgid "January"
msgstr "Styczeń"
#: calsocial/calendar_system/gregorian.py:40
msgid "February"
msgstr "Luty"
#: calsocial/calendar_system/gregorian.py:41
msgid "March"
msgstr "Marzec"
#: calsocial/calendar_system/gregorian.py:42
msgid "April"
msgstr "Kwiecień"
#: calsocial/calendar_system/gregorian.py:43
msgid "May"
msgstr "Maj"
#: calsocial/calendar_system/gregorian.py:44
msgid "June"
msgstr "Czerwiec"
#: calsocial/calendar_system/gregorian.py:45
msgid "July"
msgstr "Lipiec"
#: calsocial/calendar_system/gregorian.py:46
msgid "August"
msgstr "Sierpień"
#: calsocial/calendar_system/gregorian.py:47
msgid "September"
msgstr "Wrzesień"
#: calsocial/calendar_system/gregorian.py:48
msgid "October"
msgstr "Październik"
#: calsocial/calendar_system/gregorian.py:49
msgid "November"
msgstr "Listopad"
#: calsocial/calendar_system/gregorian.py:50
msgid "December"
msgstr "Grudzień"
#: calsocial/calendar_system/gregorian.py:54
msgid "Monday"
msgstr "Poniedziałek"
#: calsocial/calendar_system/gregorian.py:55
msgid "Tuesday"
msgstr "Wtorek"
#: calsocial/calendar_system/gregorian.py:56
msgid "Wednesday"
msgstr "Środa"
#: calsocial/calendar_system/gregorian.py:57
msgid "Thursday"
msgstr "Czwartek"
#: calsocial/calendar_system/gregorian.py:58
msgid "Friday"
msgstr "Piątek"
#: calsocial/calendar_system/gregorian.py:59
msgid "Saturday"
msgstr "Sobota"
#: calsocial/calendar_system/gregorian.py:60
msgid "Sunday"
msgstr "Niedziela"
#: calsocial/templates/base.html:29 calsocial/templates/login.html:9
#: calsocial/templates/login.html:24 calsocial/templates/welcome.html:9
#: calsocial/templates/welcome.html:16
msgid "Login"
msgstr "Zaloguj się"
#: calsocial/templates/base.html:32
#, python-format
msgid "Logged in as %(username)s"
msgstr "Zalogowano jako %(username)s"
#: calsocial/templates/base.html:36
msgid "Calendar view"
msgstr "Widok kalendarza"
#: calsocial/templates/base.html:37
msgid "Notifications"
msgstr "Powiadomienia"
#: calsocial/templates/account/settings-base.html:8
#: calsocial/templates/account/user-settings.html:5
#: calsocial/templates/base.html:38
msgid "Settings"
msgstr "Ustawienia"
#: calsocial/templates/base.html:39
msgid "Logout"
msgstr "Wyloguj się"
#: calsocial/templates/base.html:48
msgid "About this instance"
msgstr "O tej instancji"
#: calsocial/templates/event-details.html:5
#, python-format
msgid ""
"This event is organised in the %(timezone)s time zone, in which it happens "
"between %(start_time)s and %(end_time)s"
msgstr ""
"To wydarzenie jest organizowane w strefie czasowej %(timezone)s, w której "
"wydarzy się ono pomiędzy %(start_time)s a %(end_time)s"
#: calsocial/templates/event-details.html:29
msgid "Invited users"
msgstr "Zaproszeni użytkownicy"
#: calsocial/templates/event-details.html:41
#: calsocial/templates/event-details.html:47
msgid "Invite"
msgstr "Zaproś"
#: calsocial/templates/account/first-steps.html:25
#: calsocial/templates/account/profile-edit.html:20
#: calsocial/templates/account/user-settings.html:18
#: calsocial/templates/event-edit.html:24
msgid "Save"
msgstr "Zapisz"
#: calsocial/templates/index.html:4
#, python-format
msgid "Welcome to Calendar.social, %(username)s!"
msgstr "Witaj na Calendar.social, %(username)s!"
#: calsocial/templates/index.html:10
msgid "Add event"
msgstr "Dodaj wydarzenie"
#: calsocial/templates/profile-details.html:10
#: calsocial/templates/profile-details.html:11
msgid "locked profile"
msgstr "profil zablokowany"
#: calsocial/templates/profile-details.html:17
msgid "Follow"
msgstr "Obserwuj"
#: calsocial/templates/profile-details.html:21
msgid "Follows"
msgstr "Obserwacje"
#: calsocial/templates/profile-details.html:29
msgid "Followers"
msgstr "Obserwujący"
#: calsocial/templates/welcome.html:20
msgid "Or"
msgstr "Lub"
#: calsocial/templates/welcome.html:22
msgid "Register an account"
msgstr "Zarejestruj się"
#: calsocial/templates/welcome.html:26
msgid "What is Calendar.social?"
msgstr "Czym jest Calendar.social?"
#: calsocial/templates/welcome.html:28
msgid ""
"Calendar.social is a calendar app based on open protocols and free, open "
"source software."
msgstr ""
"Calendar.social jest aplikacją kalendarza opartą na otwartych protokołach i "
"wolnym, otwartoźródłowym oprogramowaniu."
#: calsocial/templates/welcome.html:29
msgid "It is decentralised like one of its counterparts, email."
msgstr ""
"Jest zdecentralizowana tak, jak jak jeden z jej odpowiedników e-mail."
#: calsocial/templates/welcome.html:33
msgid "Go to your calendar"
msgstr "Przejdź do swojego kalendarza"
#: calsocial/templates/welcome.html:37
msgid "Peek inside"
msgstr "Zajrzyj do środka"
#: calsocial/templates/welcome.html:43
msgid "Built with users in mind"
msgstr "Stworzony z myślą o użytkownikach"
#: calsocial/templates/welcome.html:45
msgid ""
"From planning your appointments to organising large scale events Calendar."
"social can help with all your scheduling needs."
msgstr ""
"Calendar.social może pomóc w każdej potrzebie związanej z planowaniem, od "
"planowania spotkań do organizowania wydarzeń na większą skalę."
#: calsocial/templates/welcome.html:54
msgid "user"
msgid_plural "users"
msgstr[0] "użytkownik"
msgstr[1] "użytkowników"
msgstr[2] "użytkowników"
#: calsocial/templates/welcome.html:60
msgid "event"
msgid_plural "events"
msgstr[0] "wydarzenia"
msgstr[1] "wydarzenia"
msgstr[2] "wydarzeń"
#: calsocial/templates/welcome.html:67
msgid "Built for people"
msgstr "Zbudowany dla ludzi"
#: calsocial/templates/welcome.html:69
msgid "Calendar.social is not a commercial network."
msgstr "Calendar.social nie jest komercyjną siecią."
#: calsocial/templates/welcome.html:70
msgid "No advertising, no data mining, no walled gardens."
msgstr "Brak reklam, zbierania danych i ograniczeń."
#: calsocial/templates/welcome.html:71
msgid "There is no central authority."
msgstr "Brak centralnej władzy."
#: calsocial/templates/welcome.html:77
msgid "Administered by"
msgstr "Administrowane przez"
#: calsocial/templates/account/active-sessions.html:4
#: calsocial/templates/account/settings-base.html:9
msgid "Active sessions"
msgstr "Aktywne sesje"
#: calsocial/templates/account/active-sessions.html:10
msgid "Invalidate"
msgstr "Unieważnij"
#: calsocial/templates/account/first-steps.html:5
msgid "First steps"
msgstr "Pierwsze kroki"
#: calsocial/templates/account/first-steps.html:7
msgid "Welcome to Calendar.social!"
msgstr "Witamy na Calendar.social!"
#: calsocial/templates/account/first-steps.html:10
msgid ""
"These are the first steps you should make before you can start using the "
"site."
msgstr ""
"Oto pierwsze kroki, które powinieneś wykonać, zanim zaczniesz używać tej "
"strony."
#: calsocial/templates/account/follow-requests.html:4
msgid "Follow requests"
msgstr "Prośby o możliwość obserwacji"
#: calsocial/templates/account/follow-requests.html:10
msgid "Accept"
msgstr "Zaakceptuj"
#: calsocial/templates/account/follow-requests.html:15
msgid "No requests to display."
msgstr "Brak próśb do wyświetlenia."
#: calsocial/templates/account/follow-requests.html:19
msgid "Your profile is not locked."
msgstr "Twój profil nie jest zablokowany."
#: calsocial/templates/account/follow-requests.html:20
msgid "Anyone can follow you without your consent."
msgstr "Każdy może Cię zaobserwować bez Twojego zezwolenia."
#: calsocial/templates/account/notifications.html:7
msgid "Nothing to show."
msgstr "Nie ma nic do pokazania."
#: calsocial/templates/account/profile-edit.html:5
#: calsocial/templates/account/settings-base.html:7
msgid "Edit profile"
msgstr "Edytuj profil"
#: calsocial/templates/account/registration.html:17
msgid "Register"
msgstr "Zarejestruj się"