diff --git a/src/api/locale/de/LC_MESSAGES/django.po b/src/api/locale/de/LC_MESSAGES/django.po
index 73f7bdac20b1c17c33b2aa8787d0af9422c4c0c7..27bdfd43b901790187d5ba1ceec8085e98b41654 100644
--- a/src/api/locale/de/LC_MESSAGES/django.po
+++ b/src/api/locale/de/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-10 08:21+0000\n"
+"POT-Creation-Date: 2020-12-23 11:52+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
diff --git a/src/api/locale/en/LC_MESSAGES/django.po b/src/api/locale/en/LC_MESSAGES/django.po
index ed3dbd1c3f568cb336fb887ebc297113f4fd79b5..80983e5cc566c9c5a8199ba352bcd2e62af0c353 100644
--- a/src/api/locale/en/LC_MESSAGES/django.po
+++ b/src/api/locale/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-10 08:21+0000\n"
+"POT-Creation-Date: 2020-12-23 11:52+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
diff --git a/src/backoffice/locale/de/LC_MESSAGES/django.po b/src/backoffice/locale/de/LC_MESSAGES/django.po
index abe2df9d02dbf0befb6e4502031ca6ee780d399c..993dc437c762ef9ae369fa46579a8fdf13a1f04a 100644
--- a/src/backoffice/locale/de/LC_MESSAGES/django.po
+++ b/src/backoffice/locale/de/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-17 23:03+0000\n"
+"POT-Creation-Date: 2020-12-23 11:52+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -603,18 +603,14 @@ msgstr "Der Raum konnte nicht gelöscht werden da noch Veranstaltungen vorhanden
 msgid "lists"
 msgstr "Listen"
 
-msgid "nav_assemblies_all"
-msgstr "alle"
+#, fuzzy
+#| msgid "nav_assemblies"
+msgid "nav_assemblies_"
+msgstr "Assembly-Team"
 
 msgid "nav_assemblies_accepted"
 msgstr "akzeptiert"
 
-msgid "nav_assemblies_pending"
-msgstr "wartend"
-
-msgid "nav_assemblies_rejected"
-msgstr "abgelehnt"
-
 # use translation from core
 msgid "Assembly__slug"
 msgstr ""
@@ -628,3 +624,12 @@ msgstr "Dieser Kurzname wird bereits verwendet."
 
 msgid "userprofile_updated"
 msgstr "Benutzerprofil aktualisiert"
+
+#~ msgid "nav_assemblies_all"
+#~ msgstr "alle"
+
+#~ msgid "nav_assemblies_pending"
+#~ msgstr "wartend"
+
+#~ msgid "nav_assemblies_rejected"
+#~ msgstr "abgelehnt"
diff --git a/src/backoffice/locale/en/LC_MESSAGES/django.po b/src/backoffice/locale/en/LC_MESSAGES/django.po
index f1ee9d843c6bb32721b937118e0547551edb6591..2c63c41443df318a8b1e45525bc144cb65c1d925 100644
--- a/src/backoffice/locale/en/LC_MESSAGES/django.po
+++ b/src/backoffice/locale/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-17 23:03+0000\n"
+"POT-Creation-Date: 2020-12-23 11:52+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -604,18 +604,14 @@ msgstr "Could not remove the room as it has at least one event assigned to it. P
 msgid "lists"
 msgstr "lists"
 
-msgid "nav_assemblies_all"
-msgstr "all"
+#, fuzzy
+#| msgid "nav_assemblies"
+msgid "nav_assemblies_"
+msgstr "assemblies team"
 
 msgid "nav_assemblies_accepted"
 msgstr "accepted"
 
-msgid "nav_assemblies_pending"
-msgstr "pending"
-
-msgid "nav_assemblies_rejected"
-msgstr "rejected"
-
 # use translation from core
 msgid "Assembly__slug"
 msgstr ""
@@ -628,3 +624,12 @@ msgstr "This slug is already in use on another page."
 
 msgid "userprofile_updated"
 msgstr "user profile updated"
+
+#~ msgid "nav_assemblies_all"
+#~ msgstr "all"
+
+#~ msgid "nav_assemblies_pending"
+#~ msgstr "pending"
+
+#~ msgid "nav_assemblies_rejected"
+#~ msgstr "rejected"
diff --git a/src/backoffice/templates/backoffice/assembly_editlinks.html b/src/backoffice/templates/backoffice/assembly_editlinks.html
index ee445d6bd7c23b16463d8baf58d4b3b21c940e4b..debb0553780072833fb7b90253584f6ae26a411e 100644
--- a/src/backoffice/templates/backoffice/assembly_editlinks.html
+++ b/src/backoffice/templates/backoffice/assembly_editlinks.html
@@ -48,7 +48,7 @@
         <ul>
           {% for link in links %}
           <li>
-            {{ link.name }}
+            {{ link.name }} (Slug: {{ link.slug }})
             <form action="{% url 'backoffice:assembly-editlinks' pk=assembly.id %}" method="POST">{% csrf_token %}
               <input type="hidden" name="delete" value="{{ link.pk }}">
               <button type="submit" class="btn btn-sm btn-secondary">{% trans 'delete' %}</button>
diff --git a/src/core/locale/de/LC_MESSAGES/django.po b/src/core/locale/de/LC_MESSAGES/django.po
index 240ae6580ac01f9fbabb20746156e7d580db8833..48531000d4bdf7fa9f3635fa97526ef597d1749d 100644
--- a/src/core/locale/de/LC_MESSAGES/django.po
+++ b/src/core/locale/de/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-17 23:03+0000\n"
+"POT-Creation-Date: 2020-12-23 11:52+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
diff --git a/src/core/locale/en/LC_MESSAGES/django.po b/src/core/locale/en/LC_MESSAGES/django.po
index 2937a6cfc20bf2271083815c5a0c3c034100db08..59e4434d80f5422bcbe4cef579b897709f32b33b 100644
--- a/src/core/locale/en/LC_MESSAGES/django.po
+++ b/src/core/locale/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-17 23:03+0000\n"
+"POT-Creation-Date: 2020-12-23 11:52+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
diff --git a/src/core/management/commands/rerender_markdown.py b/src/core/management/commands/rerender_markdown.py
new file mode 100644
index 0000000000000000000000000000000000000000..3f22ef77c34e86363105d582fb037b6c9bae8e6f
--- /dev/null
+++ b/src/core/management/commands/rerender_markdown.py
@@ -0,0 +1,34 @@
+from django.core.management.base import BaseCommand
+
+from core.models import Event, Room, Assembly, PlatformUser, StaticPage
+from core.utils import render_markdown
+
+
+class Command(BaseCommand):
+    def handle(self, *args, **options):
+        for event in Event.objects.all():
+            event.description_html = render_markdown(event.description)
+            event.save(update_fields=['description_html'])
+
+        for room in Room.objects.all():
+            room.description_html = render_markdown(room.description) if room.description is not None else None
+            room.save(update_fields=['description_html'])
+
+        for assembly in Assembly.objects.all():
+            assembly.description_html = render_markdown(assembly.description) if assembly.description is not None else None
+            assembly.save(update_fields=['description_html'])
+
+        for user in PlatformUser.objects.all():
+            user.description_html = render_markdown(user.description) if user.description is not None else None
+            user.save(update_fields=['description_html'])
+
+        for page in StaticPage.objects.all():
+            if page.public_revision > 0:
+                public_page = page.revisions.filter(revision=page.public_revision).first()
+                if public_page is None:
+                    page.rendered_body = ''
+                else:
+                    page.rendered_body = render_markdown(public_page.body)
+            else:
+                page.rendered_body = ''
+            page.save(update_fields=['rendered_body'])
diff --git a/src/core/migrations/0041_description_html_fields1.py b/src/core/migrations/0041_description_html_fields1.py
new file mode 100644
index 0000000000000000000000000000000000000000..181b789aafed23c535d01d3eee26c59674a5b96f
--- /dev/null
+++ b/src/core/migrations/0041_description_html_fields1.py
@@ -0,0 +1,34 @@
+# Generated by Django 3.1.4 on 2020-12-23 12:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0040_room_reserve_capacity'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='assembly',
+            name='description_html',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='event',
+            name='description_html',
+            field=models.TextField(blank=True, default=''),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='platformuser',
+            name='description_html',
+            field=models.TextField(blank=True, null=True),
+        ),
+        migrations.AddField(
+            model_name='room',
+            name='description_html',
+            field=models.TextField(blank=True, null=True),
+        ),
+    ]
diff --git a/src/core/migrations/0042_description_html_fields2.py b/src/core/migrations/0042_description_html_fields2.py
new file mode 100644
index 0000000000000000000000000000000000000000..581423e0170d98765d43e6328dba9b423142579e
--- /dev/null
+++ b/src/core/migrations/0042_description_html_fields2.py
@@ -0,0 +1,50 @@
+# Generated by Django 3.1.4 on 2020-12-23 13:00
+
+from django.db import migrations
+from core.utils import render_markdown
+
+
+def do(apps, schema_editor):
+    Event = apps.get_model('core', 'Event')
+    Room = apps.get_model('core', 'Room')
+    Assembly = apps.get_model('core', 'Assembly')
+    PlatformUser = apps.get_model('core', 'PlatformUser')
+    StaticPage = apps.get_model('core', 'StaticPage')
+
+    for event in Event.objects.all():
+        event.description_html = render_markdown(event.description)
+        event.save(update_fields=['description_html'])
+
+    for room in Room.objects.all():
+        room.description_html = render_markdown(room.description) if room.description is not None else None
+        room.save(update_fields=['description_html'])
+
+    for assembly in Assembly.objects.all():
+        assembly.description_html = render_markdown(assembly.description) if assembly.description is not None else None
+        assembly.save(update_fields=['description_html'])
+
+    for user in PlatformUser.objects.all():
+        user.description_html = render_markdown(user.description) if user.description is not None else None
+        user.save(update_fields=['description_html'])
+
+    for page in StaticPage.objects.all():
+        if page.public_revision > 0:
+            public_page = page.revisions.filter(revision=page.public_revision).first()
+            if public_page is None:
+                page.rendered_body = ''
+            else:
+                page.rendered_body = render_markdown(public_page.body)
+        else:
+            page.rendered_body = ''
+        page.save(update_fields=['rendered_body'])
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0041_description_html_fields1'),
+    ]
+
+    operations = [
+        migrations.RunPython(do, migrations.RunPython.noop, elidable=True)
+    ]
diff --git a/src/core/models/assemblies.py b/src/core/models/assemblies.py
index a3264330fefe8625750f97174aa0ba796962b193..9637928f10197af33d6ab110bee782372dc218c4 100644
--- a/src/core/models/assemblies.py
+++ b/src/core/models/assemblies.py
@@ -155,6 +155,7 @@ class Assembly(TaggedItemMixin, models.Model):
         blank=True, null=True,
         help_text=_('Assembly__description__help'),
         verbose_name=_('Assembly__description'))
+    description_html = models.TextField(blank=True, null=True)
     registration_details = models.TextField(
         blank=True, null=True,
         help_text=_('Assembly__registration_details__help'),
@@ -251,27 +252,6 @@ class Assembly(TaggedItemMixin, models.Model):
     def conference_slug(self):
         return self.conference.slug
 
-    @cached_property
-    def description_html(self):
-        if self.description is None:
-            return None
-        return render_markdown(self.description)
-
-        # # we safely replace double linebreaks into html paragraphs
-        # description_parts = re.split(r'\r?\n\r?\n', obj.description or '')
-        # obj.description_html = mark_safe('<p>')
-        # for partidx, part in enumerate(description_parts):
-        #     if partidx > 0:
-        #         obj.description_html += mark_safe('</p>\n<p>')
-        #     if part.startswith('# ') and '\n' not in part:
-        #         obj.description_html += format_html('<h3>{0}</h3>', part[2:])
-        #     else:
-        #         escaped = html_escape(part)
-        #         escaped = re.sub(r'\*([\w\d() ]+)\*', lambda x: format_html('<b>{}</b>', x.group(1)), escaped)
-        #         escaped = re.sub(r'__([\w\d() ]+)__', lambda x: format_html('<i>{}</i>', x.group(1)), escaped)
-        #         obj.description_html += mark_safe(escaped)
-        # obj.description_html += mark_safe('</p>')
-
     @cached_property
     def description_html_short(self):
         if not self.description:
@@ -348,6 +328,15 @@ class Assembly(TaggedItemMixin, models.Model):
 
         return self.members.filter(member=user, role__in=AssemblyMember.MANAGEMENT_ROLES).exists()
 
+    def save(self, *args, update_fields=None, **kwargs):
+        if update_fields is None or 'description' in update_fields:
+            if self.description is None:
+                self.description_html = None
+            else:
+                self.description_html = render_markdown(self.description)
+
+        return super().save(*args, update_fields=update_fields, **kwargs)
+
 
 class AssemblyLinkManager(models.Manager):
     def accessible_by_user(self, user: PlatformUser, conference: Conference):
diff --git a/src/core/models/events.py b/src/core/models/events.py
index 585bb31d10226f86a0c7fb539d79a4506d8f3bd4..611a095d0d4659fc2b5d994433d5c87e3ee9fc0b 100644
--- a/src/core/models/events.py
+++ b/src/core/models/events.py
@@ -4,7 +4,6 @@ from uuid import uuid4
 from django.conf import settings
 from django.core.exceptions import ValidationError
 from django.db import models
-from django.utils.functional import cached_property
 from django.utils.text import slugify
 from django.utils.translation import gettext_lazy as _
 
@@ -116,6 +115,7 @@ class Event(TaggedItemMixin, models.Model):
         blank=True,
         help_text=_('Event__description__help'),
         verbose_name=_('Event__description'))
+    description_html = models.TextField(blank=True)
     banner_image_height = models.PositiveIntegerField(blank=True, null=True)
     banner_image_width = models.PositiveIntegerField(blank=True, null=True)
     banner_image = models.ImageField(
@@ -168,10 +168,6 @@ class Event(TaggedItemMixin, models.Model):
     Ersteller/Eigentümer des Events, dies ist insb. für Self-Organized-Sessions relevant.
     """
 
-    @cached_property
-    def description_html(self):
-        return render_markdown(self.description)
-
     @property
     def public_speakers(self):
         """Returns a list of all public speakers of this event."""
@@ -231,7 +227,7 @@ class Event(TaggedItemMixin, models.Model):
         if errors:
             raise ValidationError(errors)
 
-    def save(self, *args, **kwargs):
+    def save(self, *args, update_fields=None, **kwargs):
         if self.schedule_start is not None and self.schedule_duration is not None:
             self.schedule_end = self.schedule_start + self.schedule_duration
         else:
@@ -242,7 +238,10 @@ class Event(TaggedItemMixin, models.Model):
             while Assembly.objects.filter(conference=self.conference, slug=self.slug).exclude(pk=self.pk).exists():
                 self.slug = gen_slug[:45] + '_' + ''.join([random.SystemRandom().choice('abcdefghijklmnopqrstuvwxyz0123456789') for i in range(4)])
 
-        return super().save(*args, **kwargs)
+        if update_fields is None or 'description' in update_fields:
+            self.description_html = render_markdown(self.description)
+
+        return super().save(*args, update_fields=update_fields, **kwargs)
 
     @property
     def conference_slug(self):
diff --git a/src/core/models/rooms.py b/src/core/models/rooms.py
index 1786968953afdb7b946384c0d45a77398591a2c1..9d6a7fbee27a1d2d363861e87dd3fc9cee8d33cf 100644
--- a/src/core/models/rooms.py
+++ b/src/core/models/rooms.py
@@ -3,7 +3,6 @@ from uuid import uuid4
 from django.core.exceptions import ValidationError
 from django.core.validators import URLValidator
 from django.db import models
-from django.utils.functional import cached_property
 from django.utils.translation import gettext_lazy as _
 
 from ..fields import ConferenceReference
@@ -117,6 +116,7 @@ class Room(models.Model):
         help_text=_('Room__description__help'),
         verbose_name=_('Room__description'))
     """Description of the room/project."""
+    description_html = models.TextField(blank=True, null=True)
 
     room_type = models.CharField(max_length=20, choices=RoomType.choices, help_text=_('Room__type__help'), verbose_name=_('Room__type'))
     """Style of the room."""
@@ -159,10 +159,6 @@ class Room(models.Model):
     def conference_slug(self):
         return self.conference.slug
 
-    @cached_property
-    def description_html(self):
-        return render_markdown(self.description)
-
     @property
     def assembly_slug(self):
         return self.assembly.slug if self.assembly is not None else None
@@ -171,6 +167,15 @@ class Room(models.Model):
     def has_backend(self):
         return self.room_type in self.BACKEND_ROOMTYPES
 
+    def save(self, *args, update_fields=None, **kwargs):
+        if update_fields is None or 'description' in update_fields:
+            if self.description is None:
+                self.description_html = None
+            else:
+                self.description_html = render_markdown(self.description)
+
+        return super().save(*args, update_fields=update_fields, **kwargs)
+
 
 class RoomLink(models.Model):
     class LinkType(models.TextChoices):
diff --git a/src/core/models/users.py b/src/core/models/users.py
index 1f4b001481f36c98fe3dedb40a7d68730c08b332..39b31aa9fc9efc607f0294cb49ea118795c714eb 100644
--- a/src/core/models/users.py
+++ b/src/core/models/users.py
@@ -57,6 +57,7 @@ class PlatformUser(AbstractUser):
         blank=True, null=True,
         help_text=_('PlatformUser__description__help'),
         verbose_name=_('PlatformUser__description'))
+    description_html = models.TextField(blank=True, null=True)
 
     status = models.CharField(
         max_length=50, blank=True, null=True,
@@ -144,10 +145,6 @@ class PlatformUser(AbstractUser):
 
         return AnonUser()
 
-    @cached_property
-    def description_html(self):
-        return render_markdown(self.description)
-
     def is_conference_staff(self, conference):
         return conference.users.filter(user=self, is_staff=True).exists()
 
@@ -165,6 +162,15 @@ class PlatformUser(AbstractUser):
         except ObjectDoesNotExist:
             return None
 
+    def save(self, *args, update_fields=None, **kwargs):
+        if update_fields is None or 'description' in update_fields:
+            if self.description is None:
+                self.description_html = None
+            else:
+                self.description_html = render_markdown(self.description)
+
+        return super().save(*args, update_fields=update_fields, **kwargs)
+
     def has_conference_staffpermission(self, conference, *perms, need_all=False):
         """
         Returns True if this user is a staff member of the given conference and has,
diff --git a/src/core/tests/__init__.py b/src/core/tests/__init__.py
index 94ef1205a4db88b070cc556e147c8074bdcfc998..17e18123c9d048c0329304b2f2fddf9c2b55cc0d 100644
--- a/src/core/tests/__init__.py
+++ b/src/core/tests/__init__.py
@@ -3,5 +3,6 @@ from .search import *  # noqa: F401, F403
 from .users import *  # noqa: F401, F403
 from .tags import *  # noqa: F401, F403
 from .tickets import *  # noqa: F401, F403
+from .markdown import *  # noqa: F401, F403
 
 __all__ = '*'
diff --git a/src/core/tests/markdown.py b/src/core/tests/markdown.py
new file mode 100644
index 0000000000000000000000000000000000000000..5e1d4a836d5e1bd3578ca37c74e665c9d9960b3d
--- /dev/null
+++ b/src/core/tests/markdown.py
@@ -0,0 +1,36 @@
+from django.test import TestCase
+
+from ..utils import render_markdown
+
+
+class MarkdownTest(TestCase):
+    def test_url_class(self):
+        tests = [
+            ('https://localhost/', False),
+            ('https://localhost/foo', False),
+            ('https://localhost/foo/', False),
+            ('https://localhost/foo.bar', False),
+            ('https://some.test/', False),
+            ('https://some.test/foo', False),
+            ('https://some.test/foo.bar', False),
+            ('/foo/bar', True),
+            ('foo/bar', True),
+            ('asdf', True),
+            ('https://rc3.world/', True),
+            ('https://rc3.world/foo', True),
+            ('https://rc3.world/foo.bar', True),
+        ]
+        for test_url, should_be_local in tests:
+            with self.subTest(test_url):
+                class_ = 'internal' if should_be_local else 'external'
+                self.assertEqual(render_markdown(f'[.]({test_url})'), f'<p><a class="{class_}" href="{test_url}">.</a></p>')
+                self.assertEqual(render_markdown(f'[.][1]\n\n[1]: {test_url}'), f'<p><a class="{class_}" href="{test_url}">.</a></p>')
+
+        forbidden = [
+            'data:text/html;charset=utf-8;base64,PGh0bWw+PGhlYWQ+PHRpdGxlPnRlc3Q8L3RpdGxlPjwvaGVhZD48Ym9keT48aDE+VGVzdDwvaDE+PC9ib2R5PjwvaHRtbD4=',
+            'javascript:alert(1)',
+        ]
+        for test_url in forbidden:
+            with self.subTest(test_url):
+                self.assertEqual(render_markdown(f'[.]({test_url})'), '<p><a class="external">.</a></p>')
+                self.assertEqual(render_markdown(f'[.][1]\n\n[1]: {test_url}'), '<p><a class="external">.</a></p>')
diff --git a/src/core/utils.py b/src/core/utils.py
index 315e7bdfcf3c2114bceb3cdf3001eeab28781190..d2621b93f12947bda69b20a8063571e87bef5a0c 100644
--- a/src/core/utils.py
+++ b/src/core/utils.py
@@ -1,7 +1,11 @@
+import bleach
 from django.utils.html import strip_tags
 from django.utils.safestring import mark_safe
 import markdown
+from markdown.extensions import Extension as MarkdownExtension
+from markdown.inlinepatterns import LinkInlineProcessor, LINK_RE, ReferenceInlineProcessor, REFERENCE_RE
 import random
+from urllib.parse import urlparse
 
 
 MARKDOWN_EXTENSIONS = [
@@ -13,8 +17,49 @@ MARKDOWN_EXTENSIONS = [
 TOKEN_CHARSET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
 
 
+def _mark_link_tag(el):
+    url = urlparse(el.get('href'))
+    is_local_domain = url.netloc == 'rc3.world'
+    class_ = 'external' if (url.scheme or url.netloc) and not is_local_domain else 'internal'
+    el.set('class', class_)
+
+
+class CustomLinkInlineProcessor(LinkInlineProcessor):
+    def handleMatch(self, m, data):
+        el, start, idx = super().handleMatch(m, data)
+        if el is None:
+            return el, start, idx
+
+        _mark_link_tag(el)
+
+        return el, start, idx
+
+
+class CustomReferenceInlineProcessor(ReferenceInlineProcessor):
+    def makeTag(self, href, title, text):
+        el = super().makeTag(href, title, text)
+        _mark_link_tag(el)
+        return el
+
+
+class ConferenceLinkExtension(MarkdownExtension):
+    def extendMarkdown(self, md: markdown.Markdown):
+        md.inlinePatterns.register(CustomLinkInlineProcessor(LINK_RE, md), 'link', 160)
+        md.inlinePatterns.register(CustomReferenceInlineProcessor(REFERENCE_RE, md), 'reference', 170)
+
+
 def render_markdown(markup: str):
-    rendered_markup = markdown.markdown(strip_tags(markup), extensions=MARKDOWN_EXTENSIONS)
+    rendered_markup = markdown.markdown(strip_tags(markup), extensions=[ConferenceLinkExtension()] + MARKDOWN_EXTENSIONS)
+
+    cleaner = bleach.Cleaner(
+        tags=['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'dl', 'table', 'th', 'td', 'tr', 'hr', 'p', 'pre'] + bleach.sanitizer.ALLOWED_TAGS,
+        attributes={
+            '*': ['class'],
+            **bleach.sanitizer.ALLOWED_ATTRIBUTES,
+        },
+        protocols=['http', 'https', 'mailto', 'ftp', 'ftps']
+    )
+    return mark_safe(cleaner.clean(rendered_markup))
     return mark_safe(rendered_markup)
 
 
diff --git a/src/plainui/jinja2/plainui/assembly.html b/src/plainui/jinja2/plainui/assembly.html
index 4b0e54be882192a7080c05db238532b5a7726bb6..87d4043af98ecf7590844bc2c752e3fd7c6ab6bf 100644
--- a/src/plainui/jinja2/plainui/assembly.html
+++ b/src/plainui/jinja2/plainui/assembly.html
@@ -24,7 +24,7 @@
         {{- markdownMacro.markdown(markdown=assembly.description_html | safe) -}}
         <hr>
     {% endif %}
-    <a class="col-auto" href="{{ url('plainui:assemblies_all', conf_slug=conf.slug) }}">all assmeblies</a>
+    <a class="col-auto" href="{{ url('plainui:assemblies_all', conf_slug=conf.slug) }}">all assemblies</a>
     <a href="{{ url('plainui:assemblies', conf_slug=conf.slug) }}">
         assemblies start seite
     </a>
@@ -46,7 +46,7 @@
 
     <hr class="my-5 border-tertiary">
 
-    <h2>{{ _("assmebly events") }}</h2>
+    <h2>{{ _("assembly events") }}</h2>
     <div class="border border-tertiary p-6">
         {{ list_events.tiles(events, is_favorite_events, is_scheduled_events ) }}
     </div>
diff --git a/src/plainui/jinja2/plainui/component_gallery.html b/src/plainui/jinja2/plainui/component_gallery.html
index 85c87ff0c60cb211e432685717c9dd60721f373e..1b9c9fb150a7a9209fc043731ade2c322e912fb1 100644
--- a/src/plainui/jinja2/plainui/component_gallery.html
+++ b/src/plainui/jinja2/plainui/component_gallery.html
@@ -26,9 +26,9 @@
 {% set event2 = {"id": "2", "name": "event example 2", "slug": "event_slug2", "banner_image": {"url": image_url}, "schedule_start": time1, "schedule_end": time2, "schedule_duration": duration, "description": "Lorem Ipsum ...", "language": "de" } %}
 {% set events = [ event1, event2 ] %}
 
-{% set assmebly1 = {"id": "1", "name": "assembly example 1", "slug": "assembly_slug1", "is_official": false,"banner_image": {"url": image_url}, "description": "Lorem Ipsum ..." } %}
+{% set assembly1 = {"id": "1", "name": "assembly example 1", "slug": "assembly_slug1", "is_official": false,"banner_image": {"url": image_url}, "description": "Lorem Ipsum ..." } %}
 {% set assembly2 = {"id": "2", "name": "assembly example 2 - official", "slug": "assembly_slug2", "is_official": true, "banner_image": {"url": image_url}, "description": "Lorem Ipsum ..." } %}
-{% set assemblies = [ assmebly1, assembly2 ] %}
+{% set assemblies = [ assembly1, assembly2 ] %}
 
 {% block content %}
 
@@ -46,7 +46,7 @@
 
     <dt class="h2 pb-3 mb-3 border-bottom">title</dt>
     <dd class="mb-10">
-        {{ titleMacro.title(title="Fantastic Headline", fav_id="12345", fav_type="assmebly", fav_is=true, sch_id="12345", sch_is=true, share_url="url", stream_url="url", report_url="url") }}
+        {{ titleMacro.title(title="Fantastic Headline", fav_id="12345", fav_type="assembly", fav_is=true, sch_id="12345", sch_is=true, share_url="url", stream_url="url", report_url="url") }}
     </dd>
 
     <dt class="h2 pb-3 mb-3 border-bottom">Markdown</dt>
diff --git a/src/plainui/jinja2/plainui/fahrplan.html b/src/plainui/jinja2/plainui/fahrplan.html
index 53ca262ca155e1a4d8ed3d5a82458cee38cd5df2..7bcc38d6ed711652eaa0a7a739a3bb31743eebdd 100644
--- a/src/plainui/jinja2/plainui/fahrplan.html
+++ b/src/plainui/jinja2/plainui/fahrplan.html
@@ -34,7 +34,7 @@
             <button type="submit" name="set" value="fday" class="m-2 btn btn-primary {{ 'active' if show_day_filters }}">{{ _("by day") }}</button>
             <button type="submit" name="set" value="ftrack" class="m-2 btn btn-primary {{ 'active' if show_track_filters }}">{{ _("by track") }}</button>
             <button type="submit" name="set" value="curated" class="m-2 btn btn-primary {{ 'active' if curated }}">{{ _("curated only") }}</button>
-            {# Assembly events are displayed on assmbly page. filter here by assmebly will mean display serveral hundred assmeblies. leave for the future
+            {# Assembly events are displayed on assmbly page. filter here by assembly will mean display serveral hundred assemblies. leave for the future
                 <button type="submit" name="set" value="fassembly" class="m-2 btn btn-primary {{ 'active' if show_assembly_filters }}">Assembly</button> #}
         </div>
 
diff --git a/src/plainui/locale/de/LC_MESSAGES/django.po b/src/plainui/locale/de/LC_MESSAGES/django.po
index e885b2f94d406f5a767bb350377cc3e206d88923..6db239aa764d496379c83b6f11b5c8ffc144e230 100644
--- a/src/plainui/locale/de/LC_MESSAGES/django.po
+++ b/src/plainui/locale/de/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-06 15:26+0000\n"
+"POT-Creation-Date: 2020-12-23 11:52+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -21,57 +21,120 @@ msgstr ""
 msgid "Unknown User!"
 msgstr ""
 
-msgid "Assemblies"
+msgid "Please enter the recipient name"
+msgstr ""
+
+msgid "Please enter a subject"
+msgstr ""
+
+msgid "Please enter a title"
+msgstr ""
+
+msgid "Placeholder text"
+msgstr ""
+
+msgid "blocked"
+msgstr ""
+
+msgid "Please enter a name"
+msgstr ""
+
+msgid "Must end with the conference!"
+msgstr ""
+
+msgid "Room is not free!"
 msgstr ""
 
-msgid "introduction"
+msgid "Can't begin before the conference begins!"
 msgstr ""
 
-msgid "assemblies list"
+msgid "Legal (Copyright, Nazi shit, ..)"
 msgstr ""
 
-msgid "assemblies overview"
+msgid "General Report (-> Security Team)"
 msgstr ""
 
-msgid "assemblies events"
+msgid "Awareness Team"
+msgstr ""
+
+msgid "Technical Problem"
+msgstr ""
+
+msgid "Assemblies"
+msgstr ""
+
+msgid "Introduction"
+msgstr ""
+
+msgid "upcoming events"
+msgstr ""
+
+msgid "recommended events"
 msgstr ""
 
 msgid "all assemblies"
 msgstr ""
 
+msgid "assembly events"
+msgstr ""
+
+msgid "running and upcoming events"
+msgstr ""
+
 msgid "all assemblies events"
 msgstr ""
 
+msgid "New Selforganized Session"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
 msgid "Your Browser is broken. Get a better one here!"
 msgstr "Dein Browser ist defekt. Hier bekommst du einen Besseren!"
 
-msgid "world"
+msgid "Contact Us"
+msgstr "Kontakt"
+
+msgid "Privacy Policy"
 msgstr ""
 
-msgid "Low Contrast"
+msgid "About RC3"
 msgstr ""
 
-msgid "low"
+msgid "Disclaimer"
 msgstr ""
 
-msgid "High Contrast"
+msgid "Bulletin Board"
 msgstr ""
 
-msgid "high"
+msgid "search"
 msgstr ""
 
-msgid "logout"
+msgid "My Board Entries"
 msgstr ""
 
-msgid "login"
+msgid "New Entry"
 msgstr ""
 
-msgid "Contact Us"
-msgstr "Kontakt"
+msgid "Update"
+msgstr ""
+
+msgid "My Bulletin Board"
+msgstr ""
+
+msgid "Public Bulletin Board"
+msgstr ""
 
 msgid "Curated Events"
 msgstr ""
 
+msgid "curated events"
+msgstr ""
+
+msgid "No entries available."
+msgstr "Keine Einträge vorhanden."
+
 msgid "Event starts in"
 msgstr "Event startet in"
 
@@ -96,8 +159,30 @@ msgstr "Language"
 msgid "Room"
 msgstr "Room"
 
-msgid "No entries available."
-msgstr "Keine Einträge vorhanden."
+msgid "remove from favorites"
+msgstr ""
+
+msgid "add to favorites"
+msgstr ""
+
+#, fuzzy
+#| msgid "personal schedule"
+msgid "remove from schedule"
+msgstr "Persönlicher Fahrplan"
+
+#, fuzzy
+#| msgid "personal schedule"
+msgid "add to schedule"
+msgstr "Persönlicher Fahrplan"
+
+msgid "share this "
+msgstr ""
+
+msgid "open stream"
+msgstr ""
+
+msgid "report this url"
+msgstr ""
 
 msgid "Live Stream"
 msgstr ""
@@ -108,34 +193,60 @@ msgstr "External Ressources"
 msgid "Tags"
 msgstr "Tags"
 
+msgid "by"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Report Content"
+msgstr ""
+
 msgid "Conferences"
 msgstr "Konferenzen"
 
-msgid "Curated"
+msgid "Hey"
+msgstr "Hey"
+
+msgid "You are leaving the »RC3-area«. For external sites, streams and applications the actual owners are completely and solely responsible regarding data protection, copyright, youth protection, etc.!"
+msgstr "Du verlässt gerade das »RC3-Gelände«. Für externe Seiten, Streams und Angebote sind vollinhaltlich die jeweiligen Betreiber in bezug auf Datenschutz, Copyright und Jugendschutz etc. verantwortlich."
+
+msgid "External Link"
 msgstr ""
 
-msgid "List"
+msgid "download"
 msgstr ""
 
-msgid "Calendar"
+msgid "Xcal"
 msgstr ""
 
-msgid "curated events"
+msgid "Xml"
 msgstr ""
 
-msgid "congress platform"
+msgid "Json"
 msgstr ""
 
-msgid "community"
+msgid "QR-Code"
 msgstr ""
 
-msgid "Skip intro"
-msgstr "Intro überspringen"
+msgid "view as list"
+msgstr ""
 
-msgid "welcome to the rc3 "
+msgid "view as calendar"
 msgstr ""
 
-msgid "ticket"
+msgid "by day"
+msgstr ""
+
+#, fuzzy
+#| msgid "Track"
+msgid "by track"
+msgstr "Track"
+
+msgid "curated only"
+msgstr ""
+
+msgid "world"
 msgstr ""
 
 msgid "platform"
@@ -144,6 +255,58 @@ msgstr ""
 msgid "info"
 msgstr ""
 
+msgid "Profile"
+msgstr "Profil"
+
+msgid "My Plan"
+msgstr ""
+
+msgid "logout"
+msgstr ""
+
+msgid "login"
+msgstr ""
+
+msgid "board"
+msgstr ""
+
+#, fuzzy
+#| msgid "External Ressources"
+msgid "Messages"
+msgstr "External Ressources"
+
+#, fuzzy
+#| msgid "External Ressources"
+msgid "Mess ages"
+msgstr "External Ressources"
+
+msgid "Fahrplan"
+msgstr ""
+
+msgid "Fahr plan"
+msgstr ""
+
+msgid "Low Contrast"
+msgstr ""
+
+msgid "High Contrast"
+msgstr ""
+
+msgid "de"
+msgstr ""
+
+msgid "en"
+msgstr ""
+
+msgid "Skip intro"
+msgstr "Intro überspringen"
+
+msgid "welcome to the rc3"
+msgstr ""
+
+msgid "ticket"
+msgstr ""
+
 msgid "enter username"
 msgstr ""
 
@@ -165,12 +328,18 @@ msgstr ""
 msgid "new Ticket"
 msgstr ""
 
+msgid "Change Password"
+msgstr ""
+
 msgid "Your password has been set. You may go ahead and log in now."
 msgstr ""
 
 msgid "back"
 msgstr ""
 
+msgid "Login"
+msgstr ""
+
 msgid "new password"
 msgstr ""
 
@@ -189,9 +358,6 @@ msgstr ""
 msgid "You will receive an e-mail with a reset link."
 msgstr ""
 
-msgid "Login"
-msgstr ""
-
 #, fuzzy
 #| msgid "External Ressources"
 msgid "Personal Messages"
@@ -206,9 +372,6 @@ msgstr ""
 msgid "New PM"
 msgstr ""
 
-msgid "Delete"
-msgstr ""
-
 msgid "Personal Messages - Send"
 msgstr ""
 
@@ -221,9 +384,6 @@ msgstr ""
 msgid "Personal Message"
 msgstr ""
 
-msgid "Profile"
-msgstr "Profil"
-
 msgid "Avatar image"
 msgstr ""
 
@@ -253,7 +413,25 @@ msgstr ""
 msgid "custom preferences"
 msgstr "Konferenzen"
 
-msgid "description"
+msgid "Badge-Token"
+msgstr ""
+
+msgid "Submit"
+msgstr ""
+
+msgid "My Favorites"
+msgstr ""
+
+msgid "My Fahrplan"
+msgstr ""
+
+msgid "Please go to the following page and choose a new password:"
+msgstr ""
+
+msgid "Thanks for using our site!"
+msgstr ""
+
+msgid "report content"
 msgstr ""
 
 msgid "Search Results"
@@ -262,43 +440,81 @@ msgstr "Suchergebnisse"
 msgid "No results, sorry!"
 msgstr "Nichts gefunden, sorry!"
 
-msgid "2D World"
+msgid "Cancel"
 msgstr ""
 
-#, python-format
-msgid "You're receiving this email because you requested a password reset for your user account at %(site_name)s."
+msgid "Upcoming"
 msgstr ""
 
-msgid "Please go to the following page and choose a new password:"
+msgid "Send PN"
 msgstr ""
 
-msgid "Your username, in case you’ve forgotten:"
+msgid "badges"
 msgstr ""
 
-msgid "Thanks for using our site!"
+msgid "2D World"
 msgstr ""
 
-#, python-format
-msgid "The %(site_name)s team"
+msgid "Please activate your Ticket to access this conference!"
 msgstr ""
 
-#, python-format
-msgid "Password reset on %(site_name)s"
+msgid "Not allowed to edit this Self Organized Session"
+msgstr ""
+
+msgid "Updated Self Organized Session"
+msgstr ""
+
+msgid "Created Self Organized Session"
+msgstr ""
+
+#, fuzzy
+#| msgid "Profile"
+msgid "Updated Profile"
+msgstr "Profil"
+
+msgid "Message sent."
 msgstr ""
 
-msgid "Profile updated successfully"
+msgid "Message deleted."
 msgstr ""
 
 msgid "Unknown Username or Password"
 msgstr "Unbekannter Benutzername oder Passwort falsch"
 
-msgid "Fahrplan"
+msgid "Password changed successfully."
 msgstr ""
 
-msgid "My Plan"
+msgid "Bulletin Board Entry updated."
+msgstr ""
+
+msgid "Bulletin Board Entry created."
+msgstr ""
+
+msgid "Bulletin Board Entry deleted."
+msgstr ""
+
+msgid "Report sent"
+msgstr ""
+
+msgid "Assemblies List"
+msgstr ""
+
+msgid "Explore Assemblies"
 msgstr ""
 
-msgid "bulletin board"
+msgid "What are Assemblies?"
+msgstr ""
+
+msgid "Assembly Events"
+msgstr ""
+
+msgid "Explore Assembly Events"
+msgstr ""
+
+msgid "Bulletin Board Entry"
+msgstr ""
+
+msgid "Create"
 msgstr ""
 
 msgid "Explore Events"
@@ -311,7 +527,22 @@ msgstr ""
 msgid "Day %(n)s"
 msgstr ""
 
-msgid "welcome to rc3"
+msgid "Welcome to rC3"
+msgstr ""
+
+msgid "What's Official?"
+msgstr ""
+
+msgid "Explore Curated Events"
+msgstr ""
+
+msgid "Congress Platform"
+msgstr ""
+
+msgid "Community"
+msgstr ""
+
+msgid "Explore Community Events"
 msgstr ""
 
 #, python-format
@@ -339,22 +570,44 @@ msgstr ""
 msgid "My Dashboard"
 msgstr ""
 
-msgid "my favorite events"
+#, python-format
+msgid "%(conf)s - Public Fahrplan"
 msgstr ""
 
-msgid "Report Content"
+#, python-format
+msgid "%(conf)s Fahrplan"
 msgstr ""
 
 #, python-format
-msgid "Tag %(name)s"
+msgid "Conference %(conf)s - Redeem Token"
 msgstr ""
 
-msgid "Upcoming events"
+msgid "Redeem Token"
 msgstr ""
 
-msgid "Hey"
-msgstr "Hey"
+#, python-format
+msgid "You're receiving this email because you requested a password reset for your user account at %(site_name)s."
+msgstr ""
 
-msgid "You are leaving the »RC3-area«. For external sites, streams and applications the actual owners are completely and solely responsible regarding data protection, copyright, youth protection, etc.!"
-msgstr "Du verlässt gerade das »RC3-Gelände«. Für externe Seiten, Streams und Angebote sind vollinhaltlich die jeweiligen Betreiber in bezug auf Datenschutz, Copyright und Jugendschutz etc. verantwortlich."
+#, python-format
+msgid "Your username, in case you’ve forgotten: %(username)s"
+msgstr ""
+
+#, python-format
+msgid "The %(site_name)s team"
+msgstr ""
+
+msgid "Selforganized Session"
+msgstr ""
 
+#, python-format
+msgid "Tag %(name)s"
+msgstr ""
+
+msgid "Upcoming events"
+msgstr ""
+
+#, fuzzy, python-format
+#| msgid "%(conf)s - Personal Schedule"
+msgid "%(conf)s - User %(name)s"
+msgstr "%(conf)s - Persönlicher Fahrplan"
diff --git a/src/plainui/locale/en/LC_MESSAGES/django.po b/src/plainui/locale/en/LC_MESSAGES/django.po
index 13e4917a71dd896b0911f06a848df2bf9c8ee2f9..49e790d108ba72564e1eab054fe01952bc499158 100644
--- a/src/plainui/locale/en/LC_MESSAGES/django.po
+++ b/src/plainui/locale/en/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-12-06 15:26+0000\n"
+"POT-Creation-Date: 2020-12-23 11:52+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -21,57 +21,120 @@ msgstr ""
 msgid "Unknown User!"
 msgstr ""
 
-msgid "Assemblies"
+msgid "Please enter the recipient name"
+msgstr ""
+
+msgid "Please enter a subject"
+msgstr ""
+
+msgid "Please enter a title"
+msgstr ""
+
+msgid "Placeholder text"
+msgstr ""
+
+msgid "blocked"
+msgstr ""
+
+msgid "Please enter a name"
+msgstr ""
+
+msgid "Must end with the conference!"
+msgstr ""
+
+msgid "Room is not free!"
 msgstr ""
 
-msgid "introduction"
+msgid "Can't begin before the conference begins!"
 msgstr ""
 
-msgid "assemblies list"
+msgid "Legal (Copyright, Nazi shit, ..)"
 msgstr ""
 
-msgid "assemblies overview"
+msgid "General Report (-> Security Team)"
 msgstr ""
 
-msgid "assemblies events"
+msgid "Awareness Team"
+msgstr ""
+
+msgid "Technical Problem"
+msgstr ""
+
+msgid "Assemblies"
+msgstr ""
+
+msgid "Introduction"
+msgstr ""
+
+msgid "upcoming events"
+msgstr ""
+
+msgid "recommended events"
 msgstr ""
 
 msgid "all assemblies"
 msgstr ""
 
+msgid "assembly events"
+msgstr ""
+
+msgid "running and upcoming events"
+msgstr ""
+
 msgid "all assemblies events"
 msgstr ""
 
+msgid "New Selforganized Session"
+msgstr ""
+
+msgid "Edit"
+msgstr ""
+
 msgid "Your Browser is broken. Get a better one here!"
 msgstr ""
 
-msgid "world"
+msgid "Contact Us"
 msgstr ""
 
-msgid "Low Contrast"
+msgid "Privacy Policy"
 msgstr ""
 
-msgid "low"
+msgid "About RC3"
 msgstr ""
 
-msgid "High Contrast"
+msgid "Disclaimer"
 msgstr ""
 
-msgid "high"
+msgid "Bulletin Board"
 msgstr ""
 
-msgid "logout"
+msgid "search"
 msgstr ""
 
-msgid "login"
+msgid "My Board Entries"
 msgstr ""
 
-msgid "Contact Us"
+msgid "New Entry"
+msgstr ""
+
+msgid "Update"
+msgstr ""
+
+msgid "My Bulletin Board"
+msgstr ""
+
+msgid "Public Bulletin Board"
 msgstr ""
 
 msgid "Curated Events"
 msgstr ""
 
+msgid "curated events"
+msgstr ""
+
+msgid "No entries available."
+msgstr ""
+
 msgid "Event starts in"
 msgstr ""
 
@@ -96,7 +159,25 @@ msgstr ""
 msgid "Room"
 msgstr ""
 
-msgid "No entries available."
+msgid "remove from favorites"
+msgstr ""
+
+msgid "add to favorites"
+msgstr ""
+
+msgid "remove from schedule"
+msgstr ""
+
+msgid "add to schedule"
+msgstr ""
+
+msgid "share this "
+msgstr ""
+
+msgid "open stream"
+msgstr ""
+
+msgid "report this url"
 msgstr ""
 
 msgid "Live Stream"
@@ -108,34 +189,58 @@ msgstr ""
 msgid "Tags"
 msgstr ""
 
+msgid "by"
+msgstr ""
+
+msgid "Delete"
+msgstr ""
+
+msgid "Report Content"
+msgstr ""
+
 msgid "Conferences"
 msgstr ""
 
-msgid "Curated"
+msgid "Hey"
 msgstr ""
 
-msgid "List"
+msgid "You are leaving the »RC3-area«. For external sites, streams and applications the actual owners are completely and solely responsible regarding data protection, copyright, youth protection, etc.!"
 msgstr ""
 
-msgid "Calendar"
+msgid "External Link"
 msgstr ""
 
-msgid "curated events"
+msgid "download"
 msgstr ""
 
-msgid "congress platform"
+msgid "Xcal"
 msgstr ""
 
-msgid "community"
+msgid "Xml"
 msgstr ""
 
-msgid "Skip intro"
+msgid "Json"
 msgstr ""
 
-msgid "welcome to the rc3 "
+msgid "QR-Code"
 msgstr ""
 
-msgid "ticket"
+msgid "view as list"
+msgstr ""
+
+msgid "view as calendar"
+msgstr ""
+
+msgid "by day"
+msgstr ""
+
+msgid "by track"
+msgstr ""
+
+msgid "curated only"
+msgstr ""
+
+msgid "world"
 msgstr ""
 
 msgid "platform"
@@ -144,6 +249,54 @@ msgstr ""
 msgid "info"
 msgstr ""
 
+msgid "Profile"
+msgstr ""
+
+msgid "My Plan"
+msgstr ""
+
+msgid "logout"
+msgstr ""
+
+msgid "login"
+msgstr ""
+
+msgid "board"
+msgstr ""
+
+msgid "Messages"
+msgstr ""
+
+msgid "Mess ages"
+msgstr ""
+
+msgid "Fahrplan"
+msgstr ""
+
+msgid "Fahr plan"
+msgstr ""
+
+msgid "Low Contrast"
+msgstr ""
+
+msgid "High Contrast"
+msgstr ""
+
+msgid "de"
+msgstr ""
+
+msgid "en"
+msgstr ""
+
+msgid "Skip intro"
+msgstr ""
+
+msgid "welcome to the rc3"
+msgstr ""
+
+msgid "ticket"
+msgstr ""
+
 msgid "enter username"
 msgstr ""
 
@@ -165,12 +318,18 @@ msgstr ""
 msgid "new Ticket"
 msgstr ""
 
+msgid "Change Password"
+msgstr ""
+
 msgid "Your password has been set. You may go ahead and log in now."
 msgstr ""
 
 msgid "back"
 msgstr ""
 
+msgid "Login"
+msgstr ""
+
 msgid "new password"
 msgstr ""
 
@@ -189,9 +348,6 @@ msgstr ""
 msgid "You will receive an e-mail with a reset link."
 msgstr ""
 
-msgid "Login"
-msgstr ""
-
 msgid "Personal Messages"
 msgstr ""
 
@@ -204,9 +360,6 @@ msgstr ""
 msgid "New PM"
 msgstr ""
 
-msgid "Delete"
-msgstr ""
-
 msgid "Personal Messages - Send"
 msgstr ""
 
@@ -219,9 +372,6 @@ msgstr ""
 msgid "Personal Message"
 msgstr ""
 
-msgid "Profile"
-msgstr ""
-
 msgid "Avatar image"
 msgstr ""
 
@@ -249,7 +399,25 @@ msgstr ""
 msgid "custom preferences"
 msgstr ""
 
-msgid "description"
+msgid "Badge-Token"
+msgstr ""
+
+msgid "Submit"
+msgstr ""
+
+msgid "My Favorites"
+msgstr ""
+
+msgid "My Fahrplan"
+msgstr ""
+
+msgid "Please go to the following page and choose a new password:"
+msgstr ""
+
+msgid "Thanks for using our site!"
+msgstr ""
+
+msgid "report content"
 msgstr ""
 
 msgid "Search Results"
@@ -258,43 +426,79 @@ msgstr ""
 msgid "No results, sorry!"
 msgstr ""
 
+msgid "Cancel"
+msgstr ""
+
+msgid "Upcoming"
+msgstr ""
+
+msgid "Send PN"
+msgstr ""
+
+msgid "badges"
+msgstr ""
+
 msgid "2D World"
 msgstr ""
 
-#, python-format
-msgid "You're receiving this email because you requested a password reset for your user account at %(site_name)s."
+msgid "Please activate your Ticket to access this conference!"
 msgstr ""
 
-msgid "Please go to the following page and choose a new password:"
+msgid "Not allowed to edit this Self Organized Session"
 msgstr ""
 
-msgid "Your username, in case you’ve forgotten:"
+msgid "Updated Self Organized Session"
 msgstr ""
 
-msgid "Thanks for using our site!"
+msgid "Created Self Organized Session"
 msgstr ""
 
-#, python-format
-msgid "The %(site_name)s team"
+msgid "Updated Profile"
 msgstr ""
 
-#, python-format
-msgid "Password reset on %(site_name)s"
+msgid "Message sent."
 msgstr ""
 
-msgid "Profile updated successfully"
+msgid "Message deleted."
 msgstr ""
 
 msgid "Unknown Username or Password"
 msgstr ""
 
-msgid "Fahrplan"
+msgid "Password changed successfully."
 msgstr ""
 
-msgid "My Plan"
+msgid "Bulletin Board Entry updated."
+msgstr ""
+
+msgid "Bulletin Board Entry created."
+msgstr ""
+
+msgid "Bulletin Board Entry deleted."
+msgstr ""
+
+msgid "Report sent"
+msgstr ""
+
+msgid "Assemblies List"
+msgstr ""
+
+msgid "Explore Assemblies"
 msgstr ""
 
-msgid "bulletin board"
+msgid "What are Assemblies?"
+msgstr ""
+
+msgid "Assembly Events"
+msgstr ""
+
+msgid "Explore Assembly Events"
+msgstr ""
+
+msgid "Bulletin Board Entry"
+msgstr ""
+
+msgid "Create"
 msgstr ""
 
 msgid "Explore Events"
@@ -307,7 +511,22 @@ msgstr ""
 msgid "Day %(n)s"
 msgstr ""
 
-msgid "welcome to rc3"
+msgid "Welcome to rC3"
+msgstr ""
+
+msgid "What's Official?"
+msgstr ""
+
+msgid "Explore Curated Events"
+msgstr ""
+
+msgid "Congress Platform"
+msgstr ""
+
+msgid "Community"
+msgstr ""
+
+msgid "Explore Community Events"
 msgstr ""
 
 #, python-format
@@ -335,10 +554,34 @@ msgstr ""
 msgid "My Dashboard"
 msgstr ""
 
-msgid "my favorite events"
+#, python-format
+msgid "%(conf)s - Public Fahrplan"
 msgstr ""
 
-msgid "Report Content"
+#, python-format
+msgid "%(conf)s Fahrplan"
+msgstr ""
+
+#, python-format
+msgid "Conference %(conf)s - Redeem Token"
+msgstr ""
+
+msgid "Redeem Token"
+msgstr ""
+
+#, python-format
+msgid "You're receiving this email because you requested a password reset for your user account at %(site_name)s."
+msgstr ""
+
+#, python-format
+msgid "Your username, in case you’ve forgotten: %(username)s"
+msgstr ""
+
+#, python-format
+msgid "The %(site_name)s team"
+msgstr ""
+
+msgid "Selforganized Session"
 msgstr ""
 
 #, python-format
@@ -347,3 +590,7 @@ msgstr ""
 
 msgid "Upcoming events"
 msgstr ""
+
+#, python-format
+msgid "%(conf)s - User %(name)s"
+msgstr ""
diff --git a/src/plainui/views.py b/src/plainui/views.py
index dc77d1a9d53e88df69b01ac5f145a2964c59921a..fcbb1150e204f0be60a4a1217696b092ff361e26 100644
--- a/src/plainui/views.py
+++ b/src/plainui/views.py
@@ -250,7 +250,9 @@ class AssemblyView(ConferenceRequiredMixin, TemplateView):
         context['scope'] = 'assembly'
         context['can_create_sos'] = assembly.has_user(self.request.user) or assembly == self.conf.self_organized_sessions_assembly
         context['sos'] = assembly.events.filter(is_public=True, kind=Event.Kind.SELF_ORGANIZED)
-        context['suggested'] = [s.assembly2 for s in assembly.suggestions.select_related('assembly2').exclude(assembly2=assembly.pk).order_by('-like_ratio')[:5]]
+        context['suggested'] = [s.assembly2 for s in assembly.suggestions.select_related('assembly2')
+                                                    .exclude(assembly2=assembly.pk)  # noqa: E127
+                                                    .order_by('-like_ratio')[:5]]
 
         can_manage_sos = context['can_manage_sos'] = assembly.user_can_manage(self.request.user)
         if can_manage_sos:
diff --git a/src/requirements.txt b/src/requirements.txt
index e4f5f374c3a7ec3bdda90176d91c22feaa3b6842..c542ed21dd5e1d2bab046692fc0181f711f287b5 100644
--- a/src/requirements.txt
+++ b/src/requirements.txt
@@ -1,3 +1,4 @@
+bleach >= 3.2.1
 Django >=3.1, <3.2
 django_bootstrap4 >= 2.3.1, <2.4
 django-cors-middleware >= 1.5.0, <1.6