From aa8d4b39848c16c7a6ea0c889758819dabe04833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89lie=20Bouttier?= Date: Sat, 2 Dec 2017 02:34:23 +0100 Subject: [PATCH] templated email to all speakers of a list of talks --- cfp/emails.py | 36 +++ cfp/environment.py | 36 +++ cfp/forms.py | 39 ++- cfp/templates/cfp/staff/talk_email.html | 114 +++++++ cfp/templates/cfp/staff/talk_list.html | 8 + cfp/urls.py | 2 + cfp/views.py | 56 +++- locale/fr/LC_MESSAGES/django.mo | Bin 26202 -> 28877 bytes locale/fr/LC_MESSAGES/django.po | 386 +++++++++++++++--------- requirements.in | 1 + 10 files changed, 531 insertions(+), 147 deletions(-) create mode 100644 cfp/emails.py create mode 100644 cfp/environment.py create mode 100644 cfp/templates/cfp/staff/talk_email.html diff --git a/cfp/emails.py b/cfp/emails.py new file mode 100644 index 0000000..78b6c30 --- /dev/null +++ b/cfp/emails.py @@ -0,0 +1,36 @@ +from django.utils.translation import ugettext as _ +from django.utils.html import escape + +from pprint import pformat +from textwrap import indent + +from mailing.utils import send_message +from .environment import TalkEnvironment + + +def talk_email_render_preview(talk, speaker, subject, body): + env = TalkEnvironment(talk, speaker) + try: + subject = env.from_string(subject).render() + except Exception: + return _('There is an error in your subject template.') + try: + body = env.from_string(body).render() + except Exception: + return _('There is an error in your body template.') + context = {'talk': env.globals['talk'], 'speaker': env.globals['speaker']} + preview = '' + _('Environment:') + '\n\n' + escape(indent(pformat(context, indent='2'), ' ')) + preview += '\n\n' + _('Subject:') + ' ' + escape(subject) + '\n' + _('Body:') + '\n' + escape(body) + return preview + + +def talk_email_send(talks, subject, body): + sent = 0 + for talk in talks.all(): + for speaker in talk.speakers.all(): + env = TalkEnvironment(talk, speaker) + s = env.from_string(subject).render() + c = env.from_string(body).render() + send_message(speaker.conversation, talk.site.conference, subject=s, content=c) + sent += 1 + return sent diff --git a/cfp/environment.py b/cfp/environment.py new file mode 100644 index 0000000..ac8247a --- /dev/null +++ b/cfp/environment.py @@ -0,0 +1,36 @@ +from django.conf import settings + +from jinja2.sandbox import SandboxedEnvironment + +import pytz + + +def talk_to_dict(talk): + return { + 'title': talk.title, + 'description': talk.description, + 'category': str(talk.category), + 'accepted': talk.accepted, + 'confirmed': talk.confirmed, + 'start_date': talk.start_date.astimezone(tz=pytz.timezone(settings.TIME_ZONE)) if talk.start_date else None, + 'duration': talk.estimated_duration, + 'track': str(talk.track) if talk.track else '', + 'video': talk.video, + 'speakers': list(map(speaker_to_dict, talk.speakers.all())), + } + + +def speaker_to_dict(speaker): + return { + 'name': speaker.name, + 'email': speaker.email, + } + + +class TalkEnvironment(SandboxedEnvironment): + def __init__(self, talk, speaker, **options): + super().__init__(**options) + self.globals.update({ + 'talk': talk_to_dict(talk), + 'speaker': speaker_to_dict(speaker), + }) diff --git a/cfp/forms.py b/cfp/forms.py index 83ec3f7..1030e0d 100644 --- a/cfp/forms.py +++ b/cfp/forms.py @@ -11,6 +11,7 @@ from django_select2.forms import ModelSelect2MultipleWidget from .models import Participant, Talk, TalkCategory, Track, Tag, \ Conference, Room, Volunteer, Activity +from .environment import TalkEnvironment ACCEPTATION_CHOICES = [ @@ -182,6 +183,7 @@ class TalkActionForm(forms.Form): track = forms.ChoiceField(required=False, choices=[], label=_('Assign to a track')) tag = forms.ChoiceField(required=False, choices=[], label=_('Add a tag')) room = forms.ChoiceField(required=False, choices=[], label=_('Put in a room')) + email = forms.BooleanField(label=_('Send a email')) def __init__(self, *args, **kwargs): site = kwargs.pop('site') @@ -246,10 +248,45 @@ class ParticipantFilterForm(forms.Form): self.fields['track'].choices = [('none', _('Not assigned'))] + list(tracks.values_list('slug', 'name')) -class MailForm(forms.Form): +class EmailForm(forms.Form): email = forms.EmailField(required=True, label=_('Email')) +class PreviewMailForm(forms.Form): + speaker = forms.IntegerField() + talk = forms.IntegerField() + subject = forms.CharField(required=False) + body = forms.CharField(required=False) + + +class SendMailForm(forms.Form): + subject = forms.CharField() + body = forms.CharField(widget=forms.Textarea) + confirm = forms.BooleanField(required=False, label=_('I read my self twice, confirm sending')) + + def __init__(self, *args, **kwargs): + self._talks = kwargs.pop('talks') + super().__init__(*args, **kwargs) + self._env = dict() + + def clean_subject(self): + return self.clean_template('subject') + + def clean_body(self): + return self.clean_template('body') + + def clean_template(self, template): + try: + for talk in self._talks.all(): + for speaker in talk.speakers.all(): + env = self._env.get((talk, speaker), TalkEnvironment(talk, speaker)) + env.from_string(self.cleaned_data.get(template)).render() + except Exception as e: + raise forms.ValidationError(_("Your template does not compile (at least) with talk '%(talk)s' and speaker '%(speaker)s'.") % + {'talk': talk, 'speaker': speaker}) + return self.cleaned_data.get(template) + + class UsersWidget(ModelSelect2MultipleWidget): model = User search_fields = [ '%s__icontains' % field for field in UserAdmin.search_fields ] diff --git a/cfp/templates/cfp/staff/talk_email.html b/cfp/templates/cfp/staff/talk_email.html new file mode 100644 index 0000000..fb91066 --- /dev/null +++ b/cfp/templates/cfp/staff/talk_email.html @@ -0,0 +1,114 @@ +{% extends 'cfp/staff/base.html' %} +{% load i18n bootstrap3 staticfiles %} + +{% block talkstab %} class="active"{% endblock %} + +{% block content %} + + + +
+{% csrf_token %} + +

{% trans "Please write your email bellow:" %}

+ +

+ {% blocktrans %}You can use Jinja2 templating language.{% endblocktrans %} + {% blocktrans %}To see available environment variables, please click on a talk and speaker combination.{% endblocktrans %} +

+ +
+
+ {% bootstrap_form form exclude='confirm' %} +
+
+ +

{% trans "To preview your email, click on a speaker and talk combination:" %}

+ +
+
+ + +
+
+ +
+
+ +
+
+ {% if form.confirm %} + {% bootstrap_field form.confirm %} + {% buttons %} + + {% endbuttons %} + {% else %} + {% buttons %} + + {% endbuttons %} + {% endif %} +
+
+ +
+ +{% endblock %} + +{% block js_end %} +{{ block.super }} +{{ form.media.js }} + + +{% endblock %} + +{% block css %} +{{ block.super }} +{{ form.media.css }} +{% endblock %} diff --git a/cfp/templates/cfp/staff/talk_list.html b/cfp/templates/cfp/staff/talk_list.html index d639bc0..01c95d0 100644 --- a/cfp/templates/cfp/staff/talk_list.html +++ b/cfp/templates/cfp/staff/talk_list.html @@ -5,6 +5,14 @@ {% block content %} +{% if pending_email %} +
+ + {% url 'talk-email' as email_url %} + {% blocktrans %}You have a pending e-mail. To continue its edition, click here.{% endblocktrans %} +
+{% endif %} +

{% trans "Talks" %}

{% trans "Show filtering options…" %}

diff --git a/cfp/urls.py b/cfp/urls.py index c676c01..062cbde 100644 --- a/cfp/urls.py +++ b/cfp/urls.py @@ -37,6 +37,8 @@ urlpatterns = [ path('staff/talks//confirm/', views.talk_acknowledgment, {'confirm': True}, name='talk-confirm-by-staff'), path('staff/talks//desist/', views.talk_acknowledgment, {'confirm': False}, name='talk-desist-by-staff'), path('staff/talks//edit/', views.TalkUpdate.as_view(), name='talk-edit'), + path('staff/talks/email/', views.talk_email, name='talk-email'), + path('staff/talks/email/preview/', views.talk_email_preview, name='talk-email-preview'), path('staff/speakers/', views.participant_list, name='participant-list'), path('staff/speakers/add/', views.ParticipantCreate.as_view(), name='participant-add'), path('staff/speakers//', views.participant_details, name='participant-details'), diff --git a/cfp/views.py b/cfp/views.py index d1c8854..1c01648 100644 --- a/cfp/views.py +++ b/cfp/views.py @@ -5,13 +5,15 @@ from django.urls import reverse, reverse_lazy from django.utils.translation import ugettext_lazy as _ from django.views.generic import DeleteView, FormView, TemplateView from django.contrib import messages -from django.db.models import Q +from django.db.models import Q, Count, Sum from django.views.generic import CreateView, DetailView, ListView, UpdateView -from django.http import HttpResponse, Http404 +from django.http import HttpResponse, Http404, HttpResponseServerError from django.utils import timezone from django.core.exceptions import PermissionDenied from django.core.mail import send_mail from django.forms import modelform_factory +from django import forms +from django.views.decorators.http import require_http_methods from django_select2.views import AutoResponseView @@ -25,10 +27,11 @@ from .decorators import speaker_required, volunteer_required, staff_required from .mixins import StaffRequiredMixin, OnSiteMixin, OnSiteFormMixin from .utils import is_staff from .models import Participant, Talk, TalkCategory, Vote, Track, Tag, Room, Volunteer, Activity +from .emails import talk_email_send, talk_email_render_preview from .forms import TalkForm, TalkStaffForm, TalkFilterForm, TalkActionForm, get_talk_speaker_form_class, \ ParticipantForm, ParticipantFilterForm, NotifyForm, \ ConferenceForm, HomepageForm, CreateUserForm, TrackForm, RoomForm, \ - VolunteerForm, VolunteerFilterForm, MailForm, \ + VolunteerForm, VolunteerFilterForm, EmailForm, PreviewMailForm, SendMailForm, \ TagForm, TalkCategoryForm, ActivityForm, \ ACCEPTATION_VALUES, CONFIRMATION_VALUES @@ -90,7 +93,7 @@ Thanks! def volunteer_mail_token(request): - form = MailForm(request.POST or None) + form = EmailForm(request.POST or None) if request.method == 'POST' and form.is_valid(): try: volunteer = Volunteer.objects.get(site=request.conference.site, email=form.cleaned_data['email']) @@ -272,7 +275,7 @@ Thanks! def proposal_mail_token(request): - form = MailForm(request.POST or None) + form = EmailForm(request.POST or None) if request.method == 'POST' and form.is_valid(): try: speaker = Participant.objects.get(site=request.conference.site, email=form.cleaned_data['email']) @@ -642,6 +645,9 @@ def talk_list(request): if data['room']: talk.room = Room.objects.get(site=request.conference.site, slug=data['room']) talk.save() + if data['email']: + request.session['talk-email-list'] = data['talks'] + return redirect(reverse('talk-email')) return redirect(request.get_full_path()) # Sorting if request.GET.get('order') == 'desc': @@ -687,6 +693,7 @@ def talk_list(request): 'sort_urls': sort_urls, 'sort_glyphicons': sort_glyphicons, 'csv_link': csv_link, + 'pending_email': bool(request.session.get('talk-email-list', None)), }) @@ -777,6 +784,45 @@ def talk_decide(request, talk_id, accept): }) +@staff_required +def talk_email(request): + talks = Talk.objects.filter(pk__in=request.session.get('talk-email-list', [])) + count = talks.annotate(speakers_count=Count('speakers', distinct=True)).aggregate(Sum('speakers_count'))['speakers_count__sum'] + if not talks.exists(): + messages.error(request, _('Please select some talks.')) + return redirect('talk-list') + form = SendMailForm(request.POST or None, initial=request.session.get('talk-email-stored'), talks=talks) + if request.method == 'POST' and form.is_valid(): + subject = form.cleaned_data['subject'] + body = form.cleaned_data['body'] + request.session['talk-email-stored'] = {'subject': subject, 'body': body} + if form.cleaned_data['confirm']: + sent = talk_email_send(talks, subject, body) + messages.success(request, _('%(count)d mails have been sent.') % {'count': sent}) + del request.session['talk-email-list'] + return redirect('talk-list') + else: + messages.info(request, _('Your ready to send %(count)d emails.') % {'count': count}) + else: + form.fields.pop('confirm') + return render(request, 'cfp/staff/talk_email.html', { + 'talks': talks, + 'form': form, + }) + + +@require_http_methods(['POST']) +@staff_required +def talk_email_preview(request): + form = PreviewMailForm(request.POST or None) + if not form.is_valid(): + return HttpResponseServerError() + speaker = get_object_or_404(Participant, site=request.conference.site, pk=form.cleaned_data['speaker']) + talk = get_object_or_404(Talk, site=request.conference.site, pk=form.cleaned_data['talk']) + preview = talk_email_render_preview(talk, speaker, form.cleaned_data['subject'], form.cleaned_data['body']) + return HttpResponse(preview) + + @staff_required def participant_list(request): participants = Participant.objects.filter(site=request.conference.site) \ diff --git a/locale/fr/LC_MESSAGES/django.mo b/locale/fr/LC_MESSAGES/django.mo index ce4146db898c4a432854faad3f70b511ed2817e4..b193baab0625a8b1548b8022980716215d845755 100644 GIT binary patch delta 9110 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 delta 6985 zcmYk=3w+P@9>?+Df15FO-= zvCO7DCZYyjjEVRhhTv8V=l<4i3K29MN8RW$Ho}{znFcnq+Z&=r9*r7cD^t%z4Wu(_ zAVuhd!!a30Vk#~|4Qv-`!n<)0_qUEvP>1o&?T*q=1Ia^Wpf^V1{a6<#V>mvI8qiYI zfGaQ#-$rHVC^p7w)WB|{uJ?_$2N;M>od~0#3*t~WOf_~!-Jmyy;viJ&#-TFvBx*)Z z)OGVry9;%rmr&QQLrrM2IbVh8)ZdRL|E(zeOhY_I#ISDI1zTe=YI8l0O4%AzCbppl zaL{-Zb;FaW^Jh@k{fR9xh)y+-EWCkT@Ke>}oTQJ!UGg>om$qQDI4tXTOv7kKrM2wO z4$=&UpgJx_?fxkkgR@WrT#IDc+K$=nff-==iftLbnY=9IAA_- z#CQrd(+jAl;}&X-LsA$NMx$QA& zDy7>{Dc*zr_$BJX?@;G|LZ$dNYI6m&u~V9X{?v0&11vy2j)PE3IL*{us7?AV*604# zVG5+eI%PbEyb`QS7=V7fJ#;<-HPC3(fD%wkl!4kqg{aieLap&U)PNS5_6k&nR--bz z5uGZ0L_w)Kftul2)P>hjYke2hal^Lu(lo&_)RRy%S%RABCe&UyXuOQtJ3(po4HJ>K zvz3Q>pFEgG{>h^CC=I&s70kyEQExa0ZwZaK5o$)!sI`noWhM=kkz8W|hEOju^%3Yz zeJpAMkD#tANA)u!o&2jXp9bAvIcgvk*avrE8Tw|}nRpzNs4q12N=%{tDQd}Xpq~Hm zOuL?mB*7YhoU&#kbF(T@{akQT&;W`J&8RsJ!FW@jZqCm!^#!Osu@rUvDh$Cj zsIS}YsF@!!?MF}(Jcr7_Mby$dZ&C=O5Rh%}fml>VQc!E!&iDW-g)>lVKHK;*Y9Lh@ zjvt_|JBp9ub#s1fJA0sIs698$t~;%z6ttTwkpHap##2~CJ(MqTrLGuR4Qn}Sv+hI9 z_;c)t-(es|<=DRilCcf-3CJ9+m8g^-M1B4`cF^;m#PO4O3Qh8oa%Q{QTIqf=|O!!(@2-VRbJX;0~5e+^GV zb@)1JAX_j3KgO0=ZO(h=+IzfcjNUOigS*== zj20NhiCp6_)EZ4gJ=ZUwI@*BRbX!r6Ta{_whq}QbQ$LQ%RJCcphP6unF}-R3=8D1~3sdpvjnk(@_~(hgyR7P#O6JqaFMfL?4Xm z!Qg3+>B(gD{C`2Aj1yCP@kN2>*!r4DFWx?SZU>+;Ga8kNnb;Cts0{4D6x@&OGwWAe zftmL*DEtK1U_oDgOJEIl!d?9=YcKb=Zcy-7Lx1~!6!s&JymbTBo<6|dBe@tveK2bC zjX|Y&1~$j}sElkhzHQF$K=rfNc-Wl(2A%4l#xz_-ZH_-sn=*j)2*DPpy^xLid_UBZ z4KwvQ=uLewhT<~Rl07|$;3L?6Sc&B2a|sd;656{uoQK{EYwUFqc&d! zhNBzx!a9K3Ts5f2^crgO`3>QRD#oFfq!03>StY1V`W9*e$4~<}>!hIF{3GgutJn~O z@3TLUfZFw`r~&3+G!|h8EJe*|BdVV*s6FCF-C!^3`hysQ$5GecKuyruXsF$BENW!g zs2O)hbua>z;&C_upTZV+7Tcol{q~!#9i~w)McrUEs-F)~Ps<6^#O`1^hCSf9-f0z3 z;GJMiMXmi#)Gj`bnpri5<9Q6mTd0}VA7&3Q1S6?OntCQ`W}Q$=RA|l*#0cu8s0lrb z_4NF&q7XyF8XSvza4seex7T(vrc(bGHpZ*i9_v47&otjS8nuLTursd39IQrVCgLIc z2To7a1f5ui`&$brDAh}FGOjdrzY%7Js0$)ZJr1>|$*37+VGI_bHGXJ6R`hLqqZgh- z4X6ga@d|1Qe>3g3QJdD64W-XTp`NaIR3~+GGWw!(5CsQ?qeOiQF~kYVp=#ua#m(3p z_52Pcv{cH@RiYv7eXuz(kC;I;G1opqSqD$7=U-LUVI#tq^Pk{B?(dmxOB%HEbu1^= z6ZL32g8I&$fcoc{+M@+!ZM;e%p7NI36GoEZ*@!`$V`VKj(bio5f%!~R>eI9c-W2{u z<8ETO%Mlgk%%X*7&~v=aU%pj5+vz>K>3@F*Q|d`*D}GL_AX=H*bfEkTaSw5Z*hpL= zwC%JL?h+mNtQJoTt0T$2*6H~tBQ@+Iw2I@2kBBPbYeGjq8*7#EIgFr#j&>)Om$A^C zZ-9C>N{RQ0n}iN+$J%2N1<${UhM9}MqoJC3htP4s#`Cume_QjpIAST~g|+9XuciD9 z@u_L6LtE{k?WiM!$RmCxbm#%q@e0qRb)EQ)&|cM%O+?bk^SDP1=6DqUBt9hc{ix$K zF;WGNPF%AU_0T>@1W@mRdND^)t~~~t!b_<8dyXOe)xV1;ntDe(Zpweh3ZgOR-@xyQ z{=_Mw_Sj3|G7)DQe#I81oWXTVDDyq+`2qhdCexOo`)5)ZL&J1JZ*qS^hknOS!E)kv zq7I>>9nsfQ;+=@!68|Jtn>I1ZlzU@3{pfg?_<`^xG6@~mH2(++JBf2dOG3v!8|%EW z1Z#-4M0+}X8V?cOh-gB`y+jbvj`KfaF#ZQm5<31Oo*;Uu!jVi|RQ?|${!M&I%q4yz z#u5L2G&PlMI`yO62=`+b!khS*SU~7_k?`ldjwIp>Pl+F7M!n=?i2}lTi$AGEKJhZ~ z8WG6FBXBhF2Qib-5lkHOl`ST6N$w{3UPyIMqg)f2k`=-Barxqr$qig zqcD|NV>eh1GxAB)Q%!jrL4mBU^?j2p|E#;PBGlD4*Vi?^IBfm6$WceJp1!eRL{tm3Q9nuIzHhyIxv)Oh#JAtSr~gZZV!p zx+-V79dn<`@9XVKFP!QAvas6Ay{A_%A9rBCCw<)82j%*?iym6-aQ!)AlKY+FwqEY< jNBMfY3dTO`iXT_*ZZJN{+qJ4B&(&k%VRuz&W1s&5@|zP< diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 4c362a7..53cc2a2 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-01 20:44+0000\n" -"PO-Revision-Date: 2017-12-01 21:44+0100\n" +"POT-Creation-Date: 2017-12-10 15:23+0000\n" +"PO-Revision-Date: 2017-12-10 16:25+0100\n" "Last-Translator: \n" "Language-Team: \n" "Language: fr\n" @@ -100,167 +100,204 @@ msgstr "Profil modifié avec succès !" msgid "Please correct those errors." msgstr "Merci de corriger ces erreurs." -#: cfp/forms.py:17 +#: cfp/emails.py:28 +msgid "There is an error in your subject template." +msgstr "Il y a une erreur dans le gabarit du sujet." + +#: cfp/emails.py:32 +msgid "There is an error in your body template." +msgstr "Il y a une erreur dans le gabarit du corps." + +#: cfp/emails.py:34 +msgid "Environment:" +msgstr "Environnement :" + +#: cfp/emails.py:35 +msgid "Subject:" +msgstr "Sujet :" + +#: cfp/emails.py:35 +msgid "Body:" +msgstr "Corps :" + +#: cfp/forms.py:18 msgid "Pending decision" msgstr "Décision en attente" -#: cfp/forms.py:18 cfp/forms.py:125 cfp/forms.py:222 +#: cfp/forms.py:19 cfp/forms.py:126 cfp/forms.py:224 msgid "Accepted" msgstr "Accepté" -#: cfp/forms.py:19 +#: cfp/forms.py:20 msgid "Declined" msgstr "Décliné" -#: cfp/forms.py:28 +#: cfp/forms.py:29 msgid "Waiting" msgstr "En attente" -#: cfp/forms.py:29 cfp/forms.py:131 cfp/forms.py:228 cfp/models.py:379 +#: cfp/forms.py:30 cfp/forms.py:132 cfp/forms.py:230 cfp/models.py:379 msgid "Confirmed" msgstr "Confirmé" -#: cfp/forms.py:30 cfp/models.py:381 +#: cfp/forms.py:31 cfp/models.py:381 msgid "Cancelled" msgstr "Annulé" -#: cfp/forms.py:62 cfp/models.py:470 +#: cfp/forms.py:63 cfp/models.py:470 msgid "Activity" msgstr "Activité" -#: cfp/forms.py:72 +#: cfp/forms.py:73 msgctxt "activity" msgid "None" msgstr "Aucune" -#: cfp/forms.py:99 +#: cfp/forms.py:100 #, python-format msgid "Default duration: %(duration)d min" msgstr "Durée par défaut : %(duration)d min" -#: cfp/forms.py:107 cfp/forms.py:119 cfp/forms.py:216 +#: cfp/forms.py:108 cfp/forms.py:120 cfp/forms.py:218 #: cfp/templates/cfp/staff/talk_details.html:15 msgid "Category" msgstr "Catégorie" -#: cfp/forms.py:108 cfp/templates/cfp/staff/talk_list.html:43 +#: cfp/forms.py:109 cfp/templates/cfp/staff/talk_list.html:51 msgid "Title" msgstr "Titre" -#: cfp/forms.py:109 cfp/models.py:163 cfp/models.py:466 +#: cfp/forms.py:110 cfp/models.py:163 cfp/models.py:466 #: cfp/templates/cfp/proposal_talk_details.html:75 #: cfp/templates/cfp/staff/talk_details.html:64 msgid "Description" msgstr "Description" -#: cfp/forms.py:110 cfp/models.py:117 cfp/models.py:491 +#: cfp/forms.py:111 cfp/models.py:117 cfp/models.py:491 #: cfp/templates/cfp/staff/participant_details.html:24 #: cfp/templates/cfp/staff/talk_details.html:83 #: cfp/templates/cfp/staff/volunteer_details.html:22 msgid "Notes" msgstr "Notes" -#: cfp/forms.py:113 +#: cfp/forms.py:114 msgid "Visible by speakers" msgstr "Visible par les orateurs" -#: cfp/forms.py:137 cfp/forms.py:234 cfp/models.py:335 +#: cfp/forms.py:138 cfp/forms.py:236 cfp/models.py:335 #: cfp/templates/cfp/staff/talk_details.html:21 -#: cfp/templates/cfp/staff/talk_list.html:46 +#: cfp/templates/cfp/staff/talk_list.html:54 #: cfp/templates/cfp/staff/track_form.html:14 msgid "Track" msgstr "Session" -#: cfp/forms.py:143 +#: cfp/forms.py:144 msgid "Tag" msgstr "Étiquette" -#: cfp/forms.py:149 cfp/templates/cfp/staff/talk_details.html:88 +#: cfp/forms.py:150 cfp/templates/cfp/staff/talk_details.html:88 msgid "Vote" msgstr "Vote" -#: cfp/forms.py:150 +#: cfp/forms.py:151 msgid "Filter talks you already / not yet voted for" msgstr "" "Filtrer les propositions pour lesquelles vous avez déjà voté / pas encore " "voté" -#: cfp/forms.py:153 cfp/templates/cfp/staff/room_form.html:14 +#: cfp/forms.py:154 cfp/templates/cfp/staff/room_form.html:14 #: cfp/templates/cfp/staff/talk_details.html:38 msgid "Room" msgstr "Salle" -#: cfp/forms.py:154 +#: cfp/forms.py:155 msgid "Filter talks already / not yet affected to a room" msgstr "Filtrer les exposés déjà / pas encore affectées à une salle" -#: cfp/forms.py:157 +#: cfp/forms.py:158 msgid "Scheduled" msgstr "Programmé" -#: cfp/forms.py:158 +#: cfp/forms.py:159 msgid "Filter talks already / not yet scheduled" msgstr "Filtrer les exposés déjà / pas encore planifiées" -#: cfp/forms.py:161 cfp/models.py:353 +#: cfp/forms.py:162 cfp/models.py:353 #: cfp/templates/cfp/proposal_talk_details.html:89 #: cfp/templates/cfp/staff/talk_details.html:54 msgid "Materials" msgstr "Supports" -#: cfp/forms.py:162 +#: cfp/forms.py:163 msgid "Filter talks with / without materials" msgstr "Filtrer les exposés avec / sans supports" -#: cfp/forms.py:165 cfp/templates/cfp/proposal_talk_details.html:93 +#: cfp/forms.py:166 cfp/templates/cfp/proposal_talk_details.html:93 #: cfp/templates/cfp/staff/talk_details.html:58 msgid "Video" msgstr "Vidéo" -#: cfp/forms.py:166 +#: cfp/forms.py:167 msgid "Filter talks with / without video" msgstr "Filtrer les exposés avec / sans vidéo" -#: cfp/forms.py:175 cfp/forms.py:246 +#: cfp/forms.py:176 cfp/forms.py:248 msgid "Not assigned" msgstr "Pas encore assignée" -#: cfp/forms.py:181 +#: cfp/forms.py:182 msgid "Accept talk?" msgstr "Accepter la proposition ?" -#: cfp/forms.py:182 +#: cfp/forms.py:183 msgid "Assign to a track" msgstr "Assigner à une session" -#: cfp/forms.py:183 cfp/templates/cfp/admin/tag_list.html:11 +#: cfp/forms.py:184 cfp/templates/cfp/admin/tag_list.html:11 msgid "Add a tag" msgstr "Ajouter une étiquette" -#: cfp/forms.py:184 +#: cfp/forms.py:185 msgid "Put in a room" msgstr "Assigner à une salle" -#: cfp/forms.py:200 +#: cfp/forms.py:186 +msgid "Send a email" +msgstr "Envoyer un e-mail" + +#: cfp/forms.py:202 msgid "Notify by mail?" msgstr "Notifier par e-mail ?" -#: cfp/forms.py:250 cfp/models.py:486 +#: cfp/forms.py:252 cfp/models.py:486 #: cfp/templates/cfp/staff/volunteer_list.html:30 msgid "Email" msgstr "E-mail" -#: cfp/forms.py:269 +#: cfp/forms.py:258 +msgid "I read my self twice, confirm sending" +msgstr "Je me suis relu 2 fois, confirmer l’envoi" + +#: cfp/forms.py:278 +#, python-format +msgid "" +"Your template does not compile (at least) with talk '%(talk)s' and speaker " +"'%(speaker)s'." +msgstr "" +"Vos gabarits ne compile pas avec (au moins) l’exposé « %(talk)s » et " +"l’intervenant « %(speaker)s »." + +#: cfp/forms.py:299 msgid "New staff members will be informed of their new position by e-mail." msgstr "" "Les nouveaux membres du staff seront informés de leur nouveau rôle par " "courrier électronique." -#: cfp/forms.py:295 +#: cfp/forms.py:325 msgid "An user with that firstname and that lastname already exists." msgstr "Un utilisateur avec ce prénom et ce nom existe déjà." -#: cfp/forms.py:300 +#: cfp/forms.py:330 msgid "A user with that email already exists." msgstr "Un utilisateur avec cet email existe déjà." @@ -383,7 +420,7 @@ msgstr "Label dans le xml du programme" #: cfp/templates/cfp/staff/base.html:10 #: cfp/templates/cfp/staff/participant_list.html:8 #: cfp/templates/cfp/staff/talk_details.html:68 -#: cfp/templates/cfp/staff/talk_list.html:45 +#: cfp/templates/cfp/staff/talk_list.html:53 msgid "Speakers" msgstr "Orateurs" @@ -462,7 +499,7 @@ msgstr "En cours, score : %(score).1f" #: cfp/templates/cfp/admin/base.html:14 #: cfp/templates/cfp/staff/volunteer_details.html:27 #: cfp/templates/cfp/staff/volunteer_list.html:32 -#: cfp/templates/cfp/volunteer.html:43 +#: cfp/templates/cfp/volunteer_dashboard.html:43 msgid "Activities" msgstr "Activités" @@ -514,7 +551,7 @@ msgstr "Catégories" #: cfp/templates/cfp/admin/base.html:13 cfp/templates/cfp/admin/tag_list.html:9 #: cfp/templates/cfp/staff/talk_details.html:28 -#: cfp/templates/cfp/staff/talk_list.html:47 +#: cfp/templates/cfp/staff/talk_list.html:55 msgid "Tags" msgstr "Étiquettes" @@ -597,6 +634,35 @@ msgstr "" "Si vous avez déjà soumis une proposition et que vous souhaitez l’éditer, " "cliquez ici." +#: cfp/templates/cfp/mails/speaker_send_token.txt:1 +msgid "" +"Hi {},\n" +"\n" +"Someone, probably you, ask to access your profile.\n" +"You can edit your talks or add new ones following this url:\n" +"\n" +" {}\n" +"\n" +"If you have any question, your can answer to this email.\n" +"\n" +"Sincerely,\n" +"\n" +"{}\n" +msgstr "" +"Bonjour {},\n" +"\n" +"Quelqu’un, sans doute vous, a demandé à accéder à votre profil.\n" +"Vous pouvez modifier vos propositions ou en soumettre de nouvelles à l’url " +"suivante :\n" +"\n" +" {}\n" +"\n" +"Si vous avez une question, vous pouvez répondre à ce mail.\n" +"\n" +"Sincèrement,\n" +"\n" +"{}\n" + #: cfp/templates/cfp/mails/volunteer_send_token.txt:1 #, python-format msgid "" @@ -641,19 +707,19 @@ msgid "Nope, Abort" msgstr "Non, annuler" #: cfp/templates/cfp/proposal_dashboard.html:11 -#: cfp/templates/cfp/volunteer.html:12 +#: cfp/templates/cfp/volunteer_dashboard.html:12 #, python-format msgid "Welcome %(name)s!" msgstr "Bienvenue %(name)s !" #: cfp/templates/cfp/proposal_dashboard.html:13 #: cfp/templates/cfp/proposal_speaker_form.html:21 -#: cfp/templates/cfp/volunteer.html:14 +#: cfp/templates/cfp/volunteer_dashboard.html:14 msgid "Edit your profile" msgstr "Éditer votre profil" #: cfp/templates/cfp/proposal_dashboard.html:18 -#: cfp/templates/cfp/volunteer.html:20 +#: cfp/templates/cfp/volunteer_dashboard.html:20 msgid "Your informations" msgstr "Vos informations" @@ -695,7 +761,7 @@ msgstr "Mastodon :" #: cfp/templates/cfp/proposal_dashboard.html:29 #: cfp/templates/cfp/staff/participant_details.html:38 #: cfp/templates/cfp/staff/volunteer_details.html:14 -#: cfp/templates/cfp/volunteer.html:27 +#: cfp/templates/cfp/volunteer_dashboard.html:27 msgid "Phone number:" msgstr "Numéro de téléphone :" @@ -715,7 +781,7 @@ msgstr "avec" #: cfp/templates/cfp/staff/participant_details.html:53 #: cfp/templates/cfp/staff/room_details.html:21 #: cfp/templates/cfp/staff/room_details.html:39 -#: cfp/templates/cfp/staff/talk_list.html:69 +#: cfp/templates/cfp/staff/talk_list.html:77 msgid "and" msgstr "et" @@ -723,13 +789,13 @@ msgstr "et" msgid "you must confirm you participation" msgstr "vous devez confirmer votre participation" -#: cfp/templates/cfp/proposal_dashboard.html:61 cfp/views.py:624 -#: cfp/views.py:743 +#: cfp/templates/cfp/proposal_dashboard.html:61 cfp/views.py:626 +#: cfp/views.py:751 msgid "accepted" msgstr "accepté" -#: cfp/templates/cfp/proposal_dashboard.html:63 cfp/views.py:378 -#: cfp/views.py:525 +#: cfp/templates/cfp/proposal_dashboard.html:63 cfp/views.py:380 +#: cfp/views.py:527 msgid "cancelled" msgstr "annulé" @@ -793,7 +859,7 @@ msgstr "Éditer cette proposition" #: cfp/templates/cfp/proposal_talk_details.html:28 #: cfp/templates/cfp/staff/talk_details.html:18 -#: cfp/templates/cfp/staff/talk_list.html:48 +#: cfp/templates/cfp/staff/talk_list.html:56 msgid "Status" msgstr "Statut" @@ -867,7 +933,7 @@ msgstr "Programme" #: cfp/templates/cfp/staff/base.html:11 #: cfp/templates/cfp/staff/participant_details.html:43 -#: cfp/templates/cfp/staff/talk_list.html:8 +#: cfp/templates/cfp/staff/talk_list.html:16 msgid "Talks" msgstr "Exposés" @@ -945,19 +1011,19 @@ msgid "Add a speaker" msgstr "Ajouter un intervenant" #: cfp/templates/cfp/staff/participant_list.html:12 -#: cfp/templates/cfp/staff/talk_list.html:10 +#: cfp/templates/cfp/staff/talk_list.html:18 #: cfp/templates/cfp/staff/volunteer_list.html:11 msgid "Show filtering options…" msgstr "Afficher les options de filtrage…" #: cfp/templates/cfp/staff/participant_list.html:32 -#: cfp/templates/cfp/staff/talk_list.html:31 +#: cfp/templates/cfp/staff/talk_list.html:39 #: cfp/templates/cfp/staff/volunteer_list.html:19 msgid "Filter" msgstr "Filtrer" #: cfp/templates/cfp/staff/participant_list.html:38 -#: cfp/templates/cfp/staff/talk_list.html:39 +#: cfp/templates/cfp/staff/talk_list.html:47 #: cfp/templates/cfp/staff/volunteer_list.html:25 msgid "Total:" msgstr "Total :" @@ -976,7 +1042,7 @@ msgid "contact by email" msgstr "contacter par e-mail" #: cfp/templates/cfp/staff/participant_list.html:53 -#: cfp/templates/cfp/staff/talk_list.html:54 +#: cfp/templates/cfp/staff/talk_list.html:62 #: cfp/templates/cfp/staff/volunteer_list.html:40 msgid "download as csv" msgstr "télécharger au format CSV" @@ -1028,7 +1094,7 @@ msgid "Some talks are not scheduled yet." msgstr "Certains exposés ne sont pas encore planifiés." #: cfp/templates/cfp/staff/room_list.html:24 -#: cfp/templates/cfp/staff/talk_list.html:39 +#: cfp/templates/cfp/staff/talk_list.html:47 #: cfp/templates/cfp/staff/track_list.html:21 msgid "talk" msgstr "exposé" @@ -1145,19 +1211,66 @@ msgstr "" "Commenter cette proposition – ce message sera reçu uniquement par " "l’équipe d’organisation" +#: cfp/templates/cfp/staff/talk_email.html:9 +msgid "Send an email to each speaker of each talk" +msgstr "Envoyer un e-mail à chaque intervenant de chaque exposé" + +#: cfp/templates/cfp/staff/talk_email.html:15 +msgid "Please write your email bellow:" +msgstr "Veuillez écrire votre e-mail ci-dessous :" + +#: cfp/templates/cfp/staff/talk_email.html:18 +msgid "" +"You can use Jinja2 " +"templating language." +msgstr "" +"Vous pouvez utiliser le langage de gabarit Jinja2." + +#: cfp/templates/cfp/staff/talk_email.html:19 +msgid "" +"To see available environment variables, please click on a talk and speaker " +"combination." +msgstr "" +"Pour voir les variables d’environnement disponibles, veuillez cliquer sur " +"une combinaison d’intervenant et exposé." + +#: cfp/templates/cfp/staff/talk_email.html:28 +msgid "To preview your email, click on a speaker and talk combination:" +msgstr "" +"Pour voir un aperçu de votre e-mail, cliquez sur une combinaison " +"d'intervenant et exposé." + +#: cfp/templates/cfp/staff/talk_email.html:52 +msgid "Send!" +msgstr "Envoyer !" + +#: cfp/templates/cfp/staff/talk_email.html:56 +msgid "Check template validity" +msgstr "Vérifier la validité des gabarits" + #: cfp/templates/cfp/staff/talk_form.html:10 msgid "Edit a talk" msgstr "Éditer un exposé" -#: cfp/templates/cfp/staff/talk_list.html:44 +#: cfp/templates/cfp/staff/talk_list.html:12 +#, python-format +msgid "" +"You have a pending e-mail. To continue its edition, click here." +msgstr "" +"Vous avez un e-mail en attente d’envoi. Pour continuer son édition, cliquez " +"ici." + +#: cfp/templates/cfp/staff/talk_list.html:52 msgid "Intervention kind" msgstr "Type d’intervention" -#: cfp/templates/cfp/staff/talk_list.html:87 +#: cfp/templates/cfp/staff/talk_list.html:95 msgid "For selected talks:" msgstr "Pour les exposés sélectionnés :" -#: cfp/templates/cfp/staff/talk_list.html:92 +#: cfp/templates/cfp/staff/talk_list.html:100 msgid "Apply" msgstr "Appliquer" @@ -1178,7 +1291,7 @@ msgid "Volunteer" msgstr "Bénévole" #: cfp/templates/cfp/staff/volunteer_details.html:11 -#: cfp/templates/cfp/volunteer.html:25 +#: cfp/templates/cfp/volunteer_dashboard.html:25 msgid "Email:" msgstr "E-mail :" @@ -1202,20 +1315,20 @@ msgstr "bénévole" msgid "Phone" msgstr "Téléphone" -#: cfp/templates/cfp/volunteer.html:32 +#: cfp/templates/cfp/volunteer_dashboard.html:32 msgctxt "phone number" msgid "not provided" msgstr "non fourni" -#: cfp/templates/cfp/volunteer.html:36 +#: cfp/templates/cfp/volunteer_dashboard.html:36 msgid "Notes:" msgstr "Notes :" -#: cfp/templates/cfp/volunteer.html:53 +#: cfp/templates/cfp/volunteer_dashboard.html:53 msgid "I will be happy to help on that!" msgstr "Je serai heureux d’aider à cela !" -#: cfp/templates/cfp/volunteer.html:55 +#: cfp/templates/cfp/volunteer_dashboard.html:55 msgid "Sorry, I have a setback" msgstr "Désolé, j’ai un contretemps" @@ -1241,7 +1354,7 @@ msgstr "" "mettre à jour vos disponibilités, cliquez ici." -#: cfp/views.py:66 +#: cfp/views.py:68 msgid "" "Hi {},\n" "\n" @@ -1269,44 +1382,44 @@ msgstr "" "{}\n" "\n" -#: cfp/views.py:82 +#: cfp/views.py:84 #, python-format msgid "[%(conference)s] Thank you for your help!" msgstr "[%(conference)s] Merci pour votre aide !" -#: cfp/views.py:85 +#: cfp/views.py:87 msgid "" "Thank you for your participation! You can now subscribe to some activities." msgstr "" "Merci pour votre participation ! Vous pouvez maintenant vous inscrire à une " "ou plusieurs activités." -#: cfp/views.py:99 cfp/views.py:281 +#: cfp/views.py:101 cfp/views.py:283 msgid "Sorry, we do not know this email." msgstr "Désolé, nous ne connaissons pas cette e-mail." -#: cfp/views.py:112 cfp/views.py:303 +#: cfp/views.py:114 cfp/views.py:305 #, python-format msgid "[%(conference)s] Someone asked to access your profil" msgstr "[%(conference)s] Quelqu’un a demandé à accéder à votre profil" -#: cfp/views.py:115 cfp/views.py:308 +#: cfp/views.py:117 cfp/views.py:310 msgid "A email have been sent with a link to access to your profil." msgstr "Un e-mail vous a été envoyé avec un lien pour accéder à votre profil." -#: cfp/views.py:135 cfp/views.py:346 cfp/views.py:433 +#: cfp/views.py:137 cfp/views.py:348 cfp/views.py:435 msgid "Changes saved." msgstr "Modifications sauvegardées." -#: cfp/views.py:148 +#: cfp/views.py:150 msgid "Thank you for your participation!" msgstr "Merci pour votre participation !" -#: cfp/views.py:151 +#: cfp/views.py:153 msgid "Okay, no problem!" msgstr "Ok, pas de soucis !" -#: cfp/views.py:234 +#: cfp/views.py:236 msgid "" "Hi {},\n" "\n" @@ -1346,16 +1459,16 @@ msgstr "" "{}\n" "\n" -#: cfp/views.py:261 +#: cfp/views.py:263 #, python-format msgid "[%(conference)s] Thank you for your proposition '%(talk)s'" msgstr "[%(conference)s] Merci pour votre proposition « %(talk)s »" -#: cfp/views.py:267 cfp/views.py:350 +#: cfp/views.py:269 cfp/views.py:352 msgid "You proposition have been successfully submitted!" msgstr "Votre proposition a été transmise avec succès !" -#: cfp/views.py:286 +#: cfp/views.py:288 msgid "" "Hi {},\n" "\n" @@ -1386,37 +1499,37 @@ msgstr "" "{}\n" "\n" -#: cfp/views.py:367 +#: cfp/views.py:369 msgid "You already confirmed your participation to this talk." msgstr "Vous avez déjà confirmé votre participation à cet exposé." -#: cfp/views.py:369 +#: cfp/views.py:371 msgid "You already cancelled your participation to this talk." msgstr "Vous avez déjà annulé votre participation à cet exposé." -#: cfp/views.py:374 +#: cfp/views.py:376 msgid "Your participation has been taken into account, thank you!" msgstr "Votre participation a été prise en compte, merci !" -#: cfp/views.py:375 cfp/views.py:521 +#: cfp/views.py:377 cfp/views.py:523 msgid "confirmed" msgstr "confirmé" -#: cfp/views.py:377 +#: cfp/views.py:379 msgid "We have noted your unavailability." msgstr "Nous avons enregistré votre indisponibilité." -#: cfp/views.py:379 +#: cfp/views.py:381 #, python-format msgid "Speaker %(speaker)s %(action)s his/her participation for %(talk)s." msgstr "L’orateur %(speaker)s a %(action) sa participation pour %(talk)s." -#: cfp/views.py:387 +#: cfp/views.py:389 #, python-format msgid "[%(conference)s] %(speaker)s %(action)s his/her participation" msgstr "[%(conference)s] %(speaker)s a %(action) sa participation" -#: cfp/views.py:440 +#: cfp/views.py:442 msgid "" "Hi {},\n" "\n" @@ -1456,87 +1569,101 @@ msgstr "" "{}\n" "\n" -#: cfp/views.py:468 +#: cfp/views.py:470 #, python-format msgid "[%(conference)s] You have been added as co-speaker to '%(talk)s'" msgstr "" "[%(conference)s] Vous avez été ajouté comme co-intervenant pour « %(talk)s »" -#: cfp/views.py:474 cfp/views.py:494 +#: cfp/views.py:476 cfp/views.py:496 msgid "Co-speaker successfully added to the talk." msgstr "Co-intervenant ajouté à l’exposé avec succès." -#: cfp/views.py:507 +#: cfp/views.py:509 msgid "Co-speaker successfully removed from the talk." msgstr "Co-intervenant supprimé de l’exposé avec succès." -#: cfp/views.py:520 +#: cfp/views.py:522 msgid "The speaker confirmation have been noted." msgstr "La confirmation de l’orateur a été notée." -#: cfp/views.py:522 +#: cfp/views.py:524 msgid "The talk have been confirmed." msgstr "L’exposé a été confirmé." -#: cfp/views.py:524 +#: cfp/views.py:526 msgid "The speaker unavailability have been noted." msgstr "L’indisponibilité de l’intervenant a été notée." -#: cfp/views.py:526 +#: cfp/views.py:528 #, python-format msgid "The talk have been %(action)s." msgstr "L’exposé a été %(action)s." -#: cfp/views.py:530 +#: cfp/views.py:532 #, python-format msgid "[%(conference)s] The talk '%(talk)s' have been %(action)s." msgstr "[%(conference)s] L’exposé « %(talk)s » a été %(action)s." -#: cfp/views.py:626 cfp/views.py:745 +#: cfp/views.py:628 cfp/views.py:753 msgid "declined" msgstr "décliné" -#: cfp/views.py:627 cfp/views.py:769 +#: cfp/views.py:629 cfp/views.py:777 #, python-format msgid "The talk has been %(action)s." msgstr "L’exposé a été %(action)s." -#: cfp/views.py:631 cfp/views.py:764 +#: cfp/views.py:633 cfp/views.py:772 #, python-format msgid "[%(conference)s] The talk '%(talk)s' have been %(action)s" msgstr "[%(conference)s] L’exposé « %(talk)s » a été %(action)s" -#: cfp/views.py:704 +#: cfp/views.py:710 #, python-format msgid "[%(conference)s] New comment about '%(talk)s'" msgstr "[%(conference)s] Nouveau commentaire sur « %(talk)s »" -#: cfp/views.py:718 cfp/views.py:841 +#: cfp/views.py:724 cfp/views.py:893 msgid "Message sent!" msgstr "Message envoyé !" -#: cfp/views.py:732 +#: cfp/views.py:740 msgid "Vote successfully created" msgstr "A voté !" -#: cfp/views.py:732 +#: cfp/views.py:740 msgid "Vote successfully updated" msgstr "Vote mis à jour" -#: cfp/views.py:753 +#: cfp/views.py:761 #, python-format msgid "[%(conference)s] Your talk '%(talk)s' have been %(action)s" msgstr "[%(conference)s] Votre exposé « %(talk)s » a été %(action)s" -#: cfp/views.py:771 +#: cfp/views.py:779 msgid "Decision taken in account" msgstr "Décision enregistrée" -#: cfp/views.py:911 +#: cfp/views.py:792 +msgid "Please select some talks." +msgstr "Veuillez sélectionner un ou plusieurs exposés." + +#: cfp/views.py:801 +#, python-format +msgid "%(count)d mails have been sent." +msgstr "%(count)d e-mails ont été envoyés." + +#: cfp/views.py:805 +#, python-format +msgid "Your ready to send %(count)d emails." +msgstr "Vous êtes prêt pour envoyer %(count)d e-mails." + +#: cfp/views.py:963 msgid "[{}] You have been added to the staff team" msgstr "[{}] Vous avez été ajouté aux membres du staff" -#: cfp/views.py:912 +#: cfp/views.py:964 msgid "" "Hi {},\n" "\n" @@ -1560,15 +1687,15 @@ msgstr "" "{}\n" "\n" -#: cfp/views.py:933 cfp/views.py:945 +#: cfp/views.py:985 cfp/views.py:997 msgid "Modifications successfully saved." msgstr "Modification enregistrée avec succès." -#: cfp/views.py:1109 +#: cfp/views.py:1161 msgid "User created successfully." msgstr "Utilisateur créé avec succès." -#: cfp/views.py:1130 +#: cfp/views.py:1182 #, python-format msgid "Format '%s' not available" msgstr "Format '%s' non disponible" @@ -1586,11 +1713,11 @@ msgstr "Envoyer" msgid "No messages." msgstr "Aucun message." -#: ponyconf/settings.py:142 +#: ponyconf/settings.py:141 msgid "English" msgstr "Anglais" -#: ponyconf/settings.py:143 +#: ponyconf/settings.py:142 msgid "French" msgstr "Français" @@ -1642,33 +1769,13 @@ msgstr "Mot de passe oublié ?" msgid "Password Change" msgstr "Changement de mot de passe" -#~ msgid "" -#~ "Hi {},\n" -#~ "\n" -#~ "Someone, probably you, ask to access your profile.\n" -#~ "You can edit your talks or add new ones following this url:\n" -#~ "\n" -#~ " {}\n" -#~ "\n" -#~ "If you have any question, your can answer to this email.\n" -#~ "\n" -#~ "Sincerely,\n" -#~ "\n" -#~ "{}\n" -#~ msgstr "" -#~ "Bonjour {},\n" -#~ "\n" -#~ "Quelqu’un, sans doute vous, a demandé à accéder à votre profil.\n" -#~ "Vous pouvez modifier vos propositions ou en soumettre de nouvelles à " -#~ "l’url suivante :\n" -#~ "\n" -#~ " {}\n" -#~ "\n" -#~ "Si vous avez une question, vous pouvez répondre à ce mail.\n" -#~ "\n" -#~ "Sincèrement,\n" -#~ "\n" -#~ "{}\n" +#~ msgid "There is an error in your content template." +#~ msgstr "Il y a une erreur dans le gabarit du corps." + +#, fuzzy +#~| msgid "Contact:" +#~ msgid "Content:" +#~ msgstr "Contacter :" #~ msgid "We are looking for help with the following activities:" #~ msgstr "Nous cherchons de l’aide pour les activités suivantes :" @@ -1694,9 +1801,6 @@ msgstr "Changement de mot de passe" #~ msgid "The talk has been declined." #~ msgstr "L’exposé a été décliné." -#~ msgid "Contact:" -#~ msgstr "Contacter :" - #~ msgid "link" #~ msgstr "lien" diff --git a/requirements.in b/requirements.in index a1a51ed..c63d306 100644 --- a/requirements.in +++ b/requirements.in @@ -11,3 +11,4 @@ markdown bleach chardet icalendar +jinja2