-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathmain.py
More file actions
1865 lines (1714 loc) · 62.1 KB
/
main.py
File metadata and controls
1865 lines (1714 loc) · 62.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Python Togo FastAPI application.
This module defines a small FastAPI server that serves HTML templates,
static assets, and a few JSON API endpoints for events, news, communities,
and basic forms (join, contact, partnership).
Notes
-----
- Translations are stored in-memory in `TRANSLATIONS` and selected via
cookie or `Accept-Language`.
- Sample data for events and news is kept in-memory for simplicity.
"""
from datetime import date
import json
import os
from typing import List, Optional
from dotenv import load_dotenv
from email_validator import EmailNotValidError, validate_email
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
from supabase import Client, create_client
app = FastAPI(
summary="Python Togo official website.",
description="The Python Software Community Togo's official website.",
docs_url=None,
redoc_url=None,
title="Python Togo",
version="1.0.0",
)
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
load_dotenv()
SUBABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
supabase: Client = create_client(SUBABASE_URL, SUPABASE_KEY)
# Simple in-memory sample data
SAMPLE_EVENTS = [
{
"id": 1,
"date": "2025-12-05",
"location": "Lomé",
"translations": {
"fr": {
"title": "Atelier Python débutant",
"description": "Introduction à Python pour les nouveaux développeurs.",
},
"en": {
"title": "Beginner Python workshop",
"description": "Introduction to Python for new developers.",
},
},
},
{
"id": 3,
"date": "2025-07-20 to 2025-08-22",
"location": "Lomé",
"translations": {
"fr": {
"title": "Challenge 30 jours de code Python",
"description": (
"Challenge d'introduction à Python pour les nouveaux développeurs."
),
},
"en": {
"title": "30 Days of Python Code Challenge",
"description": "Introductory Python challenge for new developers.",
},
},
},
{
"id": 2,
"date": "2026-01-20",
"location": "Lomé",
"translations": {
"fr": {
"title": "Data Science avec Python",
"description": "Atelier sur les bases de la data science.",
},
"en": {
"title": "Data Science with Python",
"description": "Workshop on the basics of data science.",
},
},
},
]
SAMPLE_NEWS = [
{
"id": 1,
"date": "2025-11-01",
"image": "https://res.cloudinary.com/dvg7vky5o/image/upload/v1763440928/20251117_200618_tsfc73.jpg",
"translations": {
"fr": {
"title": "Lancement d'un nouvel atelier Python",
"excerpt": "Nous organisons un atelier sur les bases de Python.",
"body": (
"Rejoignez-nous pour un atelier d'introduction à Python, destiné "
"aux débutants. Détails, date et inscription seront "
"communiqués prochainement."
),
},
"en": {
"title": "Launching a new Python workshop",
"excerpt": "We are organizing a beginner-friendly Python workshop.",
"body": (
"Join us for an introductory Python workshop for newcomers. "
"Details, date, and registration will be shared soon."
),
},
},
},
{
"id": 2,
"date": "2025-09-15",
"image": "https://res.cloudinary.com/dvg7vky5o/image/upload/v1763440928/20251117_200618_tsfc73.jpg",
"translations": {
"fr": {
"title": "Rencontre communautaire à Lomé",
"excerpt": "Retour sur la rencontre mensuelle.",
"body": (
"Compte-rendu de notre dernière rencontre communautaire à Lomé "
"avec les ressources partagées et les annonces."
),
},
"en": {
"title": "Community meetup in Lomé",
"excerpt": "Recap of the monthly meetup.",
"body": (
"A recap of our latest community meetup in Lomé with shared "
"resources and announcements."
),
},
},
},
{
"id": 3,
"date": "2025-08-23",
"image": "https://res.cloudinary.com/dvg7vky5o/image/upload/v1747588996/Group_6_r7n6id.png",
"translations": {
"fr": {
"title": "Rapport de la PyCon Togo 2025",
"excerpt": "Retour sur la PyCon Togo 2025.",
"body": (
"Compte-rendu de la PyCon Togo 2025 avec les ressources "
"partagées et les annonces. "
"lire plus: https://report.pytogo.org/rapport-de-la-pycon-togo-2025"
),
},
"en": {
"title": "PyCon Togo 2025 Report",
"excerpt": "Recap of the PyCon Togo 2025.",
"body": (
"A recap of the PyCon Togo 2025 with shared resources and "
"announcements. "
"read more: https://report.pytogo.org"
),
},
},
},
]
TRANSLATIONS = {
"fr": {
"site-title": "Python Togo",
"nav-home": "Accueil",
"nav-about": "À propos",
"nav-code": "Code de conduite",
"nav-events": "Événements",
"nav-news": "Actualités",
"nav-gallery": "Galerie",
"nav-join": "Adhérer",
"nav-contact": "Contact",
"nav-partners": "Partenaires",
"nav-communities": "Communautés",
"lang-fr": "FR",
"lang-en": "EN",
"donate": "Faire un don",
"footer-about-title": "À propos",
"footer-about-desc": (
"Python Togo promeut le langage de programmation Python au Togo."
),
"footer-links-title": "Liens",
"footer-contact-title": "Contact",
"footer-logo-by": "Logo conçu avec ❤️ par",
"footer-site-by": "Ce site a été conçu et développé avec ❤️ par",
"footer-using": "à l'aide de",
"footer-and-deployed": "et déployé sur",
"footer-rights": "Tous droits réservés.",
"footer-logos": "Logos",
"gallery-title": "Galerie",
"gallery-intro": (
"Découvrez les photos de nos événements et rencontres. Cliquez sur le "
"lien ci-dessous pour accéder à notre album."
),
"gallery-view": "Voir",
"gallery-external": "Voir notre galerie",
"gallery-external-coming": "Voir notre galerie (à venir)",
"gallery-recent": "Vignettes récentes",
"join-title": "Adhérer",
"join-intro": (
"Rejoignez la communauté Python Togo en remplissant le formulaire "
"ci-dessous."
),
"label-name": "Nom",
"label-fullname": "Nom complet",
"label-email": "Email",
"label-city": "Ville",
"label-level": "Niveau Python",
"level-beginner": "Débutant",
"level-intermediate": "Intermédiaire",
"level-advanced": "Avancé",
"btn-send": "Envoyer",
"contact-title": "Contact",
"contact-intro": (
"Pour toute question, envoyez-nous un message via le formulaire."
),
"label-subject": "Sujet",
"label-message": "Message",
"agree-privacy": "J'ai lu et j'accepte la politique de confidentialité",
"agree-coc": "J'ai lu et j'accepte le Code de conduite",
"consent-alert": (
"Veuillez accepter la politique de confidentialité et le Code de "
"conduite avant de continuer."
),
"privacy-link-text": "Politique de confidentialité",
"news-title": "Actualités",
"news-read-more": "Voir plus",
"news-back": "Retour aux actualités",
"privacy-title": "Politique de confidentialité",
"privacy-heading": "Politique de confidentialité",
"privacy-intro": (
"Chez Python Togo, nous prenons la confidentialité de vos données au "
"sérieux. Cette page explique quelles données nous collectons, "
"pourquoi nous les collectons et comment nous les utilisons."
),
"privacy-collect-heading": "Données que nous collectons",
"privacy-collect-1": (
"Données de contact : nom, adresse e‑mail, téléphone (si fournies) "
"lorsque vous remplissez un formulaire."
),
"privacy-collect-2": (
"Informations de profil : ville, niveau, intérêts (quand vous les "
"partagez)."
),
"privacy-collect-3": (
"Données techniques : adresse IP, type de navigateur et timing des "
"requêtes pour améliorer nos services."
),
"privacy-why-heading": "Pourquoi nous collectons ces données",
"privacy-why-intro": "Nous utilisons vos données pour :",
"privacy-why-1": "Répondre à vos demandes (adhésion, partenariat, contact).",
"privacy-why-2": "Organiser et informer sur les événements.",
"privacy-why-3": "Améliorer l'expérience et la sécurité du site.",
"privacy-share-heading": "Partage et conservation",
"privacy-share-text": (
"Nous ne vendons ni ne louons vos données personnelles. Les demandes "
"reçues peuvent être partagées avec les membres organisateurs et "
"conservées aussi longtemps que nécessaire pour répondre à la "
"demande ou se conformer aux obligations légales."
),
"privacy-retention-sub": "Durée de conservation",
"privacy-retention-intro": (
"Nous conservons vos données aussi longtemps que nécessaire pour "
"atteindre les objectifs pour lesquels elles ont été collectées. "
"Par exemple :"
),
"privacy-retention-1": (
"Demandes d'adhésion : conservées pendant 3 ans après la dernière "
"interaction, sauf si vous demandez leur suppression."
),
"privacy-retention-2": (
"Inscriptions à des événements : conservées pendant la durée "
"nécessaire à l'organisation et archivées pendant 3 ans pour des "
"raisons administratives."
),
"privacy-retention-3": (
"Messages de contact : conservés pendant 2 à 3 ans selon la nature "
"de la demande."
),
"privacy-disclosure-sub": "Partage et divulgation",
"privacy-disclosure-text": (
"Nous ne partageons pas vos données personnelles avec des tiers à des "
"fins commerciales sans votre consentement explicite. Les données "
"peuvent être partagées avec des prestataires qui traitent les "
"données pour notre compte (hébergement, envoi d'emails), sous "
"contrat de confidentialité."
),
"privacy-rights-heading": "Vos droits",
"privacy-rights-text": (
"Vous pouvez demander l'accès, la rectification ou la suppression de "
"vos données en nous contactant à"
),
"privacy-forms-heading": "Formulaires et consentement",
"privacy-forms-intro": (
"Tous les formulaires importants (adhésion, partenariat) exigent votre "
"consentement explicite :"
),
"privacy-forms-privacy": (
"Vous devez cocher la case indiquant que vous avez lu et accepté "
"notre politique de confidentialité."
),
"privacy-forms-coc": (
"Vous devez cocher la case indiquant que vous acceptez le Code de "
"conduite de la communauté."
),
"privacy-questions": (
"Si vous avez des questions sur cette politique, contactez-nous à"
),
"coc-title": "Code de conduite",
"coc-heading": "Code de conduite",
"coc-intro": (
"Cette charte s'inspire des meilleures pratiques utilisées par les "
"communautés Python, notamment celles de la Python Software "
"Foundation (PSF). Elle vise à garantir un environnement sûr, "
"accueillant et professionnel pour toutes et tous, quelles que "
"soient l'expérience, l'identité ou l'origine."
),
"coc-commitment": "Notre engagement",
"coc-commitment-intro": "Nous nous engageons à :",
"coc-commitment-1": (
"Fournir un espace inclusif et respectueux pour les événements, "
"forums et canaux en ligne liés à Python Togo."
),
"coc-commitment-2": "Valoriser la diversité des parcours et des contributions.",
"coc-commitment-3": (
"Répondre rapidement et de manière confidentielle aux signalements "
"de comportements inappropriés."
),
"coc-expected": "Comportements attendus",
"coc-expected-1": "Respecter les autres participants et leurs opinions.",
"coc-expected-2": (
"Être attentif(ve) au langage employé et utiliser un ton constructif."
),
"coc-expected-3": (
"Accepter les retours avec humilité et être prêt(e) à s'excuser en "
"cas d'erreur."
),
"coc-expected-4": (
"Respecter les règles spécifiques aux lieux ou aux plateformes "
"(modération, sécurité, accessibilité)."
),
"coc-unacceptable": "Comportements inacceptables",
"coc-unacceptable-intro": "Ne seront pas tolérés :",
"coc-unacceptable-1": (
"Les propos discriminatoires, le harcèlement, les attaques "
"personnelles ou menaces."
),
"coc-unacceptable-2": (
"La diffusion d'insultes, propos sexistes, racistes, homophobes, "
"transphobes ou tout autre contenu haineux."
),
"coc-unacceptable-3": (
"Le partage non consenti d'informations personnelles ou confidentielles."
),
"coc-unacceptable-4": (
"Le non-respect des consignes de sécurité et de modération des "
"organisateurs."
),
"coc-scope": "Périmètre",
"coc-scope-text": (
"Ce code s'applique à tous les espaces officiels et événements "
"organisés par Python Togo, y compris les réunions en présentiel, "
"ateliers, conférences, listes de diffusion, forums et canaux de "
"discussion en ligne associés."
),
"coc-report": "Procédure de signalement",
"coc-report-intro": (
"Si vous êtes victime ou témoin d'un comportement inacceptable :"
),
"coc-report-1": (
"Contactez d'abord les organisateurs via la page de contact ou "
"envoyez un email à"
),
"coc-report-2": (
"Fournissez autant de détails que possible : date, lieu, personnes "
"impliquées, témoins et copies de messages si pertinent."
),
"coc-report-3": (
"Indiquez si vous souhaitez que votre signalement soit traité de "
"façon confidentielle."
),
"coc-handling": "Gestion des signalements",
"coc-handling-text": (
"Les organisateurs examineront les signalements rapidement et "
"prendront des mesures proportionnées, qui peuvent inclure :"
),
"coc-handling-1": "Un avertissement formel.",
"coc-handling-2": "La suspension ou exclusion d'un événement ou d'un canal.",
"coc-handling-3": (
"La communication d'informations aux autorités compétentes si nécessaire."
),
"coc-confidentiality": "Confidentialité et protection",
"coc-confidentiality-text": (
"Les informations reçues dans le cadre d'un signalement seront "
"traitées avec la plus grande confidentialité possible. Seules les "
"personnes nécessaires à l'enquête auront accès aux informations."
),
"coc-examples": "Exemples",
"coc-examples-intro": "Exemples de comportements à signaler :",
"coc-examples-1": (
"Messages répétés et non sollicités d'un individu visant une autre "
"personne."
),
"coc-examples-2": (
"Commentaires à caractère discriminatoire sur la base d'une identité."
),
"coc-examples-3": "Partage d'une photo privée sans consentement.",
"coc-organizers": "Responsabilités des organisateurs",
"coc-organizers-intro": "Les organisateurs s'engagent à :",
"coc-organizers-1": (
"Maintenir des procédures claires pour la gestion des incidents."
),
"coc-organizers-2": (
"Former, si nécessaire, les modérateurs et responsables à la "
"gestion des signalements."
),
"coc-organizers-3": (
"Publier des mises à jour sur les mesures prises, sans compromettre "
"la confidentialité."
),
"coc-revision": "Révision",
"coc-revision-text": (
"Ce code de conduite pourra être revu périodiquement pour s'adapter "
"aux retours de la communauté et aux bonnes pratiques "
"internationales."
),
"coc-thanks": (
"Merci de contribuer à faire de Python Togo un espace sûr et "
"accueillant pour tous."
),
"home-title": "Accueil",
"home-welcome": "Bienvenue sur Python Togo",
"home-intro": (
"Python Togo est une communauté de développeurs et passionnés Python "
"au Togo. Nous organisons des événements, des formations et "
"promouvons l'usage de Python dans notre pays."
),
"home-join": "Rejoindre la communauté",
"home-view-events": "Voir les événements",
"home-news-recent": "Actualités récentes",
"home-news-all": "Voir toutes les actualités",
"partners-our": "Nos partenaires",
"partners-intro": (
"Nous remercions les organisations et individus qui nous font confiance."
),
"partners-none": "Aucun partenaire pour le moment.",
"partners-request-title": "Demander un partenariat",
"partners-request-intro": (
"Vous souhaitez nous soutenir ou devenir partenaire ? "
"Envoyez votre demande ci-dessous."
),
"label-organization": "Organisation",
"label-contact-name": "Nom du contact",
"label-website-optional": "Site web (optionnel)",
"label-message-optional": "Message (optionnel)",
"partners-send": "Envoyer la demande",
"partners-sending": "Envoi en cours...",
"partners-success": "Demande envoyée, merci !",
"partners-error-prefix": "Erreur: ",
"partners-network-error-prefix": "Erreur réseau: ",
"about-title": "À propos",
"about-heading": "À propos",
"about-blurb": (
"Python Togo rassemble les développeurs, étudiants et professionnels "
"utilisant Python au Togo. Notre mission est de promouvoir "
"l'apprentissage et l'utilisation de Python."
),
"about-mission": "Notre mission",
"about-m1": "Promouvoir l'apprentissage et l'utilisation de Python",
"about-m2": "Organiser des événements et formations",
"about-m3": "Favoriser le partage de connaissances",
"events-title": "Événements",
"events-heading": "Événements",
"events-sample-meta": "2025-12-05 • Lomé",
"events-sample-title": "Atelier Python débutant",
"events-sample-desc": "Introduction à Python pour les nouveaux développeurs.",
"communities-title": "Communautés",
"communities-heading": "Communautés locales",
"communities-card-title": "Python Togo",
"communities-card-desc": (
"Groupe local basé à Lomé, rencontre mensuelle et ateliers."
),
"error-404-title": "Page non trouvée",
"error-404-heading": "Erreur 404",
"error-404-message": "Désolé, la page que vous recherchez n'existe pas.",
"error-404-home": "Retour à l'accueil",
"error-500-title": "Erreur serveur",
"error-500-heading": "Erreur 500",
"error-500-message": (
"Une erreur interne s'est produite. Veuillez réessayer plus tard."
),
"error-403-title": "Accès interdit",
"error-403-heading": "Erreur 403",
"error-403-message": "Vous n'avez pas la permission d'accéder à cette page.",
"error-generic-title": "Erreur",
"error-generic-heading": "Une erreur s'est produite",
"error-generic-message": "Quelque chose s'est mal passé. Veuillez réessayer.",
},
"en": {
"site-title": "Python Togo",
"nav-home": "Home",
"nav-about": "About",
"nav-code": "Code of Conduct",
"nav-events": "Events",
"nav-news": "News",
"nav-gallery": "Gallery",
"nav-join": "Join",
"nav-contact": "Contact",
"nav-partners": "Partners",
"nav-communities": "Communities",
"lang-fr": "FR",
"lang-en": "EN",
"donate": "Donate",
"footer-about-title": "About",
"footer-about-desc": (
"Python Togo promotes the Python programming language in Togo."
),
"footer-links-title": "Links",
"footer-contact-title": "Contact",
"footer-logo-by": "Logo designed with ❤️ by",
"footer-site-by": "This site was designed and developed with ❤️ by",
"footer-using": "using",
"footer-and-deployed": "and deployed on",
"footer-rights": "All rights reserved.",
"footer-logos": "Logos",
"gallery-title": "Gallery",
"gallery-intro": (
"Discover photos from our events and meetups. Use the link below "
"to access our album."
),
"gallery-view": "View",
"gallery-external": "View our gallery",
"gallery-external-coming": "View our gallery (coming soon)",
"gallery-recent": "Recent thumbnails",
"join-title": "Join",
"join-intro": ("Join the Python Togo community by filling out the form below."),
"label-name": "Name",
"label-fullname": "Full name",
"label-email": "Email",
"label-city": "City",
"label-level": "Python level",
"level-beginner": "Beginner",
"level-intermediate": "Intermediate",
"level-advanced": "Advanced",
"btn-send": "Send",
"contact-title": "Contact",
"contact-intro": ("For any questions, send us a message using the form."),
"label-subject": "Subject",
"label-message": "Message",
"agree-privacy": "I have read and agree to the privacy policy",
"agree-coc": "I have read and agree to the Code of Conduct",
"consent-alert": (
"Please accept the privacy policy and Code of Conduct before continuing."
),
"privacy-link-text": "Privacy Policy",
"news-title": "News",
"news-read-more": "Read more",
"news-back": "Back to news",
"privacy-title": "Privacy Policy",
"privacy-heading": "Privacy Policy",
"privacy-intro": (
"At Python Togo, we take your data privacy seriously. This page "
"explains what data we collect, why we collect it, and how we use "
"it."
),
"privacy-collect-heading": "Data we collect",
"privacy-collect-1": (
"Contact data: name, email address, phone (if provided) when you "
"fill out a form."
),
"privacy-collect-2": (
"Profile information: city, level, interests (when you share them)."
),
"privacy-collect-3": (
"Technical data: IP address, browser type, and request timing to "
"improve our services."
),
"privacy-why-heading": "Why we collect this data",
"privacy-why-intro": "We use your data to:",
"privacy-why-1": "Respond to your requests (membership, partnership, contact).",
"privacy-why-2": "Organize and inform about events.",
"privacy-why-3": "Improve the site experience and security.",
"privacy-share-heading": "Sharing and retention",
"privacy-share-text": (
"We do not sell or rent your personal data. Requests we receive "
"may be shared with organizing members and kept as long as "
"necessary to respond or comply with legal obligations."
),
"privacy-retention-sub": "Retention period",
"privacy-retention-intro": (
"We retain your data as long as necessary to achieve the purposes "
"for which it was collected. For example:"
),
"privacy-retention-1": (
"Membership requests: kept for 3 years after the last interaction, "
"unless you request deletion."
),
"privacy-retention-2": (
"Event registrations: kept for the time needed to organize and "
"archived for 3 years for administrative reasons."
),
"privacy-retention-3": (
"Contact messages: kept for 2–3 years depending on the nature of "
"the request."
),
"privacy-disclosure-sub": "Sharing and disclosure",
"privacy-disclosure-text": (
"We do not share your personal data with third parties for "
"commercial purposes without your explicit consent. Data may be "
"shared with providers processing data on our behalf (hosting, "
"email delivery) under confidentiality agreements."
),
"privacy-rights-heading": "Your rights",
"privacy-rights-text": (
"You can request access, rectification, or deletion of your data "
"by emailing"
),
"privacy-forms-heading": "Forms and consent",
"privacy-forms-intro": (
"All key forms (membership, partnership) require your explicit consent:"
),
"privacy-forms-privacy": (
"You must check the box indicating you have read and accepted our "
"privacy policy."
),
"privacy-forms-coc": (
"You must check the box indicating you accept the community's Code "
"of Conduct."
),
"privacy-questions": ("If you have questions about this policy, contact us at"),
"coc-title": "Code of Conduct",
"coc-heading": "Code of Conduct",
"coc-intro": (
"This charter draws on best practices used by Python communities, "
"including the Python Software Foundation (PSF). It aims to ensure "
"a safe, welcoming, and professional environment for everyone, "
"regardless of experience, identity, or background."
),
"coc-commitment": "Our commitment",
"coc-commitment-intro": "We commit to:",
"coc-commitment-1": (
"Provide an inclusive and respectful space for events, forums, and "
"online channels related to Python Togo."
),
"coc-commitment-2": "Value diverse backgrounds and contributions.",
"coc-commitment-3": (
"Respond quickly and confidentially to reports of inappropriate behavior."
),
"coc-expected": "Expected behavior",
"coc-expected-1": "Respect other participants and their opinions.",
"coc-expected-2": ("Be mindful of language and use a constructive tone."),
"coc-expected-3": (
"Accept feedback humbly and be willing to apologize when wrong."
),
"coc-expected-4": (
"Respect venue- or platform-specific rules (moderation, safety, "
"accessibility)."
),
"coc-unacceptable": "Unacceptable behavior",
"coc-unacceptable-intro": "The following will not be tolerated:",
"coc-unacceptable-1": (
"Discriminatory remarks, harassment, personal attacks, or threats."
),
"coc-unacceptable-2": (
"Insults; sexist, racist, homophobic, or transphobic remarks; or "
"any hateful content."
),
"coc-unacceptable-3": (
"Sharing personal or confidential information without consent."
),
"coc-unacceptable-4": (
"Failing to follow safety and moderation guidelines from organizers."
),
"coc-scope": "Scope",
"coc-scope-text": (
"This code applies to all official spaces and events organized by "
"Python Togo, including in-person meetings, workshops, "
"conferences, mailing lists, forums, and associated online "
"channels."
),
"coc-report": "Reporting procedure",
"coc-report-intro": (
"If you are a victim or witness of unacceptable behavior:"
),
"coc-report-1": ("First, contact the organizers via the contact page or email"),
"coc-report-2": (
"Provide as many details as possible: date, location, people "
"involved, witnesses, and message copies if relevant."
),
"coc-report-3": (
"Indicate if you wish your report to be handled confidentially."
),
"coc-handling": "Handling reports",
"coc-handling-text": (
"Organizers will review reports promptly and take proportionate "
"measures, which may include:"
),
"coc-handling-1": "A formal warning.",
"coc-handling-2": "Suspension or exclusion from an event or channel.",
"coc-handling-3": ("Informing authorities if necessary."),
"coc-confidentiality": "Confidentiality and protection",
"coc-confidentiality-text": (
"Information received in connection with a report will be handled "
"with the greatest possible confidentiality. Only people "
"necessary for the investigation will have access."
),
"coc-examples": "Examples",
"coc-examples-intro": "Examples of behaviors to report:",
"coc-examples-1": (
"Repeated, unsolicited messages from one individual targeting "
"another person."
),
"coc-examples-2": "Discriminatory comments based on identity.",
"coc-examples-3": "Sharing a private photo without consent.",
"coc-organizers": "Organizers' responsibilities",
"coc-organizers-intro": "Organizers commit to:",
"coc-organizers-1": ("Maintain clear procedures for incident handling."),
"coc-organizers-2": (
"Train moderators and leads, if needed, on handling reports."
),
"coc-organizers-3": (
"Publish updates on actions taken without compromising confidentiality."
),
"coc-revision": "Revision",
"coc-revision-text": (
"This code of conduct may be reviewed periodically to reflect "
"community feedback and international best practices."
),
"coc-thanks": (
"Thank you for helping make Python Togo a safe and welcoming space for all."
),
"home-title": "Home",
"home-welcome": "Welcome to Python Togo",
"home-intro": (
"Python Togo is a community of Python developers and enthusiasts in "
"Togo. We organize events, trainings, and promote the use of "
"Python across the country."
),
"home-join": "Join the community",
"home-view-events": "View events",
"home-news-recent": "Recent news",
"home-news-all": "See all news",
"partners-our": "Our partners",
"partners-intro": (
"We thank the organizations and individuals who support us."
),
"partners-none": "No partners yet.",
"partners-request-title": "Request a partnership",
"partners-request-intro": (
"Would you like to support us or become a partner? Send your request below."
),
"label-organization": "Organization",
"label-contact-name": "Contact name",
"label-website-optional": "Website (optional)",
"label-message-optional": "Message (optional)",
"partners-send": "Send request",
"partners-sending": "Sending...",
"partners-success": "Request sent, thank you!",
"partners-error-prefix": "Error: ",
"partners-network-error-prefix": "Network error: ",
"about-title": "About",
"about-heading": "About",
"about-blurb": (
"Python Togo brings together developers, students, and "
"professionals using Python in Togo. Our mission is to promote "
"learning and use of Python."
),
"about-mission": "Our mission",
"about-m1": "Promote learning and use of Python",
"about-m2": "Organize events and training",
"about-m3": "Encourage knowledge sharing",
"events-title": "Events",
"events-heading": "Events",
"events-sample-meta": "2025-12-05 • Lomé",
"events-sample-title": "Beginner Python workshop",
"events-sample-desc": "Introduction to Python for new developers.",
"communities-title": "Communities",
"communities-heading": "Local communities",
"communities-card-title": "Python Togo",
"communities-card-desc": (
"Local group based in Lomé, monthly meetups and workshops."
),
"error-404-title": "Page not found",
"error-404-heading": "Error 404",
"error-404-message": "Sorry, the page you are looking for does not exist.",
"error-404-home": "Back to home",
"error-500-title": "Server error",
"error-500-heading": "Error 500",
"error-500-message": "An internal error occurred. Please try again later.",
"error-403-title": "Access forbidden",
"error-403-heading": "Error 403",
"error-403-message": "You do not have permission to access this page.",
"error-generic-title": "Error",
"error-generic-heading": "An error occurred",
"error-generic-message": "Something went wrong. Please try again.",
},
}
DONATE_URL = "https://www.paypal.com/donate/?hosted_button_id=A6547S7YGMZ4A"
@app.exception_handler(404)
async def not_found_handler(request: Request, exc: HTTPException):
"""Handle 404 errors with a custom bilingual page.
Parameters
----------
request : fastapi.Request
The incoming HTTP request.
exc : fastapi.HTTPException
The HTTP exception that was raised. Contains status code and detail.
Returns
-------
fastapi.responses.TemplateResponse
The rendered 404 error page with appropriate context.
"""
lang = get_language(request)
return templates.TemplateResponse(
request=request,
name="error.html",
status_code=404,
context=ctx(
request,
{
"status_code": 404,
"title_key": "error-404-title",
"heading_key": "error-404-heading",
"message_key": "error-404-message",
"detail": None,
"meta_title": TRANSLATIONS[lang]["error-404-title"] + " — Python Togo",
"meta_description": TRANSLATIONS[lang]["error-404-message"],
},
),
)
@app.exception_handler(500)
async def internal_error_handler(request: Request, exc: Exception):
"""Handle 500 errors with a custom bilingual page.
Parameters
----------
request : fastapi.Request
The incoming HTTP request.
exc : Exception
The exception that was raised.
Returns
-------
fastapi.responses.TemplateResponse
The rendered 500 error page with appropriate context.
"""
lang = get_language(request)
return templates.TemplateResponse(
request=request,
name="error.html",
status_code=500,
context=ctx(
request,
{
"status_code": 500,
"title_key": "error-500-title",
"heading_key": "error-500-heading",
"message_key": "error-500-message",
"detail": None,
"meta_title": TRANSLATIONS[lang]["error-500-title"] + " — Python Togo",
"meta_description": TRANSLATIONS[lang]["error-500-message"],
},
),
)
@app.exception_handler(403)
async def forbidden_handler(request: Request, exc: HTTPException):
"""Handle 403 errors with a custom bilingual page.
Parameters
----------
request : fastapi.Request
The incoming HTTP request.
exc : fastapi.HTTPException
The HTTP exception that was raised. Contains status code and detail.
Returns
-------
fastapi.responses.TemplateResponse
The rendered 403 error page with appropriate context.
"""
lang = get_language(request)
return templates.TemplateResponse(
request=request,
name="error.html",
status_code=403,
context=ctx(
request,
{
"status_code": 403,
"title_key": "error-403-title",
"heading_key": "error-403-heading",
"message_key": "error-403-message",
"detail": None,
"meta_title": TRANSLATIONS[lang]["error-403-title"] + " — Python Togo",
"meta_description": TRANSLATIONS[lang]["error-403-message"],
},
),
)
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
"""Handle generic HTTP exceptions with a custom bilingual page.
Parameters
----------
request : fastapi.Request
The incoming HTTP request.
exc : fastapi.HTTPException
The HTTP exception that was raised. Contains status code and detail.
Returns
-------
fastapi.responses.TemplateResponse
The rendered error page with appropriate status code and context.
"""
lang = get_language(request)
error_map = {
404: ("error-404-title", "error-404-heading", "error-404-message"),
403: ("error-403-title", "error-403-heading", "error-403-message"),
500: ("error-500-title", "error-500-heading", "error-500-message"),
}
if exc.status_code in error_map:
title_key, heading_key, message_key = error_map[exc.status_code]
else:
title_key, heading_key, message_key = (
"error-generic-title",
"error-generic-heading",
"error-generic-message",
)
return templates.TemplateResponse(
request=request,
name="error.html",
status_code=exc.status_code,
context=ctx(
request,
{
"status_code": exc.status_code,
"title_key": title_key,
"heading_key": heading_key,
"message_key": message_key,
"detail": exc.detail if hasattr(exc, "detail") else None,
"meta_title": TRANSLATIONS[lang][title_key] + " — Python Togo",
"meta_description": TRANSLATIONS[lang][message_key],
},
),
)
def get_language(request: Request) -> str:
"""
Determine the preferred language for the request.
Parameters
----------
request : fastapi.Request
The incoming HTTP request.
Returns
-------
str
The language code ("fr" or "en"). Defaults to "fr" if none matched.
"""
# 1) Query param takes precedence for SEO-friendly alternate URLs
query_lang = request.query_params.get("lang") or request.query_params.get("hl")
if query_lang in TRANSLATIONS:
return query_lang
# 2) Cookie value
cookie_lang = request.cookies.get("lang")
if cookie_lang in TRANSLATIONS:
return cookie_lang
# Fallback to Accept-Language
accept = request.headers.get("accept-language", "")
if accept:
for part in accept.split(","):
code = part.split(";")[0].strip().lower()
if code.startswith("fr"):
return "fr"
if code.startswith("en"):
return "en"
return "fr"